com.sap.cds.services.mt.impl.MtSubscriptionServiceCompatibilityHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-feature-mt Show documentation
Show all versions of cds-feature-mt Show documentation
Multi tenancy feature for CDS Services Java
package com.sap.cds.services.mt.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cds.adapter.subscription.SaasProvisioningServlet;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.EventContext;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.handler.annotations.HandlerOrder;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.mt.DependenciesEventContext;
import com.sap.cds.services.mt.MtAsyncDeployEventContext;
import com.sap.cds.services.mt.MtAsyncDeployStatusEventContext;
import com.sap.cds.services.mt.MtAsyncSubscribeEventContext;
import com.sap.cds.services.mt.MtAsyncSubscribeFinishedEventContext;
import com.sap.cds.services.mt.MtAsyncUnsubscribeEventContext;
import com.sap.cds.services.mt.MtAsyncUnsubscribeFinishedEventContext;
import com.sap.cds.services.mt.MtDeployEventContext;
import com.sap.cds.services.mt.MtGetDependenciesEventContext;
import com.sap.cds.services.mt.MtSubscribeEventContext;
import com.sap.cds.services.mt.MtSubscriptionService;
import com.sap.cds.services.mt.MtUnsubscribeEventContext;
import com.sap.cds.services.mt.SaasRegistryDependency;
import com.sap.cds.services.mt.SubscribeEventContext;
import com.sap.cds.services.mt.UnsubscribeEventContext;
import com.sap.cds.services.mt.UpgradeEventContext;
import com.sap.cds.services.runtime.CdsRuntime;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.services.utils.OrderConstants;
import com.sap.cds.services.utils.StringUtils;
import com.sap.cloud.mt.subscription.InstanceLifecycleManager;
import com.sap.cloud.mt.subscription.SubscriberImpl;
import com.sap.cloud.mt.subscription.exceptions.AuthorityError;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.NotFound;
import com.sap.cloud.mt.subscription.exceptions.NotSupported;
import com.sap.cloud.mt.subscription.exceptions.ParameterError;
import com.sap.cloud.mt.subscription.json.DeletePayload;
import com.sap.cloud.mt.subscription.json.SidecarSubscribeCallBackPayload;
import com.sap.cloud.mt.subscription.json.SidecarSubscriptionPayload;
import com.sap.cloud.mt.subscription.json.SidecarUnSubscribeCallBackPayload;
import com.sap.cloud.mt.subscription.json.SidecarUnSubscriptionPayload;
import com.sap.cloud.mt.subscription.json.SidecarUpgradePayload;
import com.sap.cloud.mt.subscription.json.SubscriptionPayload;
import com.sap.xsa.core.instancemanager.client.InstanceCreationOptions;
/**
* Compatibility handler for {@link MtSubscriptionService} API.
* Extends {@link MtDeploymentServiceHandler} and overwrites some of its handlers.
*/
@SuppressWarnings("deprecation")
public class MtSubscriptionServiceCompatibilityHandler extends MtDeploymentServiceHandler {
public static final String PARAM_APPLICATION_URL = "_internal_applicationUrlFromJava";
private static final String PARAM_UPGRADE_COMPATIBILITY = "_internal_upgradeCompatibilityMode";
private static final Logger logger = LoggerFactory.getLogger(MtSubscriptionServiceCompatibilityHandler.class);
private static final String SUCCEEDED = "SUCCEEDED";
private static final String FAILED = "FAILED";
private final MtSubscriptionService service;
private final String callbackScope;
private final String deployScope;
public MtSubscriptionServiceCompatibilityHandler(InstanceLifecycleManager instanceLifecycleManager, CdsRuntime runtime) {
super(instanceLifecycleManager, runtime);
this.service = runtime.getServiceCatalog().getService(MtSubscriptionService.class, MtSubscriptionService.DEFAULT_NAME);
this.callbackScope = runtime.getEnvironment().getCdsProperties().getMultiTenancy().getSecurity().getSubscriptionScope();
this.deployScope = runtime.getEnvironment().getCdsProperties().getMultiTenancy().getSecurity().getDeploymentScope();
}
// Authorization
@Before(service = MtSubscriptionService.DEFAULT_NAME, event = {
MtSubscriptionService.EVENT_SUBSCRIBE, MtSubscriptionService.EVENT_UNSUBSCRIBE,
MtSubscriptionService.EVENT_ASYNC_SUBSCRIBE, MtSubscriptionService.EVENT_ASYNC_UNSUBSCRIBE,
MtSubscriptionService.EVENT_ASYNC_SUBSCRIBE_FINISHED, MtSubscriptionService.EVENT_ASYNC_UNSUBSCRIBE_FINISHED,
MtSubscriptionService.EVENT_GET_DEPENDENCIES
})
@HandlerOrder(OrderConstants.Before.CHECK_AUTHORIZATION)
protected void checkAuthorization(EventContext context) {
if (!context.getUserInfo().isPrivileged() && !context.getUserInfo().hasRole(callbackScope)) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN);
}
}
@Before(service = MtSubscriptionService.DEFAULT_NAME, event = {
MtSubscriptionService.EVENT_DEPLOY,
MtSubscriptionService.EVENT_ASYNC_DEPLOY, MtSubscriptionService.EVENT_ASYNC_DEPLOY_STATUS
})
@HandlerOrder(OrderConstants.Before.CHECK_AUTHORIZATION)
protected void checkAuthorizationDeployment(EventContext context) {
if (!context.getUserInfo().isPrivileged() && !context.getUserInfo().hasRole(deployScope)) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN);
}
}
// Dependencies
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onGetDependencies(MtGetDependenciesEventContext context) {
context.setResult(new ArrayList<>());
}
@On
@HandlerOrder(OrderConstants.On.FEATURE)
private void defaultDependencies(DependenciesEventContext context) {
context.setResult(service.getDependencies().stream().map(a -> {
SaasRegistryDependency d = SaasRegistryDependency.create();
if (a.xsappname != null) d.setXsappname(a.xsappname);
if (a.appId != null) d.setAppId(a.appId);
if (a.appName != null) d.setAppName(a.appName);
return d;
}).collect(Collectors.toList()));
}
// Subscribe
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onSubscribe(MtSubscribeEventContext context) {
try {
options.set(context.getInstanceCreationOptions());
context.setResult(subscriber.subscribe(context.getTenantId(), context.getSubscriptionPayload(), forwardToken(context)));
} catch (InternalError e) {
throw new ErrorStatusException(CdsErrorStatuses.SUBSCRIPTION_FAILED, context.getTenantId(), e) ;
} catch (ParameterError e) {
throw new ErrorStatusException(ErrorStatuses.BAD_REQUEST, e);
} catch (AuthorityError e) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN, e);
} finally {
options.remove();
}
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onSubscribeAsync(MtAsyncSubscribeEventContext context) {
SidecarSubscribeCallBackPayload payload = new SidecarSubscribeCallBackPayload();
payload.tenantId = context.getTenantId();
payload.saasRequestPayload = new SidecarSubscriptionPayload(context.getSubscriptionPayload());
payload.saasCallbackUrl = context.getSaasRegistryCallbackUrl();
try {
MtSubscribeEventContext syncContext = MtSubscribeEventContext.create();
syncContext.setTenantId(context.getTenantId());
syncContext.setSubscriptionPayload(context.getSubscriptionPayload());
syncContext.setInstanceCreationOptions(context.getInstanceCreationOptions());
// synchronous subscribe => AFTER ASYNC_SUBSCRIBE is now definitely after subscription finished
onSubscribe(syncContext);
payload.saasRequestPayload._applicationUrlFromJava_ = syncContext.getResult();
payload.status = SUCCEEDED;
payload.message = "Subscription succeeded";
context.setResult("");
} catch (Exception e) {
payload.status = FAILED;
payload.message = "Subscription failed";
throw e;
} finally {
// AFTER ASYNC_SUBSCRIBE is now after ASYNC_SUBSCRIBE_FINISHED
service.finishAsyncSubscribe(payload);
context.put(PARAM_APPLICATION_URL, payload.saasRequestPayload._applicationUrlFromJava_);
}
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onSubscribeAsyncFinished(MtAsyncSubscribeFinishedEventContext context) {
// compatibility only on event handler layer not on MtSubscriptionService API layer
// asynchronous saas registry handling is now done by SaasProvisioningServlet
context.setCompleted();
}
@Override
protected void onSubscribe(SubscribeEventContext context) {
SubscriptionPayload payload = mapper.convertValue(context.getOptions(), SubscriptionPayload.class);
InstanceCreationOptions ico = buildInstanceCreationOptions(context);
String callbackUrl = context.getParameterInfo().getHeader(SaasProvisioningServlet.HEADER_STATUS_CALLBACK);
if (StringUtils.isEmpty(callbackUrl)) {
MtSubscribeEventContext mtContext = MtSubscribeEventContext.create();
mtContext.setSubscriptionPayload(payload);
mtContext.setTenantId(context.getTenant());
mtContext.setInstanceCreationOptions(ico);
service.emit(mtContext);
context.getOptions().put(PARAM_APPLICATION_URL, mtContext.getResult());
} else {
MtAsyncSubscribeEventContext mtContext = MtAsyncSubscribeEventContext.create();
mtContext.setSubscriptionPayload(payload);
mtContext.setTenantId(context.getTenant());
mtContext.setSaasRegistryCallbackUrl(callbackUrl);
mtContext.setInstanceCreationOptions(ico);
service.emit(mtContext);
context.getOptions().put(PARAM_APPLICATION_URL, mtContext.get(PARAM_APPLICATION_URL));
}
}
// Unsubscribe
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onUnsubscribe(MtUnsubscribeEventContext context) {
try {
if (context.getDelete() != null && context.getDelete()) {
logger.info("Deleting subscription of tenant '{}'", context.getTenantId());
subscriber.unsubscribe(context.getTenantId(), context.getDeletePayload(), forwardToken(context));
} else {
logger.info("Skipping subscription deletion of tenant '{}'", context.getTenantId());
}
context.setCompleted();
} catch (InternalError e) {
throw new ErrorStatusException(CdsErrorStatuses.UNSUBSCRIPTION_FAILED, context.getTenantId(), e) ;
} catch (ParameterError e) {
throw new ErrorStatusException(ErrorStatuses.BAD_REQUEST, e);
} catch (AuthorityError e) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN, e);
}
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onUnsubscribeAsync(MtAsyncUnsubscribeEventContext context) {
SidecarUnSubscribeCallBackPayload payload = new SidecarUnSubscribeCallBackPayload();
payload.tenantId = context.getTenantId();
payload.saasRequestPayload = new SidecarUnSubscriptionPayload(context.getDeletePayload());
payload.saasCallbackUrl = context.getSaasRegistryCallbackUrl();
try {
MtUnsubscribeEventContext syncContext = MtUnsubscribeEventContext.create();
syncContext.setTenantId(context.getTenantId());
syncContext.setDeletePayload(context.getDeletePayload());
syncContext.setDelete(context.getDelete() != null ? context.getDelete() : false);
// synchronous unsubscribe => AFTER ASYNC_UNSUBSCRIBE is now definitely after unsubscription finished
onUnsubscribe(syncContext);
payload.status = SUCCEEDED;
payload.message = "Removing subscription succeeded";
context.setCompleted();
} catch (Exception e) {
payload.status = FAILED;
payload.message = "Removing subscription failed";
throw e;
} finally {
// AFTER ASYNC_UNSUBSCRIBE is now after ASYNC_UNSUBSCRIBE_FINISHED
service.finishAsyncUnsubscribe(payload);
}
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onUnsubscribeAsyncFinished(MtAsyncUnsubscribeFinishedEventContext context) {
// compatibility only on event handler layer not on MtSubscriptionService API layer
// asynchronous saas registry handling is now done by SaasProvisioningServlet
context.setCompleted();
}
@Override
protected void onUnsubscribe(UnsubscribeEventContext context) {
DeletePayload payload = mapper.convertValue(context.getOptions(), DeletePayload.class);
String callbackUrl = context.getParameterInfo().getHeader(SaasProvisioningServlet.HEADER_STATUS_CALLBACK);
if (StringUtils.isEmpty(callbackUrl)) {
MtUnsubscribeEventContext mtContext = MtUnsubscribeEventContext.create();
mtContext.setDeletePayload(payload);
mtContext.setTenantId(context.getTenant());
service.emit(mtContext);
} else {
MtAsyncUnsubscribeEventContext mtContext = MtAsyncUnsubscribeEventContext.create();
mtContext.setDeletePayload(payload);
mtContext.setTenantId(context.getTenant());
mtContext.setSaasRegistryCallbackUrl(callbackUrl);
service.emit(mtContext);
}
}
// Upgrade
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onDeploy(MtDeployEventContext mtContext) {
UpgradeEventContext context = UpgradeEventContext.create();
context.setTenants(Arrays.asList(mtContext.getUpgradePayload().tenants));
super.onUpgrade(context);
mtContext.setCompleted();
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onDeployAsync(MtAsyncDeployEventContext context) {
if (Boolean.TRUE.equals(context.get(PARAM_UPGRADE_COMPATIBILITY))) {
// event handler compatibility, triggers new API internally
MtDeployEventContext mtContext = MtDeployEventContext.create();
mtContext.setUpgradePayload(context.getUpgradePayload());
onDeploy(mtContext);
context.setResult("DEPRECATED");
} else {
// REST API compatibility, doesn't trigger new API
try {
context.setResult(subscriber.setupDbTablesAsync(Arrays.asList(context.getUpgradePayload().tenants)));
} catch (InternalError e) {
throw new ErrorStatusException(CdsErrorStatuses.DEPLOYMENT_FAILED, String.join(", ", context.getUpgradePayload().tenants), e);
} catch (ParameterError e) {
throw new ErrorStatusException(ErrorStatuses.BAD_REQUEST, e);
} catch (AuthorityError e) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN, e);
}
}
}
@On(service = MtSubscriptionService.DEFAULT_NAME)
@HandlerOrder(OrderConstants.On.DEFAULT_ON)
protected void onDeployAsyncStatus(MtAsyncDeployStatusEventContext context) {
try {
context.setResult(subscriber.updateStatus(context.getJobId()));
} catch (InternalError e) {
throw new ErrorStatusException(CdsErrorStatuses.JOB_STATUS_UPDATE_FAILED, context.getJobId(), e);
} catch (ParameterError e) {
throw new ErrorStatusException(ErrorStatuses.BAD_REQUEST, e);
} catch (AuthorityError e) {
throw new ErrorStatusException(ErrorStatuses.FORBIDDEN, e);
} catch (NotSupported e) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED, e);
} catch (NotFound e) {
throw new ErrorStatusException(CdsErrorStatuses.JOB_NOT_FOUND, context.getJobId(), e);
}
}
@Override
protected void onUpgrade(UpgradeEventContext context) {
SidecarUpgradePayload payload = new SidecarUpgradePayload();
payload.tenants = context.getTenants().toArray(new String[0]);
if (subscriber instanceof SubscriberImpl) {
service.deploy(payload);
} else {
MtAsyncDeployEventContext mtContext = MtAsyncDeployEventContext.create();
mtContext.setUpgradePayload(payload);
mtContext.put(PARAM_UPGRADE_COMPATIBILITY, true);
service.emit(mtContext);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy