/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.component.workflow.execution.api; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.CommunicationService; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.common.NodeIdentifierUtils; import de.rcenvironment.core.communication.management.WorkflowHostService; import de.rcenvironment.core.notification.Notification; import de.rcenvironment.core.notification.NotificationService; import de.rcenvironment.core.notification.SimpleNotificationService; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.rpc.RemoteOperationException; import de.rcenvironment.core.utils.incubator.DebugSettings; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncExceptionListener; import de.rcenvironment.toolkit.modules.concurrency.api.CallablesGroup; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Handles the connection to the subscription service and the retrieval of "missed" notifications. * * @author Doreen Seider * @author Robert Mischke */ public class GenericSubscriptionManager { private static final String NOTIFICATION_PATTERN_WILDCARD = ".*"; private final GenericSubscriptionEventProcessor eventProcessor; private final WorkflowHostService workflowHostService; private final CommunicationService communicationService; private final Set<String> subscribedIds = new HashSet<String>(); private final boolean verboseLogging = DebugSettings.getVerboseLoggingEnabled(getClass()); private final Log log = LogFactory.getLog(getClass()); /** * Default constructor. * * @param model the {@link WorkflowStateModel} to apply received events on * @param communicationService the {@link CommunicationService} instance to use */ public GenericSubscriptionManager(GenericSubscriptionEventProcessor eventProcessor, CommunicationService communicationService, WorkflowHostService workflowHostService) { this.eventProcessor = eventProcessor; this.communicationService = communicationService; this.workflowHostService = workflowHostService; } private Set<String> updateSubscribedIds() { final Set<String> currentIdsToSubscribe = new HashSet<String>(); final Set<InstanceNodeSessionId> allWorkflowNodes = workflowHostService.getWorkflowHostNodesAndSelf(); for (InstanceNodeSessionId wfNode : allWorkflowNodes) { final String id = wfNode.getInstanceNodeSessionIdString(); currentIdsToSubscribe.add(id); } final Set<String> missingSubscribed = new HashSet<String>(currentIdsToSubscribe); missingSubscribed.removeAll(subscribedIds); // determine missing nodes/ids subscribedIds.retainAll(currentIdsToSubscribe); // purge unreachable nodes/ids return missingSubscribed; } /** * Subscribes to the relevant notification id and catches up with previous updates. It only considers "new" platforms, which where not * known during initialize. * * @param notificationIdPrefixes identifiers of notifications to subscribe */ public synchronized void updateSubscriptionsForPrefixes(String[] notificationIdPrefixes) { final Set<String> missingSubscribedIds = updateSubscribedIds(); // TODO (p2) deprecated final SimpleNotificationService sns = new SimpleNotificationService(); final CallablesGroup<Void> callablesGroup = ConcurrencyUtils.getFactory().createCallablesGroup(Void.class); for (final String missingId : missingSubscribedIds) { final InstanceNodeSessionId targetWorkflowHostNode = NodeIdentifierUtils.parseInstanceNodeSessionIdStringWithExceptionWrapping(missingId); for (final String notificationIdPrefix : notificationIdPrefixes) { callablesGroup.add(new Callable<Void>() { @Override @TaskDescription("Distributed console/input model notification subscriptions") public Void call() throws Exception { Map<String, Long> lastMissedNumbers = sns.subscribe( StringUtils.format("%s%s:" + NOTIFICATION_PATTERN_WILDCARD, notificationIdPrefix, targetWorkflowHostNode.getInstanceNodeIdString()), eventProcessor, targetWorkflowHostNode); retrieveMissedNotifications(sns, targetWorkflowHostNode, lastMissedNumbers); synchronized (subscribedIds) { subscribedIds.add(missingId); } return (Void) null; } }); } } callablesGroup.executeParallel(new AsyncExceptionListener() { @Override public void onAsyncException(Exception e1) { // unwrap ExecutionExceptions Throwable e; if (e1.getClass() == ExecutionException.class && e1.getCause() != null) { e = e1.getCause(); } else { e = e1; } if (e.getCause() == null) { // log a compressed message; this includes the case of RemoteOperationExceptions, which (by design) never have a "cause" log.warn( "Asynchronous exception during parallel console/input model notification subscriptions: " + e.toString()); } else { // on unexpected errors, log the full stacktrace log.error( "Asynchronous exception during parallel console/input model notification subscriptions", e); } } }); } private void retrieveMissedNotifications(SimpleNotificationService sns, InstanceNodeSessionId targetNode, Map<String, Long> lastMissedNumbers) throws RemoteOperationException { for (String notifId : lastMissedNumbers.keySet()) { Long lastMissedNumber = lastMissedNumbers.get(notifId); if (lastMissedNumber != NotificationService.NO_MISSED) { eventProcessor.setNumberOfLastMissingNotification(notifId, targetNode.getInstanceNodeSessionIdString(), lastMissedNumber); if (verboseLogging) { log.debug(StringUtils.format("Starting to fetch stored notifications for id %s from node %s", notifId, targetNode)); } Map<String, List<Notification>> storedNotifications = sns.getNotifications(notifId, targetNode); if (verboseLogging) { log.debug(StringUtils.format("Received %d stored notification entries for id %s from node %s", storedNotifications.size(), notifId, targetNode)); for (Entry<String, List<Notification>> e : storedNotifications.entrySet()) { log.debug(StringUtils.format(" Received %d notifications for topic %s", e.getValue().size(), e.getKey())); } } for (List<Notification> notifications : storedNotifications.values()) { eventProcessor.receiveBatchedNotifications(notifications); } } } } }