/*******************************************************************************
* Copyright (c) 2004, 2011 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.app.autagent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jubula.app.autagent.i18n.Messages;
import org.eclipse.jubula.autagent.AutStarter;
import org.eclipse.jubula.autagent.AutStarter.Verbosity;
import org.eclipse.jubula.autagent.agent.AutAgent;
import org.eclipse.jubula.autagent.desktop.DesktopIntegration;
import org.eclipse.jubula.communication.internal.connection.ConnectionState;
import org.eclipse.jubula.tools.internal.constants.EnvConstants;
import org.eclipse.jubula.tools.internal.exception.JBVersionException;
import org.eclipse.jubula.tools.internal.utils.EnvironmentUtils;
import org.eclipse.jubula.version.Vn;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author BREDEX GmbH
* @created Jun 21, 2011
*/
public class AutAgentApplication implements IApplication {
/**
* constant hostname
*/
private static final String HOSTNAME = "hostname"; //$NON-NLS-1$
/**
* commandline constant for port
*/
private static final String COMMANDLINE_PORT = "port"; //$NON-NLS-1$
/**
* constant for autagent launcher
*/
private static final String AUTAGENT_LAUNCHER = "autagent"; //$NON-NLS-1$
/** the logger */
private static final Logger LOG =
LoggerFactory.getLogger(AutAgentApplication.class);
/** constant for timeout when sending command to shutdown AUT Agent */
private static final int TIMEOUT_SEND_STOP_CMD = 10000;
/**
* <code>COMMANDLINE_OPTION_STOP</code>
*/
private static final String COMMANDLINE_OPTION_STOP = "stop"; //$NON-NLS-1$
/**
* command line argument: port number
*/
private static final String COMMANDLINE_OPTION_PORT = "p"; //$NON-NLS-1$
/**
* command line argument: show help
*/
private static final String COMMANDLINE_OPTION_HELP_SH = "h"; //$NON-NLS-1$
/**
* command line argument: show help
*/
private static final String COMMANDLINE_OPTION_HELP = "help"; //$NON-NLS-1$
/**
* command line argument: enable "lenient" mode
*/
private static final String COMMANDLINE_OPTION_LENIENT = "l"; //$NON-NLS-1$
/**
* command line argument: quiet output
*/
private static final String COMMANDLINE_OPTION_OBJECTMAPPING = "om"; //$NON-NLS-1$
/**
* command line argument: verbose output
*/
private static final String COMMANDLINE_OPTION_VERBOSE = "v"; //$NON-NLS-1$
/**
* command line argument: quiet output
*/
private static final String COMMANDLINE_OPTION_QUIET = "q"; //$NON-NLS-1$
/**
* command line argument: start
*/
private static final String COMMANDLINE_OPTION_START = "start"; //$NON-NLS-1$
/** exit code in case of invalid options */
private static final int EXIT_INVALID_OPTIONS = -1;
/** exit code in case of option -h(elp) */
private static final int EXIT_HELP_OPTION = 0;
/** exit code in case of a security exception */
private static final int EXIT_SECURITY_VIOLATION = 1;
/** exit code in case of an I/O exception */
private static final int EXIT_IO_EXCEPTION = 2;
/** exit code in case of a version error between Client and AutStarter */
private static final int EXIT_CLIENT_SERVER_VERSION_ERROR = 4;
/**
* {@inheritDoc}
*/
public Object start(IApplicationContext context) throws Exception {
String[] args = (String[])context.getArguments().get(
IApplicationContext.APPLICATION_ARGS);
if (args == null) {
args = new String[0];
} else {
args = workaroundForBug392323(args);
}
// create the single instance here
final AutStarter server = AutStarter.getInstance();
CommandLineParser parser = new PosixParser();
try {
CommandLine cmd = parser.parse(createOptions(false), args);
if (cmd.hasOption(COMMANDLINE_OPTION_HELP)
|| cmd.hasOption(COMMANDLINE_OPTION_HELP_SH)) {
printHelp();
return EXIT_HELP_OPTION;
}
int port = getPortNumber(cmd);
if (cmd.hasOption(COMMANDLINE_OPTION_STOP)) {
String hostname = EnvConstants.LOCALHOST_ALIAS;
if (cmd.getOptionValue(COMMANDLINE_OPTION_STOP) != null) {
hostname = cmd.getOptionValue(COMMANDLINE_OPTION_STOP);
}
stopAutAgent(hostname, port);
} else {
boolean killDuplicateAuts =
!cmd.hasOption(COMMANDLINE_OPTION_LENIENT);
Verbosity verbosity = Verbosity.NORMAL;
if (cmd.hasOption(COMMANDLINE_OPTION_VERBOSE)) {
verbosity = Verbosity.VERBOSE;
} else if (cmd.hasOption(COMMANDLINE_OPTION_QUIET)) {
verbosity = Verbosity.QUIET;
}
boolean objectMapping = false;
if (cmd.hasOption(COMMANDLINE_OPTION_OBJECTMAPPING)) {
objectMapping = true;
}
DesktopIntegration di =
new DesktopIntegration(server.getAgent(), objectMapping);
di.setPort(port);
server.getAgent().addPropertyChangeListener(
AutAgent.PROP_NAME_AUTS, di);
server.start(port, killDuplicateAuts, verbosity, true);
}
} catch (ParseException pe) {
String message = Messages.ParseExceptionInvalidOption;
LOG.error(message, pe);
printHelp();
return EXIT_INVALID_OPTIONS;
} catch (SecurityException se) {
LOG.error(Messages.SecurityExceptionViolation, se);
return EXIT_SECURITY_VIOLATION;
} catch (IOException ioe) {
String message = Messages.IOExceptionNotOpenSocket;
LOG.error(message, ioe);
return EXIT_IO_EXCEPTION;
} catch (NumberFormatException nfe) {
String message = Messages.NumberFormatExceptionInvalidValue;
LOG.error(message, nfe);
return EXIT_INVALID_OPTIONS;
} catch (NullPointerException npe) {
LOG.error(Messages.NullPointerExceptionNoCommandLine, npe);
printHelp();
return EXIT_INVALID_OPTIONS;
} catch (JBVersionException ve) {
LOG.error(ve.getMessage(), ve);
return EXIT_CLIENT_SERVER_VERSION_ERROR;
}
return IApplication.EXIT_OK;
}
/**
* @see http://eclip.se/392323
*
* @param args
* the arguments to check
* @return the conditionally cleaned command line arguments
*/
private String[] workaroundForBug392323(final String[] args) {
String[] commandlineArgs = args;
if (EnvironmentUtils.isMacOS()) {
final List<String> argList = Arrays.asList(args);
final int loc = argList.indexOf("-showlocation"); //$NON-NLS-1$
if (loc >= 0) {
List<String> newArgs = new ArrayList<String>(argList);
newArgs.remove(loc);
commandlineArgs = newArgs.toArray(new String[] {});
}
}
return commandlineArgs;
}
/**
*
* {@inheritDoc}
*/
public void stop() {
// no-op
}
/**
* method to create an options object, filled with all options
* @param onlyVisible if <code>true</code> hides specific information
* @return the options
*/
private static Options createOptions(boolean onlyVisible) {
Options options = new Options();
Option portOption = new Option(COMMANDLINE_OPTION_PORT, true,
Messages.CommandlineOptionPort);
portOption.setArgName(COMMANDLINE_PORT);
options.addOption(portOption);
options.addOption(COMMANDLINE_OPTION_LENIENT, false,
Messages.CommandlineOptionLenient);
options.addOption(COMMANDLINE_OPTION_HELP, false,
Messages.CommandlineOptionHelp);
options.addOption(COMMANDLINE_OPTION_HELP_SH, false,
Messages.CommandlineOptionHelp);
if (!onlyVisible) {
options.addOption(COMMANDLINE_OPTION_OBJECTMAPPING,
false, Messages.CommandlineOptionOMM);
}
OptionGroup verbosityOptions = new OptionGroup();
verbosityOptions.addOption(new Option(COMMANDLINE_OPTION_QUIET, false,
Messages.CommandlineOptionQuiet));
verbosityOptions.addOption(new Option(COMMANDLINE_OPTION_VERBOSE,
false, Messages.CommandlineOptionVerbose));
options.addOptionGroup(verbosityOptions);
OptionGroup startStopOptions = new OptionGroup();
startStopOptions.addOption(new Option(COMMANDLINE_OPTION_START, false,
Messages.CommandlineOptionStart));
OptionBuilder.hasOptionalArg();
Option stopOption = OptionBuilder.create(COMMANDLINE_OPTION_STOP);
stopOption.setDescription(NLS.bind(Messages.OptionStopDescription,
EnvConstants.LOCALHOST_ALIAS));
stopOption.setArgName(HOSTNAME);
startStopOptions.addOption(stopOption);
options.addOptionGroup(startStopOptions);
return options;
}
/**
* prints a formatted help text
*/
private void printHelp() {
System.out.println(Vn.getDefault().getVersion());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(AUTAGENT_LAUNCHER, createOptions(true), true);
}
/**
* @param br
* the buffered reader which is used to determine whether the
* agent has shutdown itself
*/
private void waitForAgentToTerminate(BufferedReader br) {
// keep process and socket alive till agent has read the shutdown command
boolean socketAlive = true;
while (socketAlive) {
try {
if (br.readLine() == null) {
socketAlive = false;
}
} catch (IOException e) {
// ok here --> autagent has shut down itself
socketAlive = false;
}
}
}
/**
* Retrieves and returns the value of the "port number" argument from the
* given command line. If the argument is incorrectly formatted, an
* exception will be thrown. If the argument is not present, An attempt
* will be made to read the port from an environment variable. If the
* environment variable is not present or incorrectly formatted, then a
* default value is returned.
*
* @param cmd The command line from which to retrieve the port number.
* @return the port number
*/
private int getPortNumber(CommandLine cmd) {
int port = EnvConstants.AUT_AGENT_DEFAULT_PORT;
if (cmd.hasOption(COMMANDLINE_OPTION_PORT)) {
port = Integer.valueOf(cmd.getOptionValue(COMMANDLINE_OPTION_PORT))
.intValue();
} else {
int envPort = EnvironmentUtils.getAUTAgentEnvironmentPortNo();
if (envPort > 0) {
port = envPort;
}
LOG.info(NLS.bind(Messages.InfoDefaultPort, port));
}
return port;
}
/**
* Issues a "stop" command to the AUT Agent running on the given host
* and port.
*
* @param hostname The hostname to which to send the command.
* @param port The port on which to send the command.
* @throws UnknownHostException
* @throws IOException
* @throws JBVersionException
*/
private void stopAutAgent(String hostname, int port)
throws UnknownHostException, IOException, JBVersionException {
try (Socket commandSocket = new Socket(hostname, port)) {
InputStream inputStream = commandSocket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream));
ConnectionState.respondToTypeRequest(TIMEOUT_SEND_STOP_CMD, br,
inputStream, new PrintStream(commandSocket.getOutputStream()),
ConnectionState.CLIENT_TYPE_COMMAND_SHUTDOWN);
waitForAgentToTerminate(br);
} catch (ConnectException ce) {
System.out.println(NLS.bind(Messages.AUTAgentNotFound, hostname,
port));
}
}
}