package org.apache.torque.generator.control;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;
import org.apache.torque.generator.GeneratorException;
import org.apache.torque.generator.configuration.Configuration;
import org.apache.torque.generator.configuration.ConfigurationException;
import org.apache.torque.generator.configuration.UnitConfiguration;
import org.apache.torque.generator.configuration.UnitDescriptor;
import org.apache.torque.generator.configuration.controller.OutletReference;
import org.apache.torque.generator.configuration.controller.Output;
import org.apache.torque.generator.configuration.outlet.OutletConfiguration;
import org.apache.torque.generator.control.existingtargetstrategy.AppendToTargetFileStrategy;
import org.apache.torque.generator.control.existingtargetstrategy.ExistingTargetStrategy;
import org.apache.torque.generator.control.existingtargetstrategy.MergeTargetFileStrategy;
import org.apache.torque.generator.control.existingtargetstrategy.ReplaceTargetFileStrategy;
import org.apache.torque.generator.control.existingtargetstrategy.SkipExistingTargetFileStrategy;
import org.apache.torque.generator.outlet.Outlet;
import org.apache.torque.generator.outlet.OutletResult;
import org.apache.torque.generator.source.Source;
import org.apache.torque.generator.source.SourceElement;
import org.apache.torque.generator.source.SourceException;
import org.apache.torque.generator.source.SourcePath;
import org.apache.torque.generator.source.SourceProcessConfiguration;
import org.apache.torque.generator.source.SourceProvider;
import org.apache.torque.generator.source.SourceTransformerDefinition;
import org.apache.torque.generator.source.skipDecider.SkipDecider;
import org.apache.torque.generator.source.transform.SourceTransformer;
import org.apache.torque.generator.source.transform.SourceTransformerException;
/**
* Reads the configuration and generates the output accordingly.
*/
public class Controller
{
/** The logger. */
private static Log log = LogFactory.getLog(Controller.class);
/**
* All known ExistingTargetStrategies.
* TODO: move to a better place.
*/
private static final List<ExistingTargetStrategy>
EXISTING_TARGET_STRATEGIES;
static
{
List<ExistingTargetStrategy> existingTargetStrategies
= new ArrayList<ExistingTargetStrategy>();
existingTargetStrategies.add(new ReplaceTargetFileStrategy());
existingTargetStrategies.add(new SkipExistingTargetFileStrategy());
existingTargetStrategies.add(new MergeTargetFileStrategy());
existingTargetStrategies.add(new AppendToTargetFileStrategy());
EXISTING_TARGET_STRATEGIES = Collections.unmodifiableList(
existingTargetStrategies);
}
/**
* Executes the controller action.
*
* @param unitDescriptors the units of generation to execute.
*
* @throws ControllerException if a ControllerException occurs during
* processing.
* @throws ConfigurationException if a ConfigurationException occurs during
* processing.
* @throws GeneratorException if a OutletException occurs during
* processing.
* @throws IOException if a IOException occurs during processing.
*/
public void run(List<UnitDescriptor> unitDescriptors)
throws GeneratorException
{
initLogging();
Configuration configuration = readConfiguration(unitDescriptors);
List<UnitConfiguration> unitConfigurations
= configuration.getUnitConfigurations();
ControllerState controllerState = new ControllerState();
for (UnitConfiguration unitConfiguration : unitConfigurations)
{
processGenerationUnit(
controllerState,
unitConfiguration);
}
controllerState.getVariableStore().endGeneration();
}
/**
* Initializes the Logging.
*/
protected void initLogging()
{
InputStream log4jStream
= Controller.class.getClassLoader().getResourceAsStream(
"org/apache/torque/generator/log4j.properties");
Properties log4jProperties = new Properties();
try
{
log4jProperties.load(log4jStream);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
PropertyConfigurator.configure(log4jProperties);
}
/**
* Reads the configuration.
*
* @param unitDescriptors the unit descriptors for which the configuration
* should be read, not null, not empty.
*
* @return the configuration.
*
* @throws ConfigurationException if the configuration is faulty.
*/
private Configuration readConfiguration(
List<UnitDescriptor> unitDescriptors)
throws ConfigurationException
{
log.info("readConfiguration() : Starting to read configuration files");
Configuration configuration = new Configuration();
configuration.addUnits(unitDescriptors);
configuration.read();
log.info("readConfiguration() : Configuration read.");
return configuration;
}
/**
* Processes a unit of generation.
*
* @param controllerState the controller state, not null.
* @param unitConfiguration the configuration of the generation unit
* to process, not null.
*
* @throws GeneratorException if a generation error occurs.
*/
protected void processGenerationUnit(
ControllerState controllerState,
UnitConfiguration unitConfiguration)
throws GeneratorException
{
log.debug("processGenerationUnit() : start");
unitConfiguration.getLoglevel().apply();
log.debug("processGenerationUnit() : Loglevel applied.");
controllerState.setUnitConfiguration(unitConfiguration);
List<Output> outputList = unitConfiguration.getOutputList();
for (Output output : outputList)
{
processOutput(
output,
controllerState,
unitConfiguration);
}
}
/**
* Processes an output definition.
*
* @param output the output definition to process, not null.
* @param controllerState the controller state, not null.
* @param unitConfiguration the configuration of the generation unit
* to process, not null.
*
* @throws GeneratorException if a generation error occurs.
*/
private void processOutput(
Output output,
ControllerState controllerState,
UnitConfiguration unitConfiguration)
throws GeneratorException
{
log.info("Processing output " + output.getName());
controllerState.setOutput(output);
SourceProvider sourceProvider = output.getSourceProvider();
SourceProvider overrideSourceProvider
= unitConfiguration.getOverrideSourceProvider();
if (overrideSourceProvider != null)
{
overrideSourceProvider = overrideSourceProvider.copy();
overrideSourceProvider.copyNotSetSettingsFrom(sourceProvider);
sourceProvider = overrideSourceProvider;
}
controllerState.setSourceProvider(sourceProvider);
sourceProvider.init(
unitConfiguration.getConfigurationHandlers(),
controllerState);
if (!sourceProvider.hasNext())
{
log.info("No sources found, skipping output");
}
while (sourceProvider.hasNext())
{
Source source = sourceProvider.next();
processSourceInOutput(
source,
output,
controllerState,
unitConfiguration);
}
controllerState.setSourceProvider(null);
}
/**
* Processes a single source in an output definition.
*
* @param source the source to process, not null.
* @param output the output to which the source belongs, not null.
* @param controllerState the controller state, not null.
* @param unitConfiguration the configuration of the current generation
* unit, not null.
*
* @throws GeneratorException if a generation error occurs.
*/
private void processSourceInOutput(
Source source,
Output output,
ControllerState controllerState,
UnitConfiguration unitConfiguration)
throws GeneratorException
{
log.info("Processing source " + source.getDescription());
SourceElement rootElement = source.getRootElement();
controllerState.setSourceFile(source.getSourceFile());
SourceProcessConfiguration sourceProcessConfiguration
= output.getSourceProcessConfiguration();
rootElement = transformSource(
rootElement,
sourceProcessConfiguration.getTransformerDefinitions(),
controllerState);
controllerState.setRootElement(rootElement);
String startElementsPath
= sourceProcessConfiguration.getStartElementsPath();
List<SourceElement> startElements
= SourcePath.getElementsFromRoot(
rootElement,
startElementsPath);
if (startElements.isEmpty())
{
log.info("No start Elements found for path "
+ startElementsPath);
}
for (SourceElement startElement : startElements)
{
processStartElement(
startElement,
output,
source,
unitConfiguration,
controllerState);
}
}
/**
* Creates the output file name and sets it in the output.
* The filename is calculated either by the filenameConfigurator in
* <code>output</code> or is given explicitly (in the latter case
* nothing needs to be done).
*
* @param controllerState the controller state, not null.
* @param output The output to process, not null.
*
* @throws ConfigurationException if an incorrect configuration is
* encountered, e.g. if neither filename nor filenameOutlet is
* set in output.
* @throws GeneratorException if an error occurs during generation of
* the output filename.
*/
protected void createOutputFilename(
Output output,
ControllerState controllerState)
throws GeneratorException
{
if (output.getFilenameOutlet() == null)
{
if (output.getFilename() == null)
{
throw new ConfigurationException(
"neither filename nor filenameOutlet are set"
+ " on output" + output);
}
}
else
{
if (log.isDebugEnabled())
{
log.debug("Start generation of Output File path");
}
controllerState.setOutputFile(null);
Outlet filenameOutlet = output.getFilenameOutlet();
OutletReference contentOutletReference
= new OutletReference(
filenameOutlet.getName());
controllerState.setRootOutletReference(
contentOutletReference);
// use the namespace not of the filenameOutlet
// but of the real outlet
// TODO: is this a good idea ? make configurable ?
controllerState.setOutletNamespace(
output.getContentOutlet().getNamespace());
filenameOutlet.beforeExecute(controllerState);
OutletResult filenameResult
= filenameOutlet.execute(controllerState);
if (!filenameResult.isStringResult())
{
throw new GeneratorException(
"The result of a filename generation must be a String,"
+ " not a byte array");
}
String filename = filenameResult.getStringResult();
filenameOutlet.afterExecute(controllerState);
if (log.isDebugEnabled())
{
log.debug("End generation of Output File path, result is "
+ filename);
}
output.setFilename(filename);
}
}
/**
* Processes the generation for a single start Element in a source.
*
* @param startElement the start element to process.
* @param output the current output, not null.
* @param source the current source, not null.
* @param unitConfiguration the current unit configuration, not null.
* @param controllerState the current controller state, not null.
*
* @throws ControllerException if startElement is null or the configured
* outlet does not exist or the output directory cannot be created
* or the output file cannot be written..
* @throws GeneratorException if the outlet throws an exception
* during execution.
*/
private void processStartElement(
SourceElement startElement,
Output output,
Source source,
UnitConfiguration unitConfiguration,
ControllerState controllerState)
throws GeneratorException
{
if (startElement == null)
{
throw new ControllerException(
"Null start element found in source "
+ "for generating the filename "
+ "of output file "
+ output);
}
controllerState.setSourceElement(startElement);
log.debug("Processing new startElement "
+ startElement.getName());
ExistingTargetStrategy existingTargetStrategy = null;
for (ExistingTargetStrategy candidate : EXISTING_TARGET_STRATEGIES)
{
if (candidate.getStrategyName().equals(
output.getExistingTargetStrategy()))
{
existingTargetStrategy = candidate;
break;
}
}
if (existingTargetStrategy == null)
{
throw new ControllerException("existingTargetStrategy "
+ output.getExistingTargetStrategy()
+ " not found");
}
createOutputFilename(output, controllerState);
File outputFile = ControllerHelper.getOutputFile(
output.getOutputDirKey(),
output.getFilename(),
unitConfiguration);
controllerState.setOutputFile(outputFile);
if (!existingTargetStrategy.beforeGeneration(
output.getOutputDirKey(),
output.getFilename(),
getOutputEncoding(output, unitConfiguration),
unitConfiguration))
{
log.info("Skipping generation of File "
+ outputFile.getAbsolutePath()
+ " because of existingTargetStrategy "
+ existingTargetStrategy.getStrategyName());
return;
}
if (log.isInfoEnabled())
{
log.info("Start generation of File "
+ outputFile.getAbsolutePath());
}
OutletReference contentOutletConfiguration
= output.getContentOutlet();
controllerState.setOutletNamespace(
contentOutletConfiguration.getNamespace());
controllerState.setRootOutletReference(
contentOutletConfiguration);
OutletConfiguration outletConfiguration
= unitConfiguration.getOutletConfiguration();
Outlet outlet = outletConfiguration.getOutlet(
contentOutletConfiguration.getName());
if (outlet == null)
{
throw new ControllerException(
"No outlet configured for outlet name \""
+ contentOutletConfiguration.getName()
+ "\"");
}
SkipDecider skipDecider
= output.getSourceProcessConfiguration().getSkipDecider();
if (skipDecider != null)
{
if (!skipDecider.proceed(controllerState))
{
log.debug("SkipDecider " + skipDecider.getClass().getName()
+ " decided to skip "
+ "generation of file "
+ controllerState.getOutputFile());
return;
}
else
{
log.debug("SkipDecider " + skipDecider.getClass().getName()
+ " decided to proceed");
}
}
{
File parentOutputDir
= controllerState.getOutputFile().getParentFile();
if (parentOutputDir != null
&& !parentOutputDir.isDirectory())
{
boolean success = parentOutputDir.mkdirs();
if (!success)
{
throw new ControllerException(
"Could not create directory \""
+ parentOutputDir.getAbsolutePath()
+ "\"");
}
}
}
outlet.beforeExecute(controllerState);
OutletResult result = outlet.execute(controllerState);
outlet.afterExecute(controllerState);
existingTargetStrategy.afterGeneration(
output.getOutputDirKey(),
output.getFilename(),
getOutputEncoding(output, unitConfiguration),
result,
unitConfiguration);
controllerState.getVariableStore().endFile();
if (log.isDebugEnabled())
{
log.debug("End generation of Output File "
+ controllerState.getOutputFile());
}
}
/**
* Applies all tarnsformer definitions to the current source.
*
* @param rootElement the root element of the source to transform,
* not null.
* @param transformerDefinitions the transformer definitions to apply,
* not null.
* @param controllerState the current controller state, not null.
*
* @return the transformed root element, not null.
*/
public SourceElement transformSource(
final SourceElement rootElement,
final List<SourceTransformerDefinition> transformerDefinitions,
final ControllerState controllerState)
throws SourceTransformerException, SourceException
{
SourceElement result = rootElement;
for (SourceTransformerDefinition transformerDefinition
: transformerDefinitions)
{
SourceTransformer sourceTransformer
= transformerDefinition.getSourceTransformer();
String elements = transformerDefinition.getElements();
log.debug("Applying source transformer "
+ sourceTransformer.getClass().getName()
+ (elements == null
? " to the root element"
: " to the elements " + elements));
List<SourceElement> toTransform
= SourcePath.getElementsFromRoot(rootElement, elements);
if (toTransform.isEmpty())
{
log.debug("No element found, nothing transformed");
}
for (SourceElement sourceElement : toTransform)
{
log.debug("transforming element " + sourceElement);
SourceElement transformedElement = sourceTransformer.transform(
sourceElement,
controllerState);
if (transformedElement == null)
{
throw new SourceTransformerException("Transformer "
+ sourceTransformer.getClass().getName()
+ " returned null for element "
+ sourceElement.getName());
}
SourceElement parent = sourceElement.getParent();
if (parent == null)
{
result = transformedElement;
}
else
{
List<SourceElement> children = parent.getChildren();
int index = children.indexOf(sourceElement);
children.set(index, transformedElement);
}
}
log.debug("Transformation ended");
}
return result;
}
/**
* Calculates the output encoding for an output.
*
* @param output The output, not null.
* @param unitConfiguration the configuration of the unit of generation
* to which the output belongs.
*
* @return the encoding, not null.
*/
private String getOutputEncoding(
Output output,
UnitConfiguration unitConfiguration)
{
if (output.getEncoding() != null)
{
return output.getEncoding();
}
if (unitConfiguration.getDefaultOutputEncoding() != null)
{
return unitConfiguration.getDefaultOutputEncoding();
}
return Charset.defaultCharset().displayName();
}
}