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

org.openbp.cockpit.plugins.debugger.DebuggerPlugin Maven / Gradle / Ivy

The newest version!
/*
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package org.openbp.cockpit.plugins.debugger;

import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingUtilities;

import org.openbp.cockpit.Cockpit;
import org.openbp.cockpit.itemeditor.NodeItemEditorPlugin;
import org.openbp.cockpit.modeler.Modeler;
import org.openbp.cockpit.modeler.ModelerColors;
import org.openbp.cockpit.modeler.ModelerGraphics;
import org.openbp.cockpit.modeler.ViewModeMgr;
import org.openbp.cockpit.modeler.figures.generic.BasicFigure;
import org.openbp.cockpit.modeler.figures.generic.XFigure;
import org.openbp.cockpit.modeler.figures.process.FlowConnection;
import org.openbp.cockpit.modeler.figures.process.NodeFigure;
import org.openbp.cockpit.modeler.figures.process.ProcessElementContainer;
import org.openbp.cockpit.modeler.figures.process.SocketFigure;
import org.openbp.cockpit.modeler.figures.spline.PolySplineConnection;
import org.openbp.cockpit.modeler.figures.tag.AbstractTagFigure;
import org.openbp.cockpit.modeler.util.FigureResources;
import org.openbp.common.ExceptionUtil;
import org.openbp.common.logger.LogUtil;
import org.openbp.common.setting.SettingUtil;
import org.openbp.core.OpenBPException;
import org.openbp.core.engine.debugger.DebuggerEvent;
import org.openbp.core.engine.debugger.DebuggerService;
import org.openbp.core.model.ModelQualifier;
import org.openbp.core.model.item.ItemTypes;
import org.openbp.core.model.item.process.ControlLink;
import org.openbp.core.model.item.process.InitialNode;
import org.openbp.core.model.item.process.Node;
import org.openbp.core.model.item.process.NodeSocket;
import org.openbp.core.model.item.process.ProcessItem;
import org.openbp.core.model.item.process.SingleSocketNode;
import org.openbp.guiclient.event.OpenEvent;
import org.openbp.guiclient.event.QualifierEvent;
import org.openbp.guiclient.remote.ServerConnection;
import org.openbp.guiclient.util.ClientFlavors;
import org.openbp.jaspira.action.JaspiraAction;
import org.openbp.jaspira.action.JaspiraActionEvent;
import org.openbp.jaspira.decoration.DecorationMgr;
import org.openbp.jaspira.decoration.Decorator;
import org.openbp.jaspira.decoration.FilteredDecorator;
import org.openbp.jaspira.decoration.ListDecorator;
import org.openbp.jaspira.event.AskEvent;
import org.openbp.jaspira.event.InteractionEvent;
import org.openbp.jaspira.event.JaspiraEvent;
import org.openbp.jaspira.event.JaspiraEventHandlerCode;
import org.openbp.jaspira.option.Option;
import org.openbp.jaspira.option.OptionMgr;
import org.openbp.jaspira.plugin.AbstractPlugin;
import org.openbp.jaspira.plugin.EventModule;
import org.openbp.jaspira.plugin.InteractionModule;
import org.openbp.jaspira.plugins.errordialog.ErrorEvent;
import org.openbp.swing.components.JMsgBox;

import CH.ifa.draw.framework.Figure;

/**
 * Simple Plugin that handles debug communication with the OpenBP server.
 *
 * @author Stephan Moritz
 */
public class DebuggerPlugin extends AbstractPlugin
{
	/** Link trace flag: Skip link */
	public static final Integer LINKTRACE_SKIP = new Integer(0);

	/** Link trace flag: Show link target only */
	public static final Integer LINKTRACE_TARGET = new Integer(1);

	/** Link trace flag: Show link animation and stop at target */
	public static final Integer LINKTRACE_ANIMATION_STOP = new Integer(2);

	/** Link trace flag: Show link animation and continue execution */
	public static final Integer LINKTRACE_ANIMATION_GO = new Integer(3);

	/** The clientId under which we are connected to the debugger service */
	private String clientId;

	/** The DebuggerService used to connect to the server */
	private static DebuggerService debuggerService;

	/** Thread that polls for debugger events */
	private PollingThread poller;

	/** True while the poller is running */
	private boolean pollerActive;

	/** Current position of a halted process */
	private ModelQualifier haltedPosition;

	/** Decorator for current position of halted process (frame stroke and color) */
	private StepDecorator stepDecorator;

	/** Decorator for breakpoints */
	private BreakPointDecorator breakpointDecorator;

	/** The thread responsible for handling animation of connections */
	private AnimationThread animator;

	/** Current process */
	private ProcessItem currentProcess;

	/** Control link trace mode (see the LINK_* constants) */
	private Integer controlLinkTraceMode;

	/** Data link trace mode (see the LINK_* constants) */
	private Integer dataLinkTraceMode;

	/** Toggle breakpoints action */
	private JaspiraAction toggleBreakpointsAction;

	/** Clear breakpoints action */
	private JaspiraAction clearBreakpointsAction;

	/** Break on workflow action */
	private JaspiraAction setBreakOnTopLevelAction;

	/** Break on top level action */
	private JaspiraAction setBreakOnWorkflowAction;

	/** Break on exception action */
	private JaspiraAction setBreakOnExceptionAction;

	/** Step into action */
	private JaspiraAction stepIntoAction;

	/** Step over action */
	private JaspiraAction stepOverAction;

	/** Step out action */
	private JaspiraAction stepOutAction;

	/** Step next action */
	private JaspiraAction stepNextAction;

	/** Resume action */
	private JaspiraAction resumeAction;

	/** Stop action */
	private JaspiraAction stopAction;

	/** Maximum number of debugger animation steps */
	private int maxStepCount;

	/** Maximum debugger animation step time in ms */
	private int maxStepTime;

	//////////////////////////////////////////////////
	// @@ Init/Activate
	//////////////////////////////////////////////////

	public String getResourceCollectionContainerName()
	{
		return "plugin.debugger";
	}

	// TODO The variables used in this method are not used currently.
	// This method exists in order to prevent compiler warnings about unused private variables.
	protected void keepCompilerHappy()
	{
		if (controlLinkTraceMode == 0 && dataLinkTraceMode == 0 && currentProcess == null)
			currentProcess = null;
	}

	/**
	 * @see org.openbp.jaspira.plugin.AbstractPlugin#getExternalOptionModuleClasses()
	 */
	protected Class[] getExternalOptionModuleClasses()
	{
		return new Class[]
		{
			DebuggerOptionModule.class
		};
	}

	/**
	 * Called when the first instance of this plugin is being created.
	 * Registers module with DebuggerService.
	 */
	public void installFirstPlugin()
	{
		super.installFirstPlugin();

		// Read Cockpit.properties animation settings
		maxStepCount = SettingUtil.getIntSetting("openbp.cockpit.debugger.maxstepcount", - 1);
		maxStepTime = SettingUtil.getIntSetting("openbp.cockpit.debugger.maxsteptime", - 1);

		registerDebugger(false);
	}

	/**
	 * Gets debugger service.
	 *
	 * @return The service
	 * @throws OpenBPException If there is no server to connect to
	 */
	DebuggerService getDebugger()
	{
		if (debuggerService == null)
		{
			if (! registerDebugger(false))
			{
				String msg = getPluginResourceCollection().getRequiredString("messages.nodebuggerservice");
				throw new OpenBPException("NoDebugger", msg);
			}
		}
		return debuggerService;
	}

