net.sf.markov4jmeter.testplangenerator.TestPlanGenerator.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.markov4jmeter.testplangenerator.TestPlanGenerator.java

Source

/***************************************************************************
 * Copyright (c) 2016 the WESSBAS project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***************************************************************************/

package net.sf.markov4jmeter.testplangenerator;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;

import m4jdsl.WorkloadModel;
import m4jdsl.impl.M4jdslPackageImpl;
import net.sf.markov4jmeter.testplangenerator.transformation.AbstractTestPlanTransformer;
import net.sf.markov4jmeter.testplangenerator.transformation.SimpleTestPlanTransformer;
import net.sf.markov4jmeter.testplangenerator.transformation.TransformationException;
import net.sf.markov4jmeter.testplangenerator.transformation.filters.AbstractFilter;
import net.sf.markov4jmeter.testplangenerator.transformation.filters.HeaderDefaultsFilter;
import net.sf.markov4jmeter.testplangenerator.util.CSVHandler;
import net.sf.markov4jmeter.testplangenerator.util.Configuration;

import org.apache.commons.cli.ParseException;
import org.apache.jmeter.save.SaveService;
import org.apache.jorphan.collections.ListedHashTree;

import wessbas.commons.util.XmiEcoreHandler;

/**
 * Generator class for building Test Plans which result from M4J-DSL models.
 *
 * <p>The generator must be initialized before it can be used properly;
 * hence, the {@link #init(String)} method must be called once for initializing
 * the default configuration of Test Plan elements. The name of the
 * configuration properties file must be passed to the initialization method
 * therefore. The {@link #isInitialized()} method might be used for requesting
 * the initialization status of the generator.
 *
 * <p>An M4J-DSL model for which a Test Plan shall be generated, might be passed
 * to the regarding <code>generate()</code> method; the model might be even
 * loaded from an XMI file alternatively, requiring the related filename to be
 * passed to the regarding <code>generate()</code> method.
 *
 * @author   Eike Schulz (esc@informatik.uni-kiel.de)
 * @version  1.0
 * @since    1.7
 */
public class TestPlanGenerator {

    /* IMPLEMENTATION NOTE:
     * --------------------
     * The following elements of the Test Plan Factory have not been used for
     * creating Markov4JMeter Test Plans, but they are already supported by the
     * framework:
     *
     *   WhileController whileController = testPlanElementFactory.createWhileController();
     *   IfController ifController = testPlanElementFactory.createIfController();
     *   CounterConfig counterConfig = testPlanElementFactory.createCounterConfig();
     *
     * The following elements are just required as nested parts for other types
     * of Test Plan elements, but they can be even created independently:
     *
     *   Arguments arguments = testPlanElementFactory.createArguments();
     *   LoopController loopController = testPlanElementFactory.createLoopController();
     */

    /** Default properties file for the Test Plan Generator, to be used in case
     *  no user-defined properties file can be read from command line. */
    private final static String GENERATOR_DEFAULT_PROPERTIES = "configuration/generator.default.properties";

    /** Property key for the JMeter home directory. */
    private final static String PKEY_JMETER__HOME = "jmeter_home";

    /** Property key for the JMeter default properties. */
    private final static String PKEY_JMETER__PROPERTIES = "jmeter_properties";

    /** Property key for the language tag which indicates the locality. */
    private final static String PKEY_JMETER__LANGUAGE_TAG = "jmeter_languageTag";

    /** Property key for the flag which indicates whether the generation process
     *  shall be aborted, if undefined arguments are detected. */
    private final static String PKEY_USE_FORCED_ARGUMENTS = "useForcedArguments";

    // info-, warn- and error-messages (names should be self-explaining);

    private final static String ERROR_CONFIGURATION_UNDEFINED = "Configuration file is null.";

    private final static String ERROR_CONFIGURATION_NOT_FOUND = "Could not find configuration file \"%s\".";

    private final static String ERROR_CONFIGURATION_READING_FAILED = "Could not read configuration file \"%s\".";

    private final static String ERROR_TEST_PLAN_PROPERTIES_UNDEFINED = "Test Plan properties file is null.";

    private final static String ERROR_TEST_PLAN_PROPERTIES_NOT_FOUND = "Could not find Test Plan properties file \"%s\".";

    private final static String ERROR_TEST_PLAN_PROPERTIES_READING_FAILED = "Could not read Test Plan properties file \"%s\".";

    private final static String ERROR_INITIALIZATION_FAILED = "Initialization of Test Plan Generator failed.";

    private final static String ERROR_INPUT_FILE_COULD_NOT_BE_READ = "Input file \"%s\" could not be read: %s";

