/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.gui.workflow.parts; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.InvalidRegistryObjectException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.ImageFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.Panel; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.Request; import org.eclipse.gef.RequestConstants; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.views.properties.IPropertySource; import de.rcenvironment.core.communication.common.LogicalNodeId; import de.rcenvironment.core.component.api.ComponentConstants; import de.rcenvironment.core.component.execution.api.Component; import de.rcenvironment.core.component.execution.api.ComponentExecutionInformation; import de.rcenvironment.core.component.execution.api.ComponentState; import de.rcenvironment.core.component.model.api.ComponentInterface; import de.rcenvironment.core.component.model.api.ComponentShape; import de.rcenvironment.core.component.model.api.ComponentSize; import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionInformation; import de.rcenvironment.core.component.workflow.model.api.WorkflowNode; import de.rcenvironment.core.gui.workflow.Activator; import de.rcenvironment.core.gui.workflow.view.ComponentRuntimeView; import de.rcenvironment.core.gui.workflow.view.properties.ComponentInstancePropertySource; import de.rcenvironment.core.notification.Notification; import de.rcenvironment.core.notification.NotificationSubscriber; 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.toolkit.modules.concurrency.api.BatchAggregator; import de.rcenvironment.toolkit.modules.concurrency.api.BatchProcessor; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Read-only EditPart representing a WorkflowNode, storing * additional workflow execution information. * * @author Heinrich Wendel * @author Doreen Seider * @author Martin Misiak */ public class WorkflowRunNodePart extends ReadOnlyWorkflowNodePart { private static final Log LOGGER = LogFactory.getLog(WorkflowRunNodePart.class); // It is intended that the component state figure is updated at most every 400msec (can be // adapted on demand). // Thus, the max batch size is large - seid_do private static final int MAX_BATCH_SIZE = 10000; private static final long MAX_BATCH_LATENCY_MSEC = 400; private static final long UNDEFINED = -1; private final NotificationSubscriber stateChangeListener; private final BatchAggregator<ComponentState> batchAggregator; private ComponentStateFigure stateFigure; private boolean initializeStatusTriggered = false; private volatile long lastStateNotificationNumber = UNDEFINED; private volatile long lastIterationCountNotificationNumber = UNDEFINED; private Label runCountLabel; public WorkflowRunNodePart() { stateChangeListener = new ComponentStateChangeListener(this); BatchProcessor<ComponentState> batchProcessor = new BatchProcessor<ComponentState>() { @Override public void processBatch(final List<ComponentState> batch) { updateComponentState(batch.get(batch.size() - 1)); } }; batchAggregator = ConcurrencyUtils.getFactory().createBatchAggregator(MAX_BATCH_SIZE, MAX_BATCH_LATENCY_MSEC, batchProcessor); } @Override public void refresh() { super.refresh(); synchronized (batchAggregator) { if (!initializeStatusTriggered) { Job job = new Job(StringUtils.format(Messages.initializingComponentState, ((WorkflowNode) getModel()).getName())) { @Override protected IStatus run(IProgressMonitor monitor) { initializeStatus(); return Status.OK_STATUS; } }; job.schedule(); // currently, there is no kind of retry, thus, submitting the job calling // initializeStatus() is seen as initialization was // performed not matter it was successful or not initializeStatusTriggered = true; } } } @Override protected String generateTooltipText() { return generateTooltipTextBase((WorkflowNode) getModel()); } @Override protected IFigure createFigure() { // get the plain figure from the parent implementation final IFigure figure = super.createBaseFigure(); // enhance the figure with an activity display element stateFigure = new ComponentStateFigureImpl(); createExecutionCountLabel(); final int stateFigureWidthHeight = 22; final int executionLabelHeight = 15; ComponentInterface ci = ((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision().getComponentInterface(); if (ci.getShape() == ComponentShape.CIRCLE) { stateFigure.setBounds(new Rectangle(1, 1, stateFigureWidthHeight, stateFigureWidthHeight)); final int x = 19; final int executionLabelWidth = 15; runCountLabel.setBounds(new Rectangle(x, 2, executionLabelWidth, executionLabelHeight)); } else if (ci.getSize() == ComponentSize.SMALL) { final int xy = -2; stateFigure.setBounds(new Rectangle(xy, xy, stateFigureWidthHeight, stateFigureWidthHeight)); final int x = 16; final int y = -1; final int executionLabelWidth = 21; runCountLabel.setBounds(new Rectangle(x, y, executionLabelWidth, executionLabelHeight)); } else { final int x = 22; final int executionLabelWidth = 50; runCountLabel.setBounds(new Rectangle(x, 1, executionLabelWidth, executionLabelHeight)); stateFigure.setBounds(new Rectangle(0, 0, stateFigureWidthHeight, stateFigureWidthHeight)); } figure.add(stateFigure); figure.add(runCountLabel); return figure; } private void createExecutionCountLabel() { runCountLabel = new Label("-"); runCountLabel.setTextPlacement(PositionConstants.NORTH_EAST); runCountLabel.setTextAlignment(PositionConstants.RIGHT); runCountLabel.setLabelAlignment(PositionConstants.RIGHT); runCountLabel.setVisible(true); runCountLabel.setOpaque(false); runCountLabel.setToolTip(new Label("Runs: -")); } @Override public void performRequest(Request req) { if (req.getType().equals(RequestConstants.REQ_OPEN)) { Job job = new Job("Opening view") { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Retrieving workflow information", 2); monitor.worked(1); monitor.worked(2); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { openDefaultView(); } }); return Status.OK_STATUS; } finally { monitor.done(); } }; }; job.setUser(true); job.schedule(); } } @Override public Object getAdapter(@SuppressWarnings("rawtypes") Class type) { if (type == IPropertySource.class) { return new ComponentInstancePropertySource(getWorkflowExecutionInformation(), ((WorkflowNode) getModel()).getIdentifier()); } return super.getAdapter(type); } private WorkflowExecutionInformation getWorkflowExecutionInformation() { return (WorkflowExecutionInformation) ((WorkflowExecutionInformationPart) ((WorkflowPart) getParent()).getParent()).getModel(); } private ComponentExecutionInformation getComponentExecutionInformation() { return getWorkflowExecutionInformation().getComponentExecutionInformation(((WorkflowNode) getModel()).getIdentifier()); } private void initializeStatus() { try { final ComponentExecutionInformation compExeInfo = getComponentExecutionInformation(); final String stateNotifId = ComponentConstants.STATE_NOTIFICATION_ID_PREFIX + compExeInfo.getExecutionIdentifier(); final String iterationCountNotifId = ComponentConstants.ITERATION_COUNT_NOTIFICATION_ID_PREFIX + compExeInfo.getExecutionIdentifier(); final LogicalNodeId ctrlerNode = getWorkflowExecutionInformation().getNodeId(); final SimpleNotificationService notificationService = new SimpleNotificationService(); notificationService.subscribe(stateNotifId, stateChangeListener, ctrlerNode); final List<Notification> stateNotifications = notificationService.getNotifications(stateNotifId, ctrlerNode).get(stateNotifId); if (stateNotifications != null && stateNotifications.size() > 0) { handleStateNotification(getLastNonDisposedStateNotification(stateNotifications)); } notificationService.subscribe(iterationCountNotifId, stateChangeListener, ctrlerNode); final List<Notification> iterationCountNotifs = notificationService.getNotifications(iterationCountNotifId, ctrlerNode).get(iterationCountNotifId); if (iterationCountNotifs != null && iterationCountNotifs.size() > 0) { handleExecutionCountNotification(iterationCountNotifs.get(iterationCountNotifs.size() - 1)); } } catch (NullPointerException e) { // TODO review: can this still occur after 7.0.0 error handling changes? LOGGER.error("Could not initialize status.", e); } catch (RemoteOperationException e) { LOGGER.error("Failed to register workflow state change listeners: " + e.getMessage()); } } private Notification getLastNonDisposedStateNotification(List<Notification> stateNotifications) { for (int i = 1; i <= stateNotifications.size(); i++) { Notification notification = stateNotifications.get(stateNotifications.size() - i); ComponentState state = ComponentState.valueOf((String) notification.getBody()); if (state != ComponentState.DISPOSED && state != ComponentState.DISPOSING) { return notification; } } return stateNotifications.get(stateNotifications.size() - 1); } private void openDefaultView() { final ComponentExecutionInformation cid = getComponentExecutionInformation(); IExtensionRegistry extReg = Platform.getExtensionRegistry(); IConfigurationElement[] confElements = extReg.getConfigurationElementsFor("de.rcenvironment.core.gui.workflow.monitoring"); //$NON-NLS-1$ IConfigurationElement[] viewConfElements = extReg.getConfigurationElementsFor("org.eclipse.ui.views"); //$NON-NLS-1$ boolean foundRuntimeView = false; for (final IConfigurationElement confElement : confElements) { if (cid.getComponentIdentifier().startsWith(confElement.getAttribute("component")) && confElement.getAttribute("default") != null && Boolean.TRUE.toString().matches(confElement.getAttribute("default"))) { //$NON-NLS-1$ for (final IConfigurationElement viewConfElement : viewConfElements) { if (viewConfElement.getAttribute("id").equals(confElement.getAttribute("view"))) { try { final IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(). showView(viewConfElement.getAttribute("class"), cid.getExecutionIdentifier(), IWorkbenchPage.VIEW_VISIBLE); //$NON-NLS-1$ ConcurrencyUtils.getAsyncTaskService().execute(new Runnable() { @Override @TaskDescription("Initialize component runtime view data") public void run() { ((ComponentRuntimeView) view).initializeData(cid); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { ((ComponentRuntimeView) view).initializeView(); } }); } }); PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(view); foundRuntimeView = true; break; } catch (PartInitException e) { throw new RuntimeException(e); } catch (InvalidRegistryObjectException e) { throw new RuntimeException(e); } } } } } if (!foundRuntimeView) { // If not existent: open properties view try { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(). showView("org.eclipse.ui.views.PropertySheet"); //$NON-NLS-1$ } catch (PartInitException e) { throw new RuntimeException(e); } } } protected synchronized void handleStateNotification(Notification notification) { long notificationNumber = notification.getHeader().getNumber(); if (notificationNumber < lastStateNotificationNumber) { return; } try { final ComponentState state = ComponentState.valueOf((String) notification.getBody()); if (state != null && state != ComponentState.DISPOSING && state != ComponentState.DISPOSED) { batchAggregator.enqueue(state); lastStateNotificationNumber = notificationNumber; } } catch (IllegalArgumentException e) { e = null; // notification is ignored because notification doesn't contain ComponentState, e.g. if // last notification is sent before publisher is removed } } protected void updateComponentState(final ComponentState state) { if (stateFigure != null) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { stateFigure.setState(state); stateFigure.setToolTip(new Label("State: " + state.getDisplayName())); } }); } } protected synchronized void handleExecutionCountNotification(final Notification notification) { long notificationNumber = notification.getHeader().getNumber(); if (notificationNumber < lastIterationCountNotificationNumber) { return; } if (notification.getBody() instanceof String) { final String[] counts = StringUtils.splitAndUnescape((String) notification.getBody()); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (counts.length == 1) { runCountLabel.setText(counts[0]); runCountLabel.setToolTip(new Label("Runs: " + counts[0])); } else { runCountLabel.setText(String.valueOf(counts.length) + "/" + String.valueOf(Integer.parseInt(counts[counts.length - 1]) - Integer.parseInt(counts[counts.length - 2]))); int innerLoopCount = 1; int previousCount = 0; String countString = ""; for (String count : counts) { int countInt = Integer.parseInt(count); countString += innerLoopCount + "/" + (countInt - previousCount) + "\n"; innerLoopCount++; previousCount = countInt; } runCountLabel.setToolTip(new Label("Nested loop run/component runs\n" + countString + "Total component runs: " + counts[counts.length - 1])); } } }); lastIterationCountNotificationNumber = notificationNumber; } } /** * Indicates the state of {@link Component}s via a figure. * * @author Chrisitian Weiss */ public interface ComponentStateFigure extends IFigure { /** * @param state of {@link Component}. */ void setState(ComponentState state); /** * @return state of {@link Component}. */ ComponentState getState(); } /** * Implementation of {@link ComponentStateFigure}. * * @author Christian Weiss */ public final class ComponentStateFigureImpl extends Panel implements ComponentStateFigure { private final ImageFigure innerImageFigure = new ImageFigure(Activator.getInstance().getImageRegistry() .get(ComponentState.UNKNOWN.name())); { innerImageFigure.setOpaque(false); add(innerImageFigure); setVisible(true); setOpaque(false); } private ComponentState state; @Override public void setBounds(final Rectangle rect) { super.setBounds(rect); final Rectangle innerRectangleBounds = new Rectangle(rect.x + 3, rect.y + 3, rect.width - 6, rect.height - 6); innerImageFigure.setBounds(innerRectangleBounds); } @Override public void setState(ComponentState state) { if (this.state == state) { return; } this.state = state; Image stateImage = Activator.getInstance().getImageRegistry().get(state.name()); if (stateImage != null) { innerImageFigure.setImage(stateImage); } else { innerImageFigure.setImage(Activator.getInstance().getImageRegistry().get(ComponentState.UNKNOWN.name())); } innerImageFigure.setOpaque(false); setVisible(true); setOpaque(false); refresh(); } @Override protected void paintFigure(Graphics graphics) {} @Override public ComponentState getState() { return state; } } }