/** * Copyright (C) 2008-2010, Squale Project - http://www.squale.org * * This file is part of Squale. * * Squale 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 3 of the * License, or any later version. * * Squale 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 General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Squale. If not, see <http://www.gnu.org/licenses/>. */ package org.squale.squalix.tools.abstractgenerictask; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.cli.CommandLineException; import org.codehaus.plexus.util.cli.CommandLineUtils; import org.codehaus.plexus.util.cli.Commandline; import org.codehaus.plexus.util.cli.StreamConsumer; import org.squale.jraf.commons.exception.JrafDaoException; import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.ListParameterBO; import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.MapParameterBO; import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.ParametersConstants; import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.StringParameterBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.ErrorBO; import org.squale.squalix.core.AbstractTask; import org.squale.squalix.core.TaskData; import org.squale.squalix.core.TaskException; import org.squale.squalix.core.exception.ConfigurationException; /** * <p> * This abstract class provides an execution environment for any tool (jar, *.sh scripts, etc...) needed to process an * audit. * </p> * <p> * Keep in mind that this class is particularly needed when trying to connect to any tool which does not offer any Maven * or Ant plug-in. * </p> * <p> * This set of classes works a bit like a <i><b>light weight framework</b></i>. * </p> */ public abstract class AbstractGenericTask extends AbstractTask { /** SeparatorChar from java.io.File (system-dependent) */ public static final char SEPARATOR_CHAR = java.io.File.separatorChar; /** Initiating the logger */ private static final Log LOGGER = LogFactory.getLog( AbstractGenericTask.class ); /** Location of the tool which has to be executed */ private StringParameterBO toolLocation; /** * Working directory of the tool launched by this generic task. This working directory allows the independence of * the class-loaders as the processes are not executed in the same JVM */ private StringParameterBO workingDirectory; /** * This String represents the storage object which will keep in memory the user inputs. It is thus the raw input as * in the presentation layer */ private ListParameterBO toolCommands; /** This collection stores URI's where the tool's results will be kept in memory after processing */ private ListParameterBO resultFiles; /** Declaration of the configuration object which will be used to configure the generic task */ private AbstractGenericTaskConfiguration configurator; /** Declaration of the map that will store the parameters for any GenericTask */ private MapParameterBO genericTaskParam; /** Getting the viewPath of the project */ private String viewPath; /** * Default constructor */ public AbstractGenericTask() { /* * All task default constructors must give a name to a "mName attribute" while instantiating. This allows for * example automatic logging by the super class. */ mName = "GenericTask"; configurator = new AbstractGenericTaskConfiguration(); } /** * This method initiate the GenericTask by calling a configurator and getting all the needed parameters ready * * @return false if no configuration informations have been set */ public boolean init() { /* Getting the parameters of the GenericTask by recovering the map of parameters for it */ this.genericTaskParam = (MapParameterBO) mProject.getParameter( mName ); /* If all values mapped in the map are null */ boolean isConfigured = isTaskConfigured(); if ( isConfigured ) { /* Logging the beginning of the init sequence [! in debug mode only !] */ LOGGER.debug( AbstractGenericTaskMessages.getMessage( "logs.agt.init.start", mProject.getName() ) ); /* Setting one by one the required parameters */ setToolLocation(); setCommands(); setResultLocations(); setWorkingDirectory(); setViewPath(); /* Will log the main parameters values of the task if Squalix is launched in debug mode */ printParameters(); /* Logging the end of the init sequence */ LOGGER.info( AbstractGenericTaskMessages.getMessage( "logs.agt.init.end", mProject.getName() ) ); } else { String message = AbstractGenericTaskMessages.getMessage( "logs.agt.warn.project.notConfigured", mName ); initError( message ); /* if no parameters have been recovered , preparing an error message and logging it */ LOGGER.warn( message ); /* Cancelling the task as no parameters have been entered by the user and logging the info */ mStatus = CANCELLED; } return isConfigured; } /** * This method establish if the task has been configured * * @return true if the task has been configured */ private boolean isTaskConfigured() { if ( genericTaskParam == null ) { return false; } Map paramMap = genericTaskParam.getParameters(); boolean isToolLocation = ( (StringParameterBO) paramMap.get( ParametersConstants.GENERICTASK_TOOLLOCATION ) ).getValue() != null; List resultLocationList = ( (ListParameterBO) genericTaskParam.getParameters().get( ParametersConstants.GENERICTASK_RESULTSLOCATIONS ) ).getParameters(); boolean isResultLocation = !( resultLocationList.size() == 1 && ( (StringParameterBO) resultLocationList.get( 0 ) ).getValue() == null ); return isToolLocation || isResultLocation; } /** * <p> * This method executes the tool. It is final so as to prevent overriding of this method by classes which extends * this class. * </p> * * @throws TaskException exception during execution of the Task. Please consider having a look at * {@link AbstractTask} for more information. * @throws JrafDaoException if there's a problem with the database */ public final void execute() throws TaskException, JrafDaoException { /* Initiating the task */ boolean isConfigured = init(); if ( isConfigured ) { /* Getting the result files from the execution of the task */ List<File> scannedResultFiles = perform(); /* The perform method never returns null as it instantiates an empty list with an initial capacity of zero */ if ( scannedResultFiles.size() > 0 ) { /* Calling the method implemented by tasks extending the AbstractGenericTask */ parseResults( scannedResultFiles ); } else { /* Logging an error as there are no result locations */ String message = AbstractGenericTaskMessages.getMessage( "logs.agt.error.noResultFile", mProject.getName() ); LOGGER.error( message ); mStatus = FAILED; initError( message, ErrorBO.CRITICITY_FATAL ); /* * Please keep in mind that if there are any problem encountered while executing the commandline they * have been handled up stream here we are handling only exception if there are no file in the list. */ } } } /** * <p> * Getting the user inputs to prepare processing of Command line arguments and execution of the tool. Please note * that <b>the shell is auto-detected</b> by the Plexus Util classes. * </p> * <p> * Please keep in mind that if one or more attributes of the <b>pGenericTask</b> is/are null when calling this * method, it won't necessarily return <b>null</b> as the {@link AbstractGenericTask} could recover * informations/metrics from tool launched thanks to a Maven, Ant or any external tool. * </p> * * @return {@link List} : the java.io.file object(s) * @throws TaskException Exception occur */ private List<File> perform() throws TaskException { /* Collection of result files */ List<File> results = new ArrayList<File>( 0 ); try { int exitValue = runSpecifiedCommand(); /* Checking if the process was executed successfully */ if ( 0 == exitValue ) { /* Preparing the result files processing */ DirectoryScanner ds = configurator.prepareResultProcessing( this.getResultLocations(), this.getViewPath() ); /* Checking if a basedir is effectively set */ if ( null != ds.getBasedir() ) { /* The returned array of result files */ String[] scannedResultFiles = null; /* Scanning the directories */ ds.scan(); /* Getting the result files names */ scannedResultFiles = ds.getIncludedFiles(); for ( String resultTmpPath : scannedResultFiles ) { /* Rebuilding the complete String value of the File that has to be created */ String fileTocreate = ds.getBasedir().toString() + SEPARATOR_CHAR + resultTmpPath; File file = new File( fileTocreate ); results.add( file ); } /* * No else block as the exception has been handled in the configurator. If the baseDir is null it * means that the result processing has failed up stream. */ } } else { /* * the exitValue of the process is different from zero indicating an abnormal termination. Informing the * user and aborting the task */ String message = AbstractGenericTaskMessages.getMessage( "logs.agt.fatal.exitValue", exitValue ); LOGGER.fatal( message ); mStatus = FAILED; initError( message, ErrorBO.CRITICITY_FATAL ); } } catch ( CommandLineException cmdLineExcep ) { /* Exception is generated by the executeCommandLine method call the task has to be cancelled */ String message = "CommandLineException : " + cmdLineExcep; mStatus = FAILED; LOGGER.fatal( message ); LOGGER.fatal( AbstractGenericTaskMessages.getMessage( "logs.agt.fatal.task.aborded" ) ); initError( message, ErrorBO.CRITICITY_FATAL ); } catch ( ConfigurationException confExcep ) { /* Catching the configuration exception thrown by the prepareResultProcessing method */ String message = AbstractGenericTaskMessages.getMessage( "logs.agt.error.fileSpecification" ); mStatus = FAILED; LOGGER.fatal( message ); LOGGER.fatal( AbstractGenericTaskMessages.getMessage( "logs.agt.fatal.task.aborded" ) ); initError( message, ErrorBO.CRITICITY_FATAL ); } return results; } /** * Run the command that the user specified. * * @return the return code of that command * @throws TaskException if there's a problem while configuring the command line to execute * @throws CommandLineException if an error occurs while executing the command */ private int runSpecifiedCommand() throws TaskException, CommandLineException { if ( StringUtils.isEmpty( this.getToolLocation().getValue() ) ) { // no command to run, let's do as if the process was OK LOGGER.debug( "No command specified for execution." ); return 0; } /* Extracting the parameters */ String parameters = configurator.extractParameters( this.getCommands() ); /* Preparing the command and the tool */ Commandline cmdLine = configurator.prepareToolExecution( this.getToolLocation(), this.getWorkingDirectory(), parameters, this.getViewPath() ); LOGGER.debug( "Executing command: " + cmdLine ); /* InputStream of bytes -> output of the external tool execution */ InputStream systemIn = null; /* InputStream of bytes -> input of the external tool execution */ StreamConsumer systemOut = new OutStreamConsumer(); /* InputStream of bytes -> error output of the external tool */ StreamConsumer systemErr = new ErrorStreamConsumer(); /* * CommandLineUtils.executeCommandLine method executes the commandLine and returns the exitValue of the process. * Please keep in mind that the executeCommandLine method uses the waitFor() method of the java.Lang.Process * class to wait for the end of the process. The I/O are redirected so as to avoid process freeze(s) due to host * limited buffer size. */ int exitValue = CommandLineUtils.executeCommandLine( cmdLine, systemIn, systemOut, systemErr ); return exitValue; } /** * <p> * This abstract method has to be implemented by each Task inheriting form the {@link AbstractGenericTask}.<br /> * </p> * * @throws TaskException Any exception occurring in the extending task have to be properly handled. If a * {@link TaskException} occurs it could be thrown to the parseResults method * @param pResults the results provided by the execution of the task * @throws JrafDaoException This exception comes from the data layer */ public abstract void parseResults( List<File> pResults ) throws TaskException, JrafDaoException; /** * When launched in debug mode one could use this method to log the values listed bellow */ public void printParameters() { LOGGER.debug( AbstractGenericTaskMessages.getMessage( "logs.agt.info.project.name", mProject.getName() ) ); LOGGER.debug( AbstractGenericTaskMessages.getMessage( "logs.agt.info.task.toolLocation", getToolLocation().getValue() ) ); LOGGER.debug( AbstractGenericTaskMessages.getMessage( "logs.agt.info.task.workingDir", getWorkingDirectory().getValue() ) ); } /* -------------------------------------------- ACCESSORS -------------------------------------------- */ /** * Getter for the mToolLocation * * @return the location of the tool (path/to/the/tool) */ public StringParameterBO getToolLocation() { return toolLocation; } /** Setter of the tool location */ public void setToolLocation() { toolLocation = (StringParameterBO) genericTaskParam.getParameters().get( ParametersConstants.GENERICTASK_TOOLLOCATION ); } /** * Getter of the working directory * * @return the directory in which the tool will be executed */ public StringParameterBO getWorkingDirectory() { return workingDirectory; } /** Setter of the working directory */ public void setWorkingDirectory() { workingDirectory = (StringParameterBO) genericTaskParam.getParameters().get( ParametersConstants.GENERICTASK_WORKDIR ); } /** * Getter of the commands the user has inputed * * @return the commands stored in a {@link ListParameterBO} */ public ListParameterBO getCommands() { return toolCommands; } /** Setter of the commands the user has inputed */ public void setCommands() { toolCommands = (ListParameterBO) genericTaskParam.getParameters().get( ParametersConstants.GENERICTASK_COMMANDS ); } /** * Getter of the location(s) of result files * * @return a collection of location the executed tool has stored the result files */ public ListParameterBO getResultLocations() { return resultFiles; } /** Setter of the location(s) of result files */ public void setResultLocations() { resultFiles = (ListParameterBO) genericTaskParam.getParameters().get( ParametersConstants.GENERICTASK_RESULTSLOCATIONS ); } /** * Getter of the configuration tool * * @return the configurator used with the task */ public AbstractGenericTaskConfiguration getTaskConfiguration() { return configurator; } /** * Getter of the viewPath of the project * * @return the viewPath of the project */ public String getViewPath() { return viewPath; } /** * Setter of the viewPath */ public void setViewPath() { viewPath = mData.getData( TaskData.VIEW_PATH ).toString(); } /** * Writer that consumes the CommandLine standard output and writes it into the LOGGER */ class OutStreamConsumer implements StreamConsumer { public void consumeLine( String line ) { LOGGER.info( line ); } } /** * Writer that consumes the CommandLine error output and writes it into the LOGGER */ class ErrorStreamConsumer implements StreamConsumer { public void consumeLine( String line ) { LOGGER.error( line ); } } }