View Javadoc
1   package org.andromda.core.metafacade;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Collection;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.Iterator;
10  import java.util.LinkedHashMap;
11  import java.util.LinkedHashSet;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import org.andromda.core.common.AndroMDALogger;
16  import org.andromda.core.common.ClassUtils;
17  import org.andromda.core.common.ComponentContainer;
18  import org.andromda.core.common.ExceptionUtils;
19  import org.andromda.core.configuration.Namespace;
20  import org.andromda.core.configuration.Namespaces;
21  import org.andromda.core.namespace.BaseNamespaceComponent;
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.log4j.Logger;
24  
25  /**
26   * The Metafacade mapping class. Used to map <code>metafacade</code> objects
27   * to <code>metamodel</code> objects.
28   *
29   * @author Chad Brandon
30   * @author Bob Fields
31   * @see MetafacadeMapping
32   * @see org.andromda.core.common.XmlObjectFactory
33   */
34  public class MetafacadeMappings
35      extends BaseNamespaceComponent
36      implements Serializable
37  {
38      private static final long serialVersionUID = 34L;
39  
40      /**
41       * Holds the references to the child MetafacadeMapping instances.
42       */
43      private final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>();
44  
45      /**
46       * Holds the namespace MetafacadeMappings. This are child MetafacadeMappings
47       * keyed by namespace name.
48       */
49      private final Map<String, MetafacadeMappings> namespaceMetafacadeMappings = new HashMap<String, MetafacadeMappings>();
50  
51      /**
52       * The default meta facade to use when there isn't a mapping found.
53       */
54      private Class defaultMetafacadeClass = null;
55  
56      /**
57       * Constructs a new instance of this class.
58       *
59       * @return MetafacadeMappings
60       */
61      public static MetafacadeMappings newInstance()
62      {
63          return new MetafacadeMappings();
64      }
65  
66      /**
67       * Adds a MetafacadeMapping instance to the set of current mappings.
68       *
69       * @param mapping the MetafacadeMapping instance.
70       */
71      public void addMapping(final MetafacadeMapping mapping)
72      {
73          ExceptionUtils.checkNull(
74              "mapping",
75              mapping);
76          ExceptionUtils.checkNull(
77              "mapping.metafacadeClass",
78              mapping.getMetafacadeClass());
79          mapping.setMetafacadeMappings(this);
80  
81          // find any mappings that match, if they do we add the properties
82          // from that mapping to the existing matched mapping (so we only
83          // have one mapping containing properties that can be 'OR'ed together).
84          final MetafacadeMapping foundMapping =
85              this.findMapping(
86                  new Condition()
87                  {
88                      public boolean evaluate(final MetafacadeMapping object)
89                      {
90                          return mapping.match(object);
91                      }
92                  });
93          if (foundMapping != null)
94          {
95              foundMapping.addMappingPropertyGroup(mapping.getMappingProperties());
96          }
97          else
98          {
99              this.mappings.add(mapping);
100             this.mappingsByMetafacadeClass.put(
101                 this.getMetafacadeInterface(mapping.getMetafacadeClass()),
102                 mapping);
103         }
104     }
105 
106     /**
107      * Gets the class of the metafacade interface that belongs to the given
108      * <code>metafacadeClass</code>.
109      * @param metafacadeClass
110      *
111      * @return the metafacade interface Class.
112      */
113     public Class getMetafacadeInterface(final Class metafacadeClass)
114     {
115         Class metafacadeInterface = null;
116         if (metafacadeClass != null)
117         {
118             metafacadeInterface = metafacadeClass;
119             final List<Class> interfaces = ClassUtils.getAllInterfaces(metafacadeClass);
120             if (interfaces != null && !interfaces.isEmpty())
121             {
122                 metafacadeInterface = interfaces.iterator().next();
123             }
124         }
125         return metafacadeInterface;
126     }
127 
128     /**
129      * Stores mappings by the metafacade class so that we can retrieve the
130      * inherited metafacade classes.
131      */
132     private final Map<Class, MetafacadeMapping> mappingsByMetafacadeClass = new HashMap<Class, MetafacadeMapping>();
133 
134     /**
135      * Copies all data from <code>mappings<code> to this instance.
136      *
137      * @param mappings the mappings to add
138      */
139     private void copyMappings(final MetafacadeMappings mappings)
140     {
141         ExceptionUtils.checkNull(
142             "mappings",
143             mappings);
144         for (final MetafacadeMapping mapping : mappings.mappings)
145         {
146             this.addMapping(mapping);
147         }
148         final Collection<String> propertyReferences = mappings.getPropertyReferences();
149         if (propertyReferences != null && !propertyReferences.isEmpty())
150         {
151             this.propertyReferences.addAll(propertyReferences);
152         }
153         this.defaultMetafacadeClass = mappings.defaultMetafacadeClass;
154     }
155 
156     /**
157      * Contains references to properties populated in the Namespaces.
158      */
159     private final Collection<String> propertyReferences = new LinkedHashSet<String>();
160 
161     /**
162      * Gets all property references defined in this mappings instance.
163      *
164      * @return the map of property references (names and values).
165      */
166     public Collection<String> getPropertyReferences()
167     {
168         return this.propertyReferences;
169     }
170 
171     /**
172      * <p> Retrieves the MetafacadeMapping belonging to the unique
173      * <code>key</code> created from the <code>mappingObject</code>'s
174      * class, <code>context</code> and given <code>stereotypes</code>. It's
175      * <strong>IMPORTANT </strong> to note that contexts have a higher priority
176      * than stereotypes. This allows us to retrieve mappings based on the
177      * following combinations:
178      * <ul>
179      * <li>A single stereotype no context</li>
180      * <li>A single stereotype with a context</li>
181      * <li>metafacade properties no context</li>
182      * <li>metafacade properties with a context</code>
183      * <li>multiple stereotypes no context</li>
184      * <li>multiple stereotypes with a context</li>
185      * </ul>
186      * </p>
187      * <p> NOTE: mapping properties are inherited from super metafacades.
188      * </p>
189      *
190      * @param mappingObject an instance of the class to which the mapping
191      *        applies.
192      * @param stereotypes the stereotypes to check.
193      * @param context the context within the namespace for which the mapping
194      *        applies (has 'root' in the name because of the fact that we also
195      *        search the context inheritance hierarchy started with this 'root'
196      *        context).
197      * @return MetafacadeMapping (or null if none was found matching the
198      *         criteria).
199      */
200     protected MetafacadeMapping getMapping(
201         final Object mappingObject,
202         final String context,
203         final Collection<String> stereotypes)
204     {
205         MetafacadeMapping mapping = this.getMapping(
206                 null,
207                 mappingObject,
208                 context,
209                 stereotypes);
210         if (mapping == null)
211         {
212             final Collection<String> hierarchy = this.getMappingObjectHierarchy(mappingObject);
213             if (hierarchy != null && !hierarchy.isEmpty())
214             {
215                 for (final Iterator<String> iterator = hierarchy.iterator(); iterator.hasNext() && mapping == null;)
216                 {
217                     mapping =
218                         this.getMapping(
219                             iterator.next(),
220                             mappingObject,
221                             context,
222                             stereotypes);
223                 }
224             }
225         }
226         return mapping;
227     }
228 
229     /**
230      * The cache containing the hierarchies for each mapping object so that we
231      * don't need to retrieve more than once.
232      */
233     private final Map<Object, Collection<String>> mappingObjectHierarchyCache = new HashMap<Object, Collection<String>>();
234 
235     /**
236      * The pattern used for substituting the package name.
237      */
238     private static final String METAFACADE_PACKAGE_REPLACE_PATTERN = "\\{0\\}";
239 
240     /**
241      * The pattern used for substituting the metafacade name.
242      */
243     private static final String METAFACADE_NAME_REPLACE_PATTERN = "\\{1\\}";
244 
245     /**
246      * Retrieves the hierarchy of class names of the given
247      * <code>mappingObject</code>.
248      *
249      * @param mappingObject the object from which to retrieve the hierarchy.
250      * @return a list containing all inherited class names.
251      */
252     protected Collection<String> getMappingObjectHierarchy(final Object mappingObject)
253     {
254         Collection<String> hierarchy = this.mappingObjectHierarchyCache.get(mappingObject);
255         if (hierarchy == null)
256         {
257             // - we construct the mapping object name from the metafacade interface
258             //  (using the underlying UML implementation name pattern).
259             final String pattern = this.getMetaclassPattern();
260             if (StringUtils.isNotBlank(pattern))
261             {
262                 hierarchy = new ArrayList<String>();
263                 List<Class> metafacadeInterfaces = ClassUtils.getAllInterfaces(mappingObject.getClass());
264                 for (final Class metafacadeInterface : metafacadeInterfaces)
265                 {
266                     final String packageName = ClassUtils.getPackageName(metafacadeInterface);
267                     final String name = ClassUtils.getShortClassName(metafacadeInterface);
268 
269                     // - replace references {0} with the package name and
270                     //   references of {1} with the name of the class
271                     final String metafacadeImplementationName =
272                         pattern != null
273                         ? pattern.replaceAll(
274                             METAFACADE_PACKAGE_REPLACE_PATTERN,
275                             packageName).replaceAll(
276                             METAFACADE_NAME_REPLACE_PATTERN,
277                             name) : metafacadeInterface.getName();
278                     hierarchy.add(metafacadeImplementationName);
279                 }
280                 this.mappingObjectHierarchyCache.put(
281                     mappingObject,
282                     hierarchy);
283             }
284         }
285         return hierarchy;
286     }
287 
288     /**
289      * <p>
290      * Stores the mappings which are currently "in process" (within the
291      * {@link #getMapping(Object, String, Collection)}. This means the mapping
292      * is being processed by the {@link #getMapping(Object, String, Collection)}
293      * operation. We store these "in process" mappings in order to keep track of
294      * the mappings currently being evaluated so we avoid stack over flow errors
295      * {@link #getMapping(Object, String, Collection)}when finding mappings
296      * that are mapped to super metafacade properties.
297      * </p>
298      * <p>
299      * Note: visibility is defined as <code>protected</code> in order to
300      * improve inner class access performance.
301      * </p>
302      */
303     protected final Collection<MetafacadeMapping> inProcessMappings = new ArrayList<MetafacadeMapping>();
304 
305     /**
306      * <p>
307      * Stores the metafacades which are currently "in process" (within the
308      * {@link #getMapping(Object, String, Collection)}. This means the
309      * metafacade being processed by the {@link #getMapping(Object, String,
310      * Collection)}operation. We store these "in process" metafacades in order
311      * to keep track of the metafacades currently being evaluated so we avoid
312      * stack over flow errors {@link #getMapping(Object, String, Collection)}when
313      * finding metafacades that are mapped to super metafacade properties.
314      * </p>
315      * <p>
316      * Note: visibility is defined as <code>protected</code> in order to
317      * improve inner class access performance.
318      * </p>
319      */
320     protected final Collection<MetafacadeBase> inProcessMetafacades = new ArrayList<MetafacadeBase>();
321 
322     /**
323      * <p>
324      * Retrieves the MetafacadeMapping belonging to the unique <code>key</code>
325      * created from the <code>mappingObject</code>'s class,
326      * <code>context</code> and given <code>stereotypes</code>. It's
327      * <strong>IMPORTANT </strong> to note that contexts have a higher priority
328      * than stereotypes. This allows us to retrieve mappings based on the
329      * following combinations:
330      * <ul>
331      * <li>A single stereotype no context</li>
332      * <li>A single stereotype with a context</li>
333      * <li>metafacade properties no context</li>
334      * <li>metafacade properties with a context</li>
335      * <li>multiple stereotypes no context</li>
336      * <li>multiple stereotypes with a context</li>
337      * </ul>
338      * </p>
339      * <p>
340      * NOTE: mapping properties are inherited from super metafacades.
341      * </p>
342      *
343      * @param mappingClassName the name of the mapping class to use instead of
344      *        the actual class name taken from the <code>mappingObject</code>.
345      *        If null then the class name from the <code>mappingObject</code>
346      *        is used.
347      * @param mappingObject an instance of the class to which the mapping
348      *        applies.
349      * @param stereotypes the stereotypes to check.
350      * @param context the context within the namespace for which the mapping
351      *        applies (has 'root' in the name because of the fact that we also
352      *        search the context inheritance hierarchy started with this 'root'
353      *        context).
354      * @return MetafacadeMapping (or null if none was found matching the
355      *         criteria).
356      */
357     private MetafacadeMapping getMapping(
358         final String mappingClassName,
359         final Object mappingObject,
360         final String context,
361         final Collection<String> stereotypes)
362     {
363         final String metaclassName = mappingClassName != null ? mappingClassName : mappingObject.getClass().getName();
364 
365         // - Verify we can at least find the meta class, so we don't perform the
366         //   rest of the search for nothing
367         final boolean validMetaclass =
368             this.findMapping(
369                 new Condition()
370                 {
371                     public boolean evaluate(final MetafacadeMapping mapping)
372                     {
373                         return mapping.getMappingClassNames().contains(metaclassName);
374                     }
375                 }) != null;
376         MetafacadeMapping mapping = null;
377         if (validMetaclass)
378         {
379             final boolean emptyStereotypes = stereotypes == null || stereotypes.isEmpty();
380 
381             // - first try to find the mapping by context and stereotypes
382             if (context != null && !emptyStereotypes)
383             {
384                 mapping =
385                     this.findMapping(
386                         new Condition()
387                         {
388                             public boolean evaluate(final MetafacadeMapping mapping)
389                             {
390                                 boolean valid = false;
391                                 if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
392                                     mapping.hasStereotypes() && !mapping.hasMappingProperties())
393                                 {
394                                     valid =
395                                         getContextHierarchy(context).contains(mapping.getContext()) &&
396                                         stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
397                                 }
398                                 return valid;
399                             }
400                         });
401             }
402 
403             // - check for context and metafacade properties
404             if (mapping == null && context != null)
405             {
406                 mapping =
407                     this.findMapping(
408                         new Condition()
409                         {
410                             public boolean evaluate(final MetafacadeMapping mapping)
411                             {
412                                 boolean valid = false;
413                                 if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
414                                     mapping.hasContext() && mapping.hasMappingProperties() &&
415                                     !inProcessMappings.contains(mapping))
416                                 {
417                                     if (getContextHierarchy(context).contains(mapping.getContext()))
418                                     {
419                                         inProcessMappings.add(mapping);
420                                         final MetafacadeBase metafacade =
421                                             MetafacadeFactory.getInstance().createMetafacade(
422                                                 mappingObject,
423                                                 mapping);
424                                         inProcessMetafacades.add(metafacade);
425 
426                                         // reset the "in process" mappings
427                                         inProcessMappings.clear();
428                                         valid =
429                                             MetafacadeUtils.propertiesValid(
430                                                 metafacade,
431                                                 mapping);
432                                     }
433                                 }
434                                 return valid;
435                             }
436                         });
437             }
438 
439             // - check just the context alone
440             if (mapping == null && context != null)
441             {
442                 mapping =
443                     this.findMapping(
444                         new Condition()
445                         {
446                             public boolean evaluate(final MetafacadeMapping mapping)
447                             {
448                                 boolean valid = false;
449                                 if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
450                                     !mapping.hasStereotypes() && !mapping.hasMappingProperties())
451                                 {
452                                     valid = getContextHierarchy(context).contains(mapping.getContext());
453                                 }
454                                 return valid;
455                             }
456                         });
457             }
458 
459             // check only stereotypes
460             if (mapping == null && !emptyStereotypes)
461             {
462                 mapping =
463                     this.findMapping(
464                         new Condition()
465                         {
466                             public boolean evaluate(final MetafacadeMapping mapping)
467                             {
468                                 boolean valid = false;
469                                 if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasStereotypes() &&
470                                     !mapping.hasContext() && !mapping.hasMappingProperties())
471                                 {
472                                     valid = stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
473                                 }
474                                 return valid;
475                             }
476                         });
477             }
478 
479             // - now check for metafacade properties
480             if (mapping == null)
481             {
482                 mapping =
483                     this.findMapping(
484                         new Condition()
485                         {
486                             public boolean evaluate(final MetafacadeMapping mapping)
487                             {
488                                 boolean valid = false;
489                                 if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
490                                     !mapping.hasContext() && mapping.hasMappingProperties() &&
491                                     (!inProcessMappings.contains(mapping)))
492                                 {
493                                     inProcessMappings.add(mapping);
494                                     final MetafacadeBase metafacade =
495                                         MetafacadeFactory.getInstance().createMetafacade(
496                                             mappingObject,
497                                             mapping);
498                                     inProcessMetafacades.add(metafacade);
499 
500                                     // reset the "in process" mappings
501                                     inProcessMappings.clear();
502                                     valid =
503                                         MetafacadeUtils.propertiesValid(
504                                             metafacade,
505                                             mapping);
506                                 }
507                                 return valid;
508                             }
509                         });
510             }
511 
512             // - finally find the mapping with just the class
513             if (mapping == null)
514             {
515                 mapping =
516                     this.findMapping(
517                         new Condition()
518                         {
519                             public boolean evaluate(final MetafacadeMapping mapping)
520                             {
521                                 return mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasContext() &&
522                                 !mapping.hasStereotypes() && !mapping.hasMappingProperties();
523                             }
524                         });
525             }
526         }
527 
528         // - if it's still null, try with the parent
529         if (mapping == null && this.getParent() != null)
530         {
531             mapping =
532                 this.getParent().getMapping(
533                     metaclassName,
534                     mappingObject,
535                     context,
536                     stereotypes);
537         }
538 
539         // - reset the "in process" metafacades
540         this.inProcessMetafacades.clear();
541         return mapping;
542     }
543 
544     /**
545      * Finds the first mapping in the internal {@link #mappings} collection that
546      * matches the given condition.
547      *
548      * @param condition the condition
549      * @return the found mapping instance
550      */
551     private MetafacadeMapping findMapping(final Condition condition)
552     {
553         MetafacadeMapping found = null;
554         for (final MetafacadeMapping mapping : this.mappings)
555         {
556             if (condition.evaluate(mapping))
557             {
558                 found = mapping;
559                 break;
560             }
561         }
562         return found;
563     }
564 
565     /**
566      * Provides a means to evaluate whether or not a condition is true.
567      */
568     static interface Condition
569     {
570         /**
571          * @param mapping
572          * @return true/false
573          */
574         public boolean evaluate(final MetafacadeMapping mapping);
575     }
576 
577     /**
578      * <p>
579      * Loads all property references into the given <code>mapping</code>
580      * inherited from any super metafacade of the given mapping's metafacade.
581      * </p>
582      *
583      * @param mapping the MetafacadeMapping to which we'll add the inherited
584      *        property references.
585      */
586     private void loadInheritedPropertyReferences(final MetafacadeMapping mapping)
587     {
588         if (mapping != null)
589         {
590             final Class[] interfaces = this.getInterfacesReversed(mapping.getMetafacadeClass().getName());
591             if (interfaces != null && interfaces.length > 0)
592             {
593                 for (final Class metafacadeClass : interfaces)
594                 {
595                     final MetafacadeMapping contextMapping =
596                             this.mappingsByMetafacadeClass.get(metafacadeClass);
597                     if (contextMapping != null)
598                     {
599                         // add all property references
600                         mapping.addPropertyReferences(contextMapping.getPropertyReferences());
601                     }
602                 }
603             }
604         }
605     }
606 
607     /**
608      * The cache containing the hierarchies for each context so that we don't
609      * need to retrieve more than once.
610      */
611     private final Map<String, List<String>> contextHierarchyCache = new HashMap<String, List<String>>();
612 
613     /**
614      * Retrieves all inherited contexts (including the root <code>context</code>)
615      * from the given <code>context</code> and returns a list containing all
616      * of them. Note that the visibility of this operation is protected to
617      * improve inner class access performance.
618      *
619      * @param context the root contexts
620      * @return a list containing all inherited contexts
621      */
622     protected final List<String> getContextHierarchy(final String context)
623     {
624         List<String> contexts = this.contextHierarchyCache.get(context);
625         if (contexts == null)
626         {
627             final List<Class> interfaces = ClassUtils.getInterfaces(context);
628             contexts = new ArrayList<String>(interfaces.size());
629             for (Class anInterface : interfaces)
630             {
631                 contexts.add(anInterface.getName());
632             }
633             this.contextHierarchyCache.put(
634                 context,
635                 contexts);
636         }
637         return contexts;
638     }
639 
640     /**
641      * The cache of interfaces for the given className in reversed order.
642      */
643     private final Map<String, Class[]> reversedInterfaceArrayCache = new HashMap<String, Class[]>();
644 
645     /**
646      * Gets the interfaces for the given <code>className</code> in reverse
647      * order.
648      *
649      * @param className the name of the class for which to retrieve the
650      *        interfaces
651      * @return the array containing the reversed interfaces.
652      */
653     private Class[] getInterfacesReversed(final String className)
654     {
655         Class[] interfaces = this.reversedInterfaceArrayCache.get(className);
656         if (interfaces == null)
657         {
658             interfaces = ClassUtils.getInterfacesReversed(className);
659             this.reversedInterfaceArrayCache.put(
660                 className,
661                 interfaces);
662         }
663         return interfaces;
664     }
665 
666     /**
667      * Adds a language mapping reference. This are used to populate metafacade
668      * impl classes with mapping files (such as those that map from model types
669      * to Java, JDBC, SQL types, etc). If its added here as opposed to each
670      * child MetafacadeMapping, then the reference will apply to all mappings.
671      *
672      * @param reference the name of the reference.
673      */
674     public void addPropertyReference(final String reference)
675     {
676         this.propertyReferences.add(reference);
677     }
678 
679     /**
680      * <p> Attempts to get the MetafacadeMapping identified by the given
681      * <code>mappingClass</code>,<code>context</code> and
682      * <code>stereotypes<code>, from the mappings for the given <code>namespace</code>. If it can <strong>not</strong>
683      * be found, it will search the default mappings and return that instead. </p>
684      * <p>
685      * <strong>IMPORTANT:</strong> The <code>context</code> will take precedence over any <code>stereotypes</code> with
686      * the mapping. </p>
687      *
688      * @param mappingObject the meta object for the mapping we are trying to find.
689      * @param namespace the namespace (i.e. a cartridge, name, etc.)
690      * @param context to which the mapping applies (note this takes precedence over stereotypes).
691      * @param stereotypes collection of stereotype names.  We'll check to see if the mapping for the given
692      *                    <code>mappingClass</code> is defined for it.
693      * @return mapping
694      */
695     public MetafacadeMapping getMetafacadeMapping(
696         final Object mappingObject,
697         final String namespace,
698         final String context,
699         final Collection<String> stereotypes)
700     {
701         if (this.getLogger().isDebugEnabled())
702         {
703             this.getLogger().debug(
704                 "performing 'MetafacadeMappings.getMetafacadeMapping' with mappingObject '" + mappingObject +
705                 "', stereotypes '" + stereotypes + "', namespace '" + namespace + "' and context '" + context + '\'');
706         }
707 
708         MetafacadeMapping mapping = null;
709 
710         final MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
711 
712         // first try the namespace mappings
713         if (mappings != null)
714         {
715             // - set the parent namespace
716             mappings.parentNamespace = this.getNamespace();
717             mapping =
718                 mappings.getMapping(
719                     mappingObject,
720                     context,
721                     stereotypes);
722         }
723 
724         // - if we've found a namespace mapping, try to get any shared mappings
725         //   that this namespace mapping may extend and copy over any property
726         //   references from the shared mapping to the namespace mapping.
727         if (mapping != null)
728         {
729             final Collection<String> propertyReferences = mapping.getPropertyReferences();
730             final MetafacadeMapping defaultMapping = this.getMapping(
731                     mappingObject,
732                     context,
733                     stereotypes);
734             if (defaultMapping != null)
735             {
736                 Collection<String> defaultPropertyReferences = defaultMapping.getPropertyReferences();
737                 final Class metafacadeInterface =
738                     this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName());
739                 final Class defaultMetafacadeInterface =
740                     this.metafacadeClasses.getMetafacadeClass(defaultMapping.getMetafacadeClass().getName());
741                 if (defaultMetafacadeInterface.isAssignableFrom(metafacadeInterface))
742                 {
743                     mapping.addPropertyReferences(defaultPropertyReferences);
744 
745                     // add the namespace property references back so
746                     // that the default ones don't override the
747                     // namespace specific ones.
748                     mapping.addPropertyReferences(propertyReferences);
749                 }
750             }
751         }
752 
753         // if the namespace mappings weren't found, try the default
754         if (mapping == null)
755         {
756             if (this.getLogger().isDebugEnabled())
757             {
758                 this.getLogger().debug("namespace mapping not found --> finding default");
759             }
760             mapping =
761                 this.getMapping(
762                     mappingObject,
763                     context,
764                     stereotypes);
765         }
766 
767         if (this.getLogger().isDebugEnabled())
768         {
769             this.getLogger().debug("found mapping --> '" + mapping + '\'');
770         }
771         return mapping;
772     }
773 
774     /**
775      * Gets the MetafacadeMappings instance belonging to the
776      * <code>namespace</code>.
777      *
778      * @param namespace the namespace name to check.
779      * @return the found MetafacadeMappings.
780      */
781     private MetafacadeMappings getNamespaceMappings(final String namespace)
782     {
783         return this.namespaceMetafacadeMappings.get(namespace);
784     }
785 
786     /**
787      * Stores the possible parents of this metafacade mappings instance (i.e. mappings for uml-1.4, emf-uml2, etc).
788      */
789     private Map<String, MetafacadeMappings> parents = new HashMap<String, MetafacadeMappings>();
790 
791     /**
792      * Retrieves the appropriate parent based on the current {@link #getNamespace()}.
793      *
794      * @return the parent metafacade mappings.
795      */
796     private MetafacadeMappings getParent()
797     {
798         return this.parents.get(this.parentNamespace);
799     }
800 
801     /**
802      * Adds a MetafacadeMappings instance to the namespace metafacade mappings
803      * of this instance.
804      *
805      * @param namespace the namespace name to which the <code>mappings</code>
806      *        will belong.
807      * @param mappings the MetafacadeMappings instance to add.
808      */
809     private void addNamespaceMappings(
810         final String namespace,
811         final MetafacadeMappings mappings)
812     {
813         if (mappings != null)
814         {
815             // - set the parent by its namespace (the parent is different depending on the current metafacade model namespace)
816             mappings.parents.put(
817                 this.getNamespace(),
818                 this);
819             this.namespaceMetafacadeMappings.put(
820                 namespace,
821                 mappings);
822         }
823     }
824 
825     /**
826      * Initializes this mappings instance, which includes discovery of all
827      * metafacade mappings instances on the classpath.
828      */
829     public void initialize()
830     {
831         final List<String> modelTypeNamespaces = new ArrayList<String>();
832         final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
833         for (final MetafacadeMappings mappings : metafacades)
834         {
835             final String namespace = mappings.getNamespace();
836             if (MetafacadeUtils.isMetafacadeModelPresent(namespace))
837             {
838                 modelTypeNamespaces.add(namespace);
839             }
840         }
841 
842         final String[] modelNamespaces = modelTypeNamespaces.toArray(new String[modelTypeNamespaces.size()]);
843         MetafacadeImpls.instance().discover(modelNamespaces);
844         this.initializeMappings(modelNamespaces);
845     }
846 
847     /**
848      * Registers all namespace properties in the shared {@link MetafacadeFactory} instance.
849      */
850     final void registerAllProperties()
851     {
852         // - register all namespace property references defined in the descriptors
853         final Namespaces namespaces = Namespaces.instance();
854         for (Namespace namespace1 : namespaces.getNamespaces())
855         {
856             final String mappingsNamespace = namespace1.getName();
857 
858             // - add the default mappings
859             final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>(this.mappings);
860             final MetafacadeMappings metafacadeMappings = this.getNamespaceMappings(mappingsNamespace);
861 
862             // - add all the references from the default namespace
863             final Collection<String> propertyReferences = new ArrayList<String>(this.propertyReferences);
864 
865             // - if we have namespace mappings, add them
866             if (metafacadeMappings != null)
867             {
868                 mappings.addAll(metafacadeMappings.mappings);
869                 propertyReferences.addAll(metafacadeMappings.propertyReferences);
870             }
871 
872             for (final MetafacadeMapping mapping : mappings)
873             {
874                 final String metafacadeInterface =
875                         this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName()).getName();
876 
877                 // - first register the references defined globally in the
878                 // descriptor for each interface
879                 // in the hierarchy
880                 final Class[] interfaces = this.getInterfacesReversed(metafacadeInterface);
881                 for (final Class anInterface : interfaces)
882                 {
883                     this.registerProperties(
884                             mappingsNamespace,
885                             propertyReferences,
886                             anInterface.getName());
887                 }
888 
889                 // - next register the references defined only within each mapping
890                 // - remember to first load the inherited property references
891                 //   into the mapping
892                 this.loadInheritedPropertyReferences(mapping);
893                 this.registerProperties(
894                         mappingsNamespace,
895                         mapping.getPropertyReferences(),
896                         metafacadeInterface);
897             }
898         }
899     }
900 
901     /**
902      * The name of the metaclass pattern.
903      */
904     private String metaclassPattern;
905 
906     /**
907      * First attempts to retrieve the metaclass pattern from this instance, and
908      * if not found, attempts to retrieve it from the parent instance (since the
909      * parent instance should always have been set at least once from a shared
910      * metafacades instance).
911      *
912      * @return the metaclass pattern.
913      */
914     private String getMetaclassPattern()
915     {
916         if (this.metaclassPattern == null && this.getParent() != null)
917         {
918             this.metaclassPattern = this.getParent().metaclassPattern;
919         }
920         return this.metaclassPattern;
921     }
922 
923     /**
924      * Sets the pattern of the metaclass implementations based on a metaclass
925      * interface name. This should only be set on a metafacade mappings
926      * instances that is marked as shared.
927      *
928      * @param metaclassPattern the pattern for the meta classes.
929      */
930     public void setMetaclassPattern(final String metaclassPattern)
931     {
932         this.metaclassPattern = metaclassPattern;
933     }
934 
935     /**
936      * Initializes all the metafacade mapping instances under the appropriate model type (defined
937      * in the <code>modelTypes</code> collection.
938      *
939      * @param metafacadeModelNamespaces a list of each namespace containing a metafacade model facade implementation.
940      */
941     private void initializeMappings(final String[] metafacadeModelNamespaces)
942     {
943         ExceptionUtils.checkNull(
944             "modelTypes",
945             metafacadeModelNamespaces);
946         final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
947 
948         // - we need to load up the allMetafacadeMappingInstances before we do
949         //   anything else
950         for (final MetafacadeMappings mappings : metafacades)
951         {
952             for (final MetafacadeMapping mapping : mappings.mappings)
953             {
954                 if (mapping.isMappingClassNamePresent())
955                 {
956                     Set<String> mappingClassNames = MetafacadeMappings.allMetafacadeMappingInstances.get(mapping.getMetafacadeClass());
957                     if (mappingClassNames == null)
958                     {
959                         mappingClassNames = new HashSet<String>();
960                         MetafacadeMappings.allMetafacadeMappingInstances.put(mapping.getMetafacadeClass(), mappingClassNames);
961                     }
962 
963                     mappingClassNames.addAll(mapping.getMappingClassNames());
964                 }
965             }
966         }
967 
968         final List<String> modelNamespaces = new ArrayList<String>(Arrays.asList(metafacadeModelNamespaces));
969         try
970         {
971             final Namespaces namespaces = Namespaces.instance();
972             for (final String modelNamespace : metafacadeModelNamespaces)
973             {
974                 if (modelNamespace != null)
975                 {
976                     // - remove the current model type so that we don't keep out the namespace
977                     //   that stores the metafacade model
978                     modelNamespaces.remove(modelNamespace);
979 
980                     MetafacadeMappings modelMetafacadeMappings =
981                             this.modelMetafacadeMappings.get(modelNamespace);
982                     if (modelMetafacadeMappings == null)
983                     {
984                         modelMetafacadeMappings = MetafacadeMappings.newInstance();
985 
986                         // - set the namespace
987                         modelMetafacadeMappings.setNamespace(modelNamespace);
988                         this.modelMetafacadeMappings.put(
989                                 modelNamespace,
990                                 modelMetafacadeMappings);
991                     }
992 
993                     for (final MetafacadeMappings mappings : metafacades)
994                     {
995                         final String namespace = mappings.getNamespace();
996 
997                         if (!modelNamespaces.contains(namespace))
998                         {
999                             // - if we have 'shared' mappings or only a single set available, they are copied
1000                             //   to this mappings instance.
1001                             if (namespaces.isShared(namespace) || metafacades.size() == 1)
1002                             {
1003                                 // - copy over any 'shared' mappings to this root instance
1004                                 modelMetafacadeMappings.copyMappings(mappings);
1005 
1006                                 // - set the metaclass pattern from the 'shared' or single
1007                                 //   instance of metafacades
1008                                 final String metaclassPattern = mappings.metaclassPattern;
1009                                 if (StringUtils.isNotBlank(metaclassPattern))
1010                                 {
1011                                     modelMetafacadeMappings.setMetaclassPattern(mappings.metaclassPattern);
1012                                 }
1013                             }
1014                             else
1015                             {
1016                                 // add all others as namespace mappings
1017                                 modelMetafacadeMappings.addNamespaceMappings(
1018                                         namespace,
1019                                         mappings);
1020                             }
1021                         }
1022                     }
1023 
1024                     // - add the metafacade model namespace back
1025                     modelNamespaces.add(modelNamespace);
1026                     if (StringUtils.isBlank(modelMetafacadeMappings.getNamespace()))
1027                     {
1028                         throw new MetafacadeMappingsException(
1029                                 "No shared metafacades found, please check your classpath, at least " +
1030                                         "one set of metafacades must be marked as 'shared'");
1031                     }
1032                     if (StringUtils.isBlank(modelMetafacadeMappings.metaclassPattern))
1033                     {
1034                         throw new MetafacadeMappingsException("At least one set of metafacades marked as shared " +
1035                                 "must have the 'metaclassPattern' attribute defined");
1036                     }
1037                 }
1038             }
1039         }
1040         catch (final Throwable throwable)
1041         {
1042             throw new MetafacadeMappingsException(throwable);
1043         }
1044         this.getLogger().debug("initializeMappings " + " size=" + MetafacadeMappings.allMetafacadeMappingInstances.size());
1045     }
1046 
1047     /**
1048      * Stores all metafacade mapping instances
1049      */
1050     private static final Map<Class, Set<String>> allMetafacadeMappingInstances = new HashMap<Class, Set<String>>();
1051 
1052     /**
1053      * Stores every metafacade mapping instance, this is used from
1054      * {@link MetafacadeUtils#getInheritedMappingClassNames(MetafacadeMapping)}.
1055      *
1056      * @return all metafacade mapping instances.
1057      */
1058     static Map<Class, Set<String>> getAllMetafacadeMappingInstances()
1059     {
1060         return allMetafacadeMappingInstances;
1061     }
1062 
1063     /**
1064      * The shared metafacade impls instance.
1065      */
1066     private MetafacadeImpls metafacadeClasses = MetafacadeImpls.instance();
1067 
1068     /**
1069      * Stores the metafacadeMapping instances by model type.
1070      */
1071     private Map<String, MetafacadeMappings> modelMetafacadeMappings = new LinkedHashMap<String, MetafacadeMappings>();
1072 
1073     /**
1074      * Should be used used instead of "this", retrieves the appropriate
1075      * metafacade mappings instance based on the current model type.
1076      *
1077      * @param metafacadeModelNamespace the namespace that contains a metafacade model facade implementation.
1078      * @return the {@link MetafacadeMappings} instance.
1079      */
1080     public MetafacadeMappings getModelMetafacadeMappings(final String metafacadeModelNamespace)
1081     {
1082         final MetafacadeMappings instance =
1083                 this.modelMetafacadeMappings.get(metafacadeModelNamespace);
1084         if (instance == null)
1085         {
1086             throw new MetafacadeMappingsException("Namespace '" + metafacadeModelNamespace +
1087                 "' is not a registered metafacade model namespace");
1088         }
1089         return instance;
1090     }
1091 
1092     /**
1093      * Stores the namespace of the parent mappings.
1094      */
1095     private String parentNamespace;
1096 
1097     /**
1098      * Gets the defaultMetafacadeClass, first looks for it in the namespace
1099      * mapping, if it can't find it it then takes the default mappings, setting.
1100      * @param namespace mapping to check for defaultMetafacadeClass
1101      * @return Returns the defaultMetafacadeClass.
1102      */
1103     final Class getDefaultMetafacadeClass(final String namespace)
1104     {
1105         Class defaultMetafacadeClass = null;
1106         MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
1107         if (mappings != null)
1108         {
1109             defaultMetafacadeClass = mappings.defaultMetafacadeClass;
1110         }
1111         if (defaultMetafacadeClass == null)
1112         {
1113             defaultMetafacadeClass = this.defaultMetafacadeClass;
1114         }
1115         return defaultMetafacadeClass;
1116     }
1117 
1118     /**
1119      * Sets the default metafacade class to use if no other is found for the
1120      * mapping class.
1121      *
1122      * @param defaultMetafacadeClass the default metafacade class.
1123      */
1124     public void setDefaultMetafacadeClass(final String defaultMetafacadeClass)
1125     {
1126         try
1127         {
1128             this.defaultMetafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(defaultMetafacadeClass));
1129         }
1130         catch (final Throwable throwable)
1131         {
1132             throw new MetafacadeMappingsException(throwable);
1133         }
1134     }
1135 
1136     /**
1137      * Retrieves all child {@link MetafacadeMapping} instances belonging to this
1138      * metafacade mappings instance.
1139      *
1140      * @return the collection of {@link MetafacadeMapping} instances
1141      */
1142     protected Collection<MetafacadeMapping> getMappings()
1143     {
1144         return this.mappings;
1145     }
1146 
1147     /**
1148      * Registers the defined property references properties in the metafacade
1149      * factory.
1150      *
1151      * @param propertyReferences the property references to register.
1152      * @param metafacadeName the name of the metafacade under which to register
1153      *        the properties.
1154      * @param namespace the namespace of the property reference.
1155      */
1156     final void registerProperties(
1157         final String namespace,
1158         final Collection<String> propertyReferences,
1159         final String metafacadeName)
1160     {
1161         final MetafacadeFactory factory = MetafacadeFactory.getInstance();
1162         for (final String reference : propertyReferences)
1163         {
1164             final String value = Namespaces.instance().getPropertyValue(
1165                     namespace,
1166                     reference);
1167             if (value != null)
1168             {
1169                 if (this.getLogger().isDebugEnabled())
1170                 {
1171                     this.getLogger().debug(
1172                             "setting context property '" + reference + "' with value '" + value + "' for namespace '" +
1173                                     namespace + "' on metafacade '" + metafacadeName + '\'');
1174                 }
1175             }
1176             factory.registerProperty(
1177                     namespace,
1178                     metafacadeName,
1179                     reference,
1180                     value);
1181         }
1182     }
1183 
1184     /**
1185      * Performs shutdown procedures for the factory. This should be called
1186      * <strong>ONLY</code> when {@link MetafacadeFactory#shutdown()}is called.
1187      */
1188     final void shutdown()
1189     {
1190         this.mappings.clear();
1191         this.inProcessMappings.clear();
1192         this.inProcessMetafacades.clear();
1193         this.namespaceMetafacadeMappings.clear();
1194         this.propertyReferences.clear();
1195         this.mappingObjectHierarchyCache.clear();
1196         this.mappingsByMetafacadeClass.clear();
1197         this.contextHierarchyCache.clear();
1198         this.reversedInterfaceArrayCache.clear();
1199         for (final MetafacadeMappings metafacadeMappings : this.modelMetafacadeMappings.values())
1200         {
1201             metafacadeMappings.shutdown();
1202         }
1203         this.modelMetafacadeMappings.clear();
1204     }
1205 
1206     /**
1207      * Returns the logger instance to be used for logging within this class.
1208      *
1209      * @return the plugin logger
1210      */
1211     private Logger getLogger()
1212     {
1213         return AndroMDALogger.getNamespaceLogger(this.getNamespace());
1214     }
1215 
1216     /**
1217      * @see Object#toString()
1218      */
1219     public String toString()
1220     {
1221         return super.toString() + '[' + this.getNamespace() + ']';
1222     }
1223 }