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
41
42
43
44
45
46
47
48
49 public class ModelProcessor
50 {
51
52
53
54 private static final Logger logger = Logger.getLogger(ModelProcessor.class);
55
56
57
58
59
60
61 public static ModelProcessor newInstance()
62 {
63 return new ModelProcessor();
64 }
65
66 private ModelProcessor()
67 {
68
69 }
70
71
72
73
74
75
76
77
78
79
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
91
92
93
94
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
126
127
128
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
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
166
167 private final MetafacadeFactory factory = MetafacadeFactory.getInstance();
168
169
170
171
172 private final Namespaces namespaces = Namespaces.instance();
173
174
175
176
177 private final Repositories repositories = Repositories.instance();
178
179
180
181
182
183 private boolean lastModifiedCheck = true;
184
185
186
187
188 private String historyDir = null;
189
190
191
192
193 public void setLastModifiedCheck(boolean lastModifiedCheck)
194 {
195 this.lastModifiedCheck = lastModifiedCheck;
196 }
197
198
199
200
201 public void setHistoryDir(String historyDir)
202 {
203 this.historyDir = historyDir;
204 }
205
206
207
208
209
210
211
212
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
223 long lastModified = 0;
224 final ResourceWriter writer = ResourceWriter.instance();
225 writer.setHistoryStorage(historyDir);
226
227
228 for (Model model : models)
229 {
230 writer.resetHistory(model.getUris()[0]);
231
232 this.lastModifiedCheck = model.isLastModifiedCheck() || this.lastModifiedCheck;
233
234
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
252 final Collection<Namespace> namespaces = this.namespaces.getNamespaces();
253
254
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
265 this.factory.setNamespace(cartridgeName);
266 cartridge.initialize();
267
268
269 for (Model model : models)
270 {
271 AndroMDALogger.info("Processing cartridge " + cartridge.getNamespace() + " on model " + model);
272
273
274
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
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
318
319
320
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
334
335
336
337
338
339
340 public void initialize(final Configuration configuration)
341 {
342 final long startTime = System.currentTimeMillis();
343
344
345 this.printConsoleHeader();
346
347
348
349
350
351
352 this.configure(configuration);
353
354
355 AndroMDALogger.initialize();
356
357
358 NamespaceComponents.instance().discover();
359
360
361 repositories.initialize();
362
363
364 this.factory.initialize();
365 this.printWorkCompleteMessage(
366 "core initialization",
367 startTime);
368 }
369
370
371
372
373
374
375
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
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
399
400
401
402
403
404
405
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
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
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
454
455
456
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
467
468
469
470
471 private double getDurationInSeconds(final long startTime)
472 {
473 return ((System.currentTimeMillis() - startTime) / 1000.0);
474 }
475
476
477
478
479 private void printValidationMessages(final List<ModelValidationMessage> messages)
480 {
481
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
507
508 private Configuration currentConfiguration = null;
509
510
511
512
513
514
515
516
517
518
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
534
535
536
537
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
557
558
559
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
576
577 private static final String VERSION = BuildInformation.instance().getBuildVersion();
578
579
580
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
591
592 private static boolean modelValidation = true;
593
594
595
596
597
598
599
600
601 public void setModelValidation(final boolean modelValidationIn)
602 {
603 ModelProcessor.modelValidation = modelValidationIn;
604 }
605
606
607
608
609
610
611
612
613 public static boolean getModelValidation()
614 {
615 return ModelProcessor.modelValidation;
616 }
617
618
619
620
621
622 private boolean failOnValidationErrors = true;
623
624
625
626
627
628
629 public void setFailOnValidationErrors(final boolean failOnValidationErrors)
630 {
631 this.failOnValidationErrors = failOnValidationErrors;
632 }
633
634
635
636
637 private List cartridgeFilter = null;
638
639
640
641
642 private boolean negateCartridgeFilter = false;
643
644
645
646
647
648
649
650
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
669
670 private static final String CARTRIDGE_FILTER_NEGATOR = "~";
671
672
673
674
675
676
677
678
679
680
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
705
706
707
708
709 public void setOutputEncoding(final String outputEncoding)
710 {
711 ResourceWriter.instance().setEncoding(outputEncoding);
712 }
713
714
715
716
717
718
719
720
721 public void setXmlValidation(final boolean xmlValidation)
722 {
723 XmlObjectFactory.setDefaultValidating(xmlValidation);
724 }
725
726
727
728
729
730
731
732
733
734
735
736 public void setLoggingConfigurationUri(final String loggingConfigurationUri)
737 {
738 AndroMDALogger.setLoggingConfigurationUri(loggingConfigurationUri);
739 }
740
741
742
743
744
745
746
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
763
764
765 public void shutdown()
766 {
767
768 this.factory.shutdown();
769
770
771 this.namespaces.clear();
772
773
774 ComponentContainer.instance().shutdown();
775
776
777 NamespaceComponents.instance().shutdown();
778
779
780 Introspector.instance().shutdown();
781
782
783 Configuration.clearCaches();
784
785
786 this.repositories.clear();
787 }
788
789
790
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
804
805
806
807
808
809
810 protected void filterAndSortValidationMessages(
811 final List<ModelValidationMessage> messages,
812 final Filters constraints)
813 {
814 if (constraints != null)
815 {
816
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
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
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 }