001: package org.andromda.core.cartridge;
002:
003: import java.io.File;
004: import java.io.StringWriter;
005:
006: import java.net.URL;
007:
008: import java.util.ArrayList;
009: import java.util.Collection;
010: import java.util.Iterator;
011: import java.util.LinkedHashMap;
012: import java.util.LinkedHashSet;
013: import java.util.List;
014: import java.util.Map;
015:
016: import org.andromda.core.cartridge.template.ModelElement;
017: import org.andromda.core.cartridge.template.ModelElements;
018: import org.andromda.core.cartridge.template.Template;
019: import org.andromda.core.cartridge.template.Type;
020: import org.andromda.core.common.AndroMDALogger;
021: import org.andromda.core.common.BasePlugin;
022: import org.andromda.core.common.ExceptionUtils;
023: import org.andromda.core.common.Introspector;
024: import org.andromda.core.common.PathMatcher;
025: import org.andromda.core.common.ResourceUtils;
026: import org.andromda.core.common.ResourceWriter;
027: import org.andromda.core.configuration.Namespaces;
028: import org.andromda.core.metafacade.MetafacadeFactory;
029: import org.andromda.core.metafacade.ModelAccessFacade;
030: import org.apache.commons.lang.BooleanUtils;
031:
032: /**
033: * The AndroMDA Cartridge implementation of the Plugin. Cartridge instances are configured from
034: * <code>META-INF/andromda/cartridge.xml</code> files discovered on the classpath.
035: *
036: * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
037: * @author Chad Brandon
038: */
039: public class Cartridge extends BasePlugin {
040: /**
041: * Processes all model elements with relevant stereotypes by retrieving the model elements from the model facade
042: * contained within the context.
043: *
044: * @param factory the metafacade factory (which is used to manage the lifecycle of metafacades).
045: */
046: public void processModelElements(final MetafacadeFactory factory) {
047: ExceptionUtils.checkNull("factory", factory);
048: final Collection resources = this .getResources();
049: if (resources != null && !resources.isEmpty()) {
050: for (final Iterator iterator = resources.iterator(); iterator
051: .hasNext();) {
052: final Resource resource = (Resource) iterator.next();
053: if (resource instanceof Template) {
054: this .processTemplate(factory, (Template) resource);
055: } else {
056: this .processResource(resource);
057: }
058: }
059: }
060: }
061:
062: /**
063: * Processes the given <code>template</code>.
064: *
065: * @param factory the metafacade factory instance.
066: * @param template the Template instance to process.
067: */
068: protected void processTemplate(final MetafacadeFactory factory,
069: final Template template) {
070: ExceptionUtils.checkNull("template", template);
071: final ModelElements templateModelElements = template
072: .getSupportedModeElements();
073:
074: // - handle the templates WITH model elements
075: if (templateModelElements != null
076: && !templateModelElements.isEmpty()) {
077: for (final Iterator iterator = templateModelElements
078: .getModelElements().iterator(); iterator.hasNext();) {
079: final ModelElement templateModelElement = (ModelElement) iterator
080: .next();
081:
082: // - if the template model element has a stereotype
083: // defined, then we filter the metafacades based
084: // on that stereotype, otherwise we get all metafacades
085: // and let the templateModelElement perform filtering on the
086: // metafacades by type and properties
087: if (templateModelElement.hasStereotype()) {
088: templateModelElement
089: .setMetafacades(factory
090: .getMetafacadesByStereotype(templateModelElement
091: .getStereotype()));
092: } else if (templateModelElement.hasTypes()) {
093: templateModelElement.setMetafacades(factory
094: .getAllMetafacades());
095: }
096: }
097: this .processTemplateWithMetafacades(factory, template);
098: } else {
099: // - handle any templates WITHOUT metafacades.
100: this .processTemplateWithoutMetafacades(template);
101: }
102: }
103:
104: /**
105: * Processes all <code>modelElements</code> for this template.
106: *
107: * @param factory the metafacade factory
108: * @param template the template to process
109: */
110: protected void processTemplateWithMetafacades(
111: final MetafacadeFactory factory, final Template template) {
112: ExceptionUtils.checkNull("template", template);
113: final ModelElements modelElements = template
114: .getSupportedModeElements();
115: if (modelElements != null && !modelElements.isEmpty()) {
116: try {
117: final Collection allMetafacades = modelElements
118: .getAllMetafacades();
119:
120: // - if outputToSingleFile is true AND outputOnEmptyElements
121: // is true or we have at least one metafacade in the
122: // allMetafacades collection, then we collect the template
123: // model elements and place them into the template context
124: // by their variable names.
125: if (template.isOutputToSingleFile()
126: && (template.isOutputOnEmptyElements() || !allMetafacades
127: .isEmpty())) {
128: final Map templateContext = new LinkedHashMap();
129:
130: // - first place all relevant model elements by the
131: // <modelElements/> variable name. If the variable
132: // isn't defined (which is possible), ignore.
133: final String modelElementsVariable = modelElements
134: .getVariable();
135: if (modelElementsVariable != null
136: && modelElementsVariable.trim().length() > 0) {
137: templateContext.put(
138: modelElements.getVariable(),
139: allMetafacades);
140: }
141:
142: // - now place the collections of elements by the given variable names.
143: // (skip if the variable is NOT defined)
144: for (final Iterator iterator = modelElements
145: .getModelElements().iterator(); iterator
146: .hasNext();) {
147: final ModelElement modelElement = (ModelElement) iterator
148: .next();
149: final String modelElementVariable = modelElement
150: .getVariable();
151: if (modelElementVariable != null
152: && modelElementVariable.trim().length() > 0) {
153: // - if a modelElement has the same variable defined
154: // more than one time, then get the existing
155: // model elements added from the last iteration
156: // and add the new ones to that collection
157: Collection metafacades = (Collection) templateContext
158: .get(modelElementVariable);
159: if (metafacades != null) {
160: metafacades.addAll(modelElement
161: .getMetafacades());
162: } else {
163: metafacades = modelElement
164: .getMetafacades();
165: templateContext.put(
166: modelElementVariable,
167: new LinkedHashSet(metafacades));
168: }
169: }
170: }
171: this .processWithTemplate(template, templateContext,
172: null, null);
173: } else {
174: // - if outputToSingleFile isn't true, then
175: // we just place the model element with the default
176: // variable defined on the <modelElements/> into the
177: // template.
178: for (final Iterator iterator = allMetafacades
179: .iterator(); iterator.hasNext();) {
180: final Map templateContext = new LinkedHashMap();
181: final Object metafacade = iterator.next();
182: final ModelAccessFacade model = factory
183: .getModel();
184: for (final Iterator elements = modelElements
185: .getModelElements().iterator(); elements
186: .hasNext();) {
187: final ModelElement modelElement = (ModelElement) elements
188: .next();
189: String variable = modelElement
190: .getVariable();
191:
192: // - if the variable isn't defined on the <modelElement/>, try
193: // the <modelElements/>
194: if (variable == null
195: || variable.trim().length() == 0) {
196: variable = modelElements.getVariable();
197: }
198:
199: // - only add the metafacade to the template context if the variable
200: // is defined (which is possible)
201: if (variable != null
202: && variable.trim().length() > 0) {
203: templateContext.put(variable,
204: metafacade);
205: }
206:
207: // - now we process any property templates (if any 'variable' attributes are defined on one or
208: // more type's given properties), otherwise we process the single metafacade as usual
209: if (!this .processPropertyTemplates(
210: template, metafacade,
211: templateContext, modelElement)) {
212: this
213: .processWithTemplate(
214: template,
215: templateContext,
216: model
217: .getName(metafacade),
218: model
219: .getPackageName(metafacade));
220: }
221: }
222: }
223: }
224: } catch (final Throwable throwable) {
225: throw new CartridgeException(throwable);
226: }
227: }
228: }
229:
230: /**
231: * Determines if any property templates need to be processed (that is templates
232: * that are processed given related <em>properties</em> of a metafacade).
233: *
234: * @param template the template to use for processing.
235: * @param metafacade the metafacade instance (the property value is retrieved from this).
236: * @param templateContext the template context containing the instance to pass to the template.
237: * @param modelElement the model element from which we retrieve the corresponding types and then
238: * properties to determine if any properties have been mapped for template processing.
239: * @return true if any property templates have been evaluated (false othewise).
240: */
241: private boolean processPropertyTemplates(final Template template,
242: final Object metafacade, final Map templateContext,
243: final ModelElement modelElement) {
244: boolean propertyTemplatesEvaluated = false;
245: for (final Iterator types = modelElement.getTypes().iterator(); types
246: .hasNext();) {
247: final Type type = (Type) types.next();
248: for (final Iterator properties = type.getProperties()
249: .iterator(); properties.hasNext();) {
250: final Type.Property property = (Type.Property) properties
251: .next();
252: final String variable = property.getVariable();
253: propertyTemplatesEvaluated = variable != null
254: && variable.trim().length() > 0;
255: if (propertyTemplatesEvaluated) {
256: final Object value = Introspector
257: .instance()
258: .getProperty(metafacade, property.getName());
259: if (value instanceof Collection) {
260: for (final Iterator values = ((Collection) value)
261: .iterator(); values.hasNext();) {
262: templateContext
263: .put(variable, values.next());
264: this .processWithTemplate(template,
265: templateContext, null, null);
266: }
267: } else {
268: templateContext.put(variable, value);
269: this .processWithTemplate(template,
270: templateContext, null, null);
271: }
272: }
273: }
274: }
275: return propertyTemplatesEvaluated;
276: }
277:
278: /**
279: * Processes the <code>template</code> without metafacades. This is useful if you need to generate something that
280: * is part of your cartridge, however you only need to use a property passed in from a namespace or a template
281: * object defined in your cartridge descriptor.
282: *
283: * @param template the template to process.
284: */
285: protected void processTemplateWithoutMetafacades(
286: final Template template) {
287: ExceptionUtils.checkNull("template", template);
288: final Map templateContext = new LinkedHashMap();
289: this .processWithTemplate(template, templateContext, null, null);
290: }
291:
292: /**
293: * <p>
294: * Perform processing with the <code>template</code>.
295: * </p>
296: *
297: * @param template the Template containing the template path to process.
298: * @param templateContext the context to which variables are added and made
299: * available to the template engine for processing. This will contain
300: * any model elements being made avaiable to the template(s) as well
301: * as properties/template objects.
302: * @param metafacadeName the name of the model element (if we are
303: * processing a single model element, otherwise this will be
304: * ignored).
305: * @param metafacadePackage the name of the package (if we are processing
306: * a single model element, otherwise this will be ignored).
307: */
308: private void processWithTemplate(final Template template,
309: final Map templateContext, final String metafacadeName,
310: final String metafacadePackage) {
311: ExceptionUtils.checkNull("template", template);
312: ExceptionUtils.checkNull("templateContext", templateContext);
313:
314: File outputFile = null;
315: try {
316: // - populate the template context with cartridge descriptor
317: // properties and template objects
318: this .populateTemplateContext(templateContext);
319:
320: final StringWriter output = new StringWriter();
321:
322: // - process the template with the set TemplateEngine
323: this .getTemplateEngine().processTemplate(
324: template.getPath(), templateContext, output);
325:
326: // - if we have an outputCondition defined make sure it evaluates to true before continuing
327: if (this .isValidOutputCondition(template
328: .getOutputCondition(), templateContext)) {
329: // - get the location and at the same time evaluate the outlet as a template engine variable (in case
330: // its defined as that).
331: final String location = Namespaces.instance()
332: .getPropertyValue(
333: this .getNamespace(),
334: this .getTemplateEngine()
335: .getEvaluatedExpression(
336: template.getOutlet(),
337: templateContext));
338:
339: if (location != null) {
340: outputFile = template
341: .getOutputLocation(
342: metafacadeName,
343: metafacadePackage,
344: new File(location),
345: this
346: .getTemplateEngine()
347: .getEvaluatedExpression(
348: template
349: .getOutputPattern(),
350: templateContext));
351: if (outputFile != null) {
352: // - only write files that do NOT exist, and
353: // those that have overwrite set to 'true'
354: if (!outputFile.exists()
355: || template.isOverwrite()) {
356: final String outputString = output
357: .toString();
358: AndroMDALogger.setSuffix(this
359: .getNamespace());
360:
361: // - check to see if generateEmptyFiles is true and if
362: // outString is not blank
363: if ((outputString != null && outputString
364: .trim().length() > 0)
365: || template.isGenerateEmptyFiles()) {
366: ResourceWriter.instance()
367: .writeStringToFile(
368: outputString,
369: outputFile,
370: this .getNamespace());
371: AndroMDALogger.info("Output: '"
372: + outputFile.toURI() + "'");
373: } else {
374: if (this .getLogger().isDebugEnabled()) {
375: this
376: .getLogger()
377: .debug(
378: "Empty Output: '"
379: + outputFile
380: .toURI()
381: + "' --> not writing");
382: }
383: }
384: AndroMDALogger.reset();
385: }
386: }
387: }
388: }
389: } catch (final Throwable throwable) {
390: if (outputFile != null) {
391: outputFile.delete();
392: this .getLogger().info("Removed: '" + outputFile + "'");
393: }
394: final String message = "Error processing template '"
395: + template.getPath() + "' with template context '"
396: + templateContext + "' using cartridge '"
397: + this .getNamespace() + "'";
398: throw new CartridgeException(message, throwable);
399: }
400: }
401:
402: /**
403: * Processes the given <code>resource</code>
404: *
405: * @param resource the resource to process.
406: */
407: protected void processResource(final Resource resource) {
408: ExceptionUtils.checkNull("resource", resource);
409:
410: URL resourceUrl = ResourceUtils.getResource(resource.getPath(),
411: this .getMergeLocation());
412: if (resourceUrl == null) {
413: // - if the resourceUrl is null, the path is probably a regular
414: // outputCondition pattern so we'll see if we can match it against
415: // the contents of the plugin and write any contents that do match
416: final List contents = this .getContents();
417: if (contents != null) {
418: AndroMDALogger.setSuffix(this .getNamespace());
419: for (final Iterator iterator = contents.iterator(); iterator
420: .hasNext();) {
421: final String content = (String) iterator.next();
422: if (content != null && content.trim().length() > 0) {
423: if (PathMatcher.wildcardMatch(content, resource
424: .getPath())) {
425: resourceUrl = ResourceUtils.getResource(
426: content, this .getMergeLocation());
427: // - don't attempt to write the directories within the resource
428: if (!resourceUrl.toString().endsWith(
429: FORWARD_SLASH)) {
430: this .writeResource(resource,
431: resourceUrl);
432: }
433: }
434: }
435: }
436: AndroMDALogger.reset();
437: }
438: } else {
439: this .writeResource(resource, resourceUrl);
440: }
441: }
442:
443: /**
444: * The forward slash constant.
445: */
446: private static final String FORWARD_SLASH = "/";
447:
448: /**
449: * Writes the contents of <code>resourceUrl</code> to the outlet specified by <code>resource</code>.
450: *
451: * @param resource contains the outlet where the resource is written.
452: * @param resourceUrl the URL contents to write.
453: */
454: private void writeResource(final Resource resource,
455: final URL resourceUrl) {
456: File outputFile = null;
457: try {
458: // - make sure we don't have any back slashes
459: final String resourceUri = ResourceUtils
460: .normalizePath(resourceUrl.toString());
461: final String uriSuffix = resourceUri.substring(resourceUri
462: .lastIndexOf(FORWARD_SLASH), resourceUri.length());
463:
464: final Map templateContext = new LinkedHashMap();
465: this .populateTemplateContext(templateContext);
466:
467: // - if we have an outputCondition defined make sure it evaluates to true before continuing
468: if (this .isValidOutputCondition(resource
469: .getOutputCondition(), templateContext)) {
470: // - get the location and at the same time evaluate the outlet as a template engine variable (in case
471: // its defined as that).
472: final String location = Namespaces.instance()
473: .getPropertyValue(
474: this .getNamespace(),
475: this .getTemplateEngine()
476: .getEvaluatedExpression(
477: resource.getOutlet(),
478: templateContext));
479:
480: if (location != null) {
481: outputFile = resource
482: .getOutputLocation(
483: new String[] { uriSuffix },
484: new File(location),
485: this
486: .getTemplateEngine()
487: .getEvaluatedExpression(
488: resource
489: .getOutputPattern(),
490: templateContext));
491:
492: final boolean lastModifiedCheck = resource
493: .isLastModifiedCheck();
494: // - if we have the last modified check set, then make sure the last modified time is greater than the outputFile
495: if (!lastModifiedCheck
496: || (lastModifiedCheck && ResourceUtils
497: .getLastModifiedTime(resourceUrl) > outputFile
498: .lastModified())) {
499: // - only write files that do NOT exist, and
500: // those that have overwrite set to 'true'
501: if (!outputFile.exists()
502: || resource.isOverwrite()) {
503: ResourceWriter.instance().writeUrlToFile(
504: resourceUrl, outputFile.toString());
505: AndroMDALogger.info("Output: '"
506: + outputFile.toURI() + "'");
507: }
508: }
509: }
510: }
511: } catch (final Throwable throwable) {
512: if (outputFile != null) {
513: outputFile.delete();
514: this .getLogger().info("Removed: '" + outputFile + "'");
515: }
516: throw new CartridgeException(throwable);
517: }
518: }
519:
520: /**
521: * Stores the loaded resources to be processed by this cartridge intance.
522: */
523: private final List resources = new ArrayList();
524:
525: /**
526: * Returns the list of templates configured in this cartridge.
527: *
528: * @return List the template list.
529: */
530: public List getResources() {
531: return this .resources;
532: }
533:
534: /**
535: * Adds a resource to the list of defined resources.
536: *
537: * @param resource the new resource to add
538: */
539: public void addResource(final Resource resource) {
540: ExceptionUtils.checkNull("resource", resource);
541: resource.setCartridge(this );
542: resources.add(resource);
543: }
544:
545: /**
546: * Populates the <code>templateContext</code> with the properties and template objects defined in the
547: * <code>plugin</code>'s descriptor. If the <code>templateContext</code> is null, a new Map instance will be created
548: * before populating the context.
549: *
550: * @param templateContext the context of the template to populate.
551: */
552: protected void populateTemplateContext(Map templateContext) {
553: super .populateTemplateContext(templateContext);
554: templateContext.putAll(this
555: .getEvaluatedConditions(templateContext));
556: }
557:
558: /**
559: * Stores the global conditions.
560: */
561: private final Map conditions = new LinkedHashMap();
562:
563: /**
564: * Adds the outputCondition given the <code>name</code> and <code>value</code>
565: * to the outputConditions map.
566: *
567: * @param name the name of the outputCondition.
568: * @param value the value of the outputCondition.
569: */
570: public void addCondition(final String name, final String value) {
571: this .conditions.put(name, value != null ? value.trim() : "");
572: }
573:
574: /**
575: * Gets the current outputConditions defined within this cartridge
576: */
577: public Map getConditions() {
578: return this .conditions;
579: }
580:
581: /**
582: * Indicates whether or not the global outputConditions have been evaluated.
583: */
584: private boolean conditionsEvaluated = false;
585:
586: /**
587: * Evaluates all conditions and stores the results in the <code>conditions</code>
588: * and returns that Map
589: *
590: * @param templateContext the template context used to evaluate the conditions.
591: * @param the map containing the evaluated conditions.
592: */
593: private Map getEvaluatedConditions(final Map templateContext) {
594: if (!this .conditionsEvaluated) {
595: for (final Iterator iterator = this .conditions.keySet()
596: .iterator(); iterator.hasNext();) {
597: final String name = (String) iterator.next();
598: final String value = (String) this .conditions.get(name);
599: String evaluationResult = value != null ? value.trim()
600: : null;
601: if (evaluationResult != null
602: && evaluationResult.trim().length() > 0) {
603: evaluationResult = this .getTemplateEngine()
604: .getEvaluatedExpression(evaluationResult,
605: templateContext);
606: }
607: this .conditions.put(name, Boolean.valueOf(BooleanUtils
608: .toBoolean(evaluationResult)));
609: }
610: this .conditionsEvaluated = true;
611: }
612: return this .conditions;
613: }
614:
615: /**
616: * Gets the evaluted outputCondition result of a global outputCondition.
617: *
618: * @param templateContext the current template context to pass the template engine if
619: * evaluation has yet to occurr.
620: * @return the evaluated outputCondition results.
621: */
622: private Boolean getGlobalConditionResult(
623: final String outputCondition, final Map templateContext) {
624: return (Boolean) this .getEvaluatedConditions(templateContext)
625: .get(outputCondition);
626: }
627:
628: /**
629: * Indicates whether or not the given <code>outputCondition</code> is a valid
630: * outputCondition, that is, whether or not it return true.
631: *
632: * @param outputCondition the outputCondition to evaluate.
633: * @param templateContext the template context containing the variables to use.
634: * @return true/false
635: */
636: private boolean isValidOutputCondition(
637: final String outputCondition, final Map templateContext) {
638: boolean validOutputCondition = true;
639: if (outputCondition != null
640: && outputCondition.trim().length() > 0) {
641: Boolean result = this .getGlobalConditionResult(
642: outputCondition, templateContext);
643: if (result == null) {
644: final String outputConditionResult = this
645: .getTemplateEngine().getEvaluatedExpression(
646: outputCondition, templateContext);
647: result = Boolean
648: .valueOf(BooleanUtils
649: .toBoolean(outputConditionResult != null ? outputConditionResult
650: .trim()
651: : null));
652: }
653: validOutputCondition = result != null ? result
654: .booleanValue() : false;
655: }
656: return validOutputCondition;
657: }
658:
659: /**
660: * Override to provide cartridge specific shutdown (
661: *
662: * @see org.andromda.core.common.Plugin#shutdown()
663: */
664: public void shutdown() {
665: super.shutdown();
666: this.conditions.clear();
667: }
668: }
|