Java tutorial
/* * Copyright(c) 2017 - Heliosphere Corp. * --------------------------------------------------------------------------- * This file is part of the Heliosphere's project which is licensed under the * Apache license version 2 and use is subject to license terms. * You should have received a copy of the license with the project's artifact * binaries and/or sources. * * License can be consulted at http://www.apache.org/licenses/LICENSE-2.0 * --------------------------------------------------------------------------- */ package com.heliosphere.demeter.base.runner; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.commons.lang.StringUtils; import com.google.common.base.Stopwatch; import com.heliosphere.demeter.base.file.FileException; import com.heliosphere.demeter.base.file.xml.base.IXmlFile; import com.heliosphere.demeter.base.runner.annotation.RunnerConfig; import com.heliosphere.demeter.base.runner.annotation.RunnerFile; import com.heliosphere.demeter.base.runner.context.Context; import com.heliosphere.demeter.base.runner.context.IContext; import com.heliosphere.demeter.base.runner.entity.Entity; import com.heliosphere.demeter.base.runner.entity.EntityType; import com.heliosphere.demeter.base.runner.entity.IEntity; import com.heliosphere.demeter.base.runner.file.xml.configuration.XmlConfigurationFile; import com.heliosphere.demeter.base.runner.file.xml.execution.XmlExecutionFile; import com.heliosphere.demeter.base.runner.parameter.base.IParameter; import com.heliosphere.demeter.base.runner.parameter.base.IParameterType; import com.heliosphere.demeter.base.runner.parameter.base.ParameterException; import com.heliosphere.demeter.base.runner.parameter.base.ParameterStatusType; import com.heliosphere.demeter.base.runner.parameter.configuration.IParameterConfiguration; import com.heliosphere.demeter.base.runner.parameter.execution.IParameterExecution; import com.heliosphere.demeter.base.runner.processor.IProcessor; import com.heliosphere.demeter.base.runner.processor.ProcessorException; import com.heliosphere.demeter.base.runner.result.ExecutionStatusType; import com.heliosphere.demeter.base.runner.result.IExecutionResult; import lombok.NonNull; import lombok.extern.log4j.Log4j; /** * Provides an abstract implementation of a {@link IRunner}. * <hr> * @author <a href="mailto:christophe.resse@gmail.com">Resse Christophe - Heliosphere</a> * @version 1.0.0 */ @Log4j public abstract class AbstractRunner implements IRunner { /** * XML configuration file. */ private XmlConfigurationFile configuration = null; /** * XML execution file. */ private XmlExecutionFile execution = null; /** * Collection of contexts for this runner. */ private List<IContext> contexts = new ArrayList<>(); /** * Processor class to use. */ private Class<? extends IProcessor> processorClass; /** * Maximum number of threads to use. */ private int threadCount; /** * Executor service used for multi-threading the processors execution. */ @SuppressWarnings("unused") private ExecutorService executor = null; /** * Collection of callables for the multi-threading. */ private List<Callable<IExecutionResult>> callables = new ArrayList<>(); /** * Collection of futures (for gathering processing results from callables) for multi-threading. */ private List<Future<IExecutionResult>> futures = null; /** * Watch to measure elapsed time. */ private Stopwatch watch; /** * Parameter type enumeration. */ @SuppressWarnings("rawtypes") private Class enumParameterClass = null; /** * Properties file name. */ public String properties = null; /** * Creates a new abstract runner given some values. * <hr> * @param configuration XML configuration file to be used by the runner. * @param execution XML execution file to be used by the runner. * @param properties Properties file name. * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. */ @SuppressWarnings("rawtypes") public AbstractRunner(final IXmlFile configuration, final IXmlFile execution, final String properties) throws RunnerException { watch = Stopwatch.createStarted(); this.properties = properties; initializeRunnerConfigAnnotation(); this.configuration = (XmlConfigurationFile) configuration; this.execution = (XmlExecutionFile) execution; initialize(); } /** * Creates a new abstract runner. * <hr> * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. */ public AbstractRunner() throws RunnerException { // This constructor is intended to be used when configuration elements are provided using annotations. watch = Stopwatch.createStarted(); initializeAnnotations(); initialize(); } /** * Creates a new abstract runner given its XML execution file. * <hr> * @param execution XML execution file to be used by the runner. * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. */ @SuppressWarnings({ "nls", "rawtypes" }) public AbstractRunner(IXmlFile execution) throws RunnerException { watch = Stopwatch.createStarted(); if (execution == null) { throw new NullPointerException("execution"); } initializeAnnotations(); this.execution = (XmlExecutionFile) execution; initialize(); } /** * Initializes the {@link IRunner} annotations. * <hr> * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. */ private final void initializeAnnotations() throws RunnerException { initializeRunnerFileAnnotation(); initializeRunnerConfigAnnotation(); } /** * Initializes the runner configuration annotation. * <hr> * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. * @see RunnerConfig */ @SuppressWarnings({ "rawtypes", "unchecked", "nls" }) private final void initializeRunnerConfigAnnotation() throws RunnerException { for (Annotation annotation : this.getClass().getAnnotations()) { Class<? extends Annotation> type = annotation.annotationType(); if (type == RunnerConfig.class) { for (Method method : type.getDeclaredMethods()) { Object value; try { value = method.invoke(annotation, (Object[]) null); if (method.getName().equals("processorClass")) { this.processorClass = (Class<? extends IProcessor>) value; } else { if (method.getName().equals("enumParameterClass")) { this.enumParameterClass = (Class) value; } else { if (method.getName().equals("threadCount")) { this.threadCount = ((Integer) value).intValue(); } } } } catch (Exception e) { throw new RunnerException(String.format("Unable to initialize runner: %1s due to: %2s", this.getClass().getName(), e.getMessage())); } } } } } /** * Initializes the runner file annotation. * <hr> * @throws RunnerException Thrown in case an error occurred while trying to initialize the runner. * @see RunnerFile */ @SuppressWarnings("nls") private final void initializeRunnerFileAnnotation() throws RunnerException { for (Annotation annotation : this.getClass().getAnnotations()) { Class<? extends Annotation> type = annotation.annotationType(); if (type == RunnerFile.class) { for (Method method : type.getDeclaredMethods()) { Object value = null; try { value = method.invoke(annotation, (Object[]) null); if (method.getName().equals("configurationFile")) { this.configuration = new XmlConfigurationFile((String) value); } else { if (method.getName().equals("executionFile")) { if (!((String) value).equals("UNKNOWN")) { this.execution = new XmlExecutionFile((String) value); } } } } catch (Exception e) { throw new RunnerException(String.format("Unable to initialize runner: %1s due to: %2s", this.getClass().getName(), e.getMessage()), e); } } } } } @SuppressWarnings("nls") @Override public final void initialize() throws RunnerException { log.info( "*********************************************************************************************************"); log.info("EXECUTION PREPARATION SUMMARY:"); log.info(" "); try { loadConfiguration(); loadExecution(); validate(); prepare(); } catch (FileException | ParameterException | ProcessorException e) { throw new RunnerException(e); } } @Override public final XmlConfigurationFile getConfiguration() { return configuration; } @Override public final XmlExecutionFile getExecution() { return execution; } /** * Prepares the internal runner structure. * <hr> * @throws ProcessorException Thrown in case an error occurred while trying to initialize the processor. */ @SuppressWarnings("nls") private void prepare() throws ProcessorException { initializeContexts(); log.info(" "); log.info(String.format("%1d context(s) have been initialized:", contexts.size())); for (IContext context : contexts) { log.info(String.format(" context for entity name: [%1s], type: [%2s]", context.getEntity().getName(), context.getEntity().getType())); } log.info(" "); } /** * Initializes the contexts. * <hr> * @throws ProcessorException Thrown in case an error occurred while trying to initialize the processor. */ public void initializeContexts() throws ProcessorException { IContext context = null; /* * Create one context per entity to process. */ for (IEntity<?> entity : initializeEntities()) { context = new Context(entity, execution.getContent()); initializeProcessor(context); contexts.add(context); } } /** * Initializes the processor. * <hr> * @param context Context to be processed by the processor. * @return Initialized processor. * @throws ProcessorException Thrown in case an error occurred while trying to initialize the processor. */ @SuppressWarnings({ "nls" }) private IProcessor initializeProcessor(final IContext context) throws ProcessorException { IProcessor processor = null; Class<?> clazz; try { clazz = Class.forName(processorClass.getName()); Constructor<?> ctor = clazz.getConstructor(IContext.class); processor = (IProcessor) ctor.newInstance(new Object[] { context }); context.setProcessor(processor); } catch (Exception e) { throw new ProcessorException(String.format("Unable to instantiate processor of class: %1s due to: %2s", processorClass.getName(), e.getMessage())); } return processor; } /** * Initializes the entities. * <hr> * @return List of entities to be processed. */ public List<IEntity<?>> initializeEntities() { List<IEntity<?>> entities = new ArrayList<>(); // The entities can be initialized (for some of them) using the parameter type. for (IParameterExecution parameter : execution.getContent().getElements()) { EntityType type = (EntityType) parameter.getEntityType(); switch (type) { case DISPLAY: // Create a fake entity for the context initialization to display a message. IEntity<String> entity = new Entity<>(parameter.getName(), type, null); entities.add(entity); break; default: // Do nothing for these entity types! case BATCH: case DAEMON: case FILE: case COMPUTATION: case MESSAGE: case RESERVED: break; } } return entities; } @SuppressWarnings("nls") @Override public void start() throws RunnerException { log.info(String.format("Runner started: dispatching [%1d] context(s) across [%2d] thread(s).", contexts.size(), threadCount)); log.info(" "); ExecutorService executor = Executors.newFixedThreadPool(this.threadCount); for (IContext context : contexts) { callables.add(context.getProcessor()); } try { futures = executor.invokeAll(callables); } catch (InterruptedException e) { throw new RunnerException("An error occurred due to: " + e.getMessage(), e); } log.info( "*********************************************************************************************************"); log.info("EXECUTION SUMMARY:"); log.info(" "); log.info(String.format(" Thread pool size..: [%1d]", threadCount)); log.info(String.format(" Configuration file: [%1s]", configuration.getResource().getFile().getName())); log.info(String.format(" Execution file....: [%1s]", execution.getResource().getFile().getName())); log.info(String.format(" Description: %1s", execution.getHeader().getDescription())); log.info(String.format(" Parameter(s):")); IParameterConfiguration configuration = null; for (IParameterExecution p : execution.getContent().getElements()) { configuration = p.getConfiguration(); log.info(String.format(" type:[%1s], name:[%2s], value:[%3s], description:[%4s]", p.getType(), p.getName(), p.getValue(), configuration.getDescription())); } log.info(" "); for (Future<IExecutionResult> future : futures) { try { IExecutionResult result = future.get(); // Dump the execution result of the execution of a processor. String message = String.format("Context name:[%1s], status:[%2s], execution:[%4s]", StringUtils.abbreviateMiddle(result.getName(), "...", 50), result.getStatus().toString(), result.getElapsed()); log.error(message); // If process has failed, then dump the exceptions! if (result.getStatus() == ExecutionStatusType.FAILED) { for (Exception exception : result.getExceptions()) { log.error(String.format(" Exception caught -> %1s", exception.getMessage()), exception); } } } catch (InterruptedException | ExecutionException e) { throw new RunnerException("An error occurred due to: " + e.getMessage(), e); } } executor.shutdown(); watch.stop(); log.info(" "); log.info(String.format("Runner finished processing: [%1d] context(s) in a total of: [%2s]", contexts.size(), watch.toString())); log.info( "*********************************************************************************************************"); } @Override public void pause() throws RunnerException { // TODO Auto-generated method stub } @Override public void resume() throws RunnerException { // TODO Auto-generated method stub } @Override public void stop() throws RunnerException { // TODO Auto-generated method stub } @Override public void reset() throws RunnerException { // TODO Auto-generated method stub } @Override public final IParameterConfiguration getConfigurationParameter(Enum<? extends IParameterType> type) { return configuration.getParameter(type); } @Override public final IParameterConfiguration getConfigurationParameter(@NonNull final String nameOrAlias) { return configuration.getParameter(nameOrAlias); } @Override public final IParameterConfiguration getConfigurationParameter( @NonNull final IParameterConfiguration parameter) { return configuration.getParameter(parameter); } @Override public final IParameterExecution getExecutionParameter(@NonNull final Enum<? extends IParameterType> type) { return execution.getParameter(type); } @Override public final IParameterExecution getExecutionParameter(String name) { return execution.getParameter(name); } @Override public final IParameterExecution getExecutionParameter(@NonNull final IParameterExecution parameter) { return execution.getParameter(parameter); } /** * Initializes the XML configuration file. * <hr> * @throws FileException Thrown in case the XML configuration file has not been found. */ @SuppressWarnings("nls") private final void loadConfiguration() throws FileException { //setAliasesForConfiguration(this.configuration.getEngine(), this.configuration); configuration.load(); log.info(String.format("Configuration file: [%1s] initialized.", configuration.getResource().getFile().getName())); } /** * Initializes the XML execution file. * <hr> * @throws FileException Thrown in case the xml configuration file has not been found. */ @SuppressWarnings("nls") private final void loadExecution() throws FileException { //setAliasesForExecution(this.execution.getEngine(), this.execution); execution.load(); log.info(String.format("Execution file...: [%1s] initialized.", execution.getResource().getFile().getName())); } /** * Validates the execution file against the configuration one. * <hr> * @throws ParameterException Thrown in case an error occurred while validating the parameters. */ @SuppressWarnings("nls") protected final void validate() throws ParameterException { for (IParameterExecution p : execution.getContent().getElements()) { validateParameter(p); } validateParameters(); log.info(String.format("Execution file has been validated against configuration file.")); log.info(" "); } /** * Service for derived classes of {@link AbstractRunner} to do a kind of pre-process of the parameters before global validation. * <hr> * @throws ParameterException Thrown in case an error occurred while validating the parameters. */ @SuppressWarnings("nls") protected void validateParameters() throws ParameterException { IParameterConfiguration pConfig; IParameterConfiguration pConfigOther; IParameterExecution pExecOther; // Check incompatible parameters. for (IParameterExecution parameter : execution.getContent().getElements()) { pConfig = configuration.getParameter(parameter.getType()); if (pConfig.getIncompatibleParameters() != null) { for (String name : pConfig.getIncompatibleParameters()) { pConfigOther = getConfigurationParameter(name); pExecOther = getExecutionParameter(pConfigOther.getType()); if (pExecOther != null) { throw new ParameterException(String.format("Parameters %1s and %2s are incompatible!", pConfig.getName(), pConfigOther.getName())); } } } } // Check required parameters. for (IParameterExecution parameter : execution.getContent().getElements()) { pConfig = configuration.getParameter(parameter.getType()); if (pConfig.getRequiredParameters() != null) { for (String name : pConfig.getRequiredParameters()) { pConfigOther = getConfigurationParameter(name); pExecOther = getExecutionParameter(pConfigOther.getType()); if (pExecOther == null) { throw new ParameterException(String.format("Parameters %1s and %2s are required!", pConfig.getName(), pConfigOther.getName())); } } } } } /** * Validates an execution parameter against its definition and its value. * <hr> * @param parameter Execution parameter to check. * @throws ParameterException Thrown in case an error occurred while validating a parameter. */ @SuppressWarnings("nls") protected final void validateParameter(final IParameter parameter) throws ParameterException { determineParameterType(enumParameterClass); if (!existParameter(parameter, configuration.getParameter(parameter))) { throw new ParameterException(String.format( "Invalid parameter: %1s for runner: %2s using configuration file: %3s and excution file: %4s", parameter.getName(), this.getClass().getName(), configuration.getResource().getFile().getName(), execution.getResource().getFile().getName())); } if (!isValueAllowed(parameter, configuration.getParameter(parameter))) { throw new ParameterException(String.format( "Invalid parameter value: %1s for parameter name: %2s for runner: %3s using configuration file: %4s and excution file: %5s", parameter.getName(), parameter.getName(), this.getClass().getName(), configuration.getResource().getFile().getName(), execution.getResource().getFile().getName())); } } /** * Determines the parameters' type. * <hr> * @param enumClass Parameter enumeration class to use to determine the parameters' type. * @throws ParameterException Thrown in case an error occurred when determining a parameter type. */ @SuppressWarnings({ "unchecked", "rawtypes", "nls" }) private final void determineParameterType(final Class enumClass) throws ParameterException { Method method; String name = null; String value = null; this.enumParameterClass = enumClass; Object o = Enum.valueOf(enumClass, "UNKNOWN"); try { method = enumClass.getDeclaredMethod("fromName", String.class); for (IParameterConfiguration parameter : configuration.getContent().getElements()) { value = parameter.getName(); parameter.setType((Enum<? extends IParameterType>) method.invoke(o, value)); parameter.setEntityType(((IParameterType) parameter.getType()).getEntityType()); if (parameter.getType() == null) { throw new ParameterException("No parameter definition found for: " + value); } } for (IParameterExecution parameter : execution.getContent().getElements()) { name = parameter.getName(); parameter.setType((Enum<? extends IParameterType>) method.invoke(o, name)); parameter.setEntityType(((IParameterType) parameter.getType()).getEntityType()); parameter.setStatus(ParameterStatusType.UNPROCESSED); parameter.setConfiguration(configuration.getParameter(parameter.getType())); } } catch (Exception e) { throw new ParameterException( String.format("Unable to create enumerated value for enumeration: %1s, parameter: %2s", enumClass.getName(), name == null ? value : name)); } checkParameterDefinitionAgainstEnumeration(o.getClass()); } /** * Checks the definition of the parameters against their enumeration class. * <hr> * @param clazz Parameter enumeration class. * @throws ParameterException Thrown in case an error occurred when validating a parameter type against its enumeration class. */ @SuppressWarnings({ "unchecked", "rawtypes", "nls" }) private final void checkParameterDefinitionAgainstEnumeration(@NonNull final Class clazz) throws ParameterException { Class<? extends Enum<? extends IParameterType>> enumeration = clazz; List<Enum<? extends IParameterType>> list = (List<Enum<? extends IParameterType>>) Arrays .asList(enumeration.getEnumConstants()); for (Enum<? extends IParameterType> e : list) { boolean found = false; for (IParameterConfiguration p : configuration.getContent().getElements()) { if (p.getType() == e) { found = true; break; } } if (!found && !e.toString().equals("UNKNOWN")) { throw new ParameterException(String.format( "Unable to find parameter name: '%1s' in file: '%2s' corresponding to enumeration class: %3s for enumerated value: %4s", ((IParameterType) e).getName(), configuration.getResource().getFile().getName(), clazz.getSimpleName(), e.toString())); } } } /** * Checks if the given execution parameter exist as a configuration parameter? * <hr> * @param execution Execution parameter. * @param configuration Configuration parameter. * @return {@code True} if able to find a configuration parameter matching the given execution parameter, {@code false} otherwise. */ @SuppressWarnings("static-method") protected boolean existParameter(@NonNull final IParameter execution, final IParameter configuration) { return configuration == null ? false : true; } /** * Checks if an execution parameter value is allowed? * <hr> * @param execution Execution parameter. * @param configuration Configuration parameter. * @return {@code True} if the given execution parameter value is allowed, {@code false} otherwise. */ @SuppressWarnings("static-method") protected final boolean isValueAllowed(@NonNull final IParameter execution, @NonNull final IParameter configuration) { IParameterExecution e = (IParameterExecution) execution; IParameterConfiguration c = (IParameterConfiguration) configuration; if (e.getValue() == null) { if (c.getAllowedValues().isEmpty()) { return true; } return false; } /* * If no allowed value specified, then consider the execution parameter value as correct. */ if (c.getAllowedValues() == null || c.getAllowedValues().isEmpty()) { return true; } for (String element : c.getAllowedValues()) { if (element.equalsIgnoreCase(e.getValue())) { return true; } } return false; } }