/*******************************************************************************
* Copyright (c) 2004, 2010 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.client.cmd;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jubula.client.cmd.constants.ClientStrings;
import org.eclipse.jubula.client.cmd.exceptions.PreValidateException;
import org.eclipse.jubula.client.cmd.i18n.Messages;
import org.eclipse.jubula.client.cmd.progess.HeadlessProgressProvider;
import org.eclipse.jubula.client.core.ClientTest;
import org.eclipse.jubula.client.core.businessprocess.ClientTestStrings;
import org.eclipse.jubula.client.core.constants.Constants;
import org.eclipse.jubula.client.core.errorhandling.ErrorMessagePresenter;
import org.eclipse.jubula.client.core.errorhandling.IErrorMessagePresenter;
import org.eclipse.jubula.client.core.model.IAUTConfigPO;
import org.eclipse.jubula.client.core.persistence.locking.LockManager;
import org.eclipse.jubula.client.core.preferences.database.DatabaseConnection;
import org.eclipse.jubula.client.core.preferences.database.DatabaseConnectionConverter;
import org.eclipse.jubula.client.core.preferences.database.H2ConnectionInfo;
import org.eclipse.jubula.client.core.preferences.database.MySQLConnectionInfo;
import org.eclipse.jubula.client.core.preferences.database.OracleConnectionInfo;
import org.eclipse.jubula.client.core.preferences.database.PostGreSQLConnectionInfo;
import org.eclipse.jubula.client.core.progress.IProgressConsole;
import org.eclipse.jubula.client.core.utils.StringHelper;
import org.eclipse.jubula.client.internal.AutAgentConnection;
import org.eclipse.jubula.client.internal.exceptions.ConnectionException;
import org.eclipse.jubula.tools.internal.constants.AutConfigConstants;
import org.eclipse.jubula.tools.internal.constants.EnvConstants;
import org.eclipse.jubula.tools.internal.constants.StringConstants;
import org.eclipse.jubula.tools.internal.exception.JBException;
import org.eclipse.jubula.tools.internal.messagehandling.Message;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.eclipse.jubula.tools.internal.registration.AutIdentifier;
import org.eclipse.jubula.tools.internal.utils.TimeUtil;
import org.eclipse.jubula.version.Vn;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author BREDEX GmbH
* @created Mar 12, 2009
*/
public abstract class AbstractCmdlineClient implements IProgressConsole {
/** <code>EXIT_CODE_ERROR</code> */
protected static final int EXIT_CODE_ERROR = 1;
/** <code>EXIT_CODE_OK</code> */
protected static final int EXIT_CODE_OK = 0;
/** error message */
protected static final String OPT_NO_VAL = Messages.NoArgumentFor;
/** error message */
protected static final String OPT_UNKNOWN = Messages.UnrecognizedOption;
/** error message */
protected static final String JDBC_UNKNOWN = Messages.UnsupportedJDBC;
/** log facility */
private static Logger log =
LoggerFactory.getLogger(AbstractCmdlineClient.class);
/** be quiet during processing */
private static boolean quiet = false;
/** did an error occur during processing */
private static boolean errorOccured = false;
/** did a validation error occur during processing */
private static boolean validationErrorOccured = false;
/** did a missing argument error occur during processing */
private static boolean missingArgErrorOccured = false;
/** is this a dry run* */
private static boolean noRunValue = false;
/** the command line representation */
private CommandLine m_cmd = null;
/** external configuration file with parameters */
private File m_configFile;
/** JobConfiguration created from configFile */
private JobConfiguration m_job;
/**
* @param name name of option
* @param hasArg option has an argument
* @param argname name of the argument
* @param text Text for help
* @param isReq option is required
* @return Option opt
*/
protected static Option createOption(String name, boolean hasArg,
String argname, String text, boolean isReq) {
Option opt = new Option(name, hasArg, text);
opt.setRequired(isReq);
opt.setArgName(argname);
return opt;
}
/**
* cleanup of connection
*/
protected void shutdown() {
try {
if (!AutAgentConnection.getInstance().isConnected()) {
printlnConsoleError(Messages.ConnectionToAutUnexpectedly);
}
} catch (ConnectionException e) {
log.info(Messages.ConnectionToAutUnexpectedly, e);
}
IAUTConfigPO startedConfig = m_job.getAutConfig();
if (startedConfig != null) {
try {
AutIdentifier startedAutId = new AutIdentifier(
startedConfig.getConfigMap().get(
AutConfigConstants.AUT_ID));
if (AutAgentConnection.getInstance().isConnected()) {
ClientTest.instance().stopAut(startedAutId);
}
} catch (ConnectionException e) {
log.info(Messages.ErrorWhileShuttingDownStopping, e);
}
}
try {
while (AutAgentConnection.getInstance().isConnected()) {
ClientTest.instance().disconnectFromAutAgent();
TimeUtil.delay(200);
}
} catch (ConnectionException e) {
log.info(Messages.ErrorWhileShuttingDownDisconnecting, e);
}
// cleanup after connections closed
if (LockManager.isRunning()) {
LockManager.instance().dispose();
}
}
/**
*
* @param args
* the command line
* @throws FileNotFoundException
* if config file is missing
* @throws ParseException
* if wrong options are present
* @throws IOException
* if io error
* @return true to continue processing the commandline run; false to stop
* processing further execution.
*/
protected boolean parseCommandLine(String[] args)
throws FileNotFoundException, ParseException, IOException {
String[] cloneArgs = args.clone();
Options options = createOptions(false);
// Command line arguments parser
CommandLineParser parser = new BasicParser();
try {
// we will parse the command line until there are no
// (more) errors
int maxTrys = 5;
Boolean parseNotOK = true;
while (parseNotOK) {
try {
m_cmd = parser.parse(options, cloneArgs);
parseNotOK = false;
} catch (ParseException exp) {
if (maxTrys-- < 0) {
cloneArgs = handleParseException(args, exp, true);
throw new ParseException(StringConstants.EMPTY);
}
cloneArgs = handleParseException(args, exp, false);
}
}
if (m_cmd.hasOption(ClientStrings.HELP)) {
printUsage();
return false;
}
// The first thing to check is, if there is a config file
// if there is a config file we read this first,
if (m_cmd.hasOption(ClientStrings.CONFIG)) {
m_configFile = new File(m_cmd
.getOptionValue(ClientStrings.CONFIG));
if (m_configFile.exists() && m_configFile.canRead()) {
printConsoleLn(Messages.ClientConfigFile
+ m_configFile.getAbsolutePath(), true);
m_job = JobConfiguration.initJob(m_configFile);
} else {
throw new FileNotFoundException(StringConstants.EMPTY);
}
} else {
m_job = JobConfiguration.initJob(null);
}
// now we should have all arguments, either from file or
// from commandline
if (m_cmd.hasOption(ClientStrings.QUIET)) {
quiet = true;
}
if (m_cmd.hasOption(ClientStrings.NORUN)
|| !StringUtils.isEmpty(
m_job.getNoRunOptMode())) {
noRunValue = true;
}
m_job.parseJobOptions(m_cmd);
handleCmdLineToProcessOptions();
// check if all needed attributes are set
// and if port number is valid
preValidate(m_job);
} catch (PreValidateException exp) {
String message = exp.getLocalizedMessage();
if (message != null && message.length() > 0) {
printlnConsoleError(message);
}
printUsage();
throw new ParseException(StringConstants.EMPTY);
}
return true;
}
/**
* To unify ITE and cmd-line tools, some options / properties
* can be provided through both command-line and other means.
* These are simply stored as process properties.
*/
private void handleCmdLineToProcessOptions() {
if (m_cmd.hasOption(EnvConstants.CLIENTIP_KEY)) {
System.setProperty(EnvConstants.CLIENTIP_KEY, m_cmd
.getOptionValue(EnvConstants.CLIENTIP_KEY));
}
if (m_cmd.hasOption(EnvConstants.CLIENTPORT_KEY)) {
System.setProperty(EnvConstants.CLIENTPORT_KEY, m_cmd
.getOptionValue(EnvConstants.CLIENTPORT_KEY));
}
}
/**
* method to create an options object, filled with all options
* @param req
* boolean flag must be true for an required option
* this is only used for printing the correct usage
* @return the options
*/
private Options createOptions(boolean req) {
Options options = new Options();
options.addOption(createOption(ClientStrings.HELP, false,
StringConstants.EMPTY,
Messages.ClientHelpOpt, false));
options.addOption(createOption(ClientStrings.QUIET, false,
StringConstants.EMPTY,
Messages.ClientQuietOpt, false));
options.addOption(createOption(ClientStrings.CONFIG, true,
ClientStrings.CONFIGFILE,
Messages.ClientConfigOpt, false));
OptionGroup ogConnection = new OptionGroup();
ogConnection.addOption(createOption(ClientTestStrings.DBURL, true,
ClientTestStrings.DATABASE,
Messages.ClientDburlOpt, false));
ogConnection.addOption(createOption(ClientTestStrings.DB_SCHEME, true,
ClientTestStrings.SCHEME,
Messages.ClientDbschemeOpt, false));
options.addOptionGroup(ogConnection);
options.addOption(createOption(ClientTestStrings.DB_USER, true,
ClientTestStrings.USER,
Messages.ClientDbuserOpt, false));
options.addOption(createOption(ClientTestStrings.DB_PW, true,
ClientTestStrings.PASSWORD,
Messages.ClientDbpwOpt, false));
extendOptions(options, req);
return options;
}
/**
* method to extend an options object, filled with all options
* @param opt Predefined options. This options will be extended
* during the method call.
* @param req
* boolean flag must be true for an required option
* this is only used for printing the correct usage
*/
protected abstract void extendOptions(Options opt, boolean req);
/**
* Do any final work required before actually running the client
*/
protected void preRun() {
// nothing in here - subclasses may override
}
/**
* writes an output to console
*
* @param text
* the message
* @param printTimestamp
* whether a timestamp should be printed
*/
public static void printConsoleLn(String text, boolean printTimestamp) {
String textToPrint = StringUtils.chomp(text);
String consoleOutput = StringConstants.EMPTY;
if (printTimestamp) {
String timeStamp = DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.MEDIUM).format(
Calendar.getInstance().getTime());
consoleOutput = NLS.bind(Messages.ClientCmdOutputWithTimeStamp,
timeStamp, textToPrint);
} else {
consoleOutput = NLS.bind(Messages.ClientCmdOutputWithoutTimeStamp,
textToPrint);
}
printConsole(consoleOutput);
}
/**
* {@inheritDoc}
*/
public void writeErrorLine(String line) {
printlnConsoleError(line);
}
/**
* {@inheritDoc}
*/
public void writeLine(String line) {
printConsole(line + StringConstants.NEWLINE);
}
/**
* {@inheritDoc}
*/
public void writeStatus(IStatus status) {
printConsoleLn("AUT " + StringHelper.getStringOf(status, false) //$NON-NLS-1$
+ "..." + StringConstants.NEWLINE, true); //$NON-NLS-1$
if (status.isMultiStatus()) {
for (IStatus s : status.getChildren()) {
printConsoleLn("AUT " + StringHelper.getStringOf(s, false) //$NON-NLS-1$
+ "..." + StringConstants.NEWLINE, true); //$NON-NLS-1$
}
}
}
/**
* {@inheritDoc}
*/
public void writeStatus(IStatus status, String id) {
writeStatus(status);
}
/**
* {@inheritDoc}
*/
public void closeConsole() {
//no op
}
/**
* writes an output to console
* @param text
* Message
*/
public static void printConsole(String text) {
if (!quiet) {
System.out.print(text);
}
}
/**
* writes an output to console
* @param text
* the message to log and println to sys.err
*/
public static void printlnConsoleError(String text) {
errorOccured = true;
log.error(Messages.AnErrorOcurred + StringConstants.COLON
+ StringConstants.SPACE + text);
System.err.println(Messages.ClientError + StringConstants.NEWLINE
+ StringConstants.TAB
+ text);
}
/**
* Execute a job
*
* @param args
* Command Line Parameter
* @return Exit Code
*/
public int run(String[] args) {
Job.getJobManager().setProgressProvider(new HeadlessProgressProvider());
ErrorMessagePresenter.setPresenter(new IErrorMessagePresenter() {
public void showErrorMessage(JBException ex, Object[] params,
String[] details) {
log.error(ex + StringConstants.COLON + StringConstants.SPACE
+ ex.getMessage());
Integer messageID = ex.getErrorId();
showErrorMessage(messageID, params, details);
}
public void showErrorMessage(Integer messageID, Object[] params,
String[] details) {
Message m = MessageIDs.getMessageObject(messageID);
if (m == null) {
log.error(Messages.NoCorrespondingMessage
+ StringConstants.COLON + StringConstants.SPACE
+ messageID);
} else {
String msgString = m.getMessage(params);
if (m.getSeverity() == Message.ERROR) {
printlnConsoleError(msgString);
} else {
printConsole(msgString);
}
}
}
});
try {
if (!parseCommandLine(args)) {
return EXIT_CODE_OK;
}
} catch (ParseException e) {
log.error(e.getLocalizedMessage(), e);
return EXIT_CODE_ERROR;
} catch (IOException e) {
log.error(e.getLocalizedMessage(), e);
return EXIT_CODE_ERROR;
}
preRun();
try {
int exitCode = doRun();
if (isErrorOccured()) {
exitCode = EXIT_CODE_ERROR;
}
printConsoleLn(Messages.ClientExitCode + exitCode, true);
return exitCode;
} catch (Throwable t) {
// Assume that, if an exception has bubbled up this far, then it is
// a big enough problem to warrant telling the user and returning a
// generic error exit code.
log.error(t.getLocalizedMessage(), t);
printlnConsoleError(t.getLocalizedMessage());
return EXIT_CODE_ERROR;
}
}
/**
* runs the job
* @return int
* Exit Code
*/
protected abstract int doRun();
/**
* checks if all job arguments are present
* @param job
* contains the job configuration
* @throws PreValidateException is arguments are missing
*/
private void preValidate(JobConfiguration job) throws PreValidateException {
StringBuilder errorMsg = new StringBuilder();
errorMsg.append(Messages.ClientMissingArgs);
StringBuilder errorInvalidArgsMsg = new StringBuilder();
errorInvalidArgsMsg.append(Messages.ClientInvalidArgs);
if (job.getDbConnectionName() == null && job.getDb() == null) {
appendError(errorMsg, ClientTestStrings.DB_SCHEME,
ClientTestStrings.SCHEME + " OR"); //$NON-NLS-1$
appendError(errorMsg, ClientTestStrings.DBURL,
ClientTestStrings.DATABASE);
}
if (job.getDb() != null
&& !(job.getDb().startsWith(OracleConnectionInfo.JDBC_PRE)
|| job.getDb().startsWith(MySQLConnectionInfo.JDBC_PRE)
|| job.getDb().startsWith(PostGreSQLConnectionInfo.JDBC_PRE)
|| job.getDb().startsWith(H2ConnectionInfo.JDBC_PRE))) {
appendError(errorMsg, JDBC_UNKNOWN, job.getDb());
}
if (job.getDbuser() == null) {
appendError(errorMsg, ClientTestStrings.DB_USER,
ClientTestStrings.USER);
}
if (job.getDbpw() == null) {
appendError(errorMsg, ClientTestStrings.DB_PW,
ClientTestStrings.PASSWORD);
}
extendValidate(job, errorMsg, errorInvalidArgsMsg);
if (missingArgErrorOccured) {
errorMsg.append(Messages.ClientReadUserManual);
throw new PreValidateException(errorMsg.toString());
}
if (job.getNoRunOptMode() != null && job.getTestJobName() != null) {
throw new PreValidateException(
Messages.NoRunOptionDoesNotSupportTestJobs
+ StringConstants.SPACE
+ Messages.ClientReadUserManual);
}
// If the datadir directory was not specified by user and the default value
// cannot be used because the platform is running without an instance location
if (job.getDataDir() == String.valueOf(
Constants.INVALID_VALUE)) {
throw new PreValidateException(
Messages.NoPlatformInstanceLocation);
}
if (validationErrorOccured) {
errorInvalidArgsMsg.append(Messages.ClientReadUserManual);
throw new PreValidateException(errorInvalidArgsMsg.toString());
}
if (job.getDbscheme() == null && job.getDb() == null) {
List<DatabaseConnection> availableConnections =
DatabaseConnectionConverter.computeAvailableConnections();
List<String> connectionNames = new ArrayList<String>();
for (DatabaseConnection conn : availableConnections) {
connectionNames.add(conn.getName());
}
throw new PreValidateException(NLS.bind(
Messages.NoSuchDatabaseConnection,
new String[] {job.getDbConnectionName(),
StringUtils.join(connectionNames, ", ")})); //$NON-NLS-1$
}
}
/**
* Do validation beyond the basic parameters
* @param job configuration to check
* @param errorMsgs storage for error messages from validation in case required arguments are missing
* @param errorInvalidArgsMsg storage for error messages from validation in case values of given arguments are invalid
*/
protected abstract void extendValidate(JobConfiguration job,
StringBuilder errorMsgs, StringBuilder errorInvalidArgsMsg);
/**
*
* @param args
* command line
* @param exp
* exception
* @param printToConsole if <code>true</code> the error
* message will be shown in the console
* @return arguments modified
*/
public String[] handleParseException(String [] args, ParseException exp,
boolean printToConsole) {
// if there is an error we will remove that token
// and try it again
String message = exp.getLocalizedMessage();
if (message != null && message.length() > 0 && printToConsole) {
printlnConsoleError(
extendMissingArgumentExceptionMessage(message, exp));
printUsage();
}
if (message.startsWith(OPT_NO_VAL)) {
message = printAndGetEndOfMessage(message, 1);
} else if (message.startsWith(OPT_UNKNOWN)) {
message = printAndGetEndOfMessage(message, 2);
}
for (int i = 0; i < args.length; i++) {
if (args[i].endsWith(message)) {
args[i] = StringConstants.EMPTY;
}
}
return args;
}
/**
* Extend parse exception message with the missing parameters
*
* @param errorMessage
* extendable message
* @param exp
* parse exception
* @return extended error message with missing argument if defined, else the
* error message given in method parameter
*/
private String extendMissingArgumentExceptionMessage(String errorMessage,
ParseException exp) {
if (exp instanceof MissingArgumentException) {
MissingArgumentException missingArgExp =
(MissingArgumentException) exp;
if (missingArgExp != null) {
Option option = missingArgExp.getOption();
if (option != null && option.getArgName() != null) {
StringBuilder errorBuilder = new StringBuilder();
appendError(errorBuilder, errorMessage,
option.getArgName());
errorBuilder.append(Messages.ClientReadUserManual);
return errorBuilder.toString();
}
}
}
return errorMessage;
}
/**
* prints the message and substrings it
* @param message the message
* @param indexForSubstring the value which should be cut
* @return the substringed message
*/
private String printAndGetEndOfMessage(String message,
int indexForSubstring) {
printlnConsoleError(message);
int idx = message.indexOf(StringConstants.COLON);
String substring = message.substring(idx + indexForSubstring);
return substring;
}
/**
*
* @param errorMsg Stringbuilder with message
* @param msg1 the missing option
* @param msg2 the missing option argument
*/
protected void appendError(StringBuilder errorMsg, String msg1,
String msg2) {
missingArgErrorOccured = true;
errorOccured = true;
errorMsg.append(StringConstants.TAB);
errorMsg.append(StringConstants.MINUS);
errorMsg.append(msg1);
errorMsg.append(StringConstants.SPACE);
errorMsg.append(msg2);
errorMsg.append(StringConstants.NEWLINE);
}
/**
*
* @param validationErrorMsg Stringbuilder with message
* @param msg1 the missing option
* @param msg2 the missing option argument
*/
protected void appendValidationError(StringBuilder validationErrorMsg,
String msg1, String msg2) {
validationErrorOccured = true;
errorOccured = true;
validationErrorMsg.append(StringConstants.TAB);
validationErrorMsg.append(StringConstants.MINUS);
validationErrorMsg.append(msg1);
validationErrorMsg.append(StringConstants.SPACE);
validationErrorMsg.append(msg2);
validationErrorMsg.append(StringConstants.NEWLINE);
}
/**
* prints the command line syntax
*/
private void printUsage() {
writeLine(Vn.getDefault().getVersion().toString());
Options options = createOptions(true);
// The "-data" argument is parsed and handled by the Eclipse RCP
// before we get a chance to see it, but we want to make sure that the
// user is aware that it's an option. In order to accomplish this, we
// add it to the options used in generating usage, but not the the
// options actually used in parsing the command line.
options.addOption(createOption(ClientTestStrings.WORKSPACE, true,
ClientTestStrings.WORKSPACE_ARG,
Messages.ClientWorkspaceOpt, false));
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(getCmdlineClientExecName(), options, true);
}
/** @return the name of the executable for the commandline Client */
public abstract String getCmdlineClientExecName();
/**
* @return the noRun
*/
public static boolean isNoRun() {
return noRunValue;
}
/**
* @return the quiet
*/
public boolean isQuiet() {
return quiet;
}
/**
* @return the errorOccured
*/
public static boolean isErrorOccured() {
return errorOccured;
}
/**
* @return the validationErrorOccured
*/
public static boolean isValidationErrorOccured() {
return validationErrorOccured;
}
/**
* @return the missingArgErrorOccured
*/
public static boolean isMissingArgErrorOccured() {
return missingArgErrorOccured;
}
/**
* @return CommandLine
* the command Line the client was started with
*/
public CommandLine getCmdLine() {
return m_cmd;
}
/**
* @return the job
*/
public JobConfiguration getJob() {
return m_job;
}
/** {@inheritDoc} */
public void writeWarningLine(String line) {
writeLine(line);
}
}