    private final static String ERROR_OUTPUT_FILE_COULD_NOT_BE_WRITTEN = "Output file \"%s\" could not be written: %s";

    private final static String ERROR_OUTPUT_FILE_ACCESS_FAILED = "Could not access file \"%s\" for writing output data: %s";

    private final static String ERROR_TREE_SAVING_FAILED = "Could not save Test Plan tree \"%s\" via SaveService: %s";

    private final static String INFO_INITIALIZATION_SUCCESSFUL = "Test Plan Generator has been successfully initialized.";

    private final static String INFO_TEST_PLAN_GENERATION_STARTED = "Generating Test  Plan ...";

    private final static String ERROR_TEST_PLAN_GENERATION_FAILED = "Test Plan generation failed.";

    private final static String INFO_MODEL_VALIDATION_SUCCESSFUL = "Validation of M4J-DSL model successful.";

    private final static String ERROR_MODEL_VALIDATION_FAILED = "Validation of M4J-DSL model failed.";

    private final static String WARNING_OUTPUT_FILE_CLOSING_FAILED = "Could not close file-output stream for file \"%s\": %s";

    private final static String ERROR_TEST_PLAN_RUN_FAILED = "Could not run Test Plan \"%s\": %s";

    /* *********************  global (non-final) fields  ******************** */

    /** Factory to be used for creating Test Plan elements. */
    private TestPlanElementFactory testPlanElementFactory;

    /* **************************  public methods  ************************** */

    /**
     * Returns the Test Plan Factory associated with the Test Plan Generator.
     *
     * @return
     *     a valid Test Plan Factory, if the Test Plan Generator has been
     *     initialized successfully; otherwise <code>null</code> will be
     *     returned.
     */
    public TestPlanElementFactory getTestPlanElementFactory() {

        return this.testPlanElementFactory;
    }

    /**
     * Returns the information whether the Test Plan Generator is initialized,
     * meaning that the {@link #init(String)} method has been called
     * successfully.
     *
     * @return
     *     <code>true</code> if and only if the Test Plan Generator is
     *     initialized.
     */
    public boolean isInitialized() {

        return this.testPlanElementFactory != null;
    }

    /**
     * Initializes the Test Plan Generator by loading the specified
     * configuration file and setting its properties accordingly.
     *
     * @param configurationFile
     *     properties file which provides required or optional properties.
     * @param testPlanProperties
     *     file with Test Plan default properties.
     */
    public void init(final String configurationFile, final String testPlanProperties) {

        // read the configuration and give an error message, if reading fails;
        // in that case, null will be returned;
        final Configuration configuration = this.readConfiguration(configurationFile);

        if (configuration != null) { // could configuration be read?

            final boolean useForcedArguments = configuration
                    .getBoolean(TestPlanGenerator.PKEY_USE_FORCED_ARGUMENTS);

            final String jMeterHome = configuration.getString(TestPlanGenerator.PKEY_JMETER__HOME);

            final String jMeterProperties = configuration.getString(TestPlanGenerator.PKEY_JMETER__PROPERTIES);

            final String jMeterLanguageTag = configuration.getString(TestPlanGenerator.PKEY_JMETER__LANGUAGE_TAG);

            final Locale jMeterLocale = Locale.forLanguageTag(jMeterLanguageTag);

            final boolean success = JMeterEngineGateway.getInstance().initJMeter(jMeterHome, jMeterProperties,
                    jMeterLocale);

            if (success) {

                // create a factory which builds Test Plan elements according
                // to the specified properties.
                this.testPlanElementFactory = this.createTestPlanFactory(testPlanProperties, useForcedArguments);

                if (this.testPlanElementFactory != null) {

                    this.logInfo(TestPlanGenerator.INFO_INITIALIZATION_SUCCESSFUL);

                    return;
                }
            }

        }

        this.logError(TestPlanGenerator.ERROR_INITIALIZATION_FAILED);
    }

    /**
     * Generates a Test Plan for the given workload model and writes the result
     * into the specified file.
     *
     * @param workloadModel
     *     workload model which provides the values for the Test Plan to be
     *     generated.
     * @param testPlanTransformer
     *     builder to be used for building a Test Plan of certain structure.
     * @param filters
     *     (optional) modification filters to be finally applied on the newly
     *     generated Test Plan.
     * @param outputFilename
     *     name of the file where the Test Plan shall be stored in.
     *
     * @return
     *     the generated Test Plan, or <code>null</code> if any error occurs.
     *
     * @throws TransformationException
     *     if any critical error in the transformation process occurs.
     */
    public ListedHashTree generate(final WorkloadModel workloadModel,
            final AbstractTestPlanTransformer testPlanTransformer, final AbstractFilter[] filters,
            final String outputFilename) throws TransformationException {

        ListedHashTree testPlanTree = null; // to be returned;

        final boolean validationSuccessful;

        this.logInfo(TestPlanGenerator.INFO_TEST_PLAN_GENERATION_STARTED);

        //validationSuccessful = validator.validateAndPrintResult(workloadModel);
        validationSuccessful = true;

        this.logInfo(TestPlanGenerator.INFO_MODEL_VALIDATION_SUCCESSFUL);

        if (!validationSuccessful) {

            this.logError(TestPlanGenerator.ERROR_MODEL_VALIDATION_FAILED);
            this.logError(TestPlanGenerator.ERROR_TEST_PLAN_GENERATION_FAILED);

        } else { // validation successful -> generate Test Plan output file;

            testPlanTree = testPlanTransformer.transform(workloadModel, this.testPlanElementFactory, filters);

            final boolean success = this.writeOutput(testPlanTree, outputFilename);

            if (!success) {

                this.logError(TestPlanGenerator.ERROR_TEST_PLAN_GENERATION_FAILED);
            }
        }

        return testPlanTree;
    }

    /**
     * Generates a Test Plan for the (Ecore) workload model which is stored in
     * the given XMI-file; the result will be written into the specified output
     * file.
     *
     * @param inputFile
     *     XMI file containing the (Ecore) workload model which provides the
     *     values for the Test Plan to be generated.
     * @param outputFile
     *     name of the file where the Test Plan shall be stored in.
     * @param testPlanTransformer
     *     builder to be used for building a Test Plan of certain structure.
     * @param filters
     *     (optional) modification filters to be finally applied on the newly
     *     generated Test Plan.
     *
     * @return
     *     the generated Test Plan, or <code>null</code> if any error occurs.
     *
     * @throws IOException
     *     in case any file reading or writing operation failed.
     * @throws TransformationException
     *     if any critical error in the transformation process occurs.
     */
    public ListedHashTree generate(final String inputFile, final String outputFile,
            final AbstractTestPlanTransformer testPlanTransformer, final AbstractFilter[] filters)
            throws IOException, TransformationException {

        // initialize the model package;
        M4jdslPackageImpl.init();

        // might throw an IOException;
        final WorkloadModel workloadModel = (WorkloadModel) XmiEcoreHandler.getInstance().xmiToEcore(inputFile,
                "xmi");

        return this.generate(workloadModel, testPlanTransformer, filters, outputFile);
    }

    /* *************************  protected methods  ************************ */

    /**
     * Writes a given Test Plan into the specified output file.
     *
     * @param testPlanTree    Test Plan to be written into the output file.
     * @param outputFilename  name of the output file.
     */
    protected boolean writeOutput(final ListedHashTree testPlanTree, final String outputFilename) {

        boolean success = true;
        FileOutputStream fileOutputStream = null;

        try {

            // might throw a FileNotFoundException or SecurityException;
            fileOutputStream = new FileOutputStream(outputFilename);

            // might throw an IOException;
            SaveService.saveTree(testPlanTree, fileOutputStream);

        } catch (final FileNotFoundException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_OUTPUT_FILE_COULD_NOT_BE_WRITTEN,
                    outputFilename, ex.getMessage());

            this.logError(message);
            success = false;

        } catch (final SecurityException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_OUTPUT_FILE_ACCESS_FAILED, outputFilename,
                    ex.getMessage());

