/*
* Created on Jun 8, 2006 Copyright (C) 2001-5, Anthony Harrison anh23@pitt.edu
* (jactr.org) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version. This library is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.eclipse.runtime.launching.norm;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commonreality.net.handler.IMessageHandler;
import org.commonreality.net.provider.INetworkingProvider;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IDebugTarget;
import org.jactr.eclipse.runtime.RuntimePlugin;
import org.jactr.eclipse.runtime.debug.ACTRDebugTarget;
import org.jactr.eclipse.runtime.handlers.TransformedEventMessageHandler;
import org.jactr.eclipse.runtime.launching.ACTRLaunchConstants;
import org.jactr.eclipse.runtime.launching.session.AbstractSession;
import org.jactr.eclipse.runtime.launching.session.SessionTracker;
import org.jactr.eclipse.runtime.preferences.RuntimePreferences;
import org.jactr.tools.async.credentials.ICredentials;
import org.jactr.tools.async.message.event.state.ModelStateEvent;
import org.jactr.tools.async.message.event.state.RuntimeStateEvent;
import org.jactr.tools.async.shadow.ShadowController;
import org.jactr.tools.tracer.transformer.AbstractTransformedEvent;
public class ACTRSession extends AbstractSession
{
static public final String LAUNCH_TYPE = "org.jactr.eclipse.runtime.launching.normal";
static private SessionTracker<ACTRSession> _sessionTracker = new SessionTracker<ACTRSession>();
static public SessionTracker<ACTRSession> getACTRSessionTracker()
{
return _sessionTracker;
}
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(ACTRSession.class);
static public String ACTR_DEBUG_MODEL = "org.jactr.eclipse.runtime.debug.DebugClient";
protected ShadowController _shadowController;
protected boolean _suspendImmediately = false;
protected TransformedEventMessageHandler _eventHandler;
/**
* @param launch
* @param mode
* @throws IllegalStateException
* if we are unable to open the service connection
*/
public ACTRSession(ILaunch launch, ILaunchConfiguration configuration)
throws IllegalStateException
{
super(launch, configuration);
try
{
setSuspendImmediately(configuration.getAttribute(
ACTRLaunchConstants.ATTR_SUSPEND, false));
}
catch (Exception e)
{
}
_shadowController = new ShadowController();
configureShadowControllerConnection(_shadowController, configuration);
configureShadowController(_shadowController, launch);
}
public void setSuspendImmediately(boolean suspendImmediately)
{
_suspendImmediately = suspendImmediately;
}
@Override
protected SessionTracker getSessionTracker()
{
return _sessionTracker;
}
protected void configureShadowControllerConnection(
ShadowController controller, ILaunchConfiguration configuration)
{
try
{
// this could become a preference for quick testing
String providerName = "org.commonreality.netty.NettyNetworkingProvider";
INetworkingProvider provider = INetworkingProvider
.getProvider(providerName);
controller.setService(provider.newServer());
controller.setTransportProvider(provider
.getTransport(INetworkingProvider.NIO_TRANSPORT));
controller.setProtocol(provider
.getProtocol(INetworkingProvider.SERIALIZED_PROTOCOL));
}
catch (Exception e)
{
throw new RuntimeException("Failed to configure shadow controller", e);
}
/**
* we need some credentials..
*/
String credentials = null;
if (credentials == null)
{
StringBuilder sb = new StringBuilder(System.getProperty("user.name"));
sb.append(":").append(generateRandomPassword());
credentials = sb.toString();
}
controller.setCredentialInformation(credentials);
/*
* now we just need to figure out where to attach..
*/
controller.setAddressInfo("localhost:0");
// controller.setExecutorService(Executors.newSingleThreadExecutor());
}
@SuppressWarnings("unchecked")
protected void configureShadowController(ShadowController controller,
ILaunch launch)
{
Map<Class<?>, IMessageHandler<?>> handlers = controller
.getDefaultHandlers();
/*
* we're going to use the existing handler for model state, plus add some
* logging for ourselves
*/
IMessageHandler fMSEHandler = handlers.get(ModelStateEvent.class);
handlers.put(
ModelStateEvent.class,
(s, m) -> {
fMSEHandler.accept(s, m); // original
ModelStateEvent mse = (ModelStateEvent) m;
if (mse.getException() != null)
RuntimePlugin.error(String.format(
"%s terminated abnormally due to %s", mse.getModelName(),
mse.getException()));
});
IMessageHandler fRSEHandler = handlers.get(RuntimeStateEvent.class);
handlers.put(
RuntimeStateEvent.class,
(s, m) -> {
fRSEHandler.accept(s, m);// original
RuntimeStateEvent message = (RuntimeStateEvent) m;
if (message.getException() != null)
RuntimePlugin.error(String.format(
"Execution terminated abnormally due to %s",
message.getException()));
});
/**
* to grab and route all the ITRansformedEvents..
*/
_eventHandler = new TransformedEventMessageHandler(this);
handlers.put(AbstractTransformedEvent.class, _eventHandler);
}
private String generateRandomPassword()
{
Random rand = new Random();
StringBuilder sb = new StringBuilder();
while (sb.length() < 16)
sb.append(rand.nextInt());
return sb.toString();
}
public ShadowController getShadowController()
{
return _shadowController;
}
@Override
protected void connect() throws CoreException
{
try
{
_shadowController.attach();
}
catch (Exception e)
{
throw new CoreException(new Status(IStatus.ERROR,
RuntimePlugin.PLUGIN_ID, "Failed to start shadow controller ", e));
}
if (_shadowController.getActiveSession() != null)
_shadowController.getActiveSession().addExceptionHandler(
(s, t) -> {
RuntimePlugin.error(
"Exception caught while listening to jACT-R execution ", t);
return false;
});
}
@Override
protected void disconnect(boolean force)
{
try
{
_shadowController.detach(force);
}
catch (Exception e)
{
RuntimePlugin.error("ACTRSession.disconnect threw Exception : ", e);
}
}
@Override
protected Job createSessionJob()
{
Job job = new ListenerJob(this);
job.setPriority(Job.LONG);
return job;
}
@Override
public InetSocketAddress getConnectionAddress()
{
return (InetSocketAddress) _shadowController.getActualAddress();
}
@Override
public ICredentials getCredentials()
{
return _shadowController.getActualCredentials();
}
private class ListenerJob extends Job
{
ACTRSession _master;
public ListenerJob(ACTRSession master)
{
super(master.getConfiguration().getName());
_master = master;
}
/**
* wait at most maxTime to a connection to be established
*
* @param maxTimeToWait
* @return true if connection is established
*/
protected boolean waitForConnection(IProgressMonitor monitor,
long maxTimeToWait) throws Exception
{
boolean connected = false;
monitor = new SubProgressMonitor(monitor, 1);
monitor.beginTask("Waiting for connection", 1);
try
{
long startTime = System.currentTimeMillis();
/*
* if the process is canceled, or the launch is terminated, we bail
*/
while (System.currentTimeMillis() - startTime < maxTimeToWait
&& !monitor.isCanceled() && !connected)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Waiting for connection " + maxTimeToWait + " "
+ monitor.isCanceled() + " " + _launch.isTerminated());
connected = _shadowController.waitForConnection(1000);
}
// RuntimePlugin.info("Connected:" + connected);
monitor.worked(1);
return connected;
}
finally
{
monitor.done();
}
}
/**
* wait for the debugger to finish its initialization (and startup)
*
* @param monitor
* @return
*/
protected boolean waitForDebugger(IProgressMonitor monitor)
throws Exception
{
monitor = new SubProgressMonitor(monitor, 2);
monitor.beginTask("Waiting for debugger", 2);
try
{
/*
* if a debugger is attached, let's wait until it is ready to go
*/
for (IDebugTarget debugger : _launch.getDebugTargets())
if (debugger instanceof ACTRDebugTarget)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Waiting for debugger " + debugger);
((ACTRDebugTarget) debugger).installBreakpoints();
if (LOGGER.isDebugEnabled()) LOGGER.debug("Debugger is ready");
}
monitor.worked(1);
monitor.setTaskName("Syncing communications");
_shadowController.getActiveSession().waitForPendingWrites();
monitor.worked(1);
return true;
}
catch (Exception e)
{
RuntimePlugin.error("Failed waiting for debugger", e);
throw e;
}
finally
{
monitor.done();
}
}
/**
* start the runtime, and wait for confirmation of the start
*
* @param monitor
* @return
*/
protected boolean startRuntime(IProgressMonitor monitor, long maxTimeToWait)
throws Exception
{
monitor = new SubProgressMonitor(monitor, 2);
boolean running = false;
try
{
monitor.beginTask("Starting runtime", 2);
_shadowController.start(_suspendImmediately);
monitor.worked(1);
monitor.setTaskName("Waiting for confirmation");
/*
* wait at most max time for the connection to start, aborting it the
* launch is terminated, or the process is canceled
*/
running = _shadowController.isRunning();
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < maxTimeToWait
&& !monitor.isCanceled() && !_launch.isTerminated() && !running)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Waiting for running " + maxTimeToWait + " "
+ monitor.isCanceled() + " " + _launch.isTerminated());
_shadowController.waitForStart(1000);
running = _shadowController.isRunning();
}
// RuntimePlugin.info("Runtime is running : " + running);
monitor.worked(1);
return running;
}
finally
{
monitor.done();
}
}
/**
* wait until the runtime has stopped running.
*
* @param monitor
*/
protected void waitForCompletion(IProgressMonitor monitor) throws Exception
{
monitor = new SubProgressMonitor(monitor, 1);
monitor.beginTask("Handling events", 1);
try
{
while (!monitor.isCanceled()
&& _shadowController.getActiveSession() != null
&& _shadowController.getActiveSession().isConnected()
&& _shadowController.isRunning())
_shadowController.waitForCompletion(1000);
}
finally
{
monitor.worked(1);
monitor.done();
}
// RuntimePlugin.info("Completed. running:" +
// _shadowController.isRunning()
// + " connected:" + _shadowController.getIOHandler().isConnected());
}
@Override
protected IStatus run(IProgressMonitor monitor)
{
monitor.beginTask("Listening to jACT-R Runtime", 14);
IStatus rtn = Status.OK_STATUS;
String label = "Connecting";
monitor.setTaskName(label);
long waitTime = 1000 * RuntimePlugin.getDefault().getPluginPreferences()
.getInt(RuntimePreferences.NORMAL_START_WAIT_PREF);
try
{
/*
* we don't look at terminated since it may be true if the launch hasn't
* started yet
*/
boolean shouldContinue = !monitor.isCanceled()
&& waitForConnection(monitor, waitTime);
label = "Configuring";
monitor.worked(1);
monitor.setTaskName(label);
if (shouldContinue)
shouldContinue = !monitor.isCanceled() && !_launch.isTerminated()
&& waitForDebugger(monitor);
monitor.worked(1);
label = "Starting";
monitor.setTaskName(label);
if (shouldContinue)
shouldContinue = !monitor.isCanceled() && !_launch.isTerminated()
&& startRuntime(monitor, waitTime / 2);
monitor.worked(1);
label = "Listening";
monitor.setTaskName(label);
if (shouldContinue && !monitor.isCanceled() && !_launch.isTerminated())
waitForCompletion(monitor);
monitor.worked(1);
/*
* now just because it has completed, doesn't actually mean all the
* messages have arrived..
*/
}
catch (Exception e)
{
rtn = new Status(IStatus.ERROR, RuntimePlugin.class.getName(),
"Failed " + label + " launch", e);
RuntimePlugin.error(rtn.getMessage(), e);
}
finally
{
monitor.done();
}
if (monitor.isCanceled()) rtn = Status.CANCEL_STATUS;
return rtn;
}
}
}