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 }