All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.services.mt.impl.MtSubscriptionServiceCompatibilityHandler Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
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