/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.component.workflow.execution.headless.internal; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.common.ResolvableNodeId; import de.rcenvironment.core.component.execution.api.ConsoleRow; import de.rcenvironment.core.component.execution.api.SingleConsoleRowsProcessor; import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionContext; import de.rcenvironment.core.component.workflow.execution.api.WorkflowState; import de.rcenvironment.core.component.workflow.execution.headless.api.ConsoleRowSubscriber; import de.rcenvironment.core.component.workflow.execution.headless.api.HeadlessWorkflowExecutionContext; import de.rcenvironment.core.component.workflow.execution.headless.api.HeadlessWorkflowExecutionService.DeletionBehavior; import de.rcenvironment.core.component.workflow.execution.headless.api.HeadlessWorkflowExecutionService.DisposalBehavior; import de.rcenvironment.core.notification.DistributedNotificationService; import de.rcenvironment.core.notification.NotificationSubscriber; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.rpc.RemoteOperationException; import de.rcenvironment.core.utils.common.textstream.TextOutputReceiver; /** * Encapsulates the specific information for a single workflow execution. * * @author Robert Mischke * @author Doreen Seider */ public class ExtendedHeadlessWorkflowExecutionContext implements HeadlessWorkflowExecutionContext { private final Log log = LogFactory.getLog(getClass()); private final HeadlessWorkflowExecutionContext headlessWfExeContext; private final CountDownLatch workflowFinishedLatch; private final CountDownLatch consoleOutputFinishedLatch; private final CountDownLatch workflowDisposedLatch; private final List<Closeable> resourcesToCloseOnFinish = new ArrayList<>(); private final List<NotificationSubscription> notificationSubscriptionsToUnsubscribeOnFinish = new ArrayList<>(); private final long startTimestampMillis; private long executionDurationMillis; // informational; not needed for execution - seid_do private WorkflowExecutionContext wfExeContext; // informational; not needed for execution - misc_ro private WorkflowState finalState; public ExtendedHeadlessWorkflowExecutionContext(HeadlessWorkflowExecutionContext headlessWfExeContext) { this.headlessWfExeContext = headlessWfExeContext; // wait for two events: the "end of console output marker", and the workflow reaching a finished state; using two separate latches // to ensure a duplicate event cannot count for the other type -misc_ro workflowFinishedLatch = new CountDownLatch(1); consoleOutputFinishedLatch = new CountDownLatch(1); workflowDisposedLatch = new CountDownLatch(1); // to keep is simple the actual time the workflow is started is expected to be very close to the instantiation of this class, if a // more precise workflow execution time is needed, the time should be set from the workflow execution code right before the actual // start -seid_do startTimestampMillis = System.currentTimeMillis(); } protected void setWorkflowExecutionContext(WorkflowExecutionContext wfExeCtx) { this.wfExeContext = wfExeCtx; } protected WorkflowExecutionContext getWorkflowExecutionContext() { return wfExeContext; } protected void addOutput(String verboseOutput) { addOutput(null, verboseOutput); } protected void addOutput(String compactOutput, String verboseOutput) { if (headlessWfExeContext.getTextOutputReceiver() != null) { if (isCompactOutput()) { if (compactOutput != null && !compactOutput.isEmpty()) { headlessWfExeContext.getTextOutputReceiver().addOutput(compactOutput); } } else { if (verboseOutput != null && !verboseOutput.isEmpty()) { headlessWfExeContext.getTextOutputReceiver().addOutput(verboseOutput); } } } } /** * @param consoleRow called if new console row was received, e.g. by the {@link ConsoleRowSubscriber} */ public final void reportConsoleRowReceived(ConsoleRow consoleRow) { if (headlessWfExeContext.getSingleConsoleRowReceiver() != null) { headlessWfExeContext.getSingleConsoleRowReceiver().onConsoleRow(consoleRow); } } protected synchronized void reportWorkflowNotAliveAnymore(String errorMessage) { log.error(StringUtils.format("Final state of workflow '%s' (%s) is %s - %s", getWorkflowExecutionContext().getInstanceName(), wfExeContext.getExecutionIdentifier(), WorkflowState.UNKNOWN.getDisplayName(), errorMessage)); finalState = WorkflowState.UNKNOWN; workflowFinishedLatch.countDown(); consoleOutputFinishedLatch.countDown(); } /** * @param newState new {@link WorkflowState} */ protected synchronized void reportWorkflowTerminated(WorkflowState newState) { if (this.finalState != null) { log.warn(StringUtils.format("Workflow '%s' (%s) was already marked as %s, new final state: %s (%s)", getWorkflowExecutionContext().getInstanceName(), wfExeContext.getExecutionIdentifier(), finalState.getDisplayName(), newState.getDisplayName(), getWorkflowFile().getAbsolutePath())); } this.finalState = newState; if (finalState != WorkflowState.FINISHED) { addOutput(StringUtils.format("'%s' terminated abnormally: %s; check log and console output for details", getWorkflowFile().getName(), finalState.getDisplayName())); } log.debug(StringUtils.format("Workflow '%s' (%s) has terminated, final state: %s (%s)", getWorkflowExecutionContext().getInstanceName(), wfExeContext.getExecutionIdentifier(), finalState.getDisplayName(), getWorkflowFile())); executionDurationMillis = System.currentTimeMillis() - startTimestampMillis; workflowFinishedLatch.countDown(); } public synchronized long getExecutionDuration() { return executionDurationMillis; } /** * @param newState new {@link WorkflowState} */ protected synchronized void reportWorkflowDisposed(WorkflowState newState) { log.debug(StringUtils.format("Workflow '%s' (%s) is done, disposed: %s (%s)", getWorkflowExecutionContext().getInstanceName(), wfExeContext.getExecutionIdentifier(), newState == WorkflowState.DISPOSED, getWorkflowFile())); workflowDisposedLatch.countDown(); } /** * Reports that all console output was received. */ public void reportConsoleOutputTerminated() { consoleOutputFinishedLatch.countDown(); } /** * Awaits termination. * * @return {@link WorkflowState} after termination * @throws InterruptedException on error */ protected WorkflowState waitForTermination() throws InterruptedException { // TODO add timeout and workflow heartbeat checking workflowFinishedLatch.await(); consoleOutputFinishedLatch.await(); synchronized (this) { return finalState; } } /** * Awaits disposal. * * @return {@link WorkflowState} after termination * @throws InterruptedException on error */ protected void waitForDisposal() throws InterruptedException { // TODO add timeout and workflow heartbeat checking workflowDisposedLatch.await(); } /** * Registers a resource which must be closed if workflow is terminated. * * @param subscriber {@link Closeable} subcriber to register */ protected synchronized void registerResourceToCloseOnFinish(Closeable resource) { resourcesToCloseOnFinish.add(resource); } /** * Registers a NS which must be closed if workflow is terminated. * * @param subscriber {@link Closeable} subcriber to register */ protected synchronized void registerNotificationSubscriptionsToUnsubscribeOnFinish(NotificationSubscription subscriber) { notificationSubscriptionsToUnsubscribeOnFinish.add(subscriber); } /** * Closes resources added via {@link #registerResourceToCloseOnFinish(Closeable)}. */ protected void closeResourcesQuietly() { for (Closeable resource : getResourcesToCloseOnFinish()) { try { resource.close(); } catch (IOException e) { log.warn(StringUtils.format("Error closing resource after end of workflow '%s' (%s) ", wfExeContext.getInstanceName(), wfExeContext.getExecutionIdentifier()), e); } } } /** * Unsubscribes {@link NotificationSubscriber}s. */ protected void unsubscribeNotificationSubscribersQuietly(DistributedNotificationService notificationService) { for (NotificationSubscription subscription : getNotificationSubscribersToUnsubscribeOnFinish()) { try { notificationService.unsubscribe(subscription.notificationId, subscription.subscriber, subscription.nodeId); } catch (RemoteOperationException e) { log.warn(StringUtils.format("Failed to unsubscribe %s from notification service (workflow '%s' (%s)", subscription.subscriber.getClass(), wfExeContext.getInstanceName(), wfExeContext.getExecutionIdentifier()), e); } } } private synchronized List<Closeable> getResourcesToCloseOnFinish() { return new ArrayList<>(resourcesToCloseOnFinish); } private synchronized List<NotificationSubscription> getNotificationSubscribersToUnsubscribeOnFinish() { return new ArrayList<>(notificationSubscriptionsToUnsubscribeOnFinish); } @Override public File getWorkflowFile() { return headlessWfExeContext.getWorkflowFile(); } @Override public File getPlaceholdersFile() { return headlessWfExeContext.getPlaceholdersFile(); } @Override public File getLogDirectory() { return headlessWfExeContext.getLogDirectory(); } @Override public File[] getLogFiles() { return headlessWfExeContext.getLogFiles(); } @Override public TextOutputReceiver getTextOutputReceiver() { return headlessWfExeContext.getTextOutputReceiver(); } @Override public SingleConsoleRowsProcessor getSingleConsoleRowReceiver() { return headlessWfExeContext.getSingleConsoleRowReceiver(); } @Override public DisposalBehavior getDisposalBehavior() { return headlessWfExeContext.getDisposalBehavior(); } @Override public DeletionBehavior getDeletionBehavior() { return headlessWfExeContext.getDeletionBehavior(); } @Override public boolean shouldAbortIfWorkflowUpdateRequired() { return headlessWfExeContext.shouldAbortIfWorkflowUpdateRequired(); } @Override public boolean isCompactOutput() { return headlessWfExeContext.isCompactOutput(); } /** * Encapsulates information about {@link NotificationSubscriber} subscribed. * * @author Doreen Seider * */ protected class NotificationSubscription { protected NotificationSubscriber subscriber; protected String notificationId; protected ResolvableNodeId nodeId; } }