/*******************************************************************************
* Copyright (c) 2012 - 2014 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.va;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.te.runtime.processes.ProcessLauncher;
import org.eclipse.tcf.te.runtime.processes.ProcessOutputReaderThread;
import org.eclipse.tcf.te.runtime.utils.Env;
import org.eclipse.tcf.te.runtime.utils.Host;
import org.eclipse.tcf.te.tcf.core.activator.CoreBundleActivator;
import org.eclipse.tcf.te.tcf.core.interfaces.tracing.ITraceIds;
import org.eclipse.tcf.te.tcf.core.nls.Messages;
import org.osgi.framework.Bundle;
/**
* Value-add launcher implementation.
*/
public class ValueAddLauncher extends ProcessLauncher {
// The target peer id
private final String id;
// The path of the value-add to launch
private final IPath path;
// The value-add id
private final String valueAddId;
// The process handle
private Process process;
// The process output reader
private ProcessOutputReaderThread outputReader;
// The process error reader
private ProcessOutputReaderThread errorReader;
/**
* Constructor.
*
* @param id The target peer id. Must not be <code>null</code>.
* @param path The value-add path. Must not be <code>null</code>.
* @param valueAddId The value-add id. Must not be <code>null</code>.
*/
public ValueAddLauncher(String id, IPath path, String valueAddId) {
super(null, null, 0);
Assert.isNotNull(id);
this.id = id;
Assert.isNotNull(path);
this.path = path;
Assert.isNotNull(valueAddId);
this.valueAddId = valueAddId;
}
/**
* Returns the target peer id.
*
* @return The target peer id.
*/
protected final String getPeerId() {
return id;
}
/**
* Returns the process handle.
*
* @return The process handle or <code>null</code>.
*/
public final Process getProcess() {
return process;
}
/**
* Returns the process output reader.
*
* @return The process output reader or <code>null</code>.
*/
public final ProcessOutputReaderThread getOutputReader() {
return outputReader;
}
/**
* Returns the process error reader.
*
* @return The process error reader or <code>null</code>.
*/
public final ProcessOutputReaderThread getErrorReader() {
return errorReader;
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.runtime.processes.ProcessLauncher#launch()
*/
@Override
public void launch() throws ValueAddException {
IPath dir = path.removeLastSegments(1);
String cmd = Host.isWindowsHost() ? path.toOSString() : "./" + path.lastSegment(); //$NON-NLS-1$
// Build up the command
List<String> command = new ArrayList<String>();
command.add(cmd);
// Add command line parameters for the value-add
addCommandLineParameters(command);
// Add the logging command line parameters for the value-add
addLoggingCommandLineParameters(command);
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_CHANNEL_MANAGER)) {
CoreBundleActivator.getTraceHandler().trace(NLS.bind(Messages.ValueAddLauncher_launch_command, new Object[] { command, id, valueAddId }),
0, ITraceIds.TRACE_CHANNEL_MANAGER, IStatus.INFO, this);
}
// Determine the environment
String[] envp = null;
// Get the set of environment variables the launcher requires to set
String[] additional = getEnvironmentVariables();
if (additional != null && additional.length > 0) {
// Get the native environment and merge the additional variables
envp = Env.getEnvironment(additional, false);
}
// Launch the value-add
process = exec(command.toArray(new String[command.size()]), envp, dir.toFile());
// Launch the process output reader
outputReader = createProcessOutputReaderThread(path, process.getInputStream());
outputReader.start();
// Launch the process error reader
errorReader = createProcessOutputReaderThread(path, process.getErrorStream());
errorReader.start();
}
/**
* Adds the given argument to the given command.
* <p>
* Custom value add launcher implementations may overwrite this method to
* validate and/or modify the command used to launch the value-add.
*
* @param command The command. Must not be <code>null</code>.
* @param arg The argument. Must not be <code>null</code>.
*/
protected void addToCommand(List<String> command, String arg) {
Assert.isNotNull(command);
Assert.isNotNull(arg);
command.add(arg);
}
/**
* Add the value-add command line parameters to the command.
*
* @param command The command. Must not be <code>null</code>.
* @throws ValueAddException In case something failed while adding the command line parameters.
*/
protected void addCommandLineParameters(List<String> command) throws ValueAddException {
Assert.isNotNull(command);
// Determine a free port to use by the value-add. We must
// avoid to launch the value-add at the default port 1534.
int port = getFreePort();
addToCommand(command, "-I180"); //$NON-NLS-1$
addToCommand(command, "-S"); //$NON-NLS-1$
addToCommand(command, "-sTCP::" + (port != -1 ? Integer.valueOf(port) : "") + ";ValueAdd=1"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Add the value-add logging command line parameters to the command.
*
* @param command The command. Must not be <code>null</code>.
* @throws ValueAddException In case something failed while adding the logging command line parameters.
*/
protected void addLoggingCommandLineParameters(List<String> command) throws ValueAddException {
Assert.isNotNull(command);
// Enable logging?
if (Boolean.getBoolean("va.logging.enable")) { //$NON-NLS-1$
// Calculate the location and name of the log file
Bundle bundle = Platform.getBundle("org.eclipse.tcf.te.tcf.log.core"); //$NON-NLS-1$
IPath location = bundle != null ? Platform.getStateLocation(bundle) : null;
if (location != null) {
location = location.append(".logs"); //$NON-NLS-1$
String name = "Output_" + valueAddId + "_" + id + ".log"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
name = name.replaceAll("\\s", "_"); //$NON-NLS-1$ //$NON-NLS-2$
name = name.replaceAll("[:/\\;,]", "_"); //$NON-NLS-1$ //$NON-NLS-2$
location = location.append(name);
addToCommand(command, "-L" + location.toString()); //$NON-NLS-1$
String level = System.getProperty("va.logging.level"); //$NON-NLS-1$
if (level != null && !"".equals(level.trim())) { //$NON-NLS-1$
addToCommand(command, "-l" + level.trim()); //$NON-NLS-1$
}
}
}
}
/**
* Execute the value-add launch command.
*
* @param cmdarray Array containing the command to call and its arguments. Must not be <code>null</code>.
* @param envp Array of strings, each element of which has environment variable settings in the format <code>name=value</code>,
* or <code>null</code> if the subprocess should inherit the environment of the current process.
* @param dir The working directory of the subprocess, or <code>null</code> if the subprocess should inherit
* the working directory of the current process.
*
* @return The process instance.
* @see Runtime#exec(String[], String[], File)
*/
protected Process exec(String[] cmdarray, String[] envp, File dir) throws ValueAddException {
Assert.isNotNull(cmdarray);
Process process = null;
try {
process = Runtime.getRuntime().exec(cmdarray, envp, dir);
} catch (IOException e) {
throw new ValueAddException(e);
}
return process;
}
/**
* Creates the process output reader thread for the given stream.
*
* @param path The process path. Must not be <code>null</code>.
* @param stream The stream. Must not be <code>null</code>.
*
* @return The not yet started process output reader thread instance.
*/
protected ProcessOutputReaderThread createProcessOutputReaderThread(IPath path, InputStream stream) {
Assert.isNotNull(path);
Assert.isNotNull(stream);
return new ProcessOutputReaderThread(path.lastSegment(), new InputStream[] { stream });
}
/**
* Determine a free port to use.
*
* @return A free port or <code>-1</code>.
*/
protected int getFreePort() {
int port = -1;
try {
ServerSocket socket = new ServerSocket(0);
port = socket.getLocalPort();
socket.close();
} catch (IOException e) { /* ignored on purpose */ }
return port;
}
/**
* Returns the set of environment variable which needs to be added to
* the native environment or overwritten in the native environment in
* order to launch the value add successfully.
*
* @return The set of environment variables or <code>null</code>.
*/
protected String[] getEnvironmentVariables() {
return null;
}
}