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