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

com.sap.cds.feature.messaging.em.mt.service.EnterpriseMessagingMtService Maven / Gradle / Ivy

The newest version!
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.feature.messaging.em.mt.service;

import static com.sap.cds.services.messaging.utils.MessagingUtils.toStringMessage;

import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sap.cds.feature.messaging.em.client.EnterpriseMessagingWebhookManagementClient;
import com.sap.cds.feature.messaging.em.mt.SubdomainUtils;
import com.sap.cds.feature.messaging.em.mt.service.EnterpriseMessagingTenantStatus.QueueStatus;
import com.sap.cds.feature.messaging.em.mt.webhook.EnterpriseMessagingWebhookAdapterFactory;
import com.sap.cds.feature.messaging.em.service.EnterpriseMessagingService;
import com.sap.cds.services.environment.CdsProperties.Messaging.MessagingServiceConfig;
import com.sap.cds.services.messaging.TopicMessageEventContext;
import com.sap.cds.services.messaging.service.MessagingBrokerQueueListener;
import com.sap.cds.services.request.RequestContext;
import com.sap.cds.services.request.UserInfo;
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.StringUtils;
import com.sap.cloud.environment.servicebinding.api.ServiceBinding;

/**
 * Implementation of the multi tenant aware enterprise messaging service.
 */
public class EnterpriseMessagingMtService extends EnterpriseMessagingService {

	private final static Logger logger = LoggerFactory.getLogger(EnterpriseMessagingMtService.class);
	private final MessagingBrokerQueueListener queueListener;
	private final String webhookUrl;

	private final EnterpriseMessagingWebhookManagementClient webhookManagementClient;

	@SuppressWarnings("deprecation")
	public EnterpriseMessagingMtService(MessagingServiceConfig serviceConfig, ServiceBinding binding, CdsRuntime runtime) {
		super(serviceConfig, binding, null, runtime);
		this.queueListener = new MessagingBrokerQueueListener(this, toFullyQualifiedQueueName(queue), queue, runtime, serviceConfig.isStructured());

		String webhookUrl = runtime.getEnvironment().getCdsProperties().getMessaging().getWebhooks().getUrl();
		if (webhookUrl == null) {
			webhookUrl = runtime.getEnvironment().getApplicationInfo().getUrl();
		}
		if (webhookUrl == null) {
			throw new ErrorStatusException(CdsErrorStatuses.NO_WEBHOOK_URL);
		}
		this.webhookUrl = "https://" + webhookUrl + "/" + EnterpriseMessagingWebhookAdapterFactory.URL_PATH;
		this.webhookManagementClient = new EnterpriseMessagingWebhookManagementClient(binding, serviceConfig.getConnection().getConnectionPool());
	}

	public void init(String tenantId) {
		String subdomain = checkSubdomain(tenantId);
		try {
			logger.info("Initializing the enterprise-messaging service '{}' for tenant '{}'", getName(), tenantId);
			if (checkTenantReadiness(tenantId, false) && createOrUpdateQueuesAndSubscriptions()) {
				webhookManagementClient.createOrUpdateWebhookRegistration(
						getName(),
						toFullyQualifiedQueueName(queue),
						webhookUrl,
						subdomain);
			}
		} catch (IOException e) {
			logger.error("Failed to initialize the enterprise-messaging service '{}' for tenant '{}'", getName(), tenantId, e);
		} finally {
			logger.debug("Finished initializing the enterprise-messaging service '{}' for tenant '{}'", getName(), tenantId);
		}
	}

