001: package org.andromda.core.metafacade;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Iterator;
006: import java.util.LinkedHashMap;
007: import java.util.LinkedHashSet;
008: import java.util.List;
009: import java.util.Map;
010:
011: import org.andromda.core.common.AndroMDALogger;
012: import org.andromda.core.common.ExceptionUtils;
013: import org.andromda.core.profile.Profile;
014: import org.apache.commons.collections.keyvalue.MultiKey;
015: import org.apache.log4j.Logger;
016:
017: /**
018: * The factory in charge of constructing Metafacade instances. In order for a
019: * metafacade (i.e. a facade around a meta model element) to be constructed, it
020: * must be constructed through this factory.
021: *
022: * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
023: * @author Chad Brandon
024: * @author Peter Friese
025: */
026: public class MetafacadeFactory {
027: /**
028: * Caches the registered properties used within metafacades.
029: */
030: private final Map metafacadeNamespaces = new LinkedHashMap();
031:
032: /**
033: * The shared instance of this factory.
034: */
035: private static MetafacadeFactory instance = null;
036:
037: private MetafacadeFactory() {
038: // make sure that nobody instantiates it
039: }
040:
041: /**
042: * Returns the facade factory singleton.
043: *
044: * @return the only instance
045: */
046: public static MetafacadeFactory getInstance() {
047: if (instance == null) {
048: instance = new MetafacadeFactory();
049: }
050: return instance;
051: }
052:
053: /**
054: * The metafacade cache for this factory.
055: */
056: private final MetafacadeCache cache = MetafacadeCache.newInstance();
057:
058: /**
059: * The metafacade mappings instance for this factory.
060: */
061: private final MetafacadeMappings mappings = MetafacadeMappings
062: .newInstance();
063:
064: /**
065: * Performs any initialization required by the factory (i.e. discovering all
066: * <code>metafacade</code> mappings, etc).
067: */
068: public void initialize() {
069: this .mappings.initialize();
070: }
071:
072: /**
073: * The shared profile instance.
074: */
075: private final Profile profile = Profile.instance();
076:
077: /**
078: * The namespace that is currently active (i.e. being used) within the factory
079: */
080: private String namespace;
081:
082: /**
083: * Sets the active namespace. The AndroMDA core and each cartridge have their own namespace for metafacade
084: * registration.
085: *
086: * @param namespace the name of the active namespace.
087: */
088: public void setNamespace(final String namespace) {
089: this .namespace = namespace;
090: this .profile.setNamespace(namespace);
091: this .cache.setNamespace(this .namespace);
092: }
093:
094: /**
095: * Returns the name of the active namespace.
096: *
097: * @return String the namespace name
098: */
099: public String getNamespace() {
100: if (this .namespace == null) {
101: throw new MetafacadeFactoryException(
102: "This metafacade factory's namespace must be populated before "
103: + "metafacade construction can occur");
104: }
105: return this .namespace;
106: }
107:
108: /**
109: * Returns a metafacade for a mappingObject, depending on its
110: * <code>mappingClass</code> and (optionally) its <code>sterotypes</code>
111: * and <code>context</code>.
112: *
113: * @param mappingObject the object used to map the metafacade (a meta model
114: * object or a metafacade itself).
115: * @param context the name of the context the meta model element is
116: * registered under.
117: * @return the new metafacade
118: */
119: public MetafacadeBase createMetafacade(final Object mappingObject,
120: final String context) {
121: return this .createMetafacade(mappingObject, context, null);
122: }
123:
124: /**
125: * Creates a metafacade given the <code>mappingObject</code>,
126: * <code>contextName</code> and <code>metafacadeClass</code>.
127: *
128: * @param mappingObject the object used to map the metafacade (a meta model
129: * object or a metafacade itself).
130: * @param context the name of the context the meta model element (if the
131: * mappObject is a meta model element) is registered under.
132: * @param metafacadeClass if not null, it contains the name of the
133: * metafacade class to be used. This is used ONLY when instantiating
134: * super metafacades in an inheritance chain. The final metafacade
135: * will NEVER have a metafacadeClass specified (it will ALWAYS be
136: * null).
137: * @return the new metafacade
138: */
139: private MetafacadeBase createMetafacade(final Object mappingObject,
140: final String context, Class metafacadeClass) {
141: final String methodName = "MetafacadeFactory.createMetafacade";
142: ExceptionUtils.checkNull("mappingObject", mappingObject);
143:
144: // - register the namespace properties (if they haven't been)
145: this .registerNamespaceProperties();
146:
147: // if the mappingObject is REALLY a metafacade, just return it
148: if (mappingObject instanceof MetafacadeBase) {
149: return (MetafacadeBase) mappingObject;
150: }
151: try {
152: final Collection stereotypes = this .getModel()
153: .getStereotypeNames(mappingObject);
154: if (this .getLogger().isDebugEnabled()) {
155: this .getLogger().debug(
156: "mappingObject stereotypes --> '" + stereotypes
157: + "'");
158: }
159:
160: MetafacadeMapping mapping = null;
161: if (metafacadeClass == null) {
162: final MetafacadeMappings modelMetafacadeMappings = this
163: .getModelMetafacadeMappings();
164: mapping = modelMetafacadeMappings.getMetafacadeMapping(
165: mappingObject, this .getNamespace(), context,
166: stereotypes);
167: if (mapping != null) {
168: metafacadeClass = mapping.getMetafacadeClass();
169: } else {
170: // get the default since no mapping was found.
171: metafacadeClass = modelMetafacadeMappings
172: .getDefaultMetafacadeClass(this
173: .getNamespace());
174: if (this .getLogger().isDebugEnabled()) {
175: this
176: .getLogger()
177: .debug(
178: "Meta object model class '"
179: + mappingObject
180: .getClass()
181: + "' has no corresponding meta facade class, default is being used --> '"
182: + metafacadeClass + "'");
183: }
184: }
185: }
186:
187: if (metafacadeClass == null) {
188: throw new MetafacadeMappingsException(
189: methodName
190: + " metafacadeClass was not retrieved from mappings"
191: + " or specified as an argument in this method for mappingObject --> '"
192: + mappingObject + "'");
193: }
194: final MetafacadeBase metafacade = this .getMetafacade(
195: metafacadeClass, mappingObject, context, mapping);
196:
197: // IMPORTANT: initialize each metafacade ONLY once (otherwise we
198: // get stack overflow errors)
199: if (metafacade != null && !metafacade.isInitialized()) {
200: metafacade.setInitialized();
201: metafacade.initialize();
202: }
203: return metafacade;
204: } catch (final Throwable throwable) {
205: final String message = "Failed to construct a meta facade of type '"
206: + metafacadeClass
207: + "' with mappingObject of type --> '"
208: + mappingObject.getClass() + "'";
209: this .getLogger().error(message);
210: throw new MetafacadeFactoryException(message, throwable);
211: }
212: }
213:
214: /**
215: * Gets the model metafacade mappings (that is the mappings that corresponding
216: * to the current metafacade model namespace).
217: *
218: * @return the model metafacade mappings.
219: */
220: private MetafacadeMappings getModelMetafacadeMappings() {
221: return this .mappings
222: .getModelMetafacadeMappings(this .metafacadeModelNamespace);
223: }
224:
225: /**
226: * Validates all metafacdes for the current namespace
227: * and collects the messages in the interal validation messages
228: * collection.
229: *
230: * @see #getValidationMessages()
231: */
232: public void validateAllMetafacades() {
233: for (final Iterator iterator = this .getAllMetafacades()
234: .iterator(); iterator.hasNext();) {
235: ((MetafacadeBase) iterator.next())
236: .validate(this .validationMessages);
237: }
238: }
239:
240: /**
241: * Creates a metacade from the passed in <code>mappingObject</code>, and
242: * <code>mapping</code> instance.
243: *
244: * @param mappingObject the mapping object for which to create the
245: * metafacade.
246: * @param mapping the mapping from which to create the metafacade
247: * @return the metafacade, or null if it can't be created.
248: */
249: protected MetafacadeBase createMetafacade(
250: final Object mappingObject, final MetafacadeMapping mapping) {
251: try {
252: return this .getMetafacade(mapping.getMetafacadeClass(),
253: mappingObject, mapping.getContext(), mapping);
254: } catch (final Throwable throwable) {
255: final String message = "Failed to construct a meta facade of type '"
256: + mapping.getMetafacadeClass()
257: + "' with mappingObject of type --> '"
258: + mapping.getMappingClassName() + "'";
259: this .getLogger().error(message);
260: throw new MetafacadeFactoryException(message, throwable);
261: }
262: }
263:
264: /**
265: * Retrieves (if one has already been constructed) or constructs a new
266: * <code>metafacade</code> from the given <code>metafacadeClass</code>
267: * and <code>mappingObject</code>.
268: *
269: * @param metafacadeClass the metafacade class.
270: * @param mappingObject the object to which the metafacade is mapped.
271: * @param context the context to which the metafacade applies
272: * @param mapping the optional MetafacadeMapping instance from which the
273: * metafacade is mapped.
274: * @return the new (or cached) metafacade.
275: * @throws Exception if any error occurs during metafacade creation
276: */
277: private MetafacadeBase getMetafacade(final Class metafacadeClass,
278: final Object mappingObject, final String context,
279: final MetafacadeMapping mapping) throws Exception {
280: MetafacadeBase metafacade = this .cache.get(mappingObject,
281: metafacadeClass);
282: if (metafacade == null) {
283: final MultiKey key = new MultiKey(mappingObject,
284: metafacadeClass);
285: if (!this .metafacadesInCreation.contains(key)) {
286: this .metafacadesInCreation.add(key);
287: if (mapping != null && mapping.isContextRoot()) {
288: metafacade = MetafacadeUtils.constructMetafacade(
289: metafacadeClass, mappingObject, null);
290: // set whether or not this metafacade is a context root
291: metafacade.setContextRoot(mapping.isContextRoot());
292: } else {
293: metafacade = MetafacadeUtils.constructMetafacade(
294: metafacadeClass, mappingObject, context);
295: }
296: this .metafacadesInCreation.remove(key);
297:
298: this .cache.add(mappingObject, metafacade);
299: }
300: }
301:
302: if (metafacade != null) {
303: // if the requested metafacadeClass is different from the one in the mapping, contextRoot should be reset
304: if (mapping != null
305: && !mapping.getMetafacadeClass().equals(
306: metafacadeClass)) {
307: metafacade.setContextRoot(false);
308: metafacade.resetMetafacadeContext(context);
309: }
310: // we need to set some things each time
311: // we change a metafacade's namespace
312: final String metafacadeNamespace = metafacade
313: .getNamespace();
314: if (metafacadeNamespace == null
315: || !metafacadeNamespace.equals(this .getNamespace())) {
316: // assign the logger and active namespace
317: metafacade.setNamespace(this .getNamespace());
318: metafacade.setLogger(this .getLogger());
319: }
320: }
321: return metafacade;
322: }
323:
324: /**
325: * Stores the metafacades being created, so that we don't get stuck in
326: * endless recursion during creation.
327: */
328: private final Collection metafacadesInCreation = new ArrayList();
329:
330: /**
331: * Returns a metafacade for a mappingObject, depending on its <code>mappingClass</code>.
332: *
333: * @param mappingObject the object which is used to map to the metafacade
334: * @return MetafacadeBase the facade object (not yet attached to mappingClass object)
335: */
336: public MetafacadeBase createMetafacade(final Object mappingObject) {
337: return this .createMetafacade(mappingObject, null, null);
338: }
339:
340: /**
341: * Create a facade implementation object for a mappingObject. The facade
342: * implementation object must be found in a way that it implements the
343: * interface <code>interfaceName</code>.
344: *
345: * @param interfaceName the name of the interface that the implementation
346: * object has to implement
347: * @param mappingObject the mappingObject for which a facade shall be
348: * created
349: * @param context the context in which this metafacade will be created.
350: * @return MetafacadeBase the metafacade
351: */
352: public MetafacadeBase createFacadeImpl(final String interfaceName,
353: final Object mappingObject, final String context) {
354: ExceptionUtils.checkEmpty("interfaceName", interfaceName);
355: ExceptionUtils.checkNull("mappingObject", mappingObject);
356:
357: Class metafacadeClass = null;
358: try {
359: metafacadeClass = this .metafacadeImpls
360: .getMetafacadeImplClass(interfaceName);
361: return this .createMetafacade(mappingObject, context,
362: metafacadeClass);
363: } catch (final Throwable throwable) {
364: final String message = "Failed to construct a meta facade of type '"
365: + metafacadeClass
366: + "' with mappingObject of type --> '"
367: + mappingObject.getClass().getName() + "'";
368: this .getLogger().error(message);
369: throw new MetafacadeFactoryException(message, throwable);
370: }
371: }
372:
373: /**
374: * Returns a metafacade for each mappingObject, contained within the
375: * <code>mappingObjects</code> collection depending on its
376: * <code>mappingClass</code> and (optionally) its <code>sterotypes</code>,
377: * and <code>contextName</code>.
378: *
379: * @param mappingObjects the meta model element.
380: * @param contextName the name of the context the meta model element is
381: * registered under.
382: * @return the Collection of newly created Metafacades.
383: */
384: protected Collection createMetafacades(
385: final Collection mappingObjects, final String contextName) {
386: final Collection metafacades = new ArrayList();
387: if (mappingObjects != null && !mappingObjects.isEmpty()) {
388: for (final Iterator iterator = mappingObjects.iterator(); iterator
389: .hasNext();) {
390: metafacades.add(this .createMetafacade(iterator.next(),
391: contextName, null));
392: }
393: }
394: return metafacades;
395: }
396:
397: /**
398: * Returns a metafacade for each mappingObject, contained within the
399: * <code>mappingObjects</code> collection depending on its
400: * <code>mappingClass</code>.
401: *
402: * @param mappingObjects the objects used to map the metafacades (can be a
403: * meta model element or an actual metafacade itself).
404: * @return Collection of metafacades
405: */
406: public Collection createMetafacades(final Collection mappingObjects) {
407: return this .createMetafacades(mappingObjects, null);
408: }
409:
410: /**
411: * The model facade which provides access to the underlying meta model.
412: */
413: private ModelAccessFacade model;
414:
415: /**
416: * Gets the model which provides access to the underlying model and is used
417: * to construct metafacades.
418: *
419: * @return the model access facade.
420: */
421: public ModelAccessFacade getModel() {
422: if (this .model == null) {
423: throw new MetafacadeFactoryException(
424: "This metafacade factory's model must be populated before "
425: + "metafacade construction can occur");
426: }
427: return this .model;
428: }
429:
430: /**
431: * The shared metafacade impls instance.
432: */
433: private MetafacadeImpls metafacadeImpls = MetafacadeImpls
434: .instance();
435:
436: /**
437: * Stores the namespace that contains the metafacade model implementation.
438: */
439: private String metafacadeModelNamespace;
440:
441: /**
442: * The model access facade instance (provides access to the meta model).
443: *
444: * @param model the model
445: * @param metafacadeModelNamespace the namespace that contains the metafacade facade implementation.
446: */
447: public void setModel(final ModelAccessFacade model,
448: final String metafacadeModelNamespace) {
449: this .metafacadeModelNamespace = metafacadeModelNamespace;
450:
451: // - set the model type as the namespace for the metafacade impls so we have
452: // access to the correct metafacade classes
453: this .metafacadeImpls
454: .setMetafacadeModelNamespace(metafacadeModelNamespace);
455: this .model = model;
456: }
457:
458: /**
459: * Gets the correct logger based on whether or not an namespace logger should be used
460: *
461: * @return the logger
462: */
463: final Logger getLogger() {
464: return AndroMDALogger.getNamespaceLogger(this .getNamespace());
465: }
466:
467: /**
468: * Registers a property with the specified <code>name</code> in the given
469: * <code>namespace</code>.
470: *
471: * @param namespace the namespace in which the property is stored.
472: * @param metafacadeName the name of the metafacade under which the property is registered
473: * @param name the name of the property
474: * @param value to give the property
475: */
476: final void registerProperty(final String namespace,
477: final String metafacadeName, final String name,
478: final Object value) {
479: ExceptionUtils.checkEmpty("name", name);
480: Map metafacadeNamespace = (Map) this .metafacadeNamespaces
481: .get(namespace);
482: if (metafacadeNamespace == null) {
483: metafacadeNamespace = new LinkedHashMap();
484: this .metafacadeNamespaces.put(namespace,
485: metafacadeNamespace);
486: }
487: Map propertyNamespace = (Map) metafacadeNamespace
488: .get(metafacadeName);
489: if (propertyNamespace == null) {
490: propertyNamespace = new LinkedHashMap();
491: metafacadeNamespace.put(metafacadeName, propertyNamespace);
492: }
493: propertyNamespace.put(name, value);
494: }
495:
496: /**
497: * Registers a property with the specified <code>name</code> in the namespace
498: * that is currently set within the factory.
499: *
500: * @param metafacadeName the name of the metafacade under which the property is registered
501: * @param name the name of the property
502: * @param value to give the property
503: */
504: final void registerProperty(final String metafacadeName,
505: final String name, final Object value) {
506: this .registerProperty(this .getNamespace(), metafacadeName,
507: name, value);
508: }
509:
510: /**
511: * Gets the metafacade's property namespace (or returns null if hasn't be registered).
512: *
513: * @param metafacade the metafacade
514: * @return the metafacade's namespace
515: */
516: private Map getMetafacadePropertyNamespace(
517: final MetafacadeBase metafacade) {
518: Map metafacadeNamespace = null;
519: if (metafacade != null) {
520: Map namespace = (Map) this .metafacadeNamespaces.get(this
521: .getNamespace());
522: if (namespace != null) {
523: metafacadeNamespace = (Map) namespace.get(metafacade
524: .getName());
525: }
526: }
527: return metafacadeNamespace;
528: }
529:
530: /**
531: * Returns true if this property is registered under the given
532: * <code>namespace</code>, false otherwise.
533: *
534: * @param metafacade the metafacade to search.
535: * @param name the name of the property.
536: * @return true if the property is registered, false otherwise.
537: */
538: final boolean isPropertyRegistered(final MetafacadeBase metafacade,
539: final String name) {
540: final Map propertyNamespace = this
541: .getMetafacadePropertyNamespace(metafacade);
542: return propertyNamespace != null
543: && propertyNamespace.containsKey(name);
544: }
545:
546: /**
547: * Finds the first property having the given <code>namespaces</code>, or
548: * <code>null</code> if the property can <strong>NOT </strong> be found.
549: *
550: * @param metafacade the metafacade to search.
551: * @param name the name of the property to find.
552: * @return the property or null if it can't be found.
553: */
554: private Object findProperty(final MetafacadeBase metafacade,
555: final String name) {
556: final Map propertyNamespace = this
557: .getMetafacadePropertyNamespace(metafacade);
558: return propertyNamespace != null ? propertyNamespace.get(name)
559: : null;
560: }
561:
562: /**
563: * Gets the registered property registered under the <code>namespace</code>
564: * with the <code>name</code>
565: *
566: * @param metafacade the metafacade to search
567: * @param name the name of the property to check.
568: * @return the registered property
569: */
570: final Object getRegisteredProperty(final MetafacadeBase metafacade,
571: final String name) {
572: final String methodName = "MetafacadeFactory.getRegisteredProperty";
573: final Object registeredProperty = this .findProperty(metafacade,
574: name);
575: if (registeredProperty == null
576: && !this .isPropertyRegistered(metafacade, name)) {
577: throw new MetafacadeFactoryException(methodName
578: + " - no property '" + name
579: + "' registered under metafacade '"
580: + metafacade.getName() + "' for namespace '"
581: + this .getNamespace() + "'");
582: }
583: return registeredProperty;
584: }
585:
586: /**
587: * The validation messages that have been collected during the
588: * execution of this factory.
589: */
590: private final Collection validationMessages = new LinkedHashSet();
591:
592: /**
593: * Gets the list of all validation messages collection during model processing.
594: *
595: * @return Returns the validationMessages.
596: * @see #validateAllMetafacades()
597: */
598: public List getValidationMessages() {
599: return new ArrayList(this .validationMessages);
600: }
601:
602: /**
603: * Stores the collection of all metafacades for
604: * each namespace.
605: */
606: private final Map allMetafacades = new LinkedHashMap();
607:
608: /**
609: * <p>
610: * Gets all metafacades for the entire model for the
611: * current namespace set within the factory.
612: * </p>
613: * <p>
614: * <strong>NOTE:</strong> The model package filter is applied
615: * before returning the results (if defined within the factory).
616: * </p>
617: *
618: * @return all metafacades
619: */
620: public Collection getAllMetafacades() {
621: final String namespace = this .getNamespace();
622: Collection metafacades = null;
623: if (this .getModel() != null) {
624: metafacades = (Collection) allMetafacades.get(namespace);
625: if (metafacades == null) {
626: metafacades = this .createMetafacades(this .getModel()
627: .getModelElements());
628: allMetafacades.put(namespace, metafacades);
629: }
630: if (metafacades != null) {
631: metafacades = new ArrayList(metafacades);
632: }
633: }
634: return metafacades;
635: }
636:
637: /**
638: * Caches the metafacdaes by stereotype.
639: */
640: private final Map metafacadesByStereotype = new LinkedHashMap();
641:
642: /**
643: * <p>
644: * Gets all metafacades for the entire model having the given
645: * stereotype.
646: * </p>
647: * <p>
648: * <strong>NOTE:</strong> The model package filter is applied
649: * before returning the results (if defined within the factory).
650: * </p>
651: *
652: * @param stereotype the stereotype by which to perform the search.
653: * @return the metafacades having the given <code>stereotype</code>.
654: */
655: public Collection getMetafacadesByStereotype(final String stereotype) {
656: final String namespace = this .getNamespace();
657: Collection metafacades = null;
658: if (this .getModel() != null) {
659: Map stereotypeMetafacades = (Map) this .metafacadesByStereotype
660: .get(namespace);
661: if (stereotypeMetafacades == null) {
662: stereotypeMetafacades = new LinkedHashMap();
663: }
664: metafacades = (Collection) stereotypeMetafacades
665: .get(stereotype);
666: if (metafacades == null) {
667: metafacades = this .createMetafacades(this .getModel()
668: .findByStereotype(stereotype));
669: stereotypeMetafacades.put(stereotype, metafacades);
670: this .metafacadesByStereotype.put(namespace,
671: stereotypeMetafacades);
672: }
673: if (metafacades != null) {
674: metafacades = new ArrayList(metafacades);
675: }
676: }
677: return metafacades;
678: }
679:
680: /**
681: * Performs shutdown procedures for the factory. This should be called <strong>ONLY</code> when model processing has
682: * completed.
683: */
684: public void shutdown() {
685: this .clearCaches();
686: this .metafacadeNamespaces.clear();
687: this .mappings.shutdown();
688: this .model = null;
689: instance = null;
690:
691: // - shutdown the profile instance
692: this .profile.shutdown();
693: }
694:
695: /**
696: * Registers all namespace properties (if required).
697: */
698: private void registerNamespaceProperties() {
699: // - only register them if they already aren't registered
700: if (this .metafacadeNamespaces.isEmpty()) {
701: if (this .metafacadeModelNamespace != null
702: && this .metafacadeModelNamespace.trim().length() > 0) {
703: final MetafacadeMappings modelMappings = this
704: .getModelMetafacadeMappings();
705: if (modelMappings != null) {
706: modelMappings.registerAllProperties();
707: }
708: }
709: }
710: }
711:
712: /**
713: * Entirely resets all the internal resources within this factory instance (such
714: * as the caches, etc).
715: */
716: public void reset() {
717: // - refresh the profile
718: this .profile.refresh();
719:
720: // - clear out the namespace properties so we can re-register them next run
721: this .metafacadeNamespaces.clear();
722:
723: // - re-register the namespace properties (if we're running again)
724: this .registerNamespaceProperties();
725:
726: // - clear out the rest of the factory's caches
727: this .clearCaches();
728: }
729:
730: /**
731: * Clears out the factory's internal caches (other
732: * than namespace properties, which can be cleared by
733: * calling {@link org.andromda.core.configuration.Namespaces#clear()}.
734: */
735: public void clearCaches() {
736: this.validationMessages.clear();
737: this.allMetafacades.clear();
738: this.metafacadesByStereotype.clear();
739: this.cache.clear();
740: this.metafacadesInCreation.clear();
741: }
742: }
|