1 package org.andromda.core.cartridge;
2
3 import java.io.File;
4 import java.io.StringWriter;
5
6 import java.net.URL;
7
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.Iterator;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedHashSet;
13 import java.util.List;
14 import java.util.Map;
15
16 import org.andromda.core.cartridge.template.ModelElement;
17 import org.andromda.core.cartridge.template.ModelElements;
18 import org.andromda.core.cartridge.template.Template;
19 import org.andromda.core.cartridge.template.Type;
20 import org.andromda.core.common.AndroMDALogger;
21 import org.andromda.core.common.BasePlugin;
22 import org.andromda.core.common.ExceptionUtils;
23 import org.andromda.core.common.Introspector;
24 import org.andromda.core.common.PathMatcher;
25 import org.andromda.core.common.ResourceUtils;
26 import org.andromda.core.common.ResourceWriter;
27 import org.andromda.core.configuration.Namespaces;
28 import org.andromda.core.metafacade.MetafacadeFactory;
29 import org.andromda.core.metafacade.ModelAccessFacade;
30 import org.apache.commons.lang.BooleanUtils;
31
32
33 /***
34 * The AndroMDA Cartridge implementation of the Plugin. Cartridge instances are configured from
35 * <code>META-INF/andromda/cartridge.xml</code> files discovered on the classpath.
36 *
37 * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
38 * @author Chad Brandon
39 */
40 public class Cartridge
41 extends BasePlugin
42 {
43 /***
44 * Processes all model elements with relevant stereotypes by retrieving the model elements from the model facade
45 * contained within the context.
46 *
47 * @param factory the metafacade factory (which is used to manage the lifecycle of metafacades).
48 */
49 public void processModelElements(final MetafacadeFactory factory)
50 {
51 ExceptionUtils.checkNull(
52 "factory",
53 factory);
54 final Collection resources = this.getResources();
55 if (resources != null && !resources.isEmpty())
56 {
57 for (final Iterator iterator = resources.iterator(); iterator.hasNext();)
58 {
59 final Resource resource = (Resource)iterator.next();
60 if (resource instanceof Template)
61 {
62 this.processTemplate(
63 factory,
64 (Template)resource);
65 }
66 else
67 {
68 this.processResource(resource);
69 }
70 }
71 }
72 }
73
74 /***
75 * Processes the given <code>template</code>.
76 *
77 * @param factory the metafacade factory instance.
78 * @param template the Template instance to process.
79 */
80 protected void processTemplate(
81 final MetafacadeFactory factory,
82 final Template template)
83 {
84 ExceptionUtils.checkNull(
85 "template",
86 template);
87 final ModelElements templateModelElements = template.getSupportedModeElements();
88
89
90 if (templateModelElements != null && !templateModelElements.isEmpty())
91 {
92 for (final Iterator iterator = templateModelElements.getModelElements().iterator(); iterator.hasNext();)
93 {
94 final ModelElement templateModelElement = (ModelElement)iterator.next();
95
96
97
98
99
100
101 if (templateModelElement.hasStereotype())
102 {
103 templateModelElement.setMetafacades(
104 factory.getMetafacadesByStereotype(templateModelElement.getStereotype()));
105 }
106 else if (templateModelElement.hasTypes())
107 {
108 templateModelElement.setMetafacades(factory.getAllMetafacades());
109 }
110 }
111 this.processTemplateWithMetafacades(
112 factory,
113 template);
114 }
115 else
116 {
117
118 this.processTemplateWithoutMetafacades(template);
119 }
120 }
121
122 /***
123 * Processes all <code>modelElements</code> for this template.
124 *
125 * @param factory the metafacade factory
126 * @param template the template to process
127 */
128 protected void processTemplateWithMetafacades(
129 final MetafacadeFactory factory,
130 final Template template)
131 {
132 ExceptionUtils.checkNull(
133 "template",
134 template);
135 final ModelElements modelElements = template.getSupportedModeElements();
136 if (modelElements != null && !modelElements.isEmpty())
137 {
138 try
139 {
140 final Collection allMetafacades = modelElements.getAllMetafacades();
141
142
143
144
145
146
147 if (template.isOutputToSingleFile() &&
148 (template.isOutputOnEmptyElements() || !allMetafacades.isEmpty()))
149 {
150 final Map templateContext = new LinkedHashMap();
151
152
153
154
155 final String modelElementsVariable = modelElements.getVariable();
156 if (modelElementsVariable != null && modelElementsVariable.trim().length() > 0)
157 {
158 templateContext.put(
159 modelElements.getVariable(),
160 allMetafacades);
161 }
162
163
164
165 for (final Iterator iterator = modelElements.getModelElements().iterator(); iterator.hasNext();)
166 {
167 final ModelElement modelElement = (ModelElement)iterator.next();
168 final String modelElementVariable = modelElement.getVariable();
169 if (modelElementVariable != null && modelElementVariable.trim().length() > 0)
170 {
171
172
173
174
175 Collection metafacades = (Collection)templateContext.get(modelElementVariable);
176 if (metafacades != null)
177 {
178 metafacades.addAll(modelElement.getMetafacades());
179 }
180 else
181 {
182 metafacades = modelElement.getMetafacades();
183 templateContext.put(
184 modelElementVariable,
185 new LinkedHashSet(metafacades));
186 }
187 }
188 }
189 this.processWithTemplate(
190 template,
191 templateContext,
192 null,
193 null);
194 }
195 else
196 {
197
198
199
200
201 for (final Iterator iterator = allMetafacades.iterator(); iterator.hasNext();)
202 {
203 final Map templateContext = new LinkedHashMap();
204 final Object metafacade = iterator.next();
205 final ModelAccessFacade model = factory.getModel();
206 for (final Iterator elements = modelElements.getModelElements().iterator(); elements.hasNext();)
207 {
208 final ModelElement modelElement = (ModelElement)elements.next();
209 String variable = modelElement.getVariable();
210
211
212
213 if (variable == null || variable.trim().length() == 0)
214 {
215 variable = modelElements.getVariable();
216 }
217
218
219
220 if (variable != null && variable.trim().length() > 0)
221 {
222 templateContext.put(
223 variable,
224 metafacade);
225 }
226
227
228
229 if (!this.processPropertyTemplates(
230 template,
231 metafacade,
232 templateContext,
233 modelElement))
234 {
235 this.processWithTemplate(
236 template,
237 templateContext,
238 model.getName(metafacade),
239 model.getPackageName(metafacade));
240 }
241 }
242 }
243 }
244 }
245 catch (final Throwable throwable)
246 {
247 throw new CartridgeException(throwable);
248 }
249 }
250 }
251
252 /***
253 * Determines if any property templates need to be processed (that is templates
254 * that are processed given related <em>properties</em> of a metafacade).
255 *
256 * @param template the template to use for processing.
257 * @param metafacade the metafacade instance (the property value is retrieved from this).
258 * @param templateContext the template context containing the instance to pass to the template.
259 * @param modelElement the model element from which we retrieve the corresponding types and then
260 * properties to determine if any properties have been mapped for template processing.
261 * @return true if any property templates have been evaluated (false othewise).
262 */
263 private boolean processPropertyTemplates(
264 final Template template,
265 final Object metafacade,
266 final Map templateContext,
267 final ModelElement modelElement)
268 {
269 boolean propertyTemplatesEvaluated = false;
270 for (final Iterator types = modelElement.getTypes().iterator(); types.hasNext();)
271 {
272 final Type type = (Type)types.next();
273 for (final Iterator properties = type.getProperties().iterator(); properties.hasNext();)
274 {
275 final Type.Property property = (Type.Property)properties.next();
276 final String variable = property.getVariable();
277 propertyTemplatesEvaluated = variable != null && variable.trim().length() > 0;
278 if (propertyTemplatesEvaluated)
279 {
280 final Object value = Introspector.instance().getProperty(
281 metafacade,
282 property.getName());
283 if (value instanceof Collection)
284 {
285 for (final Iterator values = ((Collection)value).iterator(); values.hasNext();)
286 {
287 templateContext.put(
288 variable,
289 values.next());
290 this.processWithTemplate(
291 template,
292 templateContext,
293 null,
294 null);
295 }
296 }
297 else
298 {
299 templateContext.put(
300 variable,
301 value);
302 this.processWithTemplate(
303 template,
304 templateContext,
305 null,
306 null);
307 }
308 }
309 }
310 }
311 return propertyTemplatesEvaluated;
312 }
313
314 /***
315 * Processes the <code>template</code> without metafacades. This is useful if you need to generate something that
316 * is part of your cartridge, however you only need to use a property passed in from a namespace or a template
317 * object defined in your cartridge descriptor.
318 *
319 * @param template the template to process.
320 */
321 protected void processTemplateWithoutMetafacades(final Template template)
322 {
323 ExceptionUtils.checkNull(
324 "template",
325 template);
326 final Map templateContext = new LinkedHashMap();
327 this.processWithTemplate(
328 template,
329 templateContext,
330 null,
331 null);
332 }
333
334 /***
335 * <p>
336 * Perform processing with the <code>template</code>.
337 * </p>
338 *
339 * @param template the Template containing the template path to process.
340 * @param templateContext the context to which variables are added and made
341 * available to the template engine for processing. This will contain
342 * any model elements being made avaiable to the template(s) as well
343 * as properties/template objects.
344 * @param metafacadeName the name of the model element (if we are
345 * processing a single model element, otherwise this will be
346 * ignored).
347 * @param metafacadePackage the name of the package (if we are processing
348 * a single model element, otherwise this will be ignored).
349 */
350 private void processWithTemplate(
351 final Template template,
352 final Map templateContext,
353 final String metafacadeName,
354 final String metafacadePackage)
355 {
356 ExceptionUtils.checkNull(
357 "template",
358 template);
359 ExceptionUtils.checkNull(
360 "templateContext",
361 templateContext);
362
363 File outputFile = null;
364 try
365 {
366
367
368 this.populateTemplateContext(templateContext);
369
370 final StringWriter output = new StringWriter();
371
372
373 this.getTemplateEngine().processTemplate(
374 template.getPath(),
375 templateContext,
376 output);
377
378
379 if (this.isValidOutputCondition(template.getOutputCondition(), templateContext))
380 {
381
382
383 final String location =
384 Namespaces.instance().getPropertyValue(
385 this.getNamespace(),
386 this.getTemplateEngine().getEvaluatedExpression(
387 template.getOutlet(),
388 templateContext));
389
390 if (location != null)
391 {
392 outputFile =
393 template.getOutputLocation(
394 metafacadeName,
395 metafacadePackage,
396 new File(location),
397 this.getTemplateEngine().getEvaluatedExpression(
398 template.getOutputPattern(),
399 templateContext));
400 if (outputFile != null)
401 {
402
403
404 if (!outputFile.exists() || template.isOverwrite())
405 {
406 final String outputString = output.toString();
407 AndroMDALogger.setSuffix(this.getNamespace());
408
409
410
411 if ((outputString != null && outputString.trim().length() > 0) ||
412 template.isGenerateEmptyFiles())
413 {
414 ResourceWriter.instance().writeStringToFile(
415 outputString,
416 outputFile,
417 this.getNamespace());
418 AndroMDALogger.info("Output: '" + outputFile.toURI() + "'");
419 }
420 else
421 {
422 if (this.getLogger().isDebugEnabled())
423 {
424 this.getLogger().debug("Empty Output: '" + outputFile.toURI() + "' --> not writing");
425 }
426 }
427 AndroMDALogger.reset();
428 }
429 }
430 }
431 }
432 }
433 catch (final Throwable throwable)
434 {
435 if (outputFile != null)
436 {
437 outputFile.delete();
438 this.getLogger().info("Removed: '" + outputFile + "'");
439 }
440 final String message =
441 "Error processing template '" + template.getPath() + "' with template context '" + templateContext +
442 "' using cartridge '" + this.getNamespace() + "'";
443 throw new CartridgeException(message, throwable);
444 }
445 }
446
447 /***
448 * Processes the given <code>resource</code>
449 *
450 * @param resource the resource to process.
451 */
452 protected void processResource(final Resource resource)
453 {
454 ExceptionUtils.checkNull(
455 "resource",
456 resource);
457
458 URL resourceUrl = ResourceUtils.getResource(
459 resource.getPath(),
460 this.getMergeLocation());
461 if (resourceUrl == null)
462 {
463
464
465
466 final List contents = this.getContents();
467 if (contents != null)
468 {
469 AndroMDALogger.setSuffix(this.getNamespace());
470 for (final Iterator iterator = contents.iterator(); iterator.hasNext();)
471 {
472 final String content = (String)iterator.next();
473 if (content != null && content.trim().length() > 0)
474 {
475 if (PathMatcher.wildcardMatch(
476 content,
477 resource.getPath()))
478 {
479 resourceUrl = ResourceUtils.getResource(
480 content,
481 this.getMergeLocation());
482
483 if (!resourceUrl.toString().endsWith(FORWARD_SLASH))
484 {
485 this.writeResource(
486 resource,
487 resourceUrl);
488 }
489 }
490 }
491 }
492 AndroMDALogger.reset();
493 }
494 }
495 else
496 {
497 this.writeResource(
498 resource,
499 resourceUrl);
500 }
501 }
502
503 /***
504 * The forward slash constant.
505 */
506 private static final String FORWARD_SLASH = "/";
507
508 /***
509 * Writes the contents of <code>resourceUrl</code> to the outlet specified by <code>resource</code>.
510 *
511 * @param resource contains the outlet where the resource is written.
512 * @param resourceUrl the URL contents to write.
513 */
514 private void writeResource(
515 final Resource resource,
516 final URL resourceUrl)
517 {
518 File outputFile = null;
519 try
520 {
521
522 final String resourceUri = ResourceUtils.normalizePath(resourceUrl.toString());
523 final String uriSuffix =
524 resourceUri.substring(
525 resourceUri.lastIndexOf(FORWARD_SLASH),
526 resourceUri.length());
527
528 final Map templateContext = new LinkedHashMap();
529 this.populateTemplateContext(templateContext);
530
531
532 if (this.isValidOutputCondition(resource.getOutputCondition(), templateContext))
533 {
534
535
536 final String location =
537 Namespaces.instance().getPropertyValue(
538 this.getNamespace(),
539 this.getTemplateEngine().getEvaluatedExpression(
540 resource.getOutlet(),
541 templateContext));
542
543 if (location != null)
544 {
545 outputFile =
546 resource.getOutputLocation(
547 new String[] {uriSuffix},
548 new File(location),
549 this.getTemplateEngine().getEvaluatedExpression(
550 resource.getOutputPattern(),
551 templateContext));
552
553 final boolean lastModifiedCheck = resource.isLastModifiedCheck();
554
555 if (!lastModifiedCheck || (lastModifiedCheck && ResourceUtils.getLastModifiedTime(resourceUrl) > outputFile.lastModified()))
556 {
557
558
559 if (!outputFile.exists() || resource.isOverwrite())
560 {
561 ResourceWriter.instance().writeUrlToFile(
562 resourceUrl,
563 outputFile.toString());
564 AndroMDALogger.info("Output: '" + outputFile.toURI() + "'");
565 }
566 }
567 }
568 }
569 }
570 catch (final Throwable throwable)
571 {
572 if (outputFile != null)
573 {
574 outputFile.delete();
575 this.getLogger().info("Removed: '" + outputFile + "'");
576 }
577 throw new CartridgeException(throwable);
578 }
579 }
580
581 /***
582 * Stores the loaded resources to be processed by this cartridge intance.
583 */
584 private final List resources = new ArrayList();
585
586 /***
587 * Returns the list of templates configured in this cartridge.
588 *
589 * @return List the template list.
590 */
591 public List getResources()
592 {
593 return this.resources;
594 }
595
596 /***
597 * Adds a resource to the list of defined resources.
598 *
599 * @param resource the new resource to add
600 */
601 public void addResource(final Resource resource)
602 {
603 ExceptionUtils.checkNull(
604 "resource",
605 resource);
606 resource.setCartridge(this);
607 resources.add(resource);
608 }
609
610 /***
611 * Populates the <code>templateContext</code> with the properties and template objects defined in the
612 * <code>plugin</code>'s descriptor. If the <code>templateContext</code> is null, a new Map instance will be created
613 * before populating the context.
614 *
615 * @param templateContext the context of the template to populate.
616 */
617 protected void populateTemplateContext(Map templateContext)
618 {
619 super.populateTemplateContext(templateContext);
620 templateContext.putAll(this.getEvaluatedConditions(templateContext));
621 }
622
623 /***
624 * Stores the global conditions.
625 */
626 private final Map conditions = new LinkedHashMap();
627
628 /***
629 * Adds the outputCondition given the <code>name</code> and <code>value</code>
630 * to the outputConditions map.
631 *
632 * @param name the name of the outputCondition.
633 * @param value the value of the outputCondition.
634 */
635 public void addCondition(final String name, final String value)
636 {
637 this.conditions.put(name, value != null ? value.trim() : "");
638 }
639
640 /***
641 * Gets the current outputConditions defined within this cartridge
642 */
643 public Map getConditions()
644 {
645 return this.conditions;
646 }
647
648 /***
649 * Indicates whether or not the global outputConditions have been evaluated.
650 */
651 private boolean conditionsEvaluated = false;
652
653 /***
654 * Evaluates all conditions and stores the results in the <code>conditions</code>
655 * and returns that Map
656 *
657 * @param templateContext the template context used to evaluate the conditions.
658 * @param the map containing the evaluated conditions.
659 */
660 private Map getEvaluatedConditions(final Map templateContext)
661 {
662 if (!this.conditionsEvaluated)
663 {
664 for (final Iterator iterator = this.conditions.keySet().iterator(); iterator.hasNext();)
665 {
666 final String name = (String)iterator.next();
667 final String value = (String)this.conditions.get(name);
668 String evaluationResult = value != null ? value.trim() : null;
669 if (evaluationResult != null && evaluationResult.trim().length() > 0)
670 {
671 evaluationResult = this.getTemplateEngine().getEvaluatedExpression(
672 evaluationResult,
673 templateContext);
674 }
675 this.conditions.put(name, Boolean.valueOf(BooleanUtils.toBoolean(evaluationResult)));
676 }
677 this.conditionsEvaluated = true;
678 }
679 return this.conditions;
680 }
681
682 /***
683 * Gets the evaluted outputCondition result of a global outputCondition.
684 *
685 * @param templateContext the current template context to pass the template engine if
686 * evaluation has yet to occurr.
687 * @return the evaluated outputCondition results.
688 */
689 private Boolean getGlobalConditionResult(final String outputCondition, final Map templateContext)
690 {
691 return (Boolean)this.getEvaluatedConditions(templateContext).get(outputCondition);
692 }
693
694 /***
695 * Indicates whether or not the given <code>outputCondition</code> is a valid
696 * outputCondition, that is, whether or not it return true.
697 *
698 * @param outputCondition the outputCondition to evaluate.
699 * @param templateContext the template context containing the variables to use.
700 * @return true/false
701 */
702 private boolean isValidOutputCondition(final String outputCondition, final Map templateContext)
703 {
704 boolean validOutputCondition = true;
705 if (outputCondition != null && outputCondition.trim().length() > 0)
706 {
707 Boolean result = this.getGlobalConditionResult(outputCondition, templateContext);
708 if (result == null)
709 {
710 final String outputConditionResult = this.getTemplateEngine().getEvaluatedExpression(
711 outputCondition,
712 templateContext);
713 result = Boolean.valueOf(BooleanUtils.toBoolean(outputConditionResult != null ? outputConditionResult.trim() : null));
714 }
715 validOutputCondition = result != null ? result.booleanValue() : false;
716 }
717 return validOutputCondition;
718 }
719
720 /***
721 * Override to provide cartridge specific shutdown (
722 *
723 * @see org.andromda.core.common.Plugin#shutdown()
724 */
725 public void shutdown()
726 {
727 super.shutdown();
728 this.conditions.clear();
729 }
730 }