	/**
	 * Registers the debugger.
	 * @param silentMode 
	 * true: Does not log an error message on failure.
* false: Logs any error message * @return * true: Registration successful.
* false: Error */ boolean registerDebugger(boolean silentMode) { debuggerService = (DebuggerService) ServerConnection.getInstance().lookupOptionalService(DebuggerService.class); if (debuggerService == null) return false; try { // Register with the service, 1 h timeout String givenId = OptionMgr.getInstance().getStringOption("debugger.debuggerid", null); clientId = debuggerService.registerClient(givenId, 60 * 60); // Adjust the debugger mode int mode = getDebugger().getDebuggerMode(clientId); int oldMode = mode; boolean skipSystemModel = OptionMgr.getInstance().getBooleanOption("debugger.skipsystemmodel", true); if (skipSystemModel) mode |= DebuggerService.MODE_SKIP_SYSTEM_MODEL; else mode &= ~ DebuggerService.MODE_SKIP_SYSTEM_MODEL; if (mode != oldMode) { getDebugger().setDebuggerMode(clientId, mode); } } catch (Exception e) { if (! silentMode) { LogUtil.error(getClass(), "Error connecting to the debugger service.", e); } debuggerService = null; return false; } startPoller(); return true; } void unregisterDebugger() { if (clientId != null) { // Cause the debugger polling thread to finish stopPoller(); if (debuggerService != null) { try { getDebugger().unregisterClient(clientId); } catch (Exception e) { ExceptionUtil.printTrace(e); clientId = null; } debuggerService = null; } } } /** * Displays a message box and tries to re-register the debugger * if a connection is available. */ private void handleError(Exception e) { String msg = ExceptionUtil.getNestedMessage(e); JMsgBox.show(null, msg, JMsgBox.ICON_INFO); } /** * Called after the install method of this plugin has been called. */ protected void pluginInstalled() { stepDecorator = new StepDecorator(); toggleBreakpointsAction = getAction("debugger.client.togglebreakpoints"); clearBreakpointsAction = getAction("debugger.client.clearbreakpoints"); setBreakOnTopLevelAction = getAction("debugger.client.setbreakontoplevel"); setBreakOnWorkflowAction = getAction("debugger.client.setbreakonworkflow"); setBreakOnExceptionAction = getAction("debugger.client.setbreakonexception"); stepIntoAction = getAction("debugger.client.stepinto"); stepOverAction = getAction("debugger.client.stepover"); stepOutAction = getAction("debugger.client.stepout"); stepNextAction = getAction("debugger.client.stepnext"); resumeAction = getAction("debugger.client.resume"); stopAction = getAction("debugger.client.stop"); try { int mode = getDebugger().getDebuggerMode(clientId); if (setBreakOnTopLevelAction != null) { setBreakOnTopLevelAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_TOP_LEVEL) != 0); } if (setBreakOnWorkflowAction != null) { setBreakOnWorkflowAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_WORKFLOW) != 0); } if (setBreakOnExceptionAction != null) { setBreakOnExceptionAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_EXCEPTION) != 0); } } catch (Exception e) { } updateActions(); } /** * Performs clean up (unregistering of decorators). */ protected void pluginUninstalled() { super.pluginUninstalled(); deactivateStepDecorator(); deactivateBreakpointDecorator(); } /** * Called after uninstall for the last instance has been called. */ public void uninstallLastPlugin() { super.uninstallLastPlugin(); unregisterDebugger(); } ////////////////////////////////////////////////// // @@ Current position and view update ////////////////////////////////////////////////// /** * Sets the current position of a halted process. * This will also update the current position marker and clear the context inspector. * * @param newPos ModelQualifier of the new position or null if there is no halted process.
* If the process that owns the element specified by the new position is not loaded yet, * it will be loaded automatically. The new position will also be scrolled into view. */ void setHaltedPosition(ModelQualifier newPos) { if (haltedPosition != null) { // Clear the current position, so the update will display the current object in its usual state. ModelQualifier oldPosition = haltedPosition; haltedPosition = null; // Invalidate the current position (will clear the current position marker) // This will also enforce a rebuild of the content of a tag. fireEvent(new QualifierEvent(DebuggerPlugin.this, "modeler.view.invalidate", oldPosition)); } // Update the position haltedPosition = newPos; // Activate or deactivator for the decorator for the current position if (haltedPosition != null) { activateStepDecorator(); } else { deactivateStepDecorator(); } if (newPos != null) { // First, make sure the process the new current position refers to is loaded ModelQualifier processQualifier = new ModelQualifier(newPos); processQualifier.setItemType(ItemTypes.PROCESS); processQualifier.setObjectPath(null); fireEvent(new OpenEvent(DebuggerPlugin.this, "open.modeler", processQualifier)); // Invalidate the new current position (will display the current position marker) // This will also enforce a rebuild of the content of a tag. fireEvent(new QualifierEvent(DebuggerPlugin.this, "modeler.view.invalidate", newPos)); // Move the object into view; add 100 px offset to prevent the object from hanging in the corner scrollIntoView(newPos, true); } if (animator != null) { // This will finish the spline animation if not finished yet animator.abort(); } // Cause a screen refresh of the invalidated figures fireEvent(new JaspiraEvent(DebuggerPlugin.this, "modeler.view.refresh")); // Update the action button status updateActions(); } /** * Shows the process element specified by the given position. * Also loads the process - if not loaded yet - that contains the element. * * @param position Current position * @param addEnlargement * true: Adds an offset of 100 px to the display box of the element.
* false: Does not add an enlargement offset */ void scrollIntoView(final ModelQualifier position, boolean addEnlargement) { // This method is not being called from inside the event queue. // This leads to refresh problems when scrolling the workspace, so we need to place // the following code into a runnable that will be executed by the event dispatch thread. final String eventName = addEnlargement ? "modeler.view.show" : "modeler.view.showexact"; SwingUtilities.invokeLater(new Runnable() { public void run() { // Invalidate and update the object denoted by the current position // fireEvent (new QualifierEvent (DebuggerPlugin.this, "modeler.view.invalidate", position)); // fireEvent (new JaspiraEvent (DebuggerPlugin.this, "modeler.view.refresh")); // Move the object into view fireEvent(new QualifierEvent(DebuggerPlugin.this, eventName, position)); } }); } /** * Performs a deferred screen refresh. * Will cause a refresh of the workspace from inside the event queue. */ void performDeferredRefresh() { SwingUtilities.invokeLater(new Runnable() { public void run() { // Move the object into view fireEvent("modeler.view.refresh"); } }); } /** * Updates the enable status of the run/step actions according to the current position. */ void updateActions() { boolean haveBreakpoints = breakpointDecorator != null; boolean haveHaltedProcess = haltedPosition != null; // Enable the breakpoint toggle action if any objects are selected. AskEvent ae = new AskEvent(this, "modeler.view.getselectioncount"); fireEvent(ae); Integer selectionCount = (Integer) ae.getAnswer(); boolean hasSelection = selectionCount != null && selectionCount.intValue() > 0; if (toggleBreakpointsAction != null) { toggleBreakpointsAction.setEnabled(hasSelection); } if (clearBreakpointsAction != null) { clearBreakpointsAction.setEnabled(haveBreakpoints); } if (stepIntoAction != null) { stepIntoAction.setEnabled(haveHaltedProcess); } if (stepOverAction != null) { stepOverAction.setEnabled(haveHaltedProcess); } if (stepOutAction != null) { stepOutAction.setEnabled(haveHaltedProcess); } if (stepNextAction != null) { stepNextAction.setEnabled(haveHaltedProcess); } // The resume and stop action are always enabled. } /** * Runs a process using the given initial node. * * @param initialNode Initial node to start from * @param step Run flag */ void runEntry(final InitialNode initialNode, final boolean step) { if (step) { // If we are debugging the process or we are using the internal browser, // we have to open the process in the modeler first ModelQualifier processQualifier = initialNode.getProcess().getQualifier(); fireEvent(new OpenEvent(this, "open.modeler", processQualifier)); } // The open.modeler event will be processed asynchonously, // so we will also put the request to the web browser to display the URL in the event queue, // or otherwise the internal web browser, which resides in the Modeler, will not be instantiated yet // and thus miss the event SwingUtilities.invokeLater(new Runnable() { public void run() { try { // Set a breakpoint at the only socket of the initial node if a initialNodeQualifier was given startPoller(); } catch (Exception e) { handleError(e); } } }); } /** * Automatically save any modifications to processes if the autosave option has been set. * @return Always true */ boolean autoSave() { // Auto-save any pending changes before continuing if (OptionMgr.getInstance().getBooleanOption("debugger.autosave", true)) { fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "standard.file.saveall", LEVEL_APPLICATION)); } return true; } ////////////////////////////////////////////////// // @@ Debugger client command module ////////////////////////////////////////////////// /** * Event module for client-side debugger events. * These are actually toolbar or menu actions. */ public class ClientEvents extends EventModule { public String getName() { return "debugger.client"; } /** * Returns the priority of the module. * We are low-level priority. */ public int getPriority() { return 100; } ////////////////////////////////////////////////// // @@ Action events ////////////////////////////////////////////////// /** * Event handler that toggles the breakpoints associated with the selected object(s). * * @event debugger.client.togglebreakpoints * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode togglebreakpoints(JaspiraActionEvent jae) { // Get the current selection of the active modeler AskEvent ae = new AskEvent(DebuggerPlugin.this, "modeler.view.getselection"); fireEvent(ae); List selection = (List) ae.getAnswer(); if (selection == null || selection.size() == 0) return EVENT_IGNORED; // Qualifiers of the selected sockets List qualifiers = new ArrayList(); // Iterate the selection and remember the Qualifiers of the selected sockets int nSelected = selection.size(); for (int iSelected = 0; iSelected < nSelected; ++iSelected) { Object o = selection.get(iSelected); if (o instanceof SocketFigure) { // Socket selected; add itsmodel qualifier NodeSocket socket = ((SocketFigure) o).getNodeSocket(); qualifiers.add(socket.getQualifier()); } else if (o instanceof NodeFigure) { // Node selected; add the Qualifiers of all of its sockets Node node = ((NodeFigure) o).getNode(); for (Iterator itSockets = node.getSockets(); itSockets.hasNext();) { NodeSocket socket = (NodeSocket) itSockets.next(); qualifiers.add(socket.getQualifier()); } } else if (o instanceof FlowConnection) { // Control link selected; add the Qualifiers of its start end end sockets ControlLink controlLink = ((FlowConnection) o).getControlLink(); qualifiers.add(controlLink.getSourceSocket().getQualifier()); qualifiers.add(controlLink.getTargetSocket().getQualifier()); } } int nQualifiers = qualifiers.size(); if (nQualifiers == 0) // There is nothing selected that we can use return EVENT_IGNORED; // We should breakpoint all Qualifiers if at least one of them is not yet breakpointed. boolean setBp = false; for (int iQualifiers = 0; iQualifiers < nQualifiers; ++iQualifiers) { ModelQualifier qualifier = (ModelQualifier) qualifiers.get(iQualifiers); if (! hasBreakpoint(qualifier)) { setBp = true; break; } } // Set or remove the breakpoints try { for (int iQualifiers = 0; iQualifiers < nQualifiers; ++iQualifiers) { ModelQualifier qualifier = (ModelQualifier) qualifiers.get(iQualifiers); boolean hasBp = hasBreakpoint(qualifier); if (setBp) { if (! hasBp) { // Breakpoint doesn't exist, set getDebugger().setBreakpoint(clientId, qualifier, 0); addBreakpoint(qualifier); } } else { if (hasBp) { // Breakpoint exists, clear getDebugger().clearBreakpoint(clientId, qualifier); removeBreakpoint(qualifier); } } } // Perform a screen refresh fireEvent(new JaspiraEvent(DebuggerPlugin.this, "modeler.view.refresh")); updateActions(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler that clears all breakpoints. * * @event debugger.client.clearbreakpoints * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode clearbreakpoints(JaspiraActionEvent jae) { try { getDebugger().clearBreakpoints(clientId, null); deactivateBreakpointDecorator(); // Several breakpoints may be concerned, so perform a screen redraw fireEvent(new JaspiraEvent(DebuggerPlugin.this, "modeler.view.redraw")); updateActions(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Toggles break on top level mode. * * @event debugger.client.setbreakontoplevel * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode setBreakOnTopLevel(JaspiraActionEvent jae) { try { int mode = getDebugger().getDebuggerMode(clientId); mode ^= DebuggerService.MODE_BREAK_ON_TOP_LEVEL; getDebugger().setDebuggerMode(clientId, mode); if (setBreakOnTopLevelAction != null) { setBreakOnTopLevelAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_TOP_LEVEL) != 0); } } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Toggles break on top level mode. * * @event debugger.client.setbreakonworkflow * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode setBreakOnWorkflow(JaspiraActionEvent jae) { try { int mode = getDebugger().getDebuggerMode(clientId); mode ^= DebuggerService.MODE_BREAK_ON_WORKFLOW; getDebugger().setDebuggerMode(clientId, mode); if (setBreakOnWorkflowAction != null) { setBreakOnWorkflowAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_WORKFLOW) != 0); } } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Sets break on exception mode. * * @event debugger.client.setbreakonexception * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode setBreakOnException(JaspiraActionEvent jae) { try { int mode = getDebugger().getDebuggerMode(clientId); mode ^= DebuggerService.MODE_BREAK_ON_EXCEPTION; getDebugger().setDebuggerMode(clientId, mode); if (setBreakOnExceptionAction != null) { setBreakOnExceptionAction.setSelected((mode & DebuggerService.MODE_BREAK_ON_EXCEPTION) != 0); } } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the step into command. * * @event debugger.client.stepinto * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode stepInto(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); try { debuggerService.stepInto(clientId); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the step out command. * * @event debugger.client.stepout * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode stepOut(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); try { getDebugger().stepOut(clientId); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the step over command. * * @event debugger.client.stepover * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode stepOver(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); try { getDebugger().stepOver(clientId); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the step out command. * * @event debugger.client.stepnext * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode stepNext(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); try { getDebugger().stepNext(clientId); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the stop process command. * * @event debugger.client.stop * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode stop(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); try { getDebugger().stop(clientId); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the resume process command. * * @event debugger.client.resume * @param jae Event * @return The event status code */ public JaspiraEventHandlerCode resume(JaspiraActionEvent jae) { if (! autoSave()) return EVENT_CONSUMED; if (haltedPosition == null) { // We have no halted process, so we just listen and wait. // This is necessary for processes that are not started from // the Modeler, but from external sources (e. g. a WAP browser). startPoller(); } else { setHaltedPosition(null); try { getDebugger().run(clientId); startPoller(); } catch (Exception e) { handleError(e); } } return EVENT_CONSUMED; } ////////////////////////////////////////////////// // @@ Events called from the outside ////////////////////////////////////////////////// /** * Event handler: Returns the id if the debugger client. * * @event debugger.client.getdebuggerid * @param ae Event
* The id will be stored into the answer of the event. * @return The event status code */ public JaspiraEventHandlerCode getdebuggerid(AskEvent ae) { // Make sure we are connected to the debugger try { getDebugger(); } catch (Exception e) { handleError(e); } ae.setAnswer(clientId); return EVENT_CONSUMED; } ////////////////////////////////////////////////// // @@ Internal events ////////////////////////////////////////////////// /** * Event handler: Toggles a breakpoint. * * @event debugger.client.togglebreakpoint * @param je Event * @return The event status code */ public JaspiraEventHandlerCode toggleBreakpoint(QualifierEvent je) { ModelQualifier target = je.getQualifier(); try { if (hasBreakpoint(target)) { // Breakpoint exists, clear getDebugger().clearBreakpoint(clientId, target); removeBreakpoint(target); } else { // Breakpoint doesn't exist, set getDebugger().setBreakpoint(clientId, target, 0); addBreakpoint(target); } // Perform a screen refresh fireEvent(new JaspiraEvent(DebuggerPlugin.this, "modeler.view.refresh")); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Executes the step until command. * * @event debugger.client.stepuntil * @param je Event
* Model qualifier: ModelQualifier of the step target * @return The event status code */ public JaspiraEventHandlerCode stepUntil(QualifierEvent je) { if (! autoSave()) return EVENT_CONSUMED; setHaltedPosition(null); ModelQualifier target = je.getQualifier(); try { getDebugger().stepUntil(clientId, target.toString()); startPoller(); } catch (Exception e) { handleError(e); } return EVENT_CONSUMED; } /** * Event handler: Connection to the server has been lost. * * @event debugger.client.connectionlost * @return The event status code */ public JaspiraEventHandlerCode connectionlost(QualifierEvent je) { clearbreakpoints(null); return EVENT_HANDLED; } ////////////////////////////////////////////////// // @@ Modeler events ////////////////////////////////////////////////// /** * Event handler: A modeler view has become active. * * @event modeler.view.activated * @eventobject Editor that owns the view ({@link Modeler}) * @param je Event * @return The event status code */ public JaspiraEventHandlerCode modeler_view_activated(JaspiraEvent je) { Object o = je.getObject(); if (o instanceof Modeler) { currentProcess = ((Modeler) o).getProcess(); return EVENT_HANDLED; } return EVENT_IGNORED; } /** * Event handler: A modeler view has become inactive. * * @event modeler.view.closed * @eventobject Editor that owns the view ({@link Modeler}) * @param je Event * @return The event status code */ public JaspiraEventHandlerCode modeler_view_closed(JaspiraEvent je) { Object o = je.getObject(); if (o instanceof Modeler) { currentProcess = null; return EVENT_HANDLED; } return EVENT_IGNORED; } /** * Event handler: The selection of a modeler view has changed. * * @event modeler.view.selectionchanged * @eventobject Editor that owns the view ({@link Modeler}) * @param je Event * @return The event status code */ public JaspiraEventHandlerCode modeler_view_selectionchanged(JaspiraEvent je) { Object source = je.getSource(); if (source instanceof Modeler) { Modeler modeler = (Modeler) source; if (modeler.getPluginComponent().isShowing()) { // The selection of the active modeler changed. // Update the toggle breakpoint action status. updateActions(); } return EVENT_HANDLED; } return EVENT_IGNORED; } /** * Event handler: The cockpit has reconnected to the server. * * @event plugin.serverconnection.reconnect * @param je Event * @return The event status code */ public JaspiraEventHandlerCode plugin_serverconnection_reconnect(JaspiraEvent je) { clearbreakpoints(null); return EVENT_HANDLED; } } /** * Event module for client-side debugger events. * These are actually toolbar or menu actions. */ public class OptionEvents extends EventModule { public String getName() { return "debugger"; } /** * Returns the priority of the module. * We are low-level priority. */ public int getPriority() { return 100; } /** * Event handler: The skip visual process option has changed. * * @event debugger.skipsystemmodel * @eventobject The changed option * @param je Event * @return The event status code */ public JaspiraEventHandlerCode skipsystemmodel(JaspiraEvent je) { boolean skip = ((Boolean) ((Option) je.getObject()).getValue()).booleanValue(); try { int mode = getDebugger().getDebuggerMode(clientId); int oldMode = mode; if (skip) { mode |= DebuggerService.MODE_SKIP_SYSTEM_MODEL; } else { mode &= ~ DebuggerService.MODE_SKIP_SYSTEM_MODEL; } if (mode != oldMode) { getDebugger().setDebuggerMode(clientId, mode); } } catch (Exception e) { handleError(e); } return EVENT_HANDLED; } /** * Event handler: The control link trace mode option has changed. * * @event debugger.controllinktracemode * @eventobject The changed option * @param je Event * @return The event status code */ public JaspiraEventHandlerCode controllinktracemode(JaspiraEvent je) { controlLinkTraceMode = (Integer) ((Option) je.getObject()).getValue(); return EVENT_HANDLED; } /** * Event handler: The data link trace mode option has changed. * * @event debugger.datalinktracemode * @eventobject The changed option * @param je Event * @return The event status code */ public JaspiraEventHandlerCode datalinktracemode(JaspiraEvent je) { dataLinkTraceMode = (Integer) ((Option) je.getObject()).getValue(); return EVENT_HANDLED; } /** * Event handler: The debugger id option has changed. * * @event debugger.debuggerid * @eventobject The changed option * @param je Event * @return The event status code */ public JaspiraEventHandlerCode debuggerid(JaspiraEvent je) { unregisterDebugger(); registerDebugger(false); return EVENT_HANDLED; } } ////////////////////////////////////////////////// // @@ Debugger interaction module ////////////////////////////////////////////////// /** * Interaction module. */ public class InteractionEvents extends InteractionModule { /** * Gets the module priority. * We are high priority. * * @return The priority. 0 is lowest, 100 is highest. */ public int getPriority() { return 2; } /** * Standard event handler that is called when a popup menu is to be shown. * Adds the debugger popup menu entries for entry and final nodes and node sockets. * * @event global.interaction.popup * @param ie Event * @return The event status code */ public JaspiraEventHandlerCode popup(InteractionEvent ie) { if (ie.getSource() instanceof NodeItemEditorPlugin) return EVENT_IGNORED; // InitialNode only -> Run/Step form here if (ie.isDataFlavorSupported(ClientFlavors.INITIAL_NODE) || ie.isDataFlavorSupported(ClientFlavors.FINAL_NODE)) { SingleSocketNode node; if (ie.isDataFlavorSupported(ClientFlavors.INITIAL_NODE)) node = (SingleSocketNode) ie.getSafeTransferData(ClientFlavors.INITIAL_NODE); else node = (SingleSocketNode) ie.getSafeTransferData(ClientFlavors.FINAL_NODE); final NodeSocket socket = node.getSocket(); setupSocketContextMenu(ie, socket); } // Sockets: -> set/clear Breakpoints, run to here if (ie.isDataFlavorSupported(ClientFlavors.NODE_SOCKET)) { final NodeSocket socket = (NodeSocket) ie.getSafeTransferData(ClientFlavors.NODE_SOCKET); setupSocketContextMenu(ie, socket); } return EVENT_HANDLED; } /** * Standard event handler that is called when a toolbar is (re-)generated. * Adds the toolbar entries to the modeler view plugin and the web browser plugin. * * @event global.interaction.toolbar * @param ie Event * @return The event status code */ public JaspiraEventHandlerCode toolbar(InteractionEvent ie) { if (ie.getSourcePlugin() instanceof Modeler) { JaspiraAction group; group = new JaspiraAction("breakpoint", null, null, null, null, 2, JaspiraAction.TYPE_GROUP); if (toggleBreakpointsAction != null) { group.addToolbarChild(toggleBreakpointsAction); } if (clearBreakpointsAction != null) { group.addToolbarChild(clearBreakpointsAction); } ie.add(group); group = new JaspiraAction("step", null, null, null, null, 3, JaspiraAction.TYPE_GROUP); if (stepIntoAction != null) { group.addToolbarChild(stepIntoAction); } if (stepOverAction != null) { group.addToolbarChild(stepOverAction); } if (stepOutAction != null) { group.addToolbarChild(stepOutAction); } if (stepNextAction != null) { group.addToolbarChild(stepNextAction); } if (resumeAction != null) { group.addToolbarChild(resumeAction); } if (stopAction != null) { group.addToolbarChild(stopAction); } ie.add(group); return EVENT_HANDLED; } return EVENT_IGNORED; } /** * Sets up the context menu for a node socket. * * @param ie Interaction event to add the menus to * @param socket Socket the context menu should refer to */ private void setupSocketContextMenu(InteractionEvent ie, final NodeSocket socket) { JaspiraAction group = new JaspiraAction("popupbreakpoint", null, null, null, null, 1, JaspiraAction.TYPE_GROUP); // Menu item: Toggle breakpoint group.addMenuChild(new JaspiraAction(DebuggerPlugin.this, "debugger.client.togglebreakpoint") { public void actionPerformed(ActionEvent e) { fireEvent(new QualifierEvent(DebuggerPlugin.this, "debugger.client.togglebreakpoint", socket.getQualifier())); } }); ie.add(group); if (haltedPosition != null) { // Menu item: Step until; add this only if we have a halted process group = new JaspiraAction("popupstep", null, null, null, null, 3, JaspiraAction.TYPE_GROUP); group.addMenuChild(new JaspiraAction(DebuggerPlugin.this, "debugger.client.stepuntil") { public void actionPerformed(ActionEvent e) { fireEvent(new QualifierEvent(DebuggerPlugin.this, "debugger.client.stepuntil", socket.getQualifier())); } }); ie.add(group); } } } ////////////////////////////////////////////////// // @@ Debugger server communication module ////////////////////////////////////////////////// /** * Debugger server interface module. * Attention:
* The names of the follow event methods need to correspond to the event names defined in * the EngineTraceEvent class. * These server events will be mapped automatically to the corresponding client events * by the {@link DebuggerServerEvent} class. */ public class ServerEvents extends EventModule { public String getName() { return "debugger.server"; } ////////////////////////////////////////////////// // @@ EventHandling ////////////////////////////////////////////////// /** * Event handler: Nodeentrys. * * @event debugger.server.nodeentry * @param dse Event * @return The event status code */ public JaspiraEventHandlerCode nodeentry(DebuggerServerEvent dse) { // Highlight node and socket ModelQualifier pos = new ModelQualifier(dse.getHaltedPosition()); setHaltedPosition(pos); return EVENT_HANDLED; } /** * Event handler: Nodeexits. * * @event debugger.server.nodeexit * @param dse Event * @return The event status code */ public JaspiraEventHandlerCode nodeexit(DebuggerServerEvent dse) { // Turn off highlighting ModelQualifier pos = new ModelQualifier(dse.getHaltedPosition()); setHaltedPosition(pos); return EVENT_HANDLED; } /** * Event handler: Controlflows. * * @event debugger.server.controlflow * @param dse Event * @return The event status code */ public JaspiraEventHandlerCode controlflow(DebuggerServerEvent dse) { if (maxStepCount != 0) { // Run control flow animation and // execute a step next command after the animation has finished DebuggerEvent de = dse.getDebuggerEvent(); // Scroll the flow connection into view; however don't add enlargment offset scrollIntoView(de.getControlLinkQualifier(), false); new AnimationThread(de.getControlLinkQualifier(), true); } else { // Execute a step next command right away fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "debugger.client.stepnext", LEVEL_APPLICATION)); } return EVENT_HANDLED; } /** * Event handler: Dataflows. * * @event debugger.server.dataflow * @param dse Event * @return The event status code */ public JaspiraEventHandlerCode dataflow(DebuggerServerEvent dse) { DebuggerEvent de = dse.getDebuggerEvent(); ModelQualifier pos = dse.getHaltedPosition(); if (ViewModeMgr.getInstance().isDataLinkVisible()) { setHaltedPosition(new ModelQualifier(pos)); } // Run data flow animation if we should (i. e. if allowed by cockpit settings // and if the data flow lines are visible at all). // After the animation finishes, a step next command will be issued. if (maxStepCount != 0 && ViewModeMgr.getInstance().isDataLinkVisible()) { // Scroll the flow connection into view; however don't add enlargment offset scrollIntoView(de.getDataLinkQualifier(), false); new AnimationThread(de.getDataLinkQualifier(), false); } else { // No data flow animation, proceed directly by sending a step next event fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "debugger.client.stepnext", LEVEL_APPLICATION)); } return EVENT_HANDLED; } /** * Event handler: Processexceptions. * * @event debugger.server.processexception * @param dse Event * @return The event status code */ public JaspiraEventHandlerCode processexception(DebuggerServerEvent dse) { ModelQualifier haltedPosition = dse.getHaltedPosition(); setHaltedPosition(new ModelQualifier(haltedPosition)); ModelQualifier msgPosition = new ModelQualifier(haltedPosition); msgPosition.setObjectPath(null); String message = "Exception in process '" + msgPosition + "':"; if (dse.getException() != null) { fireEvent(new ErrorEvent(DebuggerPlugin.this, message, dse.getException())); } else { fireEvent(new ErrorEvent(DebuggerPlugin.this, message, dse.getExceptionString())); } // Automatically respond to the server with a 'resume' command. // This will continue the current process (if it can be continued) with the error handling. // If it cannot be resumed, the process must be restarted again by the user... /* TODO Fix 4 Debugger not resumed any more after exception - we should notify the user! fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "debugger.client.resume", LEVEL_APPLICATION)); */ return EVENT_HANDLED; } /** * Handles any unaccounted event. * * @param ev Event * @return The event status code */ protected JaspiraEventHandlerCode handleUnaccountedEvent(JaspiraEvent ev) { // Shouldn't happen LogUtil.error(getClass(), "Unknown server event $0.", ev); return super.handleUnaccountedEvent(ev); } } ////////////////////////////////////////////////// // @@ Polling Thread ////////////////////////////////////////////////// /** * Starts the polling thread if not already active. */ void startPoller() { if (! Cockpit.disableTimersForDebug && debuggerService != null) { pollerActive = true; if (poller == null) { poller = new PollingThread(); poller.start(); } fireEvent(new JaspiraEvent(DebuggerPlugin.this, "debugger.client.statuschange", clientId)); } } /** * Stops the polling thread if currently running. */ void stopPoller() { pollerActive = false; fireEvent(new JaspiraEvent(DebuggerPlugin.this, "debugger.client.statuschange", null)); } /** * Thread that checks for new server events every 500 ms. */ class PollingThread extends Thread { /** * The constructor. */ public PollingThread() { super("Debugger event poller"); // This thread must not prevent VM shutdown. setDaemon(true); } /** * Run method. */ public void run() { while (pollerActive) { try { DebuggerEvent event = getDebugger().getEvent(clientId); if (event != null) { // We have an event, we wrap it into a JaspiraEvent. stopPoller(); fireEvent(new DebuggerServerEvent(DebuggerPlugin.this, event)); } } catch (Exception e) { if (! registerDebugger(true)) break; } // Delay for a while try { Thread.sleep(500); } catch (InterruptedException e) { } } // Kill the reference to the thread, so it will be reinstantiated by {@link #startPoller} poller = null; } } ///////////////////////////////////////////////////////////////////////// // @@ Step decorator ///////////////////////////////////////////////////////////////////////// /** * Creates and activates the step decorator. */ private void activateStepDecorator() { DecorationMgr.addDecorator(this, XFigure.DECO_FRAMESTROKE, stepDecorator); DecorationMgr.addDecorator(this, XFigure.DECO_FRAMECOLOR, stepDecorator); DecorationMgr.addDecorator(this, AbstractTagFigure.DECO_TAGCONTENTTYPE, stepDecorator); } /** * Removes activates the step decorator. */ private void deactivateStepDecorator() { DecorationMgr.removeDecorator(this, XFigure.DECO_FRAMESTROKE, stepDecorator); DecorationMgr.removeDecorator(this, XFigure.DECO_FRAMECOLOR, stepDecorator); DecorationMgr.removeDecorator(this, AbstractTagFigure.DECO_TAGCONTENTTYPE, stepDecorator); } /** * Decorator for current position of halted process. */ public class StepDecorator extends FilteredDecorator { /** * @see org.openbp.jaspira.decoration.FilteredDecorator#doDecorate(Object, String, Object) */ public Object doDecorate(Object owner, String key, Object value) { if (key == XFigure.DECO_FRAMESTROKE) return ModelerGraphics.debuggerStroke; if (key == XFigure.DECO_FRAMECOLOR) return ModelerColors.DEBUGGER_BORDER; if (key == AbstractTagFigure.DECO_TAGCONTENTTYPE) return new Integer(((Integer) value).intValue() | AbstractTagFigure.CONTENT_FLOW | AbstractTagFigure.CONTENT_TEXT); return value; } /** * @see org.openbp.jaspira.decoration.FilteredDecorator#qualifies(Object) */ public boolean qualifies(Object owner) { ModelQualifier qualifier; if (owner instanceof ModelQualifier) { qualifier = (ModelQualifier) owner; } else { if (! (owner instanceof ProcessElementContainer)) return false; qualifier = ((ProcessElementContainer) owner).getProcessElement().getQualifier(); } return qualifier.matches(haltedPosition); } } ///////////////////////////////////////////////////////////////////////// // @@ Breakpoint decorator ///////////////////////////////////////////////////////////////////////// /** * Adds a breakpoint and installs the breakpoint decorator. * * @param qualifier Model qualifier of the breakpoint to add */ protected void addBreakpoint(ModelQualifier qualifier) { activateBreakpointDecorator(); breakpointDecorator.add(qualifier); fireEvent(new QualifierEvent(DebuggerPlugin.this, "modeler.view.invalidate", qualifier)); } /** * Removes a breakpoint and deinstalls the breakpoint decorator if empty. * * @param qualifier Model qualifier of the breakpoint to remove */ void removeBreakpoint(ModelQualifier qualifier) { if (breakpointDecorator != null) { breakpointDecorator.remove(qualifier); fireEvent(new QualifierEvent(DebuggerPlugin.this, "modeler.view.invalidate", qualifier)); if (breakpointDecorator.isEmpty()) { deactivateBreakpointDecorator(); } } } /** * Checks if a breakpoint is set. * * @param qualifier Model qualifier of the breakpoint to check */ boolean hasBreakpoint(ModelQualifier qualifier) { if (breakpointDecorator != null) return breakpointDecorator.qualifies(qualifier); return false; } /** * Creates and activates the breakpoint decorator. */ private void activateBreakpointDecorator() { if (breakpointDecorator == null) { breakpointDecorator = new BreakPointDecorator(); DecorationMgr.addDecorator(this, BasicFigure.DECO_OVERLAY, breakpointDecorator); DecorationMgr.addDecorator(this, AbstractTagFigure.DECO_TAGCONTENTTYPE, breakpointDecorator); } } /** * Removes activates the breakpoint decorator. */ void deactivateBreakpointDecorator() { if (breakpointDecorator != null) { DecorationMgr.removeDecorator(this, BasicFigure.DECO_OVERLAY, breakpointDecorator); DecorationMgr.removeDecorator(this, AbstractTagFigure.DECO_TAGCONTENTTYPE, breakpointDecorator); breakpointDecorator = null; } } /** * Decorator for breakpoints. */ public class BreakPointDecorator extends ListDecorator { /** * @see org.openbp.jaspira.decoration.ListDecorator#qualifies(Object) */ public boolean qualifies(Object owner) { ModelQualifier qualifier = null; if (owner instanceof ModelQualifier) { qualifier = (ModelQualifier) owner; } else { if (owner instanceof SocketFigure) { SocketFigure socketFigure = (SocketFigure) owner; qualifier = socketFigure.getNodeSocket().getQualifier(); } } return super.qualifies(qualifier); } /** * @see org.openbp.jaspira.decoration.FilteredDecorator#doDecorate(Object, String, Object) */ public Object doDecorate(Object owner, String key, Object value) { if (key == XFigure.DECO_OVERLAY && (owner instanceof SocketFigure)) { // Make the breakpoint decoriation 2 pixels larger than the flow access figure Figure overlay = FigureResources.getBreakpointOverlay(owner); return overlay; } if (key == AbstractTagFigure.DECO_TAGCONTENTTYPE) return new Integer(((Integer) value).intValue() | AbstractTagFigure.CONTENT_FLOW | AbstractTagFigure.CONTENT_TEXT); return value; } } ////////////////////////////////////////////////// // @@ Animation Thread ////////////////////////////////////////////////// /** Step counter for stopped animation */ private static final Double noStep = new Double(Double.NaN); /** * Thread responsible for the animation of Connections */ class AnimationThread extends Thread implements Decorator { /** * The current step decoration value of the animation, or NaN if stopped. * Indicates the position of the point on the spline (0.0 < n < 1.0). */ private Double stepDecorationValue = noStep; /** Contains the connection that we are decorating */ private transient PolySplineConnection connection; /** Step width for the animation (0 < step < 1) */ private double stepWidth; /** Time to elapse between each step */ private int stepTime; /** Animation has been aborted by user command */ private boolean aborted; /** Automatically issue a step next command when the animation has finished */ private boolean autoStep; /** * Constructor. * * @param qualifier Model qualifier of the connection to animate * @param autoStep * true: Automatically issue a step next command when the animation has finished.
* false: Do nothing on finish */ public AnimationThread(ModelQualifier qualifier, boolean autoStep) { super("Debugger animation"); // This thread must not prevent VM shutdown. setDaemon(true); // First, retrieve the connection figure we want to animate AskEvent ae = new AskEvent(DebuggerPlugin.this, "modeler.view.getbyqualifier", qualifier); fireEvent(ae); if (ae.getAnswer() instanceof PolySplineConnection) { connection = (PolySplineConnection) ae.getAnswer(); // We found it animator = this; this.autoStep = autoStep; // Determine the approximate length of the spline. // We do this by measuring the length of the diagonal of the spline's display box. Rectangle box = connection.getSplineBounds(); int l = (int) Math.sqrt(box.width * box.width + box.height * box.height); // Each step should move the point by approx. 5 pixel int steps = l / 5; if (steps < 10) { // We have less than 10 steps, try 3 pixel steps = l / 3; if (steps < 10) steps = Math.max(l, 10); } else if (steps > 100) { steps = 100; } if (maxStepCount > 0 && steps > maxStepCount) { steps = maxStepCount; } stepWidth = 1d / steps; stepTime = 1000 / steps; if (maxStepTime >= 0 && stepTime > maxStepTime) { stepTime = maxStepTime; } // Add the animator as spline animation decorator as long as the thread is running DecorationMgr.addDecorator(DebuggerPlugin.this, PolySplineConnection.DECO_ANIMATION, this); // Start the animation start(); } else { // Otherwise, don't start the thread because we have nothing to animate. // However, issue the step next command if desired. if (autoStep) { // Send step next event fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "debugger.client.stepnext", LEVEL_APPLICATION)); } } } ///////////////////////////////////////////////////////////////////////// // @@ Thread ///////////////////////////////////////////////////////////////////////// /** * Run method. */ public void run() { for (double currentStep = 0d; currentStep < 1d; currentStep += stepWidth) { if (aborted) { break; } // Advance the point on the spline stepDecorationValue = new Double(currentStep); if (connection != null) { // Invalidate the connection and force a screen update connection.invalidate(); performDeferredRefresh(); } // Delay for a while if (stepTime > 0) { try { // Sleep for a while Thread.sleep(stepTime); } catch (InterruptedException e) { // break; } } } if (autoStep && ! aborted) { // Send step next event fireEvent(new JaspiraActionEvent(DebuggerPlugin.this, "debugger.client.stepnext", LEVEL_APPLICATION)); } aborted = false; animator = null; DecorationMgr.removeDecorator(DebuggerPlugin.this, PolySplineConnection.DECO_ANIMATION, this); if (connection != null) { // Invalidate the connection and force a screen update connection.invalidate(); performDeferredRefresh(); } } ///////////////////////////////////////////////////////////////////////// // @@ Decoration ///////////////////////////////////////////////////////////////////////// /** * Returns the actual step * * @param owner Object to be decorated * @param key Key under which the decorator is accessed * @param value Original decoration value (usually null) */ public Object decorate(Object owner, String key, Object value) { if (connection == owner) // We are decorating the connection in progress, return the decoration return stepDecorationValue; // Return the original decoration value (usually null) return value; } ///////////////////////////////////////////////////////////////////////// // @@ Control ///////////////////////////////////////////////////////////////////////// public void abort() { aborted = true; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy