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