001package org.andromda.core.engine; 002 003import java.text.Collator; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.Comparator; 009import java.util.LinkedHashMap; 010import java.util.List; 011import java.util.Map; 012import org.andromda.core.ModelValidationException; 013import org.andromda.core.cartridge.Cartridge; 014import org.andromda.core.common.AndroMDALogger; 015import org.andromda.core.common.BuildInformation; 016import org.andromda.core.common.ComponentContainer; 017import org.andromda.core.common.ExceptionRecorder; 018import org.andromda.core.common.Introspector; 019import org.andromda.core.common.ResourceWriter; 020import org.andromda.core.common.XmlObjectFactory; 021import org.andromda.core.configuration.Configuration; 022import org.andromda.core.configuration.Filters; 023import org.andromda.core.configuration.Model; 024import org.andromda.core.configuration.Namespace; 025import org.andromda.core.configuration.Namespaces; 026import org.andromda.core.configuration.Property; 027import org.andromda.core.configuration.Repository; 028import org.andromda.core.metafacade.MetafacadeFactory; 029import org.andromda.core.metafacade.ModelAccessFacade; 030import org.andromda.core.metafacade.ModelValidationMessage; 031import org.andromda.core.namespace.NamespaceComponents; 032import org.andromda.core.repository.Repositories; 033import org.apache.commons.collections.CollectionUtils; 034import org.apache.commons.collections.Predicate; 035import org.apache.commons.collections.comparators.ComparatorChain; 036import org.apache.commons.lang.StringUtils; 037import org.apache.log4j.Logger; 038 039/** 040 * <p> 041 * Handles the processing of models. Facilitates Model Driven 042 * Architecture by enabling the generation of source code, configuration files, and other such artifacts from a single 043 * or multiple models. </p> 044 * 045 * @author Chad Brandon 046 * @author Bob Fields 047 * @author Michail Plushnikov 048 */ 049public class ModelProcessor 050{ 051 /** 052 * The logger instance. 053 */ 054 private static final Logger logger = Logger.getLogger(ModelProcessor.class); 055 056 /** 057 * Creates a new instance the ModelProcessor. 058 * 059 * @return the shared ModelProcessor instance. 060 */ 061 public static ModelProcessor newInstance() 062 { 063 return new ModelProcessor(); 064 } 065 066 private ModelProcessor() 067 { 068 // - do not allow instantiation 069 } 070 071 /** 072 * Re-configures this model processor from the given <code>configuration</code> 073 * instance (if different from that of the one passed in during the call to 074 * {@link #initialize(Configuration)}), and runs the model processor. 075 * 076 * @param configuration the configuration from which to configure this model 077 * processor instance. 078 * @return any model validation messages collected during model processing (if 079 * model validation is enabled). 080 */ 081 public ModelValidationMessage[] process(final Configuration configuration) 082 { 083 this.configure(configuration); 084 final List<ModelValidationMessage> messages = this.process(configuration.getRepositories()); 085 return messages != null ? messages.toArray(new ModelValidationMessage[messages.size()]) 086 : new ModelValidationMessage[0]; 087 } 088 089 /** 090 * Configures (or re-configures) the model processor if configuration 091 * is required (the configuration has changed since the previous, or has 092 * yet to be used). 093 * 094 * @param configuration the AndroMDA configuration instance. 095 */ 096 private void configure(final Configuration configuration) 097 { 098 if (this.requiresConfiguration(configuration)) 099 { 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}