org.openbp.server.engine.debugger.DebuggerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openbp-server Show documentation
Show all versions of openbp-server Show documentation
The OpenBP process engine (main module)
/*
* 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.server.engine.debugger;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.openbp.common.logger.LogUtil;
import org.openbp.common.setting.SettingUtil;
import org.openbp.common.util.observer.EventObserver;
import org.openbp.common.util.observer.ObserverEvent;
import org.openbp.core.CoreConstants;
import org.openbp.core.OpenBPException;
import org.openbp.core.engine.debugger.Breakpoint;
import org.openbp.core.engine.debugger.CallStackInfo;
import org.openbp.core.engine.debugger.DebuggerEvent;
import org.openbp.core.engine.debugger.DebuggerService;
import org.openbp.core.engine.debugger.ObjectMemberInfo;
import org.openbp.core.model.ModelQualifier;
import org.openbp.core.model.item.process.InitialNode;
import org.openbp.core.model.item.process.NodeParam;
import org.openbp.core.model.item.process.NodeSocket;
import org.openbp.core.model.item.process.WorkflowNode;
import org.openbp.server.ServerConstants;
import org.openbp.server.context.CallStack;
import org.openbp.server.context.CallStackItem;
import org.openbp.server.context.SessionRegistry;
import org.openbp.server.context.TokenContext;
import org.openbp.server.context.TokenContextUtil;
import org.openbp.server.engine.EngineEventObserverMgr;
import org.openbp.server.engine.EngineTraceEvent;
import org.openbp.server.engine.EngineTraceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* The OpenBP debugger is an engine trace client used to visually debug OpenBP processes.
* This class implements the server-side of the debugger.
* The debugger registers with the {@link EngineEventObserverMgr} as engine trace client
* and handles all events needed for debugging a process.
*
* The client side of the debugger controls the server-side by using the {@link DebuggerService}
* which in turn will forward client request to this debugger class.
*
* So this class will react to respond to events generated by the engine trace manager
* of the engine as well as control requests (like single step, run, print expression etc.)
* from the debugger client.
*
* @author Heiko Erhardt
*/
@Service("debugger")
public class DebuggerImpl
implements EventObserver, Debugger
{
/** Thread for timer */
Thread timerThread;
/**
* Table of debugger clients.
* Maps client ids (Strings) to clients ({@link DebuggerClient} objects)
*/
Hashtable clients;
/** Client id generator */
private int lastClientId = 0;
/** Global debugger control: Stop at first event */
private boolean stopAtFirstEvent;
/** Global debugger control: Stop at any event */
private boolean stopAtAnyEvent;
/** Global debugger control: Wait for a matching debugger instance */
private boolean waitForDebugger;
@Autowired
private EngineEventObserverMgr observerMgr;
@Autowired(required=false)
private SessionRegistry sessionRegistry;
/**
* Private constructor.
*/
public DebuggerImpl()
{
// Initialize clients hashtable.
clients = new Hashtable();
}
/**
* The initialization method retrieves some debugger options and possibly registers the debugger as engine observer.
* This method will also be called from the IoC container that supports the JSR 250 @PostConstruct annotation.
*/
public void initialize()
{
String debuggerControl = SettingUtil.getStringSetting(ServerConstants.SYSPROP_DEBUGGER_CONTROL);
if (debuggerControl != null)
{
if ("stopAtFirstEvent".equalsIgnoreCase(debuggerControl))
{
stopAtFirstEvent = true;
}
else if ("waitAtFirstEvent".equalsIgnoreCase(debuggerControl))
{
stopAtFirstEvent = true;
waitForDebugger = true;
}
else if ("stopAtAnyEvent".equalsIgnoreCase(debuggerControl))
{
stopAtAnyEvent = true;
}
else if ("waitAtAnyEvent".equalsIgnoreCase(debuggerControl))
{
stopAtAnyEvent = true;
waitForDebugger = true;
}
if (waitForDebugger)
{
registerObserver();
}
}
}
//////////////////////////////////////////////////
// @@ Debugger client registration
//////////////////////////////////////////////////
/**
* Registers a debugger client.
* The debugger client will be automatically unregistered when the timeout expires.
*
* @param clientId The client id or null in order to generate one
* @param timeout Timeout in seconds or 0 for no automatic unregistering
* @return The client id (must be passed in subsequent calls to the debugger)
*
* @throws OpenBPException If a client with this id has already registered
*/
public String registerClient(String clientId, int timeout)
{
String id;
if (clientId != null)
id = clientId;
else
id = "Deb" + ++lastClientId;
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient client = (DebuggerClient) it.next();
if (client.getClientId().equals(id))
{
if (clientId == null)
{
// Auto-generated id, unregister current debugger
unregisterClient(id);
}
else
{
if (client.getTimeout() != 0)
throw new OpenBPException("Engine.Debugger", "Debugger with id '" + id + "' already registered, timeout is in "
+ (timeout / 60) + " min.");
throw new OpenBPException("Engine.Debugger", "Debugger with id '" + id + "' already registered");
}
}
}
// Create a new client and add it to the clients
DebuggerClient client = new DebuggerClient(id);
client.setTimestamp(System.currentTimeMillis());
client.setTimeout(timeout);
clients.put(id, client);
// Register the debugger as event observer all trace event types
registerObserver();
if (timeout != 0)
{
if (timerThread == null)
{
// Start the timer thread
timerThread = new DebuggerClientCleanupThread();
timerThread.start();
}
}
return id;
}
/**
* Checks if a debugger client has been registered.
*
* @param clientId The client id
*
* @return true of this client has been registered
*/
public synchronized boolean isClientRegistered(String clientId)
{
return getClient(clientId) != null;
}
/**
* Removes the client from the table of registered clients.
* Also removes (cleanup) all breakpoints set by this client.
*
* @param clientId Id of the client returned by {@link #registerClient}
* @throws OpenBPException If the client id is invalid or unregistering failed
*/
public synchronized void unregisterClient(String clientId)
{
unregisterClient(clientId, true);
}
/**
* Removes the client from the table of registered clients.
* Also removes (cleanup) all breakpoints set by this client.
*
* @param clientId Id of the client
* @param removeClient
* true Removes the client from the clients table.
* false Does not yet remove the client.
* @throws OpenBPException If the client id is invalid or unregistering failed
*/
private void unregisterClient(String clientId, boolean removeClient)
{
DebuggerClient client = determineClient(clientId);
// Retrieves the current halted process if there exists one
if (client.getHaltInfo() != null)
{
// There is a halted process, terminate it
try
{
stop(clientId);
}
catch (OpenBPException e)
{
}
}
clearBreakpoints(clientId, null);
// TODO Fix 5 Remove this client id from all token contexts
// Remove client from the client list
clients.remove(clientId);
// Check if there are still clients registered
if (clients.isEmpty())
{
unregisterObserver();
// Stop the timer thread
if (timerThread != null)
timerThread.interrupt();
// Reset client id generator
lastClientId = 0;
}
else
{
if (timerThread != null)
{
// Check clients if there is a at least one timeout set, if not interrupt timer
boolean needTimer = false;
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient c = (DebuggerClient) it.next();
if (c.getTimeout() != 0)
needTimer = true;
}
if (! needTimer)
timerThread.interrupt();
}
}
}
/**
* Removes all clients from the table of registered clients.
* Also removes all breakpoints.
* @throws OpenBPException If unregistering failed
*/
public synchronized void unregisterAllClients()
{
if (! clients.isEmpty())
{
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient c = (DebuggerClient) it.next();
unregisterClient(c.getClientId(), false);
}
clients.clear();
}
unregisterObserver();
// Stop the timer thread
if (timerThread != null)
timerThread.interrupt();
// Reset client id generator
lastClientId = 0;
}
/**
* Kills all halted processes.
* Will cause an exception for all currently stopped processes.
*/
public void killAllHaltedProcesses()
{
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient client = (DebuggerClient) it.next();
if (client.getHaltInfo() != null)
{
try
{
setCommand(client, CMD_STOP);
}
catch (OpenBPException e)
{
// Never happens
}
}
}
}
/**
* Gets a client by its id.
* @param clientId Id of the client
* @return The Debugger client or null if no such client exists
*/
public synchronized DebuggerClient getClient(String clientId)
{
// Check if there exists a client for the given id
return (DebuggerClient) clients.get(clientId);
}
/**
* Gets a client by its id.
* @param clientId Id of the client
* @return The Debugger client
* @throws OpenBPException If the client id is invalid
*/
private synchronized DebuggerClient determineClient(String clientId)
{
// Check if there exists a client for the given id
DebuggerClient client = (DebuggerClient) clients.get(clientId);
if (client != null)
{
// There is a sign of life of the client, update its timestamp
long timestamp = System.currentTimeMillis();
client.setTimestamp(timestamp);
return client;
}
throw new OpenBPException("Engine.Debugger", "Debugger: Client '" + clientId + "' is not a registered debugger client");
}
//////////////////////////////////////////////////
// @@ Process engine trace
//////////////////////////////////////////////////
/**
* Aborts the execution of a process.
*
* @param clientId Id of the client
* @return The event
* @throws OpenBPException If the client id is invalid
*/
public synchronized DebuggerEvent getEvent(String clientId)
{
DebuggerClient client = determineClient(clientId);
DebuggerClient.HaltInfo haltInfo = client.getHaltInfo();
if (haltInfo == null)
// Client does not have a halted process
return null;
EngineTraceEvent event = haltInfo.getEvent();
if (event == null)
// We already communicated this event to the client, so nothing to do
return null;
// Reset the event, so the next call to getEvent won't return the same event
haltInfo.setEvent(null);
return event.createDebuggerEvent(haltInfo.getPosition());
}
/**
* Kills all processes that run under control of this client.
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public void killProcesses(String clientId)
{
// Set the termination request flag of all registered processes that are debugged by the specified client
if (sessionRegistry != null)
{
sessionRegistry.requestSessionAbort(clientId);
}
// If there is a client stopped by this debugger, signalize it to stop
DebuggerClient client = determineClient(clientId);
if (client.getHaltInfo() != null)
{
try
{
setCommand(client, CMD_STOP);
}
catch (OpenBPException e)
{
// Never happens
}
}
}
/**
* Kills a halted processes - if any - of a client.
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public void killHaltedProcess(String clientId)
{
DebuggerClient client = determineClient(clientId);
if (client.getHaltInfo() != null)
{
try
{
setCommand(client, CMD_STOP);
}
catch (OpenBPException e)
{
// Never happens
}
}
}
/**
* Aborts the execution of a process.
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void stop(String clientId)
{
setCommand(clientId, CMD_STOP);
}
/**
* Steps until the next event.
* Stops before the next socket.
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void stepNext(String clientId)
{
setCommand(clientId, CMD_STEP_NEXT);
}
/**
* Steps into call or jump nodes or steps just one socket (stepNext)
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void stepInto(String clientId)
{
setCommand(clientId, CMD_STEP_INTO);
}
/**
* Steps over call or jump nodes or step just one socket (stepNext)
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void stepOver(String clientId)
{
DebuggerClient client = determineClient(clientId);
client.saveCallDepth();
setCommand(clientId, CMD_STEP_OVER);
}
/**
* Continues execution of a process until it's end (step out).
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void stepOut(String clientId)
{
DebuggerClient client = determineClient(clientId);
client.saveCallDepth();
setCommand(clientId, CMD_STEP_OUT);
}
/**
* Steps until a particular position.
* The client actually should already have reserved the process to
* debug because of {@link #setBreakpoint}
*
* @param clientId Id of the client
* @param position Reference to a process object
* @throws OpenBPException If the client id or the position is invalid
*/
public synchronized void stepUntil(String clientId, String position)
{
DebuggerClient client = determineClient(clientId);
client.setStepUntilPosition(position);
setCommand(client, CMD_STEP_UNTIL);
}
/**
* Continues a halted process.
*
* @param clientId Id of the client
* @throws OpenBPException If the client id is invalid
*/
public synchronized void run(String clientId)
{
// Notify engine to continue the process
setCommand(clientId, CMD_RUN);
}
//////////////////////////////////////////////////
// @@ Breakpoint control
//////////////////////////////////////////////////
/**
* Sets the operation mode of the debugger.
*
* @param clientId Id of the client
* @param debuggerMode {@link Debugger#MODE_SKIP_SYSTEM_MODEL}|{@link Debugger#MODE_BREAK_ON_EXCEPTION}|{@link Debugger#MODE_BREAK_ON_TOP_LEVEL}|{@link Debugger#MODE_BREAK_ON_WORKFLOW}
* @throws OpenBPException If the client id is invalid
*/
public void setDebuggerMode(String clientId, int debuggerMode)
{
DebuggerClient client = determineClient(clientId);
client.setDebuggerMode(debuggerMode);
}
/**
* Gets the operation mode of the debugger.
*
* @param clientId Id of the client
* @return {@link Debugger#MODE_SKIP_SYSTEM_MODEL}|{@link Debugger#MODE_BREAK_ON_EXCEPTION}|{@link Debugger#MODE_BREAK_ON_TOP_LEVEL}|{@link Debugger#MODE_BREAK_ON_WORKFLOW}
* @throws OpenBPException If the client id is invalid
*/
public int getDebuggerMode(String clientId)
{
DebuggerClient client = determineClient(clientId);
return client.getDebuggerMode();
}
/**
* Adds or updates a breakpoint.
* @param clientId Id of the client
* @param qualifier Reference to a node or a node socket
* @param state Any combination of {@link Breakpoint#STATE_DISABLED} | {@link Breakpoint#STATE_GLOBAL} | {@link Breakpoint#STATE_TEMPORARY} or 0
* @throws OpenBPException If the client id is invalid
* or if the process has been halted by another client
*/
public synchronized void setBreakpoint(String clientId, ModelQualifier qualifier, int state)
{
DebuggerClient client = determineClient(clientId);
client.setBreakpoint(qualifier, state);
}
/**
* Clears a breakpoint.
* @param clientId Id of the client
* @param qualifier Reference to a node or a node socket
* @throws OpenBPException If the client id is invalid
*/
public synchronized void clearBreakpoint(String clientId, ModelQualifier qualifier)
{
DebuggerClient client = determineClient(clientId);
client.clearBreakpoint(qualifier);
}
/**
* Gets a list of all breakpoints of a client.
* @param clientId Id of the client
* @return A list of {@link Breakpoint} objects or null if there are not breakpoints
* defined for this client
* @throws OpenBPException If the client id is invalid
* or if the process has been halted by another client
*/
public synchronized List getBreakpoints(String clientId)
{
DebuggerClient client = determineClient(clientId);
return client.getBreakpoints();
}
/**
* Changes the state of all breakpoints of the specified process.
* @param clientId Id of the client
* @param qualifiedProcessName Reference to the process
* or null to clear all breakpoints of the client.
* @param state Any combination of {@link Breakpoint#STATE_DISABLED} | {@link Breakpoint#STATE_GLOBAL}
* @param set
* true Sets the specified state flags.
* false Clears the specified state flags.
* @throws OpenBPException If the client id is invalid
* or if the process has been halted by another client
*/
public synchronized void updateBreakpoints(String clientId, String qualifiedProcessName, int state, boolean set)
{
DebuggerClient client = determineClient(clientId);
client.updateBreakpoints(qualifiedProcessName, state, set);
}
/**
* Clears all breakpoints of the specified process.
* @param clientId Id of the client
* @param qualifiedProcessName Reference to the process
* or null to clear all breakpoints of the client.
* @throws OpenBPException If the client id is invalid
*/
public synchronized void clearBreakpoints(String clientId, String qualifiedProcessName)
{
DebuggerClient client = determineClient(clientId);
client.clearBreakpoints(qualifiedProcessName);
}
//////////////////////////////////////////////////
// @@ Context inspection
//////////////////////////////////////////////////
/**
* Retrieves information about a parameter of the token context or a member
* of a particular parameter object within the object hierarchy of the parameter.
*
* @param clientId Id of the client
*
* @param contextPath Path of the context object we are refering to.
* The path must specify an existing context parameter.
*
* @param expression This expression may refer to a member of the parameter
* (e. g. contextPath = "CreateClient.Out.Client", expression = "User.Profile"
* will return the 'Profile' member of the 'User' object of the created client.
*
* @return The object member information or null if the request could not be resolved
* @throws OpenBPException If the client id or the expression are invalid
*/
public ObjectMemberInfo getObjectValue(String clientId, String contextPath, String expression)
{
DebuggerClient client = determineClient(clientId);
ContextInspector inspector = client.getInspector();
return inspector.getObjectValue(contextPath, expression);
}
/**
* Retrieves information about parameters of the token context or the members
* of a particular parameter object within the object hierarchy of the parameter.
*
* @param clientId Id of the client
*
* @param contextPath Path of the context object we are refering to:
* If the path is null, all parameters of the context will be returned.
* If the path specifies the full path of a context object (e. g. "node.socket.param"),
* the method returns all members of the object.
* Otherwise, all parameters beginning with the specified path will be returned.
*
* @param expression if the 'contextPath' referes to a particular parameter, this expression
* may refer to a member of this parameter (e. g. contextPath = "CreateClient.Out.Client",
* expression = "User.Profile" will return all members of the 'Profile' member of the
* 'User' object of the created client.
*
* @return A list of {@link ObjectMemberInfo} objects or null if the request could not be resolved
* @throws OpenBPException If the client id or the expression are invalid
*/
public List getObjectMembers(String clientId, String contextPath, String expression)
{
DebuggerClient client = determineClient(clientId);
ContextInspector inspector = client.getInspector();
return inspector.getObjectMembers(contextPath, expression);
}
/**
* Retrieves information about the current state of the call stack.
*
* @param clientId Id of the client
* @return A list of {@link CallStackInfo} objects or null if the call stack is empty
* @throws OpenBPException If the client id or the expression are invalid
*/
public List getCallStackElements(String clientId)
{
DebuggerClient client = determineClient(clientId);
ContextInspector inspector = client.getInspector();
CallStack callStack = inspector.getContext().getCallStack();
if (callStack.getCallDepth() == 0)
// Empty call stack
return null;
List elements = new ArrayList();
// Iterate the call stack and create the list of stack info objects
for (Iterator it = callStack.iterator(); it.hasNext();)
{
CallStackItem stackItem = (CallStackItem) it.next();
CallStackInfo stackInfo = new CallStackInfo();
stackInfo.setType(stackItem.getType());
// TODO Fix 5 No difference between current pos. and saved pos. of call stack info
NodeSocket nodeSocket = stackItem.getNodeSocket();
stackInfo.setCurrentPosition(nodeSocket.getQualifier());
stackInfo.setSavedPosition(nodeSocket.getQualifier());
elements.add(stackInfo);
}
return elements;
}
//////////////////////////////////////////////////
// @@ Synchronization with the engine
//////////////////////////////////////////////////
/**
* Sets the command that should be executed by the process engine
* and notifies the engine.
*
* @param clientId Id of the client that issues the command
* @param command Next command
* @throws OpenBPException If the client id is invalid
*/
private void setCommand(String clientId, int command)
{
// Check and get the client object, throw an Exception if not existent
DebuggerClient client = determineClient(clientId);
setCommand(client, command);
}
/**
* Sets the command that should be executed by the process engine
* and notifies the engine.
*
* @param client Client that issues the command
* @param command Next command
* @throws OpenBPException If the client id is invalid
*/
private void setCommand(DebuggerClient client, int command)
{
// Get the current process
if (client.getHaltInfo() == null)
return;
// Save the command to execute in the client object and notify the
// engine using the client object synchronization
client.setNextCommand(command);
// This call will wake up the client and continue execution after the call
// to {@link #waitForCommand} in the {@link #handleEvent} method
client.callNotify();
}
/**
* Waits until the next client command is available.
*
* @param client Client that is used for synchronization
* @param haltInfo Halt information object that identifies the halting point we are refering to
*/
private void waitForCommand(DebuggerClient client, DebuggerClient.HaltInfo haltInfo)
{
for (;;)
{
// Check if the client currently deals with the halted process we are waiting for
DebuggerClient.HaltInfo currentHaltInfo = client.getHaltInfo();
if (currentHaltInfo == haltInfo)
{
// Yes, this is our current halting point
break;
}
// The command referred to another halting process, so let's wait a little
try
{
// Sleep a for 100 ms
Thread.sleep(100);
}
catch (InterruptedException ie)
{
}
}
// Wait for the next client command;
// The synchronization object is the DebuggerClient object itself
try
{
client.callWait();
}
catch (InterruptedException ie)
{
// The client has the next command available
}
}
//////////////////////////////////////////////////
// @@ EventObserver implementation
//////////////////////////////////////////////////
/**
* Template method that will be called whenever an event occurs the observer is interested in.
* @param e Event
* @throws EngineTraceException To abort the running process
*/
public void observeEvent(ObserverEvent e)
{
EngineTraceEvent event = (EngineTraceEvent) e;
//
// First, check if there is a breakpoint defined for the position
// associated with this event
//
ModelQualifier pos1 = null;
ModelQualifier pos2 = null;
String eventType = event.getEventType();
if (eventType.equals(EngineTraceEvent.NODE_ENTRY))
{
pos1 = event.getNode().getQualifier();
pos2 = event.getNodeSocket().getQualifier();
}
else if (eventType.equals(EngineTraceEvent.NODE_EXIT))
{
pos1 = event.getNodeSocket().getQualifier();
}
else if (eventType.equals(EngineTraceEvent.CONTROL_FLOW))
{
// Nothing to do here
}
else if (eventType.equals(EngineTraceEvent.DATA_FLOW))
{
if (event.getTargetParam() instanceof NodeParam)
pos1 = event.getTargetParam().getQualifier();
else if (event.getSourceParam() instanceof NodeParam)
pos1 = event.getSourceParam().getQualifier();
}
else if (eventType.equals(EngineTraceEvent.PROCESS_EXCEPTION))
{
// Nothing to do here
}
DebuggerClient breakClient = null;
// Check if this token context already runs under control of a debugger client
TokenContext context = event.getTokenContext();
String clientId = context.getDebuggerId();
if (clientId != null)
{
DebuggerClient client = getClient(clientId);
if (client == null)
{
// There is a client id referenced in the token context, but no such client
if (waitForDebugger)
{
client = waitForDebuggerClient(clientId);
}
else
{
// Remove the id from the context and continue the process
context.setDebuggerId(null);
return;
}
}
int mode = client.getDebuggerMode();
if (eventType.equals(EngineTraceEvent.PROCESS_EXCEPTION))
{
// An exeception occurred during process execution
if ((mode & MODE_BREAK_ON_EXCEPTION) != 0)
{
breakClient = client;
}
}
if (breakClient == null)
{
if (stopAtFirstEvent)
{
stopAtFirstEvent = false;
breakClient = client;
}
if (stopAtAnyEvent)
{
breakClient = client;
}
// First, check if we ran onto a breakpoint set by this client
if (pos1 != null)
{
Breakpoint bp = client.getActiveBreakpoint(pos1);
if (bp != null)
{
breakClient = client;
if ((bp.getState() & Breakpoint.STATE_TEMPORARY) != 0)
{
// Clear temporary breakpoint
client.clearBreakpoint(pos1);
}
}
}
if (breakClient == null && pos2 != null)
{
Breakpoint bp = client.getActiveBreakpoint(pos2);
if (bp != null)
{
breakClient = client;
if ((bp.getState() & Breakpoint.STATE_TEMPORARY) != 0)
{
// Clear temporary breakpoint
client.clearBreakpoint(pos2);
}
}
}
// Check if we enter a top-level process
if (breakClient == null && (mode & MODE_BREAK_ON_TOP_LEVEL) != 0 && eventType.equals(EngineTraceEvent.NODE_EXIT)
&& event.getNode() instanceof InitialNode && context.getCallStack().getCallDepth() == 0)
{
// Yes, we are at the exit socket of an initial node of a top level process
// Check if there is a 'break on top level' client
breakClient = client;
}
// Check if we accepted a workflow task
if (breakClient == null && (mode & MODE_BREAK_ON_WORKFLOW) != 0 && eventType.equals(EngineTraceEvent.NODE_EXIT)
&& event.getNode() instanceof WorkflowNode && event.getNodeSocket().isDefaultSocket())
{
// Yes, we are leaving a worklfow node at the default socket
// (meaning we accepted a workflow task)
breakClient = client;
}
if (breakClient == null)
{
// We have no breakpoint set by this client, now check if there is
// a command pending we need to follow
// Check if the current command applies to the current position
int command = client.getNextCommand();
if (command == CMD_STEP_NEXT)
{
// The step next command halts for any events
breakClient = client;
}
else if (command == CMD_STEP_INTO)
{
// The step into command
if (eventType.equals(EngineTraceEvent.NODE_ENTRY))
breakClient = client;
}
else if (command == CMD_STEP_OVER)
{
// The step over command
// Check if the current call level <= call level at time when the command was issued
if (eventType.equals(EngineTraceEvent.NODE_ENTRY))
{
int depth = context.getCallStack().getCallDepth();
int commandDepth = client.getCallDepth();
if (depth <= commandDepth)
{
breakClient = client;
}
}
}
else if (command == CMD_STEP_OUT)
{
// The step out command
// Check if the current call level < call level at time when the command was issued
if (eventType.equals(EngineTraceEvent.NODE_ENTRY))
{
int depth = context.getCallStack().getCallDepth();
int commandDepth = client.getCallDepth();
if (depth <= commandDepth)
{
breakClient = client;
}
}
}
else if (command == CMD_STEP_UNTIL)
{
// The step until command halts at the position specified with the command
String stepUntilPosition = client.getStepUntilPosition();
if (pos1 != null && pos1.equals(stepUntilPosition) || pos2 != null && pos2.equals(stepUntilPosition))
{
breakClient = client;
}
}
if (command != CMD_STEP_UNTIL)
{
// Check if we should skip System model processes
if (breakClient != null && (mode & MODE_SKIP_SYSTEM_MODEL) != 0)
{
if (pos1 != null && CoreConstants.SYSTEM_MODEL_NAME.equals(pos1.getModel()))
{
// Process belongs to the System model, skip
breakClient = null;
}
}
}
}
}
}
else
{
// No client is associated with this context
// check if any client has set a global breakpoint for the current position
if (pos1 != null)
{
breakClient = determineGlobalBreakpointClient(pos1);
}
if (breakClient == null && pos2 != null)
{
breakClient = determineGlobalBreakpointClient(pos2);
}
}
if (breakClient != null)
{
// There is a debugger client associated with this event, stop the process
// Determine current position
ModelQualifier pos = null;
if (event.getTargetParam() instanceof NodeParam)
pos = event.getTargetParam().getQualifier();
else if (event.getSourceParam() instanceof NodeParam)
pos = event.getSourceParam().getQualifier();
else if (event.getNodeSocket() != null)
pos = event.getNodeSocket().getQualifier();
else if (event.getNode() != null)
pos = event.getNode().getQualifier();
if (pos == null)
pos = event.getProcess().getQualifier();
// Save the halt status information in the client object
DebuggerClient.HaltInfo haltInfo = breakClient.addHaltInfo(pos, event, context);
// When stopping a process, reset the step until target
breakClient.setStepUntilPosition(null);
// Connect the token context of this process to the client
context.setDebuggerId(breakClient.getClientId());
// Wait for a command from the client
waitForCommand(breakClient, haltInfo);
// Reset the client information concerning the halted process
breakClient.resetHaltInfo();
// Check if stop command is set and inform PE if yes
if (breakClient.getNextCommand() == CMD_STOP)
{
// Reset all status information of the client.
breakClient.resetHaltInfo();
breakClient.setNextCommand(CMD_NONE);
breakClient.setStepUntilPosition(null);
breakClient.resetCallDepth();
// On a stop command, we throw an exception that tells the engine to stop the process.
TokenContextUtil.resetTerminationRequest(context);
throw new EngineTraceException("Process aborted by debugger");
}
}
}
//////////////////////////////////////////////////
// @@ Helpers
//////////////////////////////////////////////////
/**
* Checks if any client has defined a global breakpoint for this position.
* @param qualifier Reference to a node, a socket etc
* @return The client or null if no client has such a breakpoint
*/
private DebuggerClient determineGlobalBreakpointClient(ModelQualifier qualifier)
{
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient client = (DebuggerClient) it.next();
Breakpoint bp = client.getActiveGlobalBreakpoint(qualifier);
if (bp != null)
return client;
}
return null;
}
private DebuggerClient waitForDebuggerClient(String clientId)
{
LogUtil.warn(getClass(), "Process waiting for debugger (debugger id $0)...", clientId);
for (;;)
{
DebuggerClient client = getClient(clientId);
if (client != null)
return client;
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
}
}
}
private void registerObserver()
{
// Register the debugger as event observer for all trace event types
if (observerMgr != null)
{
observerMgr.registerObserver(this, EngineTraceEvent.getSupportedEventTypes());
}
}
private void unregisterObserver()
{
if (observerMgr != null)
{
observerMgr.unregisterObserver(this);
}
}
/////////////////////////////////////////////////////////////////////////////
// @@ Runnable implementation: Timeout thread
/////////////////////////////////////////////////////////////////////////////
/**
* Thread that checks for debugging client timeouts.
*/
private class DebuggerClientCleanupThread extends Thread
{
/**
* The constructor.
*/
public DebuggerClientCleanupThread()
{
super("Debugger client cleanup thread");
// This thread must not prevent VM shutdown.
setDaemon(true);
}
/**
* Run method of the thread that checks Debugger client timeouts.
*
* Checks the registered clients for timeout and unregisters the client
* if the timeout is reached.
*/
public void run()
{
for (;;)
{
boolean haveTimeoutClient = false;
try
{
// Sleep a while to prevent this thread from blocking CPU resources
Thread.sleep(30000);
}
catch (InterruptedException ie)
{
}
if (clients.isEmpty())
{
// Stop timer if there are no clients left
timerThread.interrupt();
}
if (Thread.interrupted())
{
// Return from the run method and null out the timer thread
timerThread = null;
break;
}
// Get current timestamp
long currentTime = System.currentTimeMillis();
for (Iterator it = clients.values().iterator(); it.hasNext();)
{
DebuggerClient client = (DebuggerClient) it.next();
long timeout = client.getTimeout();
long timestamp = client.getTimestamp();
if (timeout > 0)
{
haveTimeoutClient = true;
// Check if timout is expired
long diff = (currentTime - timestamp) / 1000;
if (diff > timeout)
{
try
{
// Yes, unregister client
unregisterClient(client.getClientId());
// Break to prevent concurrent modification exception.
break;
}
catch (OpenBPException e)
{
}
}
}
}
// Stop timer if no timed clients remain
if (! haveTimeoutClient)
timerThread.interrupt();
}
}
}
/**
* Gets the observer mgr.
*/
public EngineEventObserverMgr getObserverMgr()
{
return observerMgr;
}
/**
* Sets the observer mgr.
*/
public void setObserverMgr(EngineEventObserverMgr observerMgr)
{
this.observerMgr = observerMgr;
}
/**
* Gets the session registry.
*/
public SessionRegistry getSessionRegistry()
{
return sessionRegistry;
}
/**
* Sets the session registry.
*/
public void setSessionRegistry(SessionRegistry sessionRegistry)
{
this.sessionRegistry = sessionRegistry;
}
}