package at.ac.tuwien.iter.modules;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import matlabcontrol.MatlabConnectionException;
import matlabcontrol.MatlabInvocationException;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.annotations.ServiceId;
import org.apache.tapestry5.ioc.annotations.SubModule;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.gambi.tapestry5.cli.data.CLIOption;
import org.gambi.tapestry5.cli.services.CLIOptionSource;
import org.gambi.tapestry5.cli.services.CLIValidator;
import org.gambi.tapestry5.cli.utils.CLISymbolConstants;
import org.slf4j.Logger;
import at.ac.tuwien.iter.data.IterApplication;
import at.ac.tuwien.iter.services.AssertionService;
import at.ac.tuwien.iter.services.DataCollectionService;
import at.ac.tuwien.iter.services.Iter;
import at.ac.tuwien.iter.services.LoadGenerator;
import at.ac.tuwien.iter.services.LoadGeneratorSource;
import at.ac.tuwien.iter.services.MathEngineDao;
import at.ac.tuwien.iter.services.TestSuiteEvolver;
import at.ac.tuwien.iter.services.TestSuiteEvolverSource;
import at.ac.tuwien.iter.services.impl.IterImpl;
import at.ac.tuwien.iter.services.impl.evo.GPMLBasedPlasticityEvolver;
import at.ac.tuwien.iter.services.impl.evo.StopTestSuiteEvolution;
import at.ac.tuwien.iter.services.impl.evo.TestSuiteEvolverSourceImpl;
import at.ac.tuwien.iter.services.impl.matlab.MatlabControlImpl;
import at.ac.tuwien.iter.services.impl.validators.TestEvolverCLIValidator;
import at.ac.tuwien.iter.utils.IterSymbolsNames;
import at.ac.tuwien.iter.utils.ServiceManifestUtils;
/**
* This class contains the definition of the object managed by the tapestry-ioc
* framework that implements the main functionalities of the tool.
*
*
*
* @author alessiogambi
* @category Module
*/
@SubModule({ AssertionModule.class, LoadGeneratorModule.class,
DataCollectionModule.class })
public class IterModule {
/**
* Define the CLI Options for ITER. Configurations for LOADGenerator are in
* the load generator module.
*
* @category Contribution
*/
public void contributeCLIParser(SymbolSource symbolSource,
Configuration<CLIOption> configuration) {
configuration.add(new CLIOption("c", "customer-name", 1, true,
"Customer name."));
configuration.add(new CLIOption("s", "service-name", 1, true,
"Service name"));
configuration.add(new CLIOption("m", "service-manifest-URL", 1, true,
"URL of the Service manifest file"));
configuration.add(new CLIOption("j", "jmeter-clients-URL", 1, true,
"URL of the JMeter file"));
// Test Execution setting. With defaults
CLIOption nParallelTests = new CLIOption("N", "n-parallel-tests", 1,
false, "Maximum number of parallel test executions.");
nParallelTests.setDefaultValue("1");
configuration.add(nParallelTests);
CLIOption nInitialRandomTests = new CLIOption("r",
"n-initial-random-tests", 1, false,
"Number of initial random tests.");
nInitialRandomTests.setDefaultValue("0");
configuration.add(nInitialRandomTests);
// Search configuration
CLIOption nBestTests = new CLIOption("n", "n-best-tests", 1, false,
"Maximum number of best expected improvement");
nBestTests.setDefaultValue("1");
configuration.add(nBestTests);
configuration.add(new CLIOption("b", "bootstrap", 0, false,
"Enable boostrap from input file."));
// Note this trick! Using the empty string is a good default for having
// not mandatory options. At least from the point of view of the
// Registry startup
CLIOption inputFile = new CLIOption("i", "input-file", 1, false,
"Input file.");
inputFile.setDefaultValue(symbolSource
.valueForSymbol(IterSymbolsNames.INPUT_FILE));
configuration.add(inputFile);
CLIOption outputFile = new CLIOption("o", "output-file", 1, false,
"The result file produced as output.");
outputFile.setDefaultValue(symbolSource
.valueForSymbol(IterSymbolsNames.TEST_RESULTS_FILE));
configuration.add(outputFile);
CLIOption evolveWith = new CLIOption("e", "evolve-with", 1, false,
"The name of the Strategy used to evolve the test suite.");
evolveWith.setDefaultValue("default");
configuration.add(evolveWith);
}
/**
* @category Contribution
*
* @param contributions
* @param plasticity
*/
public void contributeTestSuiteEvolverSource(
MappedConfiguration<String, TestSuiteEvolver> contributions,
@InjectService("PlasticityTestSuiteEvolver") TestSuiteEvolver plasticity) {
// Always there !
contributions.addInstance("default", StopTestSuiteEvolution.class);
// ESEC-FSE
contributions.add("plasticity", plasticity);
}
// NOTE Filters should never be accessible directly !!
/**
* @category Contribution
*/
public void contributeCLIValidator(final Logger logger,
final TypeCoercer typeCoercer,
OrderedConfiguration<CLIValidator> configuration) {
configuration.addInstance("TestEvolverValidator",
TestEvolverCLIValidator.class);
configuration.add("BootstrapfileValidator", new CLIValidator() {
public void validate(Map<CLIOption, String> options,
List<String> inputs, List<String> accumulator) {
try {
String inputFile = null;
// Default should already by there !
String bootstrap = "false";
for (CLIOption cliOption : options.keySet()) {
if (cliOption.getLongOpt().equals("input-file")) {
inputFile = cliOption.getValue();
} else if (cliOption.getLongOpt().equals("bootstrap")) {
bootstrap = cliOption.getValue();
}
}
if (inputFile == null && "true".equalsIgnoreCase(bootstrap)) {
accumulator.add("Input file is not specified.");
} else if (inputFile != null
&& !new File(inputFile).exists()
&& "true".equalsIgnoreCase(bootstrap)) {
accumulator.add(String.format(
"Input file %s does not exists !.", new File(
inputFile).getAbsolutePath()));
}
} catch (Exception e) {
logger.warn("Error during validation ", e);
accumulator.add("BootstrapfileValidator Failed !");
}
};
});
// NOTE THIS IS QUITE A BIG HACK !!
// Since the @PostConstructor and @PostInjection are not suitable
// options I needed to resort to this code
configuration.add("ProblemSpaceValidator", new CLIValidator() {
public void validate(Map<CLIOption, String> options,
List<String> inputs, List<String> accumulator) {
int problemSize = 1;
try {
String manifestURL = null;
for (CLIOption cliOption : options.keySet()) {
if (cliOption.getLongOpt().equals(
"service-manifest-URL")) {
manifestURL = cliOption.getValue();
}
}
// Note we could also @CLIOption("service-manifest-URL")
// inject it !
for (Integer dimension : ServiceManifestUtils
.getVariabilitySpaceFromManifest(typeCoercer
.coerce(manifestURL, URL.class))) {
problemSize = problemSize * dimension;
}
logger.info("Problem size " + problemSize);
System.getProperties().put(IterSymbolsNames.PROBLEM_SIZE,
"" + problemSize);
} catch (Exception e) {
logger.error(
"Error while checking/setting the problem size configuration",
e);
accumulator.add("Invalid Problem Size ");
}
}
});
}
/*
* The IterApplication class is the bean that contains the JSR303 annotation
* to valid inputs passed via the command line.
*/
/**
* @category Contribution
*/
public void contributeApplicationConfigurationSource(
MappedConfiguration<String, Object> configuration) {
configuration.addInstance("iterApplication", IterApplication.class);
}
/**
* @category Contribution
* @param configuration
*/
public void contributeApplicationDefaults(
MappedConfiguration<String, String> configuration) {
// This is the command that we use to start the application. We need to
// put that here for letting the CLI libraries know about the correct
// syntax to invoke the application
configuration.add(CLISymbolConstants.COMMAND_NAME, "iter");
configuration.add(IterSymbolsNames.GPML_DIR, (new File(
"./target/classes/gpml/")).getAbsolutePath());
configuration.add(IterSymbolsNames.ITER_DIR, (new File(
"./target/classes/at/ac/tuwien/iter/octave/"))
.getAbsolutePath());
// Default application values !
configuration.add(IterSymbolsNames.TEST_RESULTS_FILE, (new File(
"test-results.xml")).getAbsolutePath());
// configuration.add(IterSymbolsNames.INPUT_FILE, "input-file.xml");
configuration.add(IterSymbolsNames.INPUT_FILE, "");
configuration.add(IterSymbolsNames.TOLERANCE, "0.00001");
configuration.add(IterSymbolsNames.MIN_EI, "0.0001");
// Default experiment timeout:20 mins. Deploy + Run + Wait
String defaultExperimentTimeout = "" + 20 * 60 * 1000l;
configuration.add(IterSymbolsNames.EXPERIMENT_TIMEOUT,
defaultExperimentTimeout);
// NOT ALL THE NAMES ARE VALID !
configuration.add(IterSymbolsNames.N_BINS, "100");
configuration.add(IterSymbolsNames.MATLAB_LOG_FILE, (new File(
"iterMatlab.log")).getAbsolutePath());
}
/**
* @category Contribution
* @param configuration
*/
@SuppressWarnings("rawtypes")
public static void contributeTypeCoercer(
Configuration<CoercionTuple> configuration) {
Coercion<double[], Number[]> doubleArrayToNumberArray = new Coercion<double[], Number[]>() {
public Number[] coerce(double[] arg0) {
if (arg0 == null) {
return null;
}
if (arg0.length == 0) {
return new Number[0];
} else {
Number[] result = new Number[arg0.length];
for (int i = 0; i < arg0.length; i++) {
// result[i] = new Double(arg0[i]);
result[i] = arg0[i];
}
return result;
}
}
};
configuration.add(new CoercionTuple<double[], Number[]>(double[].class,
Number[].class, doubleArrayToNumberArray));
Coercion<Number[], double[]> numberArrayToDoubleArray = new Coercion<Number[], double[]>() {
public double[] coerce(Number[] arg0) {
if (arg0 == null) {
return null;
}
if (arg0.length == 0) {
return new double[0];
} else {
double[] result = new double[arg0.length];
// Maybe there is a smarter method for that
for (int i = 0; i < arg0.length; i++) {
result[i] = arg0[i].doubleValue();
}
return result;
}
}
};
configuration.add(new CoercionTuple<Number[], double[]>(Number[].class,
double[].class, numberArrayToDoubleArray));
Coercion<double[], String> doubleArrayToString = new Coercion<double[], String>() {
public String coerce(double[] arg0) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < arg0.length; i++) {
sb.append(arg0[i]);
sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
return sb.toString();
}
};
configuration.add(new CoercionTuple<double[], String>(double[].class,
String.class, doubleArrayToString));
Coercion<String, double[]> stringToDoubleArray = new Coercion<String, double[]>() {
public double[] coerce(String arg0) {
// Arrays.toString => [x,x,x,x]
String _array = arg0.trim().replaceAll("\\[", "")
.replaceAll("\\]", "");
String[] token = _array.split(",");
double[] result = new double[token.length];
for (int i = 0; i < token.length; i++) {
result[i] = Double.parseDouble(token[i]);
}
return result;
}
};
configuration.add(new CoercionTuple<String, double[]>(String.class,
double[].class, stringToDoubleArray));
Coercion<String, URL> stringToURL = new Coercion<String, URL>() {
public URL coerce(String arg0) {
try {
return new URL(arg0);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
};
configuration.add(new CoercionTuple<String, URL>(String.class,
URL.class, stringToURL));
Coercion<URL, String> uRLtoString = new Coercion<URL, String>() {
public String coerce(URL arg0) {
if (arg0 != null) {
return arg0.toString();
} else {
return null;
}
}
};
configuration.add(new CoercionTuple<URL, String>(URL.class,
String.class, uRLtoString));
}
// TODO Note here that we access the input CLI and we force a default
// This is an alternative method to get directly to the parsed input
// We also contribute a validator to check if the provided name was also
// contributed. THIS ASSUME A PROPER DEFAULT VALUE IS SET
// Shall this be a Strategy or something else
/**
* @category Build CommandLineTestSuiteEvolver
*/
@ServiceId("CommandLineTestSuiteEvolver")
public TestSuiteEvolver buildCLITestSuiteEvolver(
TestSuiteEvolverSource testSuiteEvolverSource,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "evolve-with") String evolverName) {
return testSuiteEvolverSource.getTestSuiteEvolver(evolverName);
}
/**
* @category Build PlasticityTestSuiteEvolver
* @param logger
* @param mathEngineDao
* @param typeCoercer
* @param loadGenerator
* @param nBestPredictions
* @return
*/
@ServiceId("PlasticityTestSuiteEvolver")
public TestSuiteEvolver buildPlasticityTestSuiteEvolver(
Logger logger,
MathEngineDao mathEngineDao,
TypeCoercer typeCoercer,
@InjectService("CommandLineLoadGenerator") LoadGenerator loadGenerator,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "n-best-tests") int nBestPredictions) {
// TODO Make these parameters
int limitCount = 2;
int minDistance = 2;
return new GPMLBasedPlasticityEvolver(logger, typeCoercer,
loadGenerator, mathEngineDao, nBestPredictions, limitCount,
minDistance);
}
/**
* @category Build Autobuild TestSuiteEvolverSource
* @param binder
*/
public static void bind(ServiceBinder binder) {
binder.bind(TestSuiteEvolverSource.class,
TestSuiteEvolverSourceImpl.class);
}
/**
* @category Build Iter
*
*
* @param logger
* @param registryShutdownHub
* @param typeCoercer
* @param assertionService
* @param dataCollectionService
* @param loadGeneratorSource
* @param loadGenerator
* @param testSuiteEvolver
* @param customerName
* @param serviceName
* @param nParallelTests
* @param nInitialTests
* @param joperaURL
* @param experimentTimetout
* @param testResultFile
* @param bootstrap
* @param bootstrapFile
* @return
*/
@Scope(ScopeConstants.PERTHREAD)
public Iter build(
Logger logger,
// THis is not safe
RegistryShutdownHub registryShutdownHub,
TypeCoercer typeCoercer,
AssertionService assertionService,
DataCollectionService dataCollectionService,
// This is needed for the Boostrap part as we generate on the fly
// tests, and tests require a proper LoadGenerator
LoadGeneratorSource loadGeneratorSource,
// Is this really needed as we already inject the loadGenSource
@InjectService("CommandLineLoadGenerator") LoadGenerator loadGenerator,
@InjectService("CommandLineTestSuiteEvolver") TestSuiteEvolver testSuiteEvolver,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "customer-name") String customerName,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "service-name") String serviceName,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "n-parallel-tests") int nParallelTests,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "n-initial-random-tests") int nInitialTests,
@Symbol(IterSymbolsNames.JOPERA_URL) URL joperaURL,
@Symbol(IterSymbolsNames.EXPERIMENT_TIMEOUT) long experimentTimetout,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "output-file") File testResultFile,
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "bootstrap") boolean bootstrap,
/*
*
* Temporary Patch: contribute the CLIOptionSource service and check
* manually for optional configurations. Optional values can be
* contributed to contributeFactoryDefaults()
*/
@org.gambi.tapestry5.cli.annotations.CLIOption(longName = "input-file") File bootstrapFile,
//
CLIOptionSource cliOptionSource) {
// File bootstrapFile = typeCoercer.coerce(
// cliOptionSource.valueForOption("input-file"), File.class);
return new IterImpl(logger, customerName, serviceName, nParallelTests,
nInitialTests, testResultFile, bootstrapFile, joperaURL,
experimentTimetout, bootstrap, loadGenerator,
registryShutdownHub, typeCoercer, assertionService,
dataCollectionService, testSuiteEvolver, loadGeneratorSource);
}
// THIS MUST BE A SINGLETON SERVICE !
/**
* @category Build matlab Singleton
*/
@Scope(ScopeConstants.DEFAULT)
@ServiceId("matlab")
public MathEngineDao buildMatlabControl(
Logger logger,
// Force the LoadGenerator to be injected BEFORE the matlab is
// constructed !!
@InjectService("CommandLineLoadGenerator") LoadGenerator loadGenerator,
RegistryShutdownHub registryShutdownHub, SymbolSource symbolSource,
TypeCoercer typeCoercer,
@Symbol(IterSymbolsNames.GPML_DIR) String gpmlDir,
@Symbol(IterSymbolsNames.ITER_DIR) String iterDir,
@Symbol(IterSymbolsNames.PROBLEM_SIZE) int problemSize,
@Symbol(IterSymbolsNames.TOLERANCE) double tol,
@Symbol(IterSymbolsNames.MIN_EI) double minEI,
@Symbol(IterSymbolsNames.N_BINS) int nBins,
@Symbol(IterSymbolsNames.MATLAB_LOG_FILE) String matlabLogFile
) throws MatlabConnectionException, MatlabInvocationException {
// TODO We need this trick because load generator NEEDS a couple of
// symbols BEFORE this service gets instantiated
// so Injecting those symbols before hand won't work. Maybe a
// ShadowProperty is the best approach, but for the moment we adopt the
// following
// 1) Force the proxy to instantiate the object by invoking some method
// on it
loadGenerator.generateRandomCase();
// 2) Use the inject SymbolSource (not the SYMBOLS!) and TypeCoercer to
// recover the values we are looking for
double[] LB = typeCoercer.coerce(
symbolSource.valueForSymbol(IterSymbolsNames.LB),
double[].class);
double[] UB = typeCoercer.coerce(
symbolSource.valueForSymbol(IterSymbolsNames.UB),
double[].class);
// TODO Not sure this is really required... but we need to force the
// proxy to
// instantiate the object for real
return new MatlabControlImpl(logger, registryShutdownHub, gpmlDir,
iterDir, problemSize, tol, minEI, LB, UB, nBins, matlabLogFile);
}
}