View Javadoc

1   package org.apache.torque.generator.control;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.nio.charset.Charset;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Properties;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.log4j.PropertyConfigurator;
34  import org.apache.torque.generator.GeneratorException;
35  import org.apache.torque.generator.configuration.Configuration;
36  import org.apache.torque.generator.configuration.ConfigurationException;
37  import org.apache.torque.generator.configuration.UnitConfiguration;
38  import org.apache.torque.generator.configuration.UnitDescriptor;
39  import org.apache.torque.generator.configuration.controller.OutletReference;
40  import org.apache.torque.generator.configuration.controller.Output;
41  import org.apache.torque.generator.configuration.outlet.OutletConfiguration;
42  import org.apache.torque.generator.control.existingtargetstrategy.AppendToTargetFileStrategy;
43  import org.apache.torque.generator.control.existingtargetstrategy.ExistingTargetStrategy;
44  import org.apache.torque.generator.control.existingtargetstrategy.MergeTargetFileStrategy;
45  import org.apache.torque.generator.control.existingtargetstrategy.ReplaceTargetFileStrategy;
46  import org.apache.torque.generator.control.existingtargetstrategy.SkipExistingTargetFileStrategy;
47  import org.apache.torque.generator.outlet.Outlet;
48  import org.apache.torque.generator.outlet.OutletResult;
49  import org.apache.torque.generator.source.Source;
50  import org.apache.torque.generator.source.SourceElement;
51  import org.apache.torque.generator.source.SourceException;
52  import org.apache.torque.generator.source.SourcePath;
53  import org.apache.torque.generator.source.SourceProcessConfiguration;
54  import org.apache.torque.generator.source.SourceProvider;
55  import org.apache.torque.generator.source.SourceTransformerDefinition;
56  import org.apache.torque.generator.source.skipDecider.SkipDecider;
57  import org.apache.torque.generator.source.transform.SourceTransformer;
58  import org.apache.torque.generator.source.transform.SourceTransformerException;
59  
60  /**
61   * Reads the configuration and generates the output accordingly.
62   */
63  public class Controller
64  {
65      /** The logger. */
66      private static Log log = LogFactory.getLog(Controller.class);
67  
68      /**
69       * All known ExistingTargetStrategies.
70       * TODO: move to a better place.
71       */
72      private static final List<ExistingTargetStrategy>
73          EXISTING_TARGET_STRATEGIES;
74  
75      static
76      {
77          List<ExistingTargetStrategy> existingTargetStrategies
78              = new ArrayList<ExistingTargetStrategy>();
79          existingTargetStrategies.add(new ReplaceTargetFileStrategy());
80          existingTargetStrategies.add(new SkipExistingTargetFileStrategy());
81          existingTargetStrategies.add(new MergeTargetFileStrategy());
82          existingTargetStrategies.add(new AppendToTargetFileStrategy());
83          EXISTING_TARGET_STRATEGIES = Collections.unmodifiableList(
84                  existingTargetStrategies);
85      }
86  
87      /**
88       * Executes the controller action.
89       *
90       * @param unitDescriptors the units of generation to execute.
91       *
92       * @throws ControllerException if a ControllerException occurs during
93       *         processing.
94       * @throws ConfigurationException if a ConfigurationException occurs during
95       *         processing.
96       * @throws GeneratorException if a OutletException occurs during
97       *         processing.
98       * @throws IOException if a IOException occurs during processing.
99       */
100     public void run(List<UnitDescriptor> unitDescriptors)
101         throws GeneratorException
102     {
103         initLogging();
104         Configuration configuration = readConfiguration(unitDescriptors);
105 
106         List<UnitConfiguration> unitConfigurations
107                 = configuration.getUnitConfigurations();
108         ControllerState controllerState = new ControllerState();
109         for (UnitConfiguration unitConfiguration : unitConfigurations)
110         {
111             processGenerationUnit(
112                     controllerState,
113                     unitConfiguration);
114         }
115         controllerState.getVariableStore().endGeneration();
116     }
117 
118     /**
119      * Initializes the Logging.
120      */
121     protected void initLogging()
122     {
123         InputStream log4jStream
124                 = Controller.class.getClassLoader().getResourceAsStream(
125                     "org/apache/torque/generator/log4j.properties");
126         Properties log4jProperties = new Properties();
127         try
128         {
129             log4jProperties.load(log4jStream);
130         }
131         catch (IOException e)
132         {
133             throw new RuntimeException(e);
134         }
135         PropertyConfigurator.configure(log4jProperties);
136     }
137 
138     /**
139      * Reads the configuration.
140      *
141      * @param unitDescriptors the unit descriptors for which the configuration
142      *        should be read, not null, not empty.
143      *
144      * @return the configuration.
145      *
146      * @throws ConfigurationException if the configuration is faulty.
147      */
148     private Configuration readConfiguration(
149                 List<UnitDescriptor> unitDescriptors)
150             throws ConfigurationException
151     {
152         log.info("readConfiguration() : Starting to read configuration files");
153         Configuration configuration = new Configuration();
154         configuration.addUnits(unitDescriptors);
155         configuration.read();
156         log.info("readConfiguration() : Configuration read.");
157         return configuration;
158     }
159 
160     /**
161      * Processes a unit of generation.
162      *
163      * @param controllerState the controller state, not null.
164      * @param unitConfiguration the configuration of the generation unit
165      *        to process, not null.
166      *
167      * @throws GeneratorException if a generation error occurs.
168      */
169     protected void processGenerationUnit(
170                 ControllerState controllerState,
171                 UnitConfiguration unitConfiguration)
172             throws GeneratorException
173     {
174         log.debug("processGenerationUnit() : start");
175         unitConfiguration.getLoglevel().apply();
176         log.debug("processGenerationUnit() : Loglevel applied.");
177         controllerState.setUnitConfiguration(unitConfiguration);
178         List<Output> outputList = unitConfiguration.getOutputList();
179         for (Output output : outputList)
180         {
181             processOutput(
182                     output,
183                     controllerState,
184                     unitConfiguration);
185         }
186     }
187 
188     /**
189      * Processes an output definition.
190      *
191      * @param output the output definition to process, not null.
192      * @param controllerState the controller state, not null.
193      * @param unitConfiguration the configuration of the generation unit
194      *        to process, not null.
195      *
196      * @throws GeneratorException if a generation error occurs.
197      */
198     private void processOutput(
199                 Output output,
200                 ControllerState controllerState,
201                 UnitConfiguration unitConfiguration)
202             throws GeneratorException
203     {
204         log.info("Processing output " + output.getName());
205         controllerState.setOutput(output);
206 
207         SourceProvider sourceProvider = output.getSourceProvider();
208         SourceProvider overrideSourceProvider
209                 = unitConfiguration.getOverrideSourceProvider();
210         if (overrideSourceProvider != null)
211         {
212             overrideSourceProvider = overrideSourceProvider.copy();
213             overrideSourceProvider.copyNotSetSettingsFrom(sourceProvider);
214             sourceProvider = overrideSourceProvider;
215         }
216         controllerState.setSourceProvider(sourceProvider);
217         sourceProvider.init(
218                 unitConfiguration.getConfigurationHandlers(),
219                 controllerState);
220         if (!sourceProvider.hasNext())
221         {
222             log.info("No sources found, skipping output");
223         }
224 
225         while (sourceProvider.hasNext())
226         {
227             Source source = sourceProvider.next();
228             processSourceInOutput(
229                     source,
230                     output,
231                     controllerState,
232                     unitConfiguration);
233         }
234         controllerState.setSourceProvider(null);
235     }
236 
237     /**
238      * Processes a single source in an output definition.
239      *
240      * @param source the source to process, not null.
241      * @param output the output to which the source belongs, not null.
242      * @param controllerState the controller state, not null.
243      * @param unitConfiguration the configuration of the current generation
244      *        unit, not null.
245      *
246      * @throws GeneratorException if a generation error occurs.
247      */
248     private void processSourceInOutput(
249                 Source source,
250                 Output output,
251                 ControllerState controllerState,
252                 UnitConfiguration unitConfiguration)
253             throws GeneratorException
254     {
255         log.info("Processing source " + source.getDescription());
256         SourceElement rootElement = source.getRootElement();
257         controllerState.setSourceFile(source.getSourceFile());
258         SourceProcessConfiguration sourceProcessConfiguration
259                 = output.getSourceProcessConfiguration();
260         rootElement = transformSource(
261                 rootElement,
262                 sourceProcessConfiguration.getTransformerDefinitions(),
263                 controllerState);
264         controllerState.setRootElement(rootElement);
265 
266         String startElementsPath
267                 = sourceProcessConfiguration.getStartElementsPath();
268         List<SourceElement> startElements
269                 = SourcePath.getElementsFromRoot(
270                         rootElement,
271                         startElementsPath);
272         if (startElements.isEmpty())
273         {
274             log.info("No start Elements found for path "
275                     + startElementsPath);
276         }
277         for (SourceElement startElement : startElements)
278         {
279             processStartElement(
280                     startElement,
281                     output,
282                     source,
283                     unitConfiguration,
284                     controllerState);
285         }
286     }
287 
288     /**
289      * Creates the output file name and sets it in the output.
290      * The filename is calculated either by the filenameConfigurator in
291      * <code>output</code> or is given explicitly (in the latter case
292      * nothing needs to be done).
293      *
294      * @param controllerState the controller state, not null.
295      * @param output The output to  process, not null.
296      *
297      * @throws ConfigurationException if an incorrect configuration is
298      *         encountered, e.g. if neither filename nor filenameOutlet is
299      *         set in output.
300      * @throws GeneratorException if an error occurs during generation of
301      *         the output filename.
302      */
303     protected void createOutputFilename(
304                 Output output,
305                 ControllerState controllerState)
306             throws GeneratorException
307     {
308         if (output.getFilenameOutlet() == null)
309         {
310             if (output.getFilename() == null)
311             {
312                 throw new ConfigurationException(
313                     "neither filename nor filenameOutlet are set"
314                         + " on output" + output);
315             }
316         }
317         else
318         {
319             if (log.isDebugEnabled())
320             {
321                 log.debug("Start generation of Output File path");
322             }
323             controllerState.setOutputFile(null);
324 
325             Outlet filenameOutlet = output.getFilenameOutlet();
326             OutletReference contentOutletReference
327                     = new OutletReference(
328                             filenameOutlet.getName());
329             controllerState.setRootOutletReference(
330                     contentOutletReference);
331             // use the namespace not of the filenameOutlet
332             // but of the real outlet
333             // TODO: is this a good idea ? make configurable ?
334             controllerState.setOutletNamespace(
335                     output.getContentOutlet().getNamespace());
336             filenameOutlet.beforeExecute(controllerState);
337             OutletResult filenameResult
338                 = filenameOutlet.execute(controllerState);
339             if (!filenameResult.isStringResult())
340             {
341                 throw new GeneratorException(
342                         "The result of a filename generation must be a String,"
343                         + " not a byte array");
344             }
345             String filename = filenameResult.getStringResult();
346             filenameOutlet.afterExecute(controllerState);
347             if (log.isDebugEnabled())
348             {
349                 log.debug("End generation of Output File path, result is "
350                         + filename);
351             }
352             output.setFilename(filename);
353         }
354     }
355 
356     /**
357      * Processes the generation for a single start Element in a source.
358      *
359      * @param startElement the start element to process.
360      * @param output the current output, not null.
361      * @param source the current source, not null.
362      * @param unitConfiguration the current unit configuration, not null.
363      * @param controllerState the current controller state, not null.
364      *
365      * @throws ControllerException if startElement is null or the configured
366      *         outlet does not exist or the output directory cannot be created
367      *         or the output file cannot be written..
368      * @throws GeneratorException if the outlet throws an exception
369      *         during execution.
370      */
371     private void processStartElement(
372                 SourceElement startElement,
373                 Output output,
374                 Source source,
375                 UnitConfiguration unitConfiguration,
376                 ControllerState controllerState)
377             throws GeneratorException
378     {
379         if (startElement == null)
380         {
381             throw new ControllerException(
382                 "Null start element found in source "
383                     + "for generating the filename "
384                     + "of output file "
385                     + output);
386         }
387         controllerState.setSourceElement(startElement);
388         log.debug("Processing new startElement "
389                 + startElement.getName());
390 
391         ExistingTargetStrategy existingTargetStrategy = null;
392         for (ExistingTargetStrategy candidate : EXISTING_TARGET_STRATEGIES)
393         {
394             if (candidate.getStrategyName().equals(
395                     output.getExistingTargetStrategy()))
396             {
397                 existingTargetStrategy = candidate;
398                 break;
399             }
400         }
401         if (existingTargetStrategy == null)
402         {
403             throw new ControllerException("existingTargetStrategy "
404                     + output.getExistingTargetStrategy()
405                     + " not found");
406         }
407 
408         createOutputFilename(output, controllerState);
409         File outputFile = ControllerHelper.getOutputFile(
410                 output.getOutputDirKey(),
411                 output.getFilename(),
412                 unitConfiguration);
413         controllerState.setOutputFile(outputFile);
414 
415         if (!existingTargetStrategy.beforeGeneration(
416                 output.getOutputDirKey(),
417                 output.getFilename(),
418                 getOutputEncoding(output, unitConfiguration),
419                 unitConfiguration))
420         {
421             log.info("Skipping generation of File "
422                     + outputFile.getAbsolutePath()
423                     + " because of existingTargetStrategy "
424                     + existingTargetStrategy.getStrategyName());
425             return;
426         }
427         if (log.isInfoEnabled())
428         {
429             log.info("Start generation of File "
430                     + outputFile.getAbsolutePath());
431         }
432 
433         OutletReference contentOutletConfiguration
434                 = output.getContentOutlet();
435         controllerState.setOutletNamespace(
436                 contentOutletConfiguration.getNamespace());
437         controllerState.setRootOutletReference(
438                 contentOutletConfiguration);
439 
440         OutletConfiguration outletConfiguration
441                 = unitConfiguration.getOutletConfiguration();
442 
443         Outlet outlet = outletConfiguration.getOutlet(
444                 contentOutletConfiguration.getName());
445         if (outlet == null)
446         {
447             throw new ControllerException(
448                     "No outlet configured for outlet name \""
449                         + contentOutletConfiguration.getName()
450                         + "\"");
451         }
452 
453         SkipDecider skipDecider
454                 = output.getSourceProcessConfiguration().getSkipDecider();
455         if (skipDecider != null)
456         {
457             if (!skipDecider.proceed(controllerState))
458             {
459                 log.debug("SkipDecider " + skipDecider.getClass().getName()
460                         + " decided to skip "
461                         + "generation of file "
462                         + controllerState.getOutputFile());
463                 return;
464             }
465             else
466             {
467                 log.debug("SkipDecider " + skipDecider.getClass().getName()
468                         + " decided to proceed");
469             }
470         }
471 
472         {
473             File parentOutputDir
474                     = controllerState.getOutputFile().getParentFile();
475             if (parentOutputDir != null
476                     && !parentOutputDir.isDirectory())
477             {
478                 boolean success = parentOutputDir.mkdirs();
479                 if (!success)
480                 {
481                     throw new ControllerException(
482                             "Could not create directory \""
483                                 + parentOutputDir.getAbsolutePath()
484                                 + "\"");
485                 }
486             }
487         }
488 
489         outlet.beforeExecute(controllerState);
490         OutletResult result = outlet.execute(controllerState);
491         outlet.afterExecute(controllerState);
492 
493         existingTargetStrategy.afterGeneration(
494                 output.getOutputDirKey(),
495                 output.getFilename(),
496                 getOutputEncoding(output, unitConfiguration),
497                 result,
498                 unitConfiguration);
499 
500         controllerState.getVariableStore().endFile();
501         if (log.isDebugEnabled())
502         {
503             log.debug("End generation of Output File "
504                     + controllerState.getOutputFile());
505         }
506     }
507 
508     /**
509      * Applies all tarnsformer definitions to the current source.
510      *
511      * @param rootElement the root element of the source to transform,
512      *        not null.
513      * @param transformerDefinitions the transformer definitions to apply,
514      *        not null.
515      * @param controllerState the current controller state, not null.
516      *
517      * @return the transformed root element, not null.
518      */
519     public SourceElement transformSource(
520                     final SourceElement rootElement,
521                     final List<SourceTransformerDefinition> transformerDefinitions,
522                     final ControllerState controllerState)
523             throws SourceTransformerException, SourceException
524     {
525         SourceElement result = rootElement;
526         for (SourceTransformerDefinition transformerDefinition
527                 : transformerDefinitions)
528         {
529             SourceTransformer sourceTransformer
530                     = transformerDefinition.getSourceTransformer();
531             String elements = transformerDefinition.getElements();
532             log.debug("Applying source transformer "
533                     + sourceTransformer.getClass().getName()
534                     + (elements == null
535                             ? " to the root element"
536                             : " to the elements " + elements));
537 
538             List<SourceElement> toTransform
539                     = SourcePath.getElementsFromRoot(rootElement, elements);
540             if (toTransform.isEmpty())
541             {
542                 log.debug("No element found, nothing transformed");
543             }
544             for (SourceElement sourceElement : toTransform)
545             {
546                 log.debug("transforming element " + sourceElement);
547                 SourceElement transformedElement = sourceTransformer.transform(
548                         sourceElement,
549                         controllerState);
550                 if (transformedElement == null)
551                 {
552                     throw new SourceTransformerException("Transformer "
553                             + sourceTransformer.getClass().getName()
554                             + " returned null for element "
555                             + sourceElement.getName());
556                 }
557                 SourceElement parent = sourceElement.getParent();
558                 if (parent == null)
559                 {
560                     result = transformedElement;
561                 }
562                 else
563                 {
564                     List<SourceElement> children = parent.getChildren();
565                     int index = children.indexOf(sourceElement);
566                     children.set(index, transformedElement);
567                 }
568             }
569             log.debug("Transformation ended");
570         }
571         return result;
572     }
573 
574     /**
575      * Calculates the output encoding for an output.
576      *
577      * @param output The output, not null.
578      * @param unitConfiguration the configuration of the unit of generation
579      *        to which the output belongs.
580      *
581      * @return the encoding, not null.
582      */
583     private String getOutputEncoding(
584             Output output,
585             UnitConfiguration unitConfiguration)
586     {
587         if (output.getEncoding() != null)
588         {
589             return output.getEncoding();
590         }
591         if (unitConfiguration.getDefaultOutputEncoding() != null)
592         {
593             return unitConfiguration.getDefaultOutputEncoding();
594         }
595         return Charset.defaultCharset().displayName();
596     }
597 
598 }