	public EnterpriseMessagingTenantStatus getTenantStatus(String tenantId, boolean verbose) {
		EnterpriseMessagingTenantStatus result = new EnterpriseMessagingTenantStatus(tenantId);
		QueueStatus queueStatus = new QueueStatus();
		String queueName = toFullyQualifiedQueueName(queue);
		result.getServices().put(getName(), queueStatus);
		try {
			if (checkTenantReadiness(tenantId, true)) {
				// check the service queue
				ArrayNode createdQueues = managementClient.getQueues();
				createdQueues.forEach(createdQueue -> {
					String createdQueueName = createdQueue.get("name").asText();
					if (createdQueueName.equals(queueName)) {
						queueStatus.setQueue(verbose ? createdQueue : createdQueueName);

						try {
							// check the topics
							ArrayNode topicsFromBroker = managementClient.getQueueSubscriptions(createdQueueName);
							Set topicsFromModel = queue.getTopics().stream().map(t ->  t.getBrokerName()).collect(Collectors.toSet());

							if (topicsFromBroker != null && topicsFromBroker.size() > 0) {
								topicsFromBroker.forEach(t -> {
									String topic = t.get("topicPattern").asText();
									queueStatus.getTopics().add(topic);
									if (!topicsFromModel.remove(topic)) {
										queueStatus.getUnmanagedTopics().add(topic);
									}
								});

								// now check whether all modeled topics are managed
								if (!topicsFromModel.isEmpty()) {
									queueStatus.setError("Missing topic subscriptions: " + topicsFromModel.stream().collect(Collectors.joining(", ")));
								}

								if (!queueStatus.getUnmanagedTopics().isEmpty()) {
									queueStatus.setWarning("There are unmanaged topics subscribed. This could potentially lead to unexpected messages received by the queue.");
								}

							} else {

								// check whether all modeled topics are managed
								if (!topicsFromModel.isEmpty()) {
									queueStatus.getUnsubscribedTopics().addAll(topicsFromModel);
									queueStatus.setError("Missing topic subscriptions: " + topicsFromModel.stream().collect(Collectors.joining(", ")));
								}
							}

							// check all webhook registrations
							ArrayNode webhooks = webhookManagementClient.getRegisteredWebhooks();
							webhooks.forEach(w -> {
								String webhookAddress = w.get("address").asText();
								if (webhookAddress.endsWith(createdQueueName)) {
									queueStatus.getWebhooks().add(verbose ? w : w.get("name").asText());
								} else {
									result.getUnmanagedWebhooks().add(verbose ? w : w.get("name").asText());
								}
							});

							if (queueStatus.getWebhooks().isEmpty() && !queueStatus.getTopics().isEmpty()) {
								queueStatus.setError("No webhook registration available.");
							}

						} catch (IOException e) {
							logger.debug("Could not retrieve status of tenant '{}' for queue '{}' of service '{}'", tenantId, createdQueueName, getName(), e);
						}
					} else {
						result.getUnmanagedQueues().add(verbose ? createdQueue : createdQueueName);
					}
				});

				if (queueStatus.getQueue() == null) {
					queueStatus.setInitial();
				}
			} else {
				queueStatus.setOnboarding();
			}
		} catch (IOException e) {
			queueStatus.setError("The status of the tenant could not be retrieved.");
			logger.debug("The status of the tenant '{}' on service '{}' could not be retrieved.", tenantId, getName(), e);
		}
		return result;
	}

	private boolean checkTenantReadiness(String tenantId, boolean skipOnOnboarding) throws IOException {
		// check whether the messaging bus was created before initializing
		while(!managementClient.checkTenantInstanceReadiness()) {
			logger.info("The messaging bus for service '{}' is not yet ready for the tenant '{}'", getName(), tenantId);
			if (skipOnOnboarding) {
				return false;
			} else {
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) { // NOSONAR
					Thread.currentThread().interrupt();
					return false;
				}
			}
		}
		logger.debug("The messaging bus for service '{}' is ready for the tenant '{}'", getName(), tenantId);
		return true;
	}

	private String checkSubdomain(String tenantId) {
		UserInfo userInfo = RequestContext.getCurrent(runtime).getUserInfo();
		if (!Objects.equals(userInfo.getTenant(), tenantId) || StringUtils.isEmpty((String) userInfo.getAdditionalAttribute(SubdomainUtils.USER_INFO_SUBDOMAIN))) {
			throw new ErrorStatusException(CdsErrorStatuses.NO_TENANT_INFO);
		}
		return (String) userInfo.getAdditionalAttribute(SubdomainUtils.USER_INFO_SUBDOMAIN);
	}

	/**
	 * @return the queue listener of the service.
	 */
	public MessagingBrokerQueueListener getQueueListener() {
		return queueListener;
	}

	@Override
	public void init() {
		// no initialization on startup in the SaaS environment
	}

	@Override
	protected void registerQueueListener(String queue, MessagingBrokerQueueListener listener) throws IOException {
		// we don't use queue listener in the SaaS environment (Webhook approach is used)
	}

	@Override
	protected void emitTopicMessage(String topic, TopicMessageEventContext messageEventContext) {
		try {
			webhookManagementClient.sendMessage(topic, toStringMessage(messageEventContext));
		} catch (IOException e) {
			throw new ErrorStatusException(CdsErrorStatuses.EVENT_EMITTING_FAILED, topic, e);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy