View Javadoc
1   package org.andromda.core.cartridge;
2   
3   import java.io.File;
4   import java.io.StringWriter;
5   import java.net.URL;
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.LinkedHashMap;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import org.andromda.core.cartridge.template.ModelElement;
13  import org.andromda.core.cartridge.template.ModelElements;
14  import org.andromda.core.cartridge.template.Template;
15  import org.andromda.core.cartridge.template.Type;
16  import org.andromda.core.common.AndroMDALogger;
17  import org.andromda.core.common.BasePlugin;
18  import org.andromda.core.common.ExceptionUtils;
19  import org.andromda.core.common.Introspector;
20  import org.andromda.core.common.PathMatcher;
21  import org.andromda.core.common.PostProcessor;
22  import org.andromda.core.common.ResourceUtils;
23  import org.andromda.core.common.ResourceWriter;
24  import org.andromda.core.common.TemplateObject;
25  import org.andromda.core.configuration.Namespaces;
26  import org.andromda.core.metafacade.MetafacadeBase;
27  import org.andromda.core.metafacade.MetafacadeFactory;
28  import org.andromda.core.metafacade.ModelAccessFacade;
29  import org.apache.commons.io.FileUtils;
30  import org.apache.commons.lang.BooleanUtils;
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.log4j.Logger;
33  
34  /**
35   * The AndroMDA Cartridge implementation of the Plugin. Cartridge instances are configured from
36   * <code>META-INF/andromda/cartridge.xml</code> files discovered on the classpath.
37   *
38   * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
39   * @author Chad Brandon
40   * @author Bob Fields
41   * @author Michail Plushnikov
42   */
43  public class Cartridge
44      extends BasePlugin
45  {
46      /** The logger instance. */
47      private static final Logger LOGGER = Logger.getLogger(Cartridge.class);
48  
49      /**
50       * Processes all model elements with relevant stereotypes by retrieving the model elements from the model facade
51       * contained within the context.
52       *
53       * @param factory the metafacade factory (which is used to manage the lifecycle of metafacades).
54       */
55      public void processModelElements(final MetafacadeFactory factory)
56      {
57          ExceptionUtils.checkNull(
58              "factory",
59              factory);
60          final Collection<Resource> resources = this.getResources();
61          if (resources != null && !resources.isEmpty())
62          {
63              for (Resource resource : resources)
64              {
65                  try
66                  {
67                      if (resource instanceof Template)
68                      {
69                          this.processTemplate(
70                              factory,
71                              (Template)resource);
72                      }
73                      else
74                      {
75                          this.processResource(resource);
76                      }
77                  }
78                  catch (Exception e)
79                  {
80                      // Don't kill the entire code generation if one output fails
81                      LOGGER.error("Error processing resource " + resource.toString(), e);
82                  }
83              }
84          }
85      }
86  
87      /**
88       * Processes the given <code>template</code>.
89       *
90       * @param factory the metafacade factory instance.
91       * @param template the Template instance to process.
92       */
93      protected void processTemplate(
94          final MetafacadeFactory factory,
95          final Template template)
96      {
97          ExceptionUtils.checkNull(
98              "template",
99              template);
100         final ModelElements templateModelElements = template.getSupportedModeElements();
101 
102         // - handle the templates WITH model elements
103         if (templateModelElements != null && !templateModelElements.isEmpty())
104         {
105             for (ModelElement templateModelElement : templateModelElements.getModelElements())
106             {
107                 // - if the template model element has a stereotype
108                 //   defined, then we filter the metafacades based
109                 //   on that stereotype, otherwise we get all metafacades
110                 //   and let the templateModelElement perform filtering on the
111                 //   metafacades by type and properties
112                 if (templateModelElement.hasStereotype())
113                 {
114                     templateModelElement.setMetafacades(
115                         factory.getMetafacadesByStereotype(templateModelElement.getStereotype()));
116                 }
117                 else if (templateModelElement.hasTypes())
118                 {
119                     templateModelElement.setMetafacades(factory.getAllMetafacades());
120                 }
121             }
122             this.processTemplateWithMetafacades(
123                 factory,
124                 template);
125         }
126         else
127         {
128             // - handle any templates WITHOUT metafacades.
129             this.processTemplateWithoutMetafacades(template);
130         }
131     }
132 
133     /**
134      * Processes all <code>modelElements</code> for this template.
135      *
136      * @param factory the metafacade factory
137      * @param template the template to process
138      */
139     protected void processTemplateWithMetafacades(
140         final MetafacadeFactory factory,
141         final Template template)
142     {
143         ExceptionUtils.checkNull(
144             "template",
145             template);
146         final ModelElements modelElements = template.getSupportedModeElements();
147         if (modelElements != null && !modelElements.isEmpty())
148         {
149             try
150             {
151                 final Collection<MetafacadeBase> allMetafacades = modelElements.getAllMetafacades();
152                 // Tell us which template is processed against how many metafacade elements
153                 LOGGER.info("Processing " + template.getPath() + " with " + allMetafacades.size() + " metafacades from " + modelElements.getModelElements().size() + " model elements");
154 
155                 // - if outputToSingleFile is true AND outputOnEmptyElements
156                 //   is true or we have at least one metafacade in the
157                 //   allMetafacades collection, then we collect the template
158                 //   model elements and place them into the template context
159                 //   by their variable names.
160                 if (template.isOutputToSingleFile() &&
161                     (template.isOutputOnEmptyElements() || !allMetafacades.isEmpty()))
162                 {
163                     final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
164 
165                     // - first place all relevant model elements by the
166                     //   <modelElements/> variable name. If the variable
167                     //   isn't defined (which is possible), ignore.
168                     final String modelElementsVariable = modelElements.getVariable();
169                     if (StringUtils.isNotBlank(modelElementsVariable))
170                     {
171                         templateContext.put(
172                             modelElementsVariable,
173                             allMetafacades);
174                     }
175 
176                     // - now place the collections of elements by the given variable names.
177                     //   (skip if the variable is NOT defined)
178                     for (final ModelElement modelElement : modelElements.getModelElements())
179                     {
180                         final String modelElementVariable = modelElement.getVariable();
181                         if (StringUtils.isNotBlank(modelElementVariable))
182                         {
183                             // - if a modelElement has the same variable defined
184                             //   more than one time, then get the existing
185                             //   model elements added from the last iteration
186                             //   and add the new ones to that collection
187                             Collection<MetafacadeBase> metafacades = (Collection<MetafacadeBase>)templateContext.get(modelElementVariable);
188                             if (metafacades != null)
189                             {
190                                 metafacades.addAll(modelElement.getMetafacades());
191                             }
192                             else
193                             {
194                                 metafacades = modelElement.getMetafacades();
195                                 templateContext.put(
196                                     modelElementVariable,
197                                     new LinkedHashSet<MetafacadeBase>(metafacades));
198                             }
199                         }
200                     }
201                     this.processWithTemplate(
202                         template,
203                         templateContext,
204                         null,
205                         null);
206                 }
207                 else
208                 {
209                     // - if outputToSingleFile isn't true, then
210                     //   we just place the model element with the default
211                     //   variable defined on the <modelElements/> into the
212                     //   template.
213                     for (Object metafacade : allMetafacades)
214                     {
215                         final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
216                         final ModelAccessFacade model = factory.getModel();
217                         for (final ModelElement modelElement : modelElements.getModelElements())
218                         {
219                             String variable = modelElement.getVariable();
220 
221                             // - if the variable isn't defined on the <modelElement/>, try
222                             //   the <modelElements/>
223                             if (StringUtils.isBlank(variable))
224                             {
225                                 variable = modelElements.getVariable();
226                             }
227 
228                             // - only add the metafacade to the template context if the variable
229                             //   is defined (which is possible)
230                             if (StringUtils.isNotBlank(variable))
231                             {
232                                 templateContext.put(
233                                     variable,
234                                     metafacade);
235                             }
236 
237                             // - now we process any property templates (if any 'variable' attributes are defined on one or
238                             //   more type's given properties), otherwise we process the single metafacade as usual
239                             if (!this.processPropertyTemplates(
240                                     template,
241                                     metafacade,
242                                     templateContext,
243                                     modelElement))
244                             {
245                                 this.processWithTemplate(
246                                     template,
247                                     templateContext,
248                                     model.getName(metafacade),
249                                     model.getPackageName(metafacade));
250                             }
251                         }
252                     }
253                 }
254             }
255             catch (final Throwable throwable)
256             {
257                 LOGGER.error("Error Processing " + template.getPath(), throwable);
258                 throw new CartridgeException(throwable);
259             }
260         }
261     }
262 
263     /**
264      * Determines if any property templates need to be processed (that is templates
265      * that are processed given related <em>properties</em> of a metafacade).
266      *
267      * @param template the template to use for processing.
268      * @param metafacade the metafacade instance (the property value is retrieved from this).
269      * @param templateContext the template context containing the instance to pass to the template.
270      * @param modelElement the model element from which we retrieve the corresponding types and then
271      *        properties to determine if any properties have been mapped for template processing.
272      * @return true if any property templates have been evaluated (false otherwise).
273      */
274     private boolean processPropertyTemplates(
275         final Template template,
276         final Object metafacade,
277         final Map<String, Object> templateContext,
278         final ModelElement modelElement)
279     {
280         boolean propertyTemplatesEvaluated = false;
281         for (final Type type : modelElement.getTypes())
282         {
283             for (final Type.Property property : type.getProperties())
284             {
285                 final String variable = property.getVariable();
286                 propertyTemplatesEvaluated = StringUtils.isNotBlank(variable);
287                 if (propertyTemplatesEvaluated)
288                 {
289                     final Object value = Introspector.instance().getProperty(
290                             metafacade,
291                             property.getName());
292                     if (value instanceof Collection)
293                     {
294                         for (Object entry : (Collection) value)
295                         {
296                             templateContext.put(
297                                 variable,
298                                 entry);
299                             this.processWithTemplate(
300                                 template,
301                                 templateContext,
302                                 null,
303                                 null);
304                         }
305                     }
306                     else
307                     {
308                         templateContext.put(
309                             variable,
310                             value);
311                         this.processWithTemplate(
312                             template,
313                             templateContext,
314                             null,
315                             null);
316                     }
317                 }
318             }
319         }
320         return propertyTemplatesEvaluated;
321     }
322 
323     /**
324      * Processes the <code>template</code> without metafacades. This is useful if you need to generate something that
325      * is part of your cartridge, however you only need to use a property passed in from a namespace or a template
326      * object defined in your cartridge descriptor.
327      *
328      * @param template the template to process.
329      */
330     protected void processTemplateWithoutMetafacades(final Template template)
331     {
332         ExceptionUtils.checkNull(
333             "template",
334             template);
335         final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
336         this.processWithTemplate(
337             template,
338             templateContext,
339             null,
340             null);
341     }
342 
343     /**
344      * <p>
345      * Perform processing with the <code>template</code>.
346      * </p>
347      *
348      * @param template the Template containing the template path to process.
349      * @param templateContext the context to which variables are added and made
350      *        available to the template engine for processing. This will contain
351      *        any model elements being made available to the template(s) as well
352      *        as properties/template objects.
353      * @param metafacadeName the name of the model element (if we are
354      *        processing a single model element, otherwise this will be
355      *        ignored).
356      * @param metafacadePackage the name of the package (if we are processing
357      *        a single model element, otherwise this will be ignored).
358      */
359     private void processWithTemplate(
360         final Template template,
361         final Map<String, Object> templateContext,
362         final String metafacadeName,
363         final String metafacadePackage)
364     {
365         ExceptionUtils.checkNull(
366             "template",
367             template);
368         ExceptionUtils.checkNull(
369             "templateContext",
370             templateContext);
371 
372         File outputFile = null;
373         try
374         {
375             // - populate the template context with cartridge descriptor
376             //   properties and template objects
377             this.populateTemplateContext(templateContext);
378 
379             final StringWriter output = new StringWriter();
380 
381             // - process the template with the set TemplateEngine
382             this.getTemplateEngine().processTemplate(
383                 template.getPath(),
384                 templateContext,
385                 output);
386 
387             // - if we have an outputCondition defined make sure it evaluates to true before continuing
388             if (this.isValidOutputCondition(template.getOutputCondition(), templateContext))
389             {
390                 // - get the location and at the same time evaluate the outlet as a template engine variable (in case
391                 //   its defined as that).
392                 final String location =
393                     Namespaces.instance().getPropertyValue(
394                         this.getNamespace(),
395                         this.getTemplateEngine().getEvaluatedExpression(
396                             template.getOutlet(),
397                             templateContext));
398                 if (location == null)
399                 {
400                     LOGGER.warn("Template outlet location '" + template.getOutlet() + "' is not defined in namespace " + this.getNamespace());
401                 }
402                 else
403                 {
404                     outputFile =
405                         template.getOutputLocation(
406                             metafacadeName,
407                             metafacadePackage,
408                             new File(location),
409                             this.getTemplateEngine().getEvaluatedExpression(
410                                 template.getOutputPattern(),
411                                 templateContext));
412                     if (outputFile == null)
413                     {
414                         LOGGER.warn("Template outputFile is null for location " + location);
415                     }
416                     else
417                     {
418                         // - only write files that do NOT exist, and
419                         //   those that have overwrite set to 'true'
420                         if (!outputFile.exists() || template.isOverwrite())
421                         {
422                             String outputString = output.toString();
423                             AndroMDALogger.setSuffix(this.getNamespace());
424 
425                             // - check to see if generateEmptyFiles is true and if
426                             //   outString is not blank
427                             if (StringUtils.isNotBlank(outputString) ||
428                                 template.isGenerateEmptyFiles())
429                             {
430                                 for (PostProcessor postProcessor : getTemplatePostProcessor())
431                                 {
432                                     if(postProcessor.acceptFile(outputFile))
433                                     {
434                                         try
435                                         {
436                                             String lResult = postProcessor.postProcess(outputString, null);
437                                             if(StringUtils.isNotBlank(lResult))
438                                             {
439                                                 outputString = lResult;
440                                             }
441                                             else
442                                             {
443                                                 LOGGER.warn("Error PostProcessing " + outputFile.toURI());
444                                             }
445                                         }
446                                         catch (Exception exc)
447                                         {
448                                             LOGGER.warn("Error PostProcessing " + outputFile.toURI() + ": " + exc.getMessage());
449                                         }
450                                     }
451                                 }
452 
453                                 ResourceWriter.instance().writeStringToFile(
454                                     outputString,
455                                     outputFile,
456                                     this.getNamespace());
457                                 AndroMDALogger.info("Output: '" + outputFile.toURI() + '\'');
458                             }
459                             else
460                             {
461                                 if (this.getLogger().isDebugEnabled())
462                                 {
463                                     this.getLogger().debug("Empty Output: '" + outputFile.toURI() + "' --> not writing");
464                                 }
465                             }
466                             AndroMDALogger.reset();
467                         }
468                     }
469                 }
470             }
471         }
472         catch (final Throwable throwable)
473         {
474             if (FileUtils.deleteQuietly(outputFile))
475             {
476                 this.getLogger().info("Removed: '" + outputFile + '\'');
477             }
478             final String message =
479                 "Error processing template '" + template.getPath() + "' with template context '" + templateContext +
480                 "' using cartridge '" + this.getNamespace() + '\'';
481             LOGGER.error(message, throwable);
482             //throw new CartridgeException(message, throwable);
483         }
484     }
485 
486     /**
487      * Processes the given <code>resource</code>
488      *
489      * @param resource the resource to process.
490      */
491     protected void processResource(final Resource resource)
492     {
493         ExceptionUtils.checkNull(
494             "resource",
495             resource);
496 
497         URL resourceUrl = ResourceUtils.getResource(
498                 resource.getPath(),
499                 this.getMergeLocation());
500         if (resourceUrl == null)
501         {
502             // - if the resourceUrl is null, the path is probably a regular
503             //   outputCondition pattern so we'll see if we can match it against
504             //   the contents of the plugin and write any contents that do match
505             final List<String> contents = this.getContents();
506             if (contents != null)
507             {
508                 AndroMDALogger.setSuffix(this.getNamespace());
509                 for (final String content : contents)
510                 {
511                     if (StringUtils.isNotBlank(content))
512                     {
513                         if (PathMatcher.wildcardMatch(
514                                 content,
515                                 resource.getPath()))
516                         {
517                             resourceUrl = ResourceUtils.getResource(
518                                     content,
519                                     this.getMergeLocation());
520                             // - don't attempt to write the directories within the resource
521                             if (!resourceUrl.toString().endsWith(FORWARD_SLASH))
522                             {
523                                 this.writeResource(
524                                     resource,
525                                     resourceUrl);
526                             }
527                         }
528                     }
529                 }
530                 AndroMDALogger.reset();
531             }
532         }
533         else
534         {
535             this.writeResource(
536                 resource,
537                 resourceUrl);
538         }
539     }
540 
541     /**
542      * The forward slash constant.
543      */
544     private static final String FORWARD_SLASH = "/";
545 
546     private static final String PATH_PATTERN = "\\*.*";
547 
548     /**
549      * Writes the contents of <code>resourceUrl</code> to the outlet specified by <code>resource</code>.
550      *
551      * @param resource contains the outlet where the resource is written.
552      * @param resourceUrl the URL contents to write.
553      */
554     private void writeResource(
555         final Resource resource,
556         final URL resourceUrl)
557     {
558         File outputFile = null;
559         try
560         {
561             // - make sure we don't have any back slashes
562             final String resourceUri = ResourceUtils.normalizePath(resourceUrl.toString());
563             String uriSuffix = resource.getPath().replaceAll(PATH_PATTERN, "");
564             if (resourceUri.contains(uriSuffix))
565             {
566                 uriSuffix = resourceUri.substring(resourceUri.indexOf(uriSuffix) + uriSuffix.length(), resourceUri.length());
567             }
568             else
569             {
570                 uriSuffix =
571                     resourceUri.substring(
572                         resourceUri.lastIndexOf(FORWARD_SLASH),
573                         resourceUri.length());
574             }
575 
576             final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
577             this.populateTemplateContext(templateContext);
578 
579             // - if we have an outputCondition defined make sure it evaluates to true before continuing
580             if (this.isValidOutputCondition(resource.getOutputCondition(), templateContext))
581             {
582                 // - get the location and at the same time evaluate the outlet as a template engine variable (in case
583                 //   its defined as that).
584                 final String location =
585                     Namespaces.instance().getPropertyValue(
586                         this.getNamespace(),
587                         this.getTemplateEngine().getEvaluatedExpression(
588                             resource.getOutlet(),
589                             templateContext));
590 
591                 if (location != null)
592                 {
593                     outputFile =
594                         resource.getOutputLocation(
595                             new String[] {uriSuffix},
596                             new File(location),
597                             this.getTemplateEngine().getEvaluatedExpression(
598                                 resource.getOutputPattern(),
599                                 templateContext));
600 
601                     final boolean lastModifiedCheck = resource.isLastModifiedCheck();
602                     // - if we have the last modified check set, then make sure the last modified time is greater than the outputFile
603                     if (!lastModifiedCheck || (lastModifiedCheck && ResourceUtils.getLastModifiedTime(resourceUrl) > outputFile.lastModified()))
604                     {
605                         // - only write files that do NOT exist, and
606                         //   those that have overwrite set to 'true'
607                         if (!outputFile.exists() || resource.isOverwrite())
608                         {
609                             ResourceWriter.instance().writeUrlToFile(
610                                 resourceUrl,
611                                 outputFile.toString());
612                             AndroMDALogger.info("Output: '" + outputFile.toURI() + '\'');
613                         }
614                     }
615                 }
616             }
617         }
618         catch (final Throwable throwable)
619         {
620             if (outputFile != null)
621             {
622                 outputFile.delete();
623                 this.getLogger().info("Removed: '" + outputFile + '\'');
624             }
625             LOGGER.error("Error writing resource " + resource.getOutlet() + " to URL " + resourceUrl.toString(), throwable);
626             //throw new CartridgeException(throwable);
627         }
628     }
629 
630     /**
631      * Stores the loaded resources to be processed by this cartridge instance.
632      */
633     private final List<Resource> resources = new ArrayList<Resource>();
634 
635     /**
636      * Returns the list of templates configured in this cartridge.
637      *
638      * @return List the template list.
639      */
640     public List<Resource> getResources()
641     {
642         return this.resources;
643     }
644 
645     /**
646      * Adds a resource to the list of defined resources.
647      *
648      * @param resource the new resource to add
649      */
650     public void addResource(final Resource resource)
651     {
652         ExceptionUtils.checkNull(
653             "resource",
654             resource);
655         resource.setCartridge(this);
656         resources.add(resource);
657     }
658 
659     /**
660      * Populates the <code>templateContext</code> with the properties and template objects defined in the
661      * <code>plugin</code>'s descriptor. If the <code>templateContext</code> is null, a new Map instance will be created
662      * before populating the context.
663      *
664      * @param templateContext the context of the template to populate.
665      */
666     protected void populateTemplateContext(Map<String, Object> templateContext)
667     {
668         super.populateTemplateContext(templateContext);
669         templateContext.putAll(this.getEvaluatedConditions(templateContext));
670     }
671 
672     /**
673      * Stores the global conditions from cartridge.xml condition expressions
674      */
675     private final Map<String, String> conditions = new LinkedHashMap<String, String>();
676 
677     /**
678      * Stores the evaluated conditions from cartridge.xml condition expressions
679      */
680     private final Map<String, Boolean> evaluatedConditions = new LinkedHashMap<String, Boolean>();
681 
682     /**
683      * Stores the postProcessor from cartridge.xml configuration
684      */
685     private final Collection<PostProcessor> templatePostProcessor = new ArrayList<PostProcessor>();
686 
687     /**
688      * Adds the outputCondition given the <code>name</code> and <code>value</code>
689      * to the outputConditions map.
690      *
691      * @param name the name of the outputCondition.
692      * @param value the value of the outputCondition.
693      */
694     public void addCondition(final String name, final String value)
695     {
696         this.conditions.put(name, StringUtils.trimToEmpty(value));
697     }
698 
699     /**
700      * Gets the current outputConditions defined within this cartridge
701      * @return this.conditions
702      */
703     public Map<String, String> getConditions()
704     {
705         return this.conditions;
706     }
707 
708     /**
709      * Indicates whether or not the global outputConditions have been evaluated.
710      */
711     private boolean conditionsEvaluated = false;
712 
713     /**
714      * Evaluates all conditions and stores the results in the <code>evaluatedConditions</code>
715      * and returns that Map
716      *
717      * @param templateContext the template context used to evaluate the conditions.
718      * @return the map containing the evaluated conditions.
719      */
720     private Map<String, Boolean> getEvaluatedConditions(final Map<String, Object> templateContext)
721     {
722         if (!this.conditionsEvaluated)
723         {
724             for (final Map.Entry<String, String> entry : conditions.entrySet())
725             {
726                 final String value = entry.getValue();
727                 if (StringUtils.isNotBlank(value))
728                 {
729                     final String evaluationResult = this.getTemplateEngine().getEvaluatedExpression(
730                         value,
731                         templateContext);
732                     final String name = entry.getKey();
733                     this.evaluatedConditions.put(name, BooleanUtils.toBoolean(evaluationResult));
734                 }
735             }
736             this.conditionsEvaluated = true;
737         }
738         return this.evaluatedConditions;
739     }
740 
741     /**
742      * Gets the evaluated outputCondition result of a global outputCondition.
743      *
744      * @param outputCondition the outputCondition to evaluate.
745      * @param templateContext the current template context to pass the template engine if
746      *        evaluation has yet to occur.
747      * @return the evaluated outputCondition results.
748      */
749     private Boolean getGlobalConditionResult(final String outputCondition, final Map<String, Object> templateContext)
750     {
751         return this.getEvaluatedConditions(templateContext).get(outputCondition);
752     }
753 
754     /**
755      * Indicates whether or not the given <code>outputCondition</code> is a valid
756      * outputCondition, that is, whether or not it returns true.
757      *
758      * @param outputCondition the outputCondition to evaluate.
759      * @param templateContext the template context containing the variables to use.
760      * @return true/false
761      */
762     private boolean isValidOutputCondition(final String outputCondition, final Map<String, Object> templateContext)
763     {
764         boolean validOutputCondition = true;
765         if (StringUtils.isNotBlank(outputCondition))
766         {
767             Boolean result = this.getGlobalConditionResult(outputCondition, templateContext);
768             if (result == null)
769             {
770                 final String outputConditionResult = this.getTemplateEngine().getEvaluatedExpression(
771                     outputCondition,
772                     templateContext);
773                 result = outputConditionResult != null ? BooleanUtils.toBoolean(outputConditionResult.trim()) : null;
774             }
775             validOutputCondition = BooleanUtils.toBoolean(result);
776         }
777         return validOutputCondition;
778     }
779 
780     /**
781      * Gets the current postProcessors defined within this cartridge
782      * @return this.postProcessors
783      */
784     public Collection<PostProcessor> getTemplatePostProcessor()
785     {
786         return this.templatePostProcessor;
787     }
788 
789     /**
790      * Adds new postProcessor to the cartridge
791      * @param postProcessor new postProcessor
792      */
793     public void addTemplatePostProcessor(final TemplateObject postProcessor)
794     {
795         final PostProcessor lPostProcessor = (PostProcessor) postProcessor.getObject();
796         this.templatePostProcessor.add(lPostProcessor);
797     }
798 
799     /**
800      * Override to provide cartridge specific shutdown (
801      *
802      * @see org.andromda.core.common.Plugin#shutdown()
803      */
804     public void shutdown()
805     {
806         super.shutdown();
807         this.conditions.clear();
808         this.evaluatedConditions.clear();
809         this.templatePostProcessor.clear();
810     }
811 
812     /**
813      * @see Object#toString()
814      */
815     @Override
816     public String toString()
817     {
818         StringBuilder builder = new StringBuilder();
819         builder.append(super.toString());
820         //builder.append(" [resources=").append(this.resources); // StackOverflow
821         builder.append(" [conditions=").append(this.conditions);
822         builder.append(", evaluatedConditions=").append(this.evaluatedConditions);
823         builder.append(", templatePostProcessor=").append(this.templatePostProcessor);
824         builder.append(", conditionsEvaluated=").append(this.conditionsEvaluated).append("]");
825         return builder.toString();
826     }
827 }