com.sap.cds.feature.messaging.em.mt.service.EnterpriseMessagingMtService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-feature-enterprise-messaging Show documentation
Show all versions of cds-feature-enterprise-messaging Show documentation
Enterprise Messaging feature for CDS Services Java
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