View Javadoc

1   package org.andromda.core.engine;
2   
3   import java.text.Collator;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Collection;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.Iterator;
10  import java.util.LinkedHashMap;
11  import java.util.List;
12  import java.util.Map;
13  
14  import org.andromda.core.ModelValidationException;
15  import org.andromda.core.cartridge.Cartridge;
16  import org.andromda.core.common.AndroMDALogger;
17  import org.andromda.core.common.BuildInformation;
18  import org.andromda.core.common.ComponentContainer;
19  import org.andromda.core.common.ExceptionRecorder;
20  import org.andromda.core.common.Introspector;
21  import org.andromda.core.common.ResourceWriter;
22  import org.andromda.core.common.XmlObjectFactory;
23  import org.andromda.core.configuration.Configuration;
24  import org.andromda.core.configuration.Filters;
25  import org.andromda.core.configuration.Model;
26  import org.andromda.core.configuration.Namespace;
27  import org.andromda.core.configuration.Namespaces;
28  import org.andromda.core.configuration.Property;
29  import org.andromda.core.metafacade.MetafacadeFactory;
30  import org.andromda.core.metafacade.ModelAccessFacade;
31  import org.andromda.core.metafacade.ModelValidationMessage;
32  import org.andromda.core.namespace.NamespaceComponents;
33  import org.andromda.core.repository.Repositories;
34  import org.apache.commons.collections.comparators.ComparatorChain;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.log4j.Logger;
37  
38  
39  /***
40   * <p>
41   * Handles the processing of models. Facilitates Model Driven
42   * Architecture by enabling the generation of source code, configuration files, and other such artifacts from a single
43   * or multiple models. </p>
44   *
45   * @author Chad Brandon
46   */
47  public class ModelProcessor
48  {
49      /***
50       * The logger instance.
51       */
52      private static final Logger logger = Logger.getLogger(ModelProcessor.class);
53  
54      /***
55       * Creates a new instance the ModelProcessor.
56       *
57       * @return the shared ModelProcessor instance.
58       */
59      public static ModelProcessor newInstance()
60      {
61          return new ModelProcessor();
62      }
63  
64      private ModelProcessor()
65      {
66          // - do not allow instantiation
67      }
68  
69      /***
70       * Re-configures this model processor from the given <code>configuration</code>
71       * instance (if different from that of the one passed in during the call to
72       * {@link #initialize(Configuration)}), and runs the model processor.
73       *
74       * @param configuration the configuration from which to configure this model
75       *        processor instance.
76       * @return any model validation messages collected during model processing (if
77       *         model validation is enabled).
78       */
79      public ModelValidationMessage[] process(final Configuration configuration)
80      {
81          this.configure(configuration);
82          final List messages = this.process(configuration.getRepositories());
83          return messages != null ? (ModelValidationMessage[])messages.toArray(new ModelValidationMessage[0])
84                                  : new ModelValidationMessage[0];
85      }
86  
87      /***
88       * Configures (or re-configures) the model processor if configuration
89       * is required (the configuration has changed since the previous, or has
90       * yet to be used).
91       *
92       * @param configuration the AndroMDA configuration instance.
93       */
94      private void configure(final Configuration configuration)
95      {
96          if (this.requiresConfiguration(configuration))
97          {
98              configuration.initialize();
99              this.reset();
100             final Property[] properties = configuration.getProperties();
101             final int propertyNumber = properties.length;
102             final Introspector introspector = Introspector.instance();
103             for (int ctr = 0; ctr < propertyNumber; ctr++)
104             {
105                 final Property property = properties[ctr];
106                 try
107                 {
108                     introspector.setProperty(
109                         this,
110                         property.getName(),
111                         property.getValue());
112                 }
113                 catch (final Throwable throwable)
114                 {
115                     AndroMDALogger.warn(
116                         "Could not set model processor property '" + property.getName() + "' with a value of '" +
117                         property.getValue() + "'");
118                 }
119             }
120             this.currentConfiguration = configuration;
121         }
122     }
123 
124     /***
125      * Processes all models contained within the <code>repositories</code>
126      * with the discovered cartridges.
127      *
128      * @return any model validation messages that may have been collected during model loading/validation.
129      */
130     private List process(final org.andromda.core.configuration.Repository[] repositories)
131     {
132         List messages = null;
133         final long startTime = System.currentTimeMillis();
134         final int repositoryNumber = repositories.length;
135         for (int ctr = 0; ctr < repositoryNumber; ctr++)
136         {
137             final org.andromda.core.configuration.Repository repository = repositories[ctr];
138             if (repository != null)
139             {
140                 final String repositoryName = repository.getName();
141 
142                 // - filter out any invalid models (ones that don't have any uris defined)
143                 final Model[] models = this.filterInvalidModels(repository.getModels());
144                 if (models.length > 0)
145                 {
146                     messages = this.processModels(
147                             repositoryName,
148                             models);
149                     AndroMDALogger.info(
150                         "completed model processing --> TIME: " + this.getDurationInSeconds(startTime) +
151                         "[s], RESOURCES WRITTEN: " + ResourceWriter.instance().getWrittenCount());
152                 }
153                 else
154                 {
155                     AndroMDALogger.warn("No model(s) found to process for repository '" + repositoryName + "'");
156                 }
157             }
158         }
159         return messages == null ? Collections.EMPTY_LIST : messages;
160     }
161 
162     /***
163      * The shared metafacade factory instance.
164      */
165     private final MetafacadeFactory factory = MetafacadeFactory.getInstance();
166 
167     /***
168      * The shared namespaces instance.
169      */
170     private final Namespaces namespaces = Namespaces.instance();
171 
172     /***
173      * The shared repositories instance.
174      */
175     private final Repositories repositories = Repositories.instance();
176 
177     /***
178      * Processes multiple <code>models</code>.
179      *
180      * @param repositoryName the name of the repository that loads/reads the model.
181      * @param models the Model(s) to process.
182      * @return any model validation messages that may have been collected during validation/loading of
183      *         the <code>models</code>.
184      */
185     private List processModels(
186         final String repositoryName,
187         final Model[] models)
188     {
189         List messages = null;
190         String cartridgeName = null;
191         try
192         {
193             boolean lastModifiedCheck = true;
194             long lastModified = 0;
195             final ResourceWriter writer = ResourceWriter.instance();
196 
197             // - get the time from the model that has the latest modified time
198             for (int ctr = 0; ctr < models.length; ctr++)
199             {
200                 final Model model = models[ctr];
201                 writer.resetHistory(model.getUris()[0]);
202                 lastModifiedCheck = model.isLastModifiedCheck() && lastModifiedCheck;
203 
204                 // - we go off the model that was most recently modified.
205                 if (model.getLastModified() > lastModified)
206                 {
207                     lastModified = model.getLastModified();
208                 }
209             }
210 
211             if (!lastModifiedCheck || writer.isHistoryBefore(lastModified))
212             {
213                 final Collection cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
214                 if (cartridges.isEmpty())
215                 {
216                     AndroMDALogger.warn("WARNING! No cartridges found, check your classpath!");
217                 }
218                 
219                 final Map cartridgesByNamespace = this.loadCartridgesByNamespace(cartridges);
220                 
221                 // - we want to process by namespace so that the order within the configuration is kept
222                 final Collection namespaces = this.namespaces.getNamespaces();
223 
224                 // - pre-load the models
225                 messages = this.loadIfNecessary(models);
226                 for (final Iterator iterator = namespaces.iterator(); iterator.hasNext();)
227                 {
228                     final Namespace namespace = (Namespace)iterator.next();
229                     final Cartridge cartridge = (Cartridge)cartridgesByNamespace.get(namespace.getName());
230                     if (cartridge != null)
231                     {
232                         cartridgeName = cartridge.getNamespace();
233                         if (this.shouldProcess(cartridgeName))
234                         {
235                             // - set the active namespace on the shared factory and profile instances
236                             this.factory.setNamespace(cartridgeName);
237                             cartridge.initialize();
238     
239                             // - process each model with the cartridge
240                             for (int ctr = 0; ctr < models.length; ctr++)
241                             {
242                                 final Model model = models[ctr];
243     
244                                 // - set the namespace on the metafacades instance so we know the 
245                                 //   correct facades to use
246                                 this.factory.setModel(
247                                     this.repositories.getImplementation(repositoryName).getModel(),
248                                     model.getType());
249                                 cartridge.processModelElements(this.factory);
250                                 writer.writeHistory();
251                             }
252                             cartridge.shutdown();
253                         }
254                     }
255                 }
256             }
257         }
258         catch (final ModelValidationException exception)
259         {
260             // - we don't want to record model validation exceptions
261             throw exception;
262         }
263         catch (final Throwable throwable)
264         {
265             final String messsage =
266                 "Error performing ModelProcessor.process with model(s) --> '" + StringUtils.join(
267                     models,
268                     ",") + "'";
269             logger.error(messsage);
270             ExceptionRecorder.instance().record(
271                 messsage,
272                 throwable,
273                 cartridgeName);
274             throw new ModelProcessorException(messsage, throwable);
275         }
276         return messages == null ? Collections.EMPTY_LIST : messages;
277     }
278     
279     /***
280      * Loads the given list of <code>cartridges</code> into a map keyed by namespace.
281      * 
282      * @param cartridges the cartridges loaded.
283      * @return the loaded cartridge map.
284      */
285     private Map loadCartridgesByNamespace(final Collection cartridges)
286     {
287         final Map cartridgesByNamespace = new LinkedHashMap();
288         for (final Iterator iterator = cartridges.iterator(); iterator.hasNext();)
289         {
290             final Cartridge cartridge = (Cartridge)iterator.next();
291             cartridgesByNamespace.put(cartridge.getNamespace(), cartridge);
292         }
293         return cartridgesByNamespace;
294     }
295 
296     /***
297      * Initializes this model processor instance with the given
298      * configuration.  This configuration information is overridden (if changed)
299      * when calling {@link #process(Configuration)}
300      *
301      * @param configuration the configuration instance by which to initialize this
302      *        model processor instance.
303      */
304     public void initialize(final Configuration configuration)
305     {
306         final long startTime = System.currentTimeMillis();
307 
308         // - first, print the AndroMDA header
309         this.printConsoleHeader();
310 
311         // - second, configure this model processor 
312         // - the ordering of this step is important: it needs to occur
313         //   before everything else in the framework is initialized so that 
314         //   we have all configuration information available (such as the
315         //   namespace properties)
316         this.configure(configuration);
317 
318         // - the logger configuration may have changed - re-init the logger.
319         AndroMDALogger.initialize();
320 
321         // - discover all namespace components
322         NamespaceComponents.instance().discover();
323 
324         // - find and initialize any repositories
325         repositories.initialize();
326 
327         // - finally initialize the metafacade factory
328         this.factory.initialize();
329         this.printWorkCompleteMessage(
330             "core initialization",
331             startTime);
332     }
333 
334     /***
335      * Loads the model into the repository only when necessary (the model has a timestamp
336      * later than the last timestamp of the loaded model).
337      *
338      * @param model the model to be loaded.
339      */
340     protected final List loadModelIfNecessary(final Model model)
341     {
342         final List validationMessages = new ArrayList();
343         final long startTime = System.currentTimeMillis();
344         if (this.repositories.loadModel(model))
345         {
346             this.printWorkCompleteMessage(
347                 "loading",
348                 startTime);
349 
350             // - validate the model since loading has successfully occurred
351             final org.andromda.core.configuration.Repository repository = model.getRepository();
352             final String repositoryName = repository != null ? repository.getName() : null;
353             validationMessages.addAll(this.validateModel(
354                     repositoryName,
355                     model));
356         }
357         return validationMessages;
358     }
359 
360     /***
361      * Validates the entire model with each cartridge namespace,
362      * and returns any validation messages that occurred during validation
363      * (also logs any validation failures).
364      *
365      * @param repositoryName the name of the repository storing the model to validate.
366      * @return any {@link ModelValidationMessage} instances that may have been collected
367      *         during validation.
368      */
369     private List validateModel(
370         final String repositoryName,
371         final Model model)
372     {
373         final Filters constraints = model != null ? model.getConstraints() : null;
374         final List validationMessages = new ArrayList();
375         if (this.modelValidation)
376         {
377             final long startTime = System.currentTimeMillis();
378             AndroMDALogger.info("- validating model -");
379             final Collection cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
380             final ModelAccessFacade modelAccessFacade =
381                 this.repositories.getImplementation(repositoryName).getModel();
382 
383             // - clear out the factory's caches (such as any previous validation messages, etc.)
384             this.factory.clearCaches();
385             this.factory.setModel(
386                 modelAccessFacade,
387                 model.getType());
388             for (final Iterator iterator = cartridges.iterator(); iterator.hasNext();)
389             {
390                 final Cartridge cartridge = (Cartridge)iterator.next();
391                 final String cartridgeName = cartridge.getNamespace();
392                 if (this.shouldProcess(cartridgeName))
393                 {
394                     // - set the active namespace on the shared factory and profile instances
395                     this.factory.setNamespace(cartridgeName);
396                     this.factory.validateAllMetafacades();
397                 }
398             }
399             final List messages = this.factory.getValidationMessages();
400             this.filterAndSortValidationMessages(
401                 messages,
402                 constraints);
403             this.printValidationMessages(messages);
404             this.printWorkCompleteMessage(
405                 "validation",
406                 startTime);
407             if (messages != null && !messages.isEmpty())
408             {
409                 validationMessages.addAll(messages);
410             }
411         }
412         return validationMessages;
413     }
414 
415     /***
416      * Prints a work complete message using the type of <code>unitOfWork</code> and
417      * <code>startTime</code> as input.
418      * @param unitOfWork the type of unit of work that was completed
419      * @param startTime the time the unit of work was started.
420      */
421     private void printWorkCompleteMessage(
422         final String unitOfWork,
423         final long startTime)
424     {
425         AndroMDALogger.info("- " + unitOfWork + " complete: " + this.getDurationInSeconds(startTime) + "[s] -");
426     }
427 
428     /***
429      * Calcuates the duration in seconds between the
430      * given <code>startTime</code> and the current time.
431      * @param startTime the time to compare against.
432      * @return the duration of time in seconds.
433      */
434     private double getDurationInSeconds(final long startTime)
435     {
436         return ((System.currentTimeMillis() - startTime) / 1000.0);
437     }
438 
439     /***
440      * Prints any model validation errors stored within the <code>factory</code>.
441      */
442     private void printValidationMessages(final List messages)
443     {
444         // - log all error messages
445         if (messages != null && !messages.isEmpty())
446         {
447             final StringBuffer header =
448                 new StringBuffer("Model Validation Failed - " + messages.size() + " VALIDATION ERROR");
449             if (messages.size() > 1)
450             {
451                 header.append("S");
452             }
453             AndroMDALogger.error(header);
454             final Iterator iterator = messages.iterator();
455             for (int ctr = 1; iterator.hasNext(); ctr++)
456             {
457                 final ModelValidationMessage message = (ModelValidationMessage)iterator.next();
458                 AndroMDALogger.error(ctr + ") " + message);
459             }
460             AndroMDALogger.reset();
461             if (this.failOnValidationErrors)
462             {
463                 throw new ModelValidationException("Model validation failed!");
464             }
465         }
466     }
467 
468     /***
469      * The current configuration of this model processor.
470      */
471     private Configuration currentConfiguration = null;
472 
473     /***
474      * Determines whether or not this model processor needs to be reconfigured.
475      * This is based on whether or not the new configuration is different
476      * than the <code>currentConfiguration</code>.  We determine this checking
477      * if their contents are equal or not, if not equal this method will
478      * return true, otherwise false.
479      *
480      * @param configuration the configuration to compare to the lastConfiguration.
481      * @return true/false
482      */
483     private boolean requiresConfiguration(final Configuration configuration)
484     {
485         boolean requiresConfiguration =
486             this.currentConfiguration == null || this.currentConfiguration.getContents() == null ||
487             configuration.getContents() == null;
488         if (!requiresConfiguration)
489         {
490             requiresConfiguration = !this.currentConfiguration.getContents().equals(configuration.getContents());
491         }
492         return requiresConfiguration;
493     }
494 
495     /***
496      * Checks to see if <em>any</em> of the repositories contain models
497      * that need to be reloaded, and if so, re-loads them.
498      *
499      * @param repositories the repositories from which to load the model(s).
500      */
501     final List loadIfNecessary(final org.andromda.core.configuration.Repository[] repositories)
502     {
503         final List messages = new ArrayList();
504         if (repositories != null && repositories.length > 0)
505         {
506             final int repositoryNumber = repositories.length;
507             for (int repositoryCtr = 0; repositoryCtr < repositoryNumber; repositoryCtr++)
508             {
509                 final org.andromda.core.configuration.Repository repository = repositories[repositoryCtr];
510                 if (repository != null)
511                 {
512                     messages.addAll(this.loadIfNecessary(repository.getModels()));
513                 }
514             }
515         }
516         return messages;
517     }
518 
519     /***
520      * Checks to see if <em>any</em> of the models need to be reloaded, and if so, re-loads them.
521      *
522      * @param models that will be loaded (if necessary).
523      * @return any validation messages collected during loading.
524      */
525     private List loadIfNecessary(final Model[] models)
526     {
527         final List messages = new ArrayList();
528         if (models != null && models.length > 0)
529         {
530             final int modelNumber = models.length;
531             for (int modelCtr = 0; modelCtr < modelNumber; modelCtr++)
532             {
533                 messages.addAll(this.loadModelIfNecessary(models[modelCtr]));
534             }
535         }
536         return messages;
537     }
538 
539     /***
540      * Stores the current version of AndroMDA.
541      */
542     private static final String VERSION = BuildInformation.instance().getBuildVersion();
543 
544     /***
545      * Prints the console header.
546      */
547     protected void printConsoleHeader()
548     {
549         AndroMDALogger.info("");
550         AndroMDALogger.info("A n d r o M D A  -  " + VERSION);
551         AndroMDALogger.info("");
552     }
553 
554     /***
555      * Whether or not model validation should be performed.
556      */
557     private boolean modelValidation = true;
558 
559     /***
560      * Sets whether or not model validation should occur. This is useful for
561      * performance reasons (i.e. if you have a large model it can significatly descrease the amount of time it takes for
562      * AndroMDA to process a model). By default this is set to <code>true</code>.
563      *
564      * @param modelValidation true/false on whether model validation should be performed or not.
565      */
566     public void setModelValidation(final boolean modelValidation)
567     {
568         this.modelValidation = modelValidation;
569     }
570 
571     /***
572      * A flag indicating whether or not failure should occur
573      * when model validation errors are present.
574      */
575     private boolean failOnValidationErrors = true;
576 
577     /***
578      * Sets whether or not processing should fail when validation errors occur, default is <code>true</code>.
579      *
580      * @param failOnValidationErrors whether or not processing should fail if any validation errors are present.
581      */
582     public void setFailOnValidationErrors(final boolean failOnValidationErrors)
583     {
584         this.failOnValidationErrors = failOnValidationErrors;
585     }
586 
587     /***
588      * Stores the cartridge filter.
589      */
590     private List cartridgeFilter = null;
591 
592     /***
593      * Denotes whether or not the complement of filtered cartridges should be processed
594      */
595     private boolean negateCartridgeFilter = false;
596 
597     /***
598      * Indicates whether or not the <code>namespace</code> should be processed. This is determined in conjunction with
599      * {@link #setCartridgeFilter(String)}. If the <code>cartridgeFilter</code> is not defined and the namespace is
600      * present within the configuration, then this method will <strong>ALWAYS </strong> return true.
601      *
602      * @param namespace the name of the namespace to check whether or not it should be processed.
603      * @return true/false on whether or not it should be processed.
604      */
605     protected boolean shouldProcess(final String namespace)
606     {
607         boolean shouldProcess = this.namespaces.namespacePresent(namespace);
608         if (shouldProcess)
609         {
610             shouldProcess = this.cartridgeFilter == null || this.cartridgeFilter.isEmpty();
611             if (!shouldProcess)
612             {
613                 shouldProcess =
614                     this.negateCartridgeFilter ^ this.cartridgeFilter.contains(StringUtils.trimToEmpty(namespace));
615             }
616         }
617         return shouldProcess;
618     }
619 
620     /***
621      * The prefix used for cartridge filter negation.
622      */
623     private static final String CARTRIDGE_FILTER_NEGATOR = "~";
624 
625     /***
626      * <p/>
627      * Sets the current cartridge filter. This is a comma seperated list of namespaces (matching cartridges names) that
628      * should be processed. </p>
629      * <p/>
630      * If this filter is defined, then any cartridge names found in this list <strong>will be processed </strong>, while
631      * any other discovered cartridges <strong>will not be processed </strong>. </p>
632      *
633      * @param namespaces a comma seperated list of the cartridge namespaces to be processed.
634      */
635     public void setCartridgeFilter(String namespaces)
636     {
637         if (namespaces != null)
638         {
639             namespaces = StringUtils.deleteWhitespace(namespaces);
640             if (namespaces.startsWith(CARTRIDGE_FILTER_NEGATOR))
641             {
642                 this.negateCartridgeFilter = true;
643                 namespaces = namespaces.substring(1);
644             }
645             else
646             {
647                 this.negateCartridgeFilter = false;
648             }
649             if (StringUtils.isNotBlank(namespaces))
650             {
651                 this.cartridgeFilter = Arrays.asList(namespaces.split(","));
652             }
653         }
654     }
655 
656     /***
657      * Sets the encoding (UTF-8, ISO-8859-1, etc) for all output
658      * produced during model processing.
659      *
660      * @param outputEncoding the encoding.
661      */
662     public void setOutputEncoding(final String outputEncoding)
663     {
664         ResourceWriter.instance().setEncoding(outputEncoding);
665     }
666 
667     /***
668      * Sets <code>xmlValidation</code> to be true/false. This defines whether XML resources loaded by AndroMDA (such as
669      * plugin descriptors) should be validated. Sometimes underlying parsers don't support XML Schema validation and in
670      * that case, we want to be able to turn it off.
671      *
672      * @param xmlValidation true/false on whether we should validate XML resources used by AndroMDA
673      */
674     public void setXmlValidation(final boolean xmlValidation)
675     {
676         XmlObjectFactory.setDefaultValidating(xmlValidation);
677     }
678 
679     /***
680      * <p/>
681      * Sets the <code>loggingConfigurationUri</code> for AndroMDA. This is the URI to an external logging configuration
682      * file. This is useful when you want to override the default logging configuration of AndroMDA. </p>
683      * <p/>
684      * You can retrieve the default log4j.xml contained within the {@link org.andromda.core.common}package, customize
685      * it, and then specify the location of this logging file with this operation. </p>
686      *
687      * @param loggingConfigurationUri the URI to the external logging configuation file.
688      */
689     public void setLoggingConfigurationUri(final String loggingConfigurationUri)
690     {
691         AndroMDALogger.setLoggingConfigurationUri(loggingConfigurationUri);
692     }
693 
694     /***
695      * Filters out any <em>invalid</em> models. This means models that either are null within the specified
696      * <code>models</code> array or those that don't have URLs set.
697      *
698      * @param models the models to filter.
699      * @return the array of valid models
700      */
701     private Model[] filterInvalidModels(final Model[] models)
702     {
703         final Collection validModels = new ArrayList(Arrays.asList(models));
704         for (final Iterator iterator = validModels.iterator(); iterator.hasNext();)
705         {
706             final Model model = (Model)iterator.next();
707             if (!(model != null && model.getUris() != null && model.getUris().length > 0))
708             {
709                 iterator.remove();
710             }
711         }
712         return (Model[])validModels.toArray(new Model[0]);
713     }
714 
715     /***
716      * Shuts down the model processor (reclaims any
717      * resources).
718      */
719     public void shutdown()
720     {
721         // - shutdown the metafacade factory instance
722         this.factory.shutdown();
723 
724         // - shutdown the configuration namespaces instance
725         this.namespaces.clear();
726 
727         // - shutdown the container instance
728         ComponentContainer.instance().shutdown();
729 
730         // - shutdown the namespace components registry
731         NamespaceComponents.instance().shutdown();
732 
733         // - shutdown the introspector
734         Introspector.instance().shutdown();
735 
736         // - clear out any caches used by the configuration
737         Configuration.clearCaches();
738 
739         // - clear out any repositories
740         this.repositories.clear();
741     }
742 
743     /***
744      * Reinitializes the model processor's resources.
745      */
746     private void reset()
747     {
748         this.factory.reset();
749         this.cartridgeFilter = null;
750         this.setXmlValidation(true);
751         this.setOutputEncoding(null);
752         this.setModelValidation(true);
753         this.setLoggingConfigurationUri(null);
754         this.setFailOnValidationErrors(true);
755     }
756 
757     /***
758      * Filters out any messages that should not be applied according to the AndroMDA configuration's
759      * constraints and sorts the resulting <code>messages</code> first by type (i.e. the metafacade class)
760      * and then by the <code>name</code> of the model element to which the validation message applies.
761      *
762      * @param messages the collection of messages to sort.
763      * @param constraints any constraint filters to apply to the validation messages.
764      */
765     protected void filterAndSortValidationMessages(
766         final List messages,
767         final Filters constraints)
768     {
769         if (constraints != null)
770         {
771             // - perform constraint filtering (if any applies)
772             for (final Iterator iterator = messages.iterator(); iterator.hasNext();)
773             {
774                 final ModelValidationMessage message = (ModelValidationMessage)iterator.next();
775                 if (message != null && !constraints.isApply(message.getName()))
776                 {
777                     iterator.remove();
778                 }
779             }
780         }
781 
782         if (messages != null && !messages.isEmpty())
783         {
784             final ComparatorChain chain = new ComparatorChain();
785             chain.addComparator(new ValidationMessageTypeComparator());
786             chain.addComparator(new ValidationMessageNameComparator());
787             Collections.sort(
788                 messages,
789                 chain);
790         }
791     }
792 
793     /***
794      * Used to sort validation messages by <code>metafacadeClass</code>.
795      */
796     private final static class ValidationMessageTypeComparator
797         implements Comparator
798     {
799         private final Collator collator = Collator.getInstance();
800 
801         ValidationMessageTypeComparator()
802         {
803             collator.setStrength(Collator.PRIMARY);
804         }
805 
806         public int compare(
807             final Object objectA,
808             final Object objectB)
809         {
810             final ModelValidationMessage a = (ModelValidationMessage)objectA;
811             final ModelValidationMessage b = (ModelValidationMessage)objectB;
812             return collator.compare(
813                 a.getMetafacadeClass().getName(),
814                 b.getMetafacadeClass().getName());
815         }
816     }
817 
818     /***
819      * Used to sort validation messages by <code>modelElementName</code>.
820      */
821     private final static class ValidationMessageNameComparator
822         implements Comparator
823     {
824         private final Collator collator = Collator.getInstance();
825 
826         ValidationMessageNameComparator()
827         {
828             collator.setStrength(Collator.PRIMARY);
829         }
830 
831         public int compare(
832             final Object objectA,
833             final Object objectB)
834         {
835             final ModelValidationMessage a = (ModelValidationMessage)objectA;
836             final ModelValidationMessage b = (ModelValidationMessage)objectB;
837             return collator.compare(
838                 StringUtils.trimToEmpty(a.getMetafacadeName()),
839                 StringUtils.trimToEmpty(b.getMetafacadeName()));
840         }
841     }
842 }