/******************************************************************************* * Copyright (c) 2011, 2012 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tcf.core.scripting.launcher; import java.io.IOException; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.tcf.core.AbstractChannel; import org.eclipse.tcf.core.AbstractChannel.TraceListener; import org.eclipse.tcf.core.Command; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IChannel.IChannelListener; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.IService; import org.eclipse.tcf.te.runtime.callback.Callback; import org.eclipse.tcf.te.runtime.events.EventManager; import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback; import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer; import org.eclipse.tcf.te.tcf.core.Tcf; import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager; import org.eclipse.tcf.te.tcf.core.scripting.activator.CoreBundleActivator; import org.eclipse.tcf.te.tcf.core.scripting.events.ScriptEvent; import org.eclipse.tcf.te.tcf.core.scripting.interfaces.IScriptLauncher; import org.eclipse.tcf.te.tcf.core.scripting.interfaces.IScriptLauncherProperties; import org.eclipse.tcf.te.tcf.core.scripting.nls.Messages; import org.eclipse.tcf.te.tcf.core.scripting.parser.Parser; import org.eclipse.tcf.te.tcf.core.scripting.parser.Token; import org.eclipse.tcf.te.tcf.core.util.JSONUtils; /** * Script launcher implementation. */ public class ScriptLauncher extends PlatformObject implements IScriptLauncher { // The channel instance /* default */ IChannel channel; // The process properties instance private IPropertiesContainer properties; // The callback instance private ICallback callback; // The channel trace listener instance /* default */ TraceListener traceListener; /** * Constructor. */ public ScriptLauncher() { super(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.core.scripting.interfaces.IScriptLauncher#dispose() */ @Override public void dispose() { if (channel != null) { // Remove the trace listener if (traceListener != null) { ((AbstractChannel)channel).removeTraceListener(traceListener); traceListener = null; } // Close the channel as all disposal is done Tcf.getChannelManager().closeChannel(channel); // Fire the stop event ScriptEvent event = new ScriptEvent(ScriptLauncher.this, ScriptEvent.Type.STOP, null); EventManager.getInstance().fireEvent(event); // Dissociate the channel channel = null; } } /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.core.scripting.interfaces.IScriptLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) */ @Override public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) { Assert.isNotNull(peer); Assert.isNotNull(properties); // Normalize the callback if (callback == null) { this.callback = new Callback() { /* (non-Javadoc) * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus) */ @Override public void internalDone(Object caller, IStatus status) { } }; } else { this.callback = callback; } // Remember the process properties this.properties = properties; // Open a channel to the given peer if necessary Tcf.getChannelManager().openChannel(peer, null, new IChannelManager.DoneOpenChannel() { /* (non-Javadoc) * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel) */ @Override public void doneOpenChannel(Throwable error, IChannel channel) { if (error == null) { ScriptLauncher.this.channel = channel; // Fire the start event ScriptEvent event = new ScriptEvent(ScriptLauncher.this, ScriptEvent.Type.START, null); EventManager.getInstance().fireEvent(event); // Attach a channel listener so we can dispose ourself if the channel // is closed from the remote side. channel.addChannelListener(new IChannelListener() { /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened() */ @Override public void onChannelOpened() { } /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable) */ @Override public void onChannelClosed(Throwable error) { if (traceListener != null && ScriptLauncher.this.channel != null) { ((AbstractChannel)ScriptLauncher.this.channel).removeTraceListener(traceListener); traceListener = null; } if (error != null) { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()), error); invokeCallback(status, null); } } /* (non-Javadoc) * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int) */ @Override public void congestionLevel(int level) { } }); // Create the trace listener instance traceListener = new TraceListener() { @Override public void onMessageSent(char type, String token, String service, String name, byte[] data) { if (isFiltered(type, name)) return; String message = formatMessage(type, token, service, name, data, false); ScriptEvent event = new ScriptEvent(ScriptLauncher.this, ScriptEvent.Type.OUTPUT, new ScriptEvent.Message(type, message)); EventManager.getInstance().fireEvent(event); } @Override public void onMessageReceived(char type, String token, String service, String name, byte[] data) { if (isFiltered(type, name)) return; String message = formatMessage(type, token, service, name, data, true); ScriptEvent event = new ScriptEvent(ScriptLauncher.this, ScriptEvent.Type.OUTPUT, new ScriptEvent.Message(type, message)); EventManager.getInstance().fireEvent(event); } @Override public void onChannelClosed(Throwable error) { } /** * Checks if a given message is filtered. Filtered messages are not send to * the script output console. * * @param type The message type. * @param name The message name. * * @return <code>True</code> if the message is filtered, <code>false</code> otherwise. */ private boolean isFiltered(char type, String name) { boolean filtered = false; // Filter out the heart beat and framework messages if (type == 'F' || (name != null && name.toLowerCase().contains("heartbeat"))) { //$NON-NLS-1$ filtered = true; } return filtered; } /** * Format the trace message. */ protected String formatMessage(char type, String token, String service, String name, byte[] data, boolean received) { // Decode the arguments again for tracing purpose String args = JSONUtils.decodeStringFromByteArray(data); // Construct the full message // // The message format is: [<---|--->] <type> <token> <service>#<name> <args> StringBuilder message = new StringBuilder(); message.append(received ? "<---" : "--->"); //$NON-NLS-1$ //$NON-NLS-2$ message.append(" ").append(Character.valueOf(type)); //$NON-NLS-1$ if (token != null) message.append(" ").append(token); //$NON-NLS-1$ if (service != null) message.append(" ").append(service); //$NON-NLS-1$ if (name != null) message.append(" ").append(name); //$NON-NLS-1$ if (args != null && args.trim().length() > 0) message.append(" ").append(args.trim()); //$NON-NLS-1$ return message.toString(); } }; // Register the trace listener ((AbstractChannel)channel).addTraceListener(traceListener); // Check if the channel is in connected state if (channel.getState() != IChannel.STATE_OPEN) { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.ScriptLauncher_error_channelNotConnected, new IllegalStateException()); invokeCallback(status, null); return; } // Do some very basic sanity checking on the script properties if (properties.getStringProperty(IScriptLauncherProperties.PROP_SCRIPT) == null) { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.ScriptLauncher_error_missingScript, new IllegalArgumentException(IScriptLauncherProperties.PROP_SCRIPT)); invokeCallback(status, null); return; } // Execute the launch now executeLaunch(); } else { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()), error); invokeCallback(status, null); } } }); } /** * Executes the script launch. */ protected void executeLaunch() { // Get the script properties container final IPropertiesContainer properties = getProperties(); if (properties == null) { // This is an illegal argument. Properties must be set IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$ new IllegalArgumentException()); invokeCallback(status, null); return; } // Get the script to execute String script = properties.getStringProperty(IScriptLauncherProperties.PROP_SCRIPT); if (script == null || "".equals(script.trim())) { //$NON-NLS-1$ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), Messages.ScriptLauncher_error_missingScript, new IllegalArgumentException(IScriptLauncherProperties.PROP_SCRIPT)); invokeCallback(status, null); return; } // Create the script parser instance Parser parser = new Parser(script); try { // Parse the script Token[] tokens = parser.parse(); // And execute the tokens extracted, one by one sequentially if (tokens != null && tokens.length > 0) { executeToken(tokens, 0); } else { invokeCallback(Status.OK_STATUS, null); return; } } catch (IOException e) { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_parsingScript, e.getLocalizedMessage()), e); invokeCallback(status, null); return; } } /** * Executes the token at the given index. * * @param tokens The tokens. Must not be <code>null</code>. * @param index The index. */ @SuppressWarnings("unused") protected void executeToken(final Token[] tokens, final int index) { Assert.isNotNull(tokens); if (index < 0 || index >= tokens.length) { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_illegalIndex, Integer.valueOf(index)), new IllegalArgumentException("index")); //$NON-NLS-1$ invokeCallback(status, null); return; } Token token = tokens[index]; IService service = channel != null ? channel.getRemoteService(token.getServiceName()) : null; if (service != null) { new Command(channel, service, token.getCommandName(), token.getArguments()) { @Override public void done(Exception error, Object[] args) { if (error == null) { // Execute the next token int nextIndex = index + 1; if (nextIndex == tokens.length) { // All tokens executed invokeCallback(Status.OK_STATUS, null); } else { executeToken(tokens, nextIndex); } } else { // Stop the execution IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_parsingScript, error.getLocalizedMessage()), error); invokeCallback(status, null); } } }; } else { IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), NLS.bind(Messages.ScriptLauncher_error_serviceNotAvailable, token.getServiceName(), channel.getRemotePeer().getID()), null); invokeCallback(status, null); } } /** * Invoke the callback with the given parameters. If the given status severity * is {@link IStatus#ERROR}, the process launcher object is disposed automatically. * * @param status The status. Must not be <code>null</code>. * @param result The result object or <code>null</code>. */ protected void invokeCallback(IStatus status, Object result) { // Dispose the process launcher if we report an error if (status.getSeverity() == IStatus.ERROR) { dispose(); } // Invoke the callback ICallback callback = getCallback(); if (callback != null) { callback.setResult(result); callback.done(this, status); } } /** * Returns the channel instance. * * @return The channel instance or <code>null</code> if none. */ public final IChannel getChannel() { return channel; } /** * Returns the process properties container. * * @return The process properties container or <code>null</code> if none. */ public final IPropertiesContainer getProperties() { return properties; } /** * Returns the callback instance. * * @return The callback instance or <code>null</code> if none. */ protected final ICallback getCallback() { return callback; } }