            this.logError(message);
            success = false;

        } catch (final IOException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_TREE_SAVING_FAILED, outputFilename,
                    ex.getMessage());

            this.logError(message);
            success = false;

        } finally {

            if (fileOutputStream != null) {

                try {

                    fileOutputStream.close();

                } catch (final IOException ex) {

                    final String message = String.format(TestPlanGenerator.WARNING_OUTPUT_FILE_CLOSING_FAILED,
                            outputFilename, ex.getMessage());

                    this.logWarning(message);
                    // success remains true, since output file content has been
                    // written, just the file could not be closed;
                }
            }
        }

        return success;
    }

    /* **************************  private methods  ************************* */

    /**
     * Reads all configuration properties from the specified file and gives an
     * error message, if reading fails.
     *
     * @param propertiesFile  properties file to be read.
     *
     * @return
     *     a valid configuration, if the specified properties file could be
     *     read successfully; otherwise <code>null</code> will be returned.
     */
    private Configuration readConfiguration(final String propertiesFile) {

        Configuration configuration = new Configuration();

        try {
            // might throw FileNotFound-, IO-, or NullPointerException;
            configuration.load(propertiesFile);

        } catch (final FileNotFoundException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_CONFIGURATION_NOT_FOUND, propertiesFile);

            this.logError(message);
            configuration = null; // indicates an error;

        } catch (final IOException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_CONFIGURATION_READING_FAILED,
                    propertiesFile);

            this.logError(message);
            configuration = null; // indicates an error;

        } catch (final NullPointerException ex) {

            this.logError(TestPlanGenerator.ERROR_CONFIGURATION_UNDEFINED);
            configuration = null; // indicates an error;
        }

        return configuration;
    }

    /**
     * Creates a Factory which builds Test Plan according to the default
     * properties defined in the specified file.
     *
     * @param propertiesFile
     *     properties file with default properties for Test Plan elements.
     * @param useForcedValues
     *     <code>true</code> if and only if the generation process shall be
     *     aborted, if undefined arguments are detected.
     *
     * @return
     *     a valid Test Plan Factory if the properties file could be read
     *     successfully; otherwise <code>null</code> will be returned.
     */
    private TestPlanElementFactory createTestPlanFactory(final String propertiesFile,
            final boolean useForcedValues) {

        // to be returned;
        TestPlanElementFactory testPlanElementFactory = null;

        final Configuration testPlanProperties = new Configuration();

        try {

            // might throw FileNotFound-, IO-, or NullPointerException;
            testPlanProperties.load(propertiesFile);

            // store factory globally for regular and simplified access;
            testPlanElementFactory = new TestPlanElementFactory(testPlanProperties, useForcedValues);

        } catch (final FileNotFoundException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_TEST_PLAN_PROPERTIES_NOT_FOUND,
                    propertiesFile);

            this.logError(message);

        } catch (final IOException ex) {

            final String message = String.format(TestPlanGenerator.ERROR_TEST_PLAN_PROPERTIES_READING_FAILED,
                    propertiesFile);

            this.logError(message);

        } catch (final NullPointerException ex) {

            this.logError(TestPlanGenerator.ERROR_TEST_PLAN_PROPERTIES_UNDEFINED);
        }

        return testPlanElementFactory;
    }

    /**
     * Logs an information to standard output.
     *
     * @param message  information to be logged.
     */
    private void logInfo(final String message) {

        // TODO: remove print-command, solve the issue below;
        System.out.println(message);

        // this command would print in red color, indicating an error:
        //
        //   TestPlanGenerator.LOG.info(message);
        //
        // --> adjust "commons-logging.properties" accordingly;
        // --> even better: use JMeter logging unit;
    }

    /**
     * Logs a warning message to standard output.
     *
     * @param message  warning message to be logged.
     */
    private void logWarning(final String message) {

        // TODO: remove print-command, solve the issue below;
        System.err.println(message);

        // this command would print in non-uniform format:
        //
        //   TestPlanGenerator.LOG.warning(message);
        //
        // --> adjust "commons-logging.properties" accordingly;
        // --> even better: use JMeter logging unit;
    }

    /**
     * Logs an error message to standard output.
     *
     * @param message  error message to be logged.
     */
    private void logError(final String message) {

        // TODO: remove print-command, solve the issue below;
        System.err.println(message);

        // this command would print in non-uniform format:
        //
        //   TestPlanGenerator.LOG.error(message);
        //
        // --> adjust "commons-logging.properties" accordingly;
        // --> even better: use JMeter logging unit;
    }

    /**
     * Generates a Test Plan for the (Ecore) workload model which is stored in
     * the given XMI-file; the result will be written into the specified output
     * file.
     *
     * @param inputFile
     *     XMI file containing the (Ecore) workload model which provides the
     *     values for the Test Plan to be generated.
     * @param outputFile
     *     name of the file where the Test Plan shall be stored in.
     * @param generatorPropertiesFile
     *     properties file which provides the core settings for the Test Plan
     *     Generator.
     * @param testPlanPropertiesFile
     *     properties file which provides the default settings for the Test
     *     Plans to be generated.
     * @param filterFlags
     *     (optional) modification filters to be finally applied on the newly
     *     generated Test Plan.
     * @param lineBreakType
     *     OS-specific line-break type; this must be one of the
     *     <code>LINEBREAK_TYPE</code> constants defined in class
     *     {@link CSVHandler}.
     *
     * @return
     *     the generated Test Plan, or <code>null</code> if any error occurs.
     *
     * @throws TransformationException
     *     if any critical error in the transformation process occurs.
     */
    private ListedHashTree generate(final String inputFile, final String outputFile, final String outputPath,
            final int lineBreakType, final String testPlanPropertiesFile, final String generatorPropertiesFile,
            final String filterFlags) throws TransformationException {

        ListedHashTree testPlan = null; // to be returned;

        // TODO: collect these filters according to the command line flags;
        final AbstractFilter[] filters = new AbstractFilter[] {

                // new ConstantWorkloadIntensityFilter(),

                /* this is just for testing, think times will be taken from the
                 * workload model and managed by the Markov Controller;
                 *
                 new GaussianThinkTimeDistributionFilter(
                    "Think Time",
                    "",
                    true,
                    300.0d,
                    100.0d),
                 */
                new HeaderDefaultsFilter() };

        this.init(generatorPropertiesFile, testPlanPropertiesFile);

        if (this.isInitialized()) {

            final CSVHandler csvHandler = new CSVHandler(lineBreakType);

            // TODO: destination path must exist; create path, if necessary;
            // TODO: use output path also for test plan?
            final String behaviorModelsOutputPath = outputPath == null ? "./" : outputPath; // path "" denotes "/";

            final AbstractTestPlanTransformer testPlanTransformer = new SimpleTestPlanTransformer(csvHandler,
                    behaviorModelsOutputPath);

            try {

                testPlan = this.generate(inputFile, outputFile, testPlanTransformer, filters);

            } catch (final IOException ex) {

                final String message = String.format(TestPlanGenerator.ERROR_INPUT_FILE_COULD_NOT_BE_READ,
                        inputFile, ex.getMessage());

                this.logError(message);
            }
        }

        return testPlan;
    }

    /* ************************  static main content  *********************** */

    /**
     * Main method which parses the command line parameters and generates a
     * Test Plan afterwards.
     *
     * @param argv arguments vector.
     */
    public static void main(final String[] argv) {

        try {

            System.out.println("****************************");
            System.out.println("Start ApacheJMeter Testplan generation");
            System.out.println("****************************");

            // initialize arguments handler for requesting the command line
            // values afterwards via get() methods; might throw a
            // NullPointer-, IllegalArgument- or ParseException;
            CommandLineArgumentsHandler.init(argv);

            TestPlanGenerator.readArgumentsAndGenerate();

            System.out.println("****************************");
            System.out.println("END ApacheJMeter Testplan generation");
            System.out.println("****************************");

        } catch (final NullPointerException | IllegalArgumentException | ParseException
                | TransformationException ex) {

            System.err.println(ex.getMessage());
            CommandLineArgumentsHandler.printUsage();
        }
    }

    /**
     * Starts the generation process with the arguments which have been passed
     * to command line.
     *
     * @throws TransformationException
     *     if any critical error in the transformation process occurs.
     */
    private static void readArgumentsAndGenerate() throws TransformationException {

        final TestPlanGenerator testPlanGenerator = new TestPlanGenerator();

        final String inputFile = CommandLineArgumentsHandler.getInputFile();

        final String outputFile = CommandLineArgumentsHandler.getOutputFile();

        final String outputPath = CommandLineArgumentsHandler.getPath();

        final int lineBreakType = CommandLineArgumentsHandler.getLineBreakType();

        final String testPlanPropertiesFile = CommandLineArgumentsHandler.getTestPlanPropertiesFile();

        final String filters = CommandLineArgumentsHandler.getFilters();

        final boolean runTest = CommandLineArgumentsHandler.getRunTest();

        String generatorPropertiesFile = CommandLineArgumentsHandler.getGeneratorPropertiesFile();

        if (generatorPropertiesFile == null) {

            generatorPropertiesFile = TestPlanGenerator.GENERATOR_DEFAULT_PROPERTIES;
        }

        // ignore returned Test Plan, since the output file will provide it
        // for being tested in the (possibly) following test run;
        testPlanGenerator.generate(inputFile, outputFile, outputPath, lineBreakType, testPlanPropertiesFile,
                generatorPropertiesFile, filters);

        if (runTest) {

            // TODO: libraries need to be added for running tests correctly;
            // otherwise the tests fail at runtime (e.g., class HC3CookieHandler
            // is declared to be still unknown);

            try {

                JMeterEngineGateway.getInstance().startJMeterEngine(outputFile);

            } catch (final Exception ex) {

                final String message = String.format(TestPlanGenerator.ERROR_TEST_PLAN_RUN_FAILED, outputFile,
                        ex.getMessage());

                System.err.println(message);
                ex.printStackTrace();
            }
        }
    }
}