MetafacadeMappings.java
package org.andromda.core.metafacade;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.andromda.core.common.AndroMDALogger;
import org.andromda.core.common.ClassUtils;
import org.andromda.core.common.ComponentContainer;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.configuration.Namespace;
import org.andromda.core.configuration.Namespaces;
import org.andromda.core.namespace.BaseNamespaceComponent;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/**
* The Metafacade mapping class. Used to map <code>metafacade</code> objects
* to <code>metamodel</code> objects.
*
* @author Chad Brandon
* @author Bob Fields
* @see MetafacadeMapping
* @see org.andromda.core.common.XmlObjectFactory
*/
public class MetafacadeMappings
extends BaseNamespaceComponent
implements Serializable
{
private static final long serialVersionUID = 34L;
/**
* Holds the references to the child MetafacadeMapping instances.
*/
private final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>();
/**
* Holds the namespace MetafacadeMappings. This are child MetafacadeMappings
* keyed by namespace name.
*/
private final Map<String, MetafacadeMappings> namespaceMetafacadeMappings = new HashMap<String, MetafacadeMappings>();
/**
* The default meta facade to use when there isn't a mapping found.
*/
private Class defaultMetafacadeClass = null;
/**
* Constructs a new instance of this class.
*
* @return MetafacadeMappings
*/
public static MetafacadeMappings newInstance()
{
return new MetafacadeMappings();
}
/**
* Adds a MetafacadeMapping instance to the set of current mappings.
*
* @param mapping the MetafacadeMapping instance.
*/
public void addMapping(final MetafacadeMapping mapping)
{
ExceptionUtils.checkNull(
"mapping",
mapping);
ExceptionUtils.checkNull(
"mapping.metafacadeClass",
mapping.getMetafacadeClass());
mapping.setMetafacadeMappings(this);
// find any mappings that match, if they do we add the properties
// from that mapping to the existing matched mapping (so we only
// have one mapping containing properties that can be 'OR'ed together).
final MetafacadeMapping foundMapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping object)
{
return mapping.match(object);
}
});
if (foundMapping != null)
{
foundMapping.addMappingPropertyGroup(mapping.getMappingProperties());
}
else
{
this.mappings.add(mapping);
this.mappingsByMetafacadeClass.put(
this.getMetafacadeInterface(mapping.getMetafacadeClass()),
mapping);
}
}
/**
* Gets the class of the metafacade interface that belongs to the given
* <code>metafacadeClass</code>.
* @param metafacadeClass
*
* @return the metafacade interface Class.
*/
public Class getMetafacadeInterface(final Class metafacadeClass)
{
Class metafacadeInterface = null;
if (metafacadeClass != null)
{
metafacadeInterface = metafacadeClass;
final List<Class> interfaces = ClassUtils.getAllInterfaces(metafacadeClass);
if (interfaces != null && !interfaces.isEmpty())
{
metafacadeInterface = interfaces.iterator().next();
}
}
return metafacadeInterface;
}
/**
* Stores mappings by the metafacade class so that we can retrieve the
* inherited metafacade classes.
*/
private final Map<Class, MetafacadeMapping> mappingsByMetafacadeClass = new HashMap<Class, MetafacadeMapping>();
/**
* Copies all data from <code>mappings<code> to this instance.
*
* @param mappings the mappings to add
*/
private void copyMappings(final MetafacadeMappings mappings)
{
ExceptionUtils.checkNull(
"mappings",
mappings);
for (final MetafacadeMapping mapping : mappings.mappings)
{
this.addMapping(mapping);
}
final Collection<String> propertyReferences = mappings.getPropertyReferences();
if (propertyReferences != null && !propertyReferences.isEmpty())
{
this.propertyReferences.addAll(propertyReferences);
}
this.defaultMetafacadeClass = mappings.defaultMetafacadeClass;
}
/**
* Contains references to properties populated in the Namespaces.
*/
private final Collection<String> propertyReferences = new LinkedHashSet<String>();
/**
* Gets all property references defined in this mappings instance.
*
* @return the map of property references (names and values).
*/
public Collection<String> getPropertyReferences()
{
return this.propertyReferences;
}
/**
* <p> Retrieves the MetafacadeMapping belonging to the unique
* <code>key</code> created from the <code>mappingObject</code>'s
* class, <code>context</code> and given <code>stereotypes</code>. It's
* <strong>IMPORTANT </strong> to note that contexts have a higher priority
* than stereotypes. This allows us to retrieve mappings based on the
* following combinations:
* <ul>
* <li>A single stereotype no context</li>
* <li>A single stereotype with a context</li>
* <li>metafacade properties no context</li>
* <li>metafacade properties with a context</code>
* <li>multiple stereotypes no context</li>
* <li>multiple stereotypes with a context</li>
* </ul>
* </p>
* <p> NOTE: mapping properties are inherited from super metafacades.
* </p>
*
* @param mappingObject an instance of the class to which the mapping
* applies.
* @param stereotypes the stereotypes to check.
* @param context the context within the namespace for which the mapping
* applies (has 'root' in the name because of the fact that we also
* search the context inheritance hierarchy started with this 'root'
* context).
* @return MetafacadeMapping (or null if none was found matching the
* criteria).
*/
protected MetafacadeMapping getMapping(
final Object mappingObject,
final String context,
final Collection<String> stereotypes)
{
MetafacadeMapping mapping = this.getMapping(
null,
mappingObject,
context,
stereotypes);
if (mapping == null)
{
final Collection<String> hierarchy = this.getMappingObjectHierarchy(mappingObject);
if (hierarchy != null && !hierarchy.isEmpty())
{
for (final Iterator<String> iterator = hierarchy.iterator(); iterator.hasNext() && mapping == null;)
{
mapping =
this.getMapping(
iterator.next(),
mappingObject,
context,
stereotypes);
}
}
}
return mapping;
}
/**
* The cache containing the hierarchies for each mapping object so that we
* don't need to retrieve more than once.
*/
private final Map<Object, Collection<String>> mappingObjectHierarchyCache = new HashMap<Object, Collection<String>>();
/**
* The pattern used for substituting the package name.
*/
private static final String METAFACADE_PACKAGE_REPLACE_PATTERN = "\\{0\\}";
/**
* The pattern used for substituting the metafacade name.
*/
private static final String METAFACADE_NAME_REPLACE_PATTERN = "\\{1\\}";
/**
* Retrieves the hierarchy of class names of the given
* <code>mappingObject</code>.
*
* @param mappingObject the object from which to retrieve the hierarchy.
* @return a list containing all inherited class names.
*/
protected Collection<String> getMappingObjectHierarchy(final Object mappingObject)
{
Collection<String> hierarchy = this.mappingObjectHierarchyCache.get(mappingObject);
if (hierarchy == null)
{
// - we construct the mapping object name from the metafacade interface
// (using the underlying UML implementation name pattern).
final String pattern = this.getMetaclassPattern();
if (StringUtils.isNotBlank(pattern))
{
hierarchy = new ArrayList<String>();
List<Class> metafacadeInterfaces = ClassUtils.getAllInterfaces(mappingObject.getClass());
for (final Class metafacadeInterface : metafacadeInterfaces)
{
final String packageName = ClassUtils.getPackageName(metafacadeInterface);
final String name = ClassUtils.getShortClassName(metafacadeInterface);
// - replace references {0} with the package name and
// references of {1} with the name of the class
final String metafacadeImplementationName =
pattern != null
? pattern.replaceAll(
METAFACADE_PACKAGE_REPLACE_PATTERN,
packageName).replaceAll(
METAFACADE_NAME_REPLACE_PATTERN,
name) : metafacadeInterface.getName();
hierarchy.add(metafacadeImplementationName);
}
this.mappingObjectHierarchyCache.put(
mappingObject,
hierarchy);
}
}
return hierarchy;
}
/**
* <p>
* Stores the mappings which are currently "in process" (within the
* {@link #getMapping(Object, String, Collection)}. This means the mapping
* is being processed by the {@link #getMapping(Object, String, Collection)}
* operation. We store these "in process" mappings in order to keep track of
* the mappings currently being evaluated so we avoid stack over flow errors
* {@link #getMapping(Object, String, Collection)}when finding mappings
* that are mapped to super metafacade properties.
* </p>
* <p>
* Note: visibility is defined as <code>protected</code> in order to
* improve inner class access performance.
* </p>
*/
protected final Collection<MetafacadeMapping> inProcessMappings = new ArrayList<MetafacadeMapping>();
/**
* <p>
* Stores the metafacades which are currently "in process" (within the
* {@link #getMapping(Object, String, Collection)}. This means the
* metafacade being processed by the {@link #getMapping(Object, String,
* Collection)}operation. We store these "in process" metafacades in order
* to keep track of the metafacades currently being evaluated so we avoid
* stack over flow errors {@link #getMapping(Object, String, Collection)}when
* finding metafacades that are mapped to super metafacade properties.
* </p>
* <p>
* Note: visibility is defined as <code>protected</code> in order to
* improve inner class access performance.
* </p>
*/
protected final Collection<MetafacadeBase> inProcessMetafacades = new ArrayList<MetafacadeBase>();
/**
* <p>
* Retrieves the MetafacadeMapping belonging to the unique <code>key</code>
* created from the <code>mappingObject</code>'s class,
* <code>context</code> and given <code>stereotypes</code>. It's
* <strong>IMPORTANT </strong> to note that contexts have a higher priority
* than stereotypes. This allows us to retrieve mappings based on the
* following combinations:
* <ul>
* <li>A single stereotype no context</li>
* <li>A single stereotype with a context</li>
* <li>metafacade properties no context</li>
* <li>metafacade properties with a context</li>
* <li>multiple stereotypes no context</li>
* <li>multiple stereotypes with a context</li>
* </ul>
* </p>
* <p>
* NOTE: mapping properties are inherited from super metafacades.
* </p>
*
* @param mappingClassName the name of the mapping class to use instead of
* the actual class name taken from the <code>mappingObject</code>.
* If null then the class name from the <code>mappingObject</code>
* is used.
* @param mappingObject an instance of the class to which the mapping
* applies.
* @param stereotypes the stereotypes to check.
* @param context the context within the namespace for which the mapping
* applies (has 'root' in the name because of the fact that we also
* search the context inheritance hierarchy started with this 'root'
* context).
* @return MetafacadeMapping (or null if none was found matching the
* criteria).
*/
private MetafacadeMapping getMapping(
final String mappingClassName,
final Object mappingObject,
final String context,
final Collection<String> stereotypes)
{
final String metaclassName = mappingClassName != null ? mappingClassName : mappingObject.getClass().getName();
// - Verify we can at least find the meta class, so we don't perform the
// rest of the search for nothing
final boolean validMetaclass =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
return mapping.getMappingClassNames().contains(metaclassName);
}
}) != null;
MetafacadeMapping mapping = null;
if (validMetaclass)
{
final boolean emptyStereotypes = stereotypes == null || stereotypes.isEmpty();
// - first try to find the mapping by context and stereotypes
if (context != null && !emptyStereotypes)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
boolean valid = false;
if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
mapping.hasStereotypes() && !mapping.hasMappingProperties())
{
valid =
getContextHierarchy(context).contains(mapping.getContext()) &&
stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
}
return valid;
}
});
}
// - check for context and metafacade properties
if (mapping == null && context != null)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
boolean valid = false;
if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
mapping.hasContext() && mapping.hasMappingProperties() &&
!inProcessMappings.contains(mapping))
{
if (getContextHierarchy(context).contains(mapping.getContext()))
{
inProcessMappings.add(mapping);
final MetafacadeBase metafacade =
MetafacadeFactory.getInstance().createMetafacade(
mappingObject,
mapping);
inProcessMetafacades.add(metafacade);
// reset the "in process" mappings
inProcessMappings.clear();
valid =
MetafacadeUtils.propertiesValid(
metafacade,
mapping);
}
}
return valid;
}
});
}
// - check just the context alone
if (mapping == null && context != null)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
boolean valid = false;
if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasContext() &&
!mapping.hasStereotypes() && !mapping.hasMappingProperties())
{
valid = getContextHierarchy(context).contains(mapping.getContext());
}
return valid;
}
});
}
// check only stereotypes
if (mapping == null && !emptyStereotypes)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
boolean valid = false;
if (mapping.getMappingClassNames().contains(metaclassName) && mapping.hasStereotypes() &&
!mapping.hasContext() && !mapping.hasMappingProperties())
{
valid = stereotypes!=null && stereotypes.containsAll(mapping.getStereotypes());
}
return valid;
}
});
}
// - now check for metafacade properties
if (mapping == null)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
boolean valid = false;
if (mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasStereotypes() &&
!mapping.hasContext() && mapping.hasMappingProperties() &&
(!inProcessMappings.contains(mapping)))
{
inProcessMappings.add(mapping);
final MetafacadeBase metafacade =
MetafacadeFactory.getInstance().createMetafacade(
mappingObject,
mapping);
inProcessMetafacades.add(metafacade);
// reset the "in process" mappings
inProcessMappings.clear();
valid =
MetafacadeUtils.propertiesValid(
metafacade,
mapping);
}
return valid;
}
});
}
// - finally find the mapping with just the class
if (mapping == null)
{
mapping =
this.findMapping(
new Condition()
{
public boolean evaluate(final MetafacadeMapping mapping)
{
return mapping.getMappingClassNames().contains(metaclassName) && !mapping.hasContext() &&
!mapping.hasStereotypes() && !mapping.hasMappingProperties();
}
});
}
}
// - if it's still null, try with the parent
if (mapping == null && this.getParent() != null)
{
mapping =
this.getParent().getMapping(
metaclassName,
mappingObject,
context,
stereotypes);
}
// - reset the "in process" metafacades
this.inProcessMetafacades.clear();
return mapping;
}
/**
* Finds the first mapping in the internal {@link #mappings} collection that
* matches the given condition.
*
* @param condition the condition
* @return the found mapping instance
*/
private MetafacadeMapping findMapping(final Condition condition)
{
MetafacadeMapping found = null;
for (final MetafacadeMapping mapping : this.mappings)
{
if (condition.evaluate(mapping))
{
found = mapping;
break;
}
}
return found;
}
/**
* Provides a means to evaluate whether or not a condition is true.
*/
static interface Condition
{
/**
* @param mapping
* @return true/false
*/
public boolean evaluate(final MetafacadeMapping mapping);
}
/**
* <p>
* Loads all property references into the given <code>mapping</code>
* inherited from any super metafacade of the given mapping's metafacade.
* </p>
*
* @param mapping the MetafacadeMapping to which we'll add the inherited
* property references.
*/
private void loadInheritedPropertyReferences(final MetafacadeMapping mapping)
{
if (mapping != null)
{
final Class[] interfaces = this.getInterfacesReversed(mapping.getMetafacadeClass().getName());
if (interfaces != null && interfaces.length > 0)
{
for (final Class metafacadeClass : interfaces)
{
final MetafacadeMapping contextMapping =
this.mappingsByMetafacadeClass.get(metafacadeClass);
if (contextMapping != null)
{
// add all property references
mapping.addPropertyReferences(contextMapping.getPropertyReferences());
}
}
}
}
}
/**
* The cache containing the hierarchies for each context so that we don't
* need to retrieve more than once.
*/
private final Map<String, List<String>> contextHierarchyCache = new HashMap<String, List<String>>();
/**
* Retrieves all inherited contexts (including the root <code>context</code>)
* from the given <code>context</code> and returns a list containing all
* of them. Note that the visibility of this operation is protected to
* improve inner class access performance.
*
* @param context the root contexts
* @return a list containing all inherited contexts
*/
protected final List<String> getContextHierarchy(final String context)
{
List<String> contexts = this.contextHierarchyCache.get(context);
if (contexts == null)
{
final List<Class> interfaces = ClassUtils.getInterfaces(context);
contexts = new ArrayList<String>(interfaces.size());
for (Class anInterface : interfaces)
{
contexts.add(anInterface.getName());
}
this.contextHierarchyCache.put(
context,
contexts);
}
return contexts;
}
/**
* The cache of interfaces for the given className in reversed order.
*/
private final Map<String, Class[]> reversedInterfaceArrayCache = new HashMap<String, Class[]>();
/**
* Gets the interfaces for the given <code>className</code> in reverse
* order.
*
* @param className the name of the class for which to retrieve the
* interfaces
* @return the array containing the reversed interfaces.
*/
private Class[] getInterfacesReversed(final String className)
{
Class[] interfaces = this.reversedInterfaceArrayCache.get(className);
if (interfaces == null)
{
interfaces = ClassUtils.getInterfacesReversed(className);
this.reversedInterfaceArrayCache.put(
className,
interfaces);
}
return interfaces;
}
/**
* Adds a language mapping reference. This are used to populate metafacade
* impl classes with mapping files (such as those that map from model types
* to Java, JDBC, SQL types, etc). If its added here as opposed to each
* child MetafacadeMapping, then the reference will apply to all mappings.
*
* @param reference the name of the reference.
*/
public void addPropertyReference(final String reference)
{
this.propertyReferences.add(reference);
}
/**
* <p> Attempts to get the MetafacadeMapping identified by the given
* <code>mappingClass</code>,<code>context</code> and
* <code>stereotypes<code>, from the mappings for the given <code>namespace</code>. If it can <strong>not</strong>
* be found, it will search the default mappings and return that instead. </p>
* <p>
* <strong>IMPORTANT:</strong> The <code>context</code> will take precedence over any <code>stereotypes</code> with
* the mapping. </p>
*
* @param mappingObject the meta object for the mapping we are trying to find.
* @param namespace the namespace (i.e. a cartridge, name, etc.)
* @param context to which the mapping applies (note this takes precedence over stereotypes).
* @param stereotypes collection of stereotype names. We'll check to see if the mapping for the given
* <code>mappingClass</code> is defined for it.
* @return mapping
*/
public MetafacadeMapping getMetafacadeMapping(
final Object mappingObject,
final String namespace,
final String context,
final Collection<String> stereotypes)
{
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug(
"performing 'MetafacadeMappings.getMetafacadeMapping' with mappingObject '" + mappingObject +
"', stereotypes '" + stereotypes + "', namespace '" + namespace + "' and context '" + context + '\'');
}
MetafacadeMapping mapping = null;
final MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
// first try the namespace mappings
if (mappings != null)
{
// - set the parent namespace
mappings.parentNamespace = this.getNamespace();
mapping =
mappings.getMapping(
mappingObject,
context,
stereotypes);
}
// - if we've found a namespace mapping, try to get any shared mappings
// that this namespace mapping may extend and copy over any property
// references from the shared mapping to the namespace mapping.
if (mapping != null)
{
final Collection<String> propertyReferences = mapping.getPropertyReferences();
final MetafacadeMapping defaultMapping = this.getMapping(
mappingObject,
context,
stereotypes);
if (defaultMapping != null)
{
Collection<String> defaultPropertyReferences = defaultMapping.getPropertyReferences();
final Class metafacadeInterface =
this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName());
final Class defaultMetafacadeInterface =
this.metafacadeClasses.getMetafacadeClass(defaultMapping.getMetafacadeClass().getName());
if (defaultMetafacadeInterface.isAssignableFrom(metafacadeInterface))
{
mapping.addPropertyReferences(defaultPropertyReferences);
// add the namespace property references back so
// that the default ones don't override the
// namespace specific ones.
mapping.addPropertyReferences(propertyReferences);
}
}
}
// if the namespace mappings weren't found, try the default
if (mapping == null)
{
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug("namespace mapping not found --> finding default");
}
mapping =
this.getMapping(
mappingObject,
context,
stereotypes);
}
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug("found mapping --> '" + mapping + '\'');
}
return mapping;
}
/**
* Gets the MetafacadeMappings instance belonging to the
* <code>namespace</code>.
*
* @param namespace the namespace name to check.
* @return the found MetafacadeMappings.
*/
private MetafacadeMappings getNamespaceMappings(final String namespace)
{
return this.namespaceMetafacadeMappings.get(namespace);
}
/**
* Stores the possible parents of this metafacade mappings instance (i.e. mappings for uml-1.4, emf-uml2, etc).
*/
private Map<String, MetafacadeMappings> parents = new HashMap<String, MetafacadeMappings>();
/**
* Retrieves the appropriate parent based on the current {@link #getNamespace()}.
*
* @return the parent metafacade mappings.
*/
private MetafacadeMappings getParent()
{
return this.parents.get(this.parentNamespace);
}
/**
* Adds a MetafacadeMappings instance to the namespace metafacade mappings
* of this instance.
*
* @param namespace the namespace name to which the <code>mappings</code>
* will belong.
* @param mappings the MetafacadeMappings instance to add.
*/
private void addNamespaceMappings(
final String namespace,
final MetafacadeMappings mappings)
{
if (mappings != null)
{
// - set the parent by its namespace (the parent is different depending on the current metafacade model namespace)
mappings.parents.put(
this.getNamespace(),
this);
this.namespaceMetafacadeMappings.put(
namespace,
mappings);
}
}
/**
* Initializes this mappings instance, which includes discovery of all
* metafacade mappings instances on the classpath.
*/
public void initialize()
{
final List<String> modelTypeNamespaces = new ArrayList<String>();
final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
for (final MetafacadeMappings mappings : metafacades)
{
final String namespace = mappings.getNamespace();
if (MetafacadeUtils.isMetafacadeModelPresent(namespace))
{
modelTypeNamespaces.add(namespace);
}
}
final String[] modelNamespaces = modelTypeNamespaces.toArray(new String[modelTypeNamespaces.size()]);
MetafacadeImpls.instance().discover(modelNamespaces);
this.initializeMappings(modelNamespaces);
}
/**
* Registers all namespace properties in the shared {@link MetafacadeFactory} instance.
*/
final void registerAllProperties()
{
// - register all namespace property references defined in the descriptors
final Namespaces namespaces = Namespaces.instance();
for (Namespace namespace1 : namespaces.getNamespaces())
{
final String mappingsNamespace = namespace1.getName();
// - add the default mappings
final Collection<MetafacadeMapping> mappings = new ArrayList<MetafacadeMapping>(this.mappings);
final MetafacadeMappings metafacadeMappings = this.getNamespaceMappings(mappingsNamespace);
// - add all the references from the default namespace
final Collection<String> propertyReferences = new ArrayList<String>(this.propertyReferences);
// - if we have namespace mappings, add them
if (metafacadeMappings != null)
{
mappings.addAll(metafacadeMappings.mappings);
propertyReferences.addAll(metafacadeMappings.propertyReferences);
}
for (final MetafacadeMapping mapping : mappings)
{
final String metafacadeInterface =
this.metafacadeClasses.getMetafacadeClass(mapping.getMetafacadeClass().getName()).getName();
// - first register the references defined globally in the
// descriptor for each interface
// in the hierarchy
final Class[] interfaces = this.getInterfacesReversed(metafacadeInterface);
for (final Class anInterface : interfaces)
{
this.registerProperties(
mappingsNamespace,
propertyReferences,
anInterface.getName());
}
// - next register the references defined only within each mapping
// - remember to first load the inherited property references
// into the mapping
this.loadInheritedPropertyReferences(mapping);
this.registerProperties(
mappingsNamespace,
mapping.getPropertyReferences(),
metafacadeInterface);
}
}
}
/**
* The name of the metaclass pattern.
*/
private String metaclassPattern;
/**
* First attempts to retrieve the metaclass pattern from this instance, and
* if not found, attempts to retrieve it from the parent instance (since the
* parent instance should always have been set at least once from a shared
* metafacades instance).
*
* @return the metaclass pattern.
*/
private String getMetaclassPattern()
{
if (this.metaclassPattern == null && this.getParent() != null)
{
this.metaclassPattern = this.getParent().metaclassPattern;
}
return this.metaclassPattern;
}
/**
* Sets the pattern of the metaclass implementations based on a metaclass
* interface name. This should only be set on a metafacade mappings
* instances that is marked as shared.
*
* @param metaclassPattern the pattern for the meta classes.
*/
public void setMetaclassPattern(final String metaclassPattern)
{
this.metaclassPattern = metaclassPattern;
}
/**
* Initializes all the metafacade mapping instances under the appropriate model type (defined
* in the <code>modelTypes</code> collection.
*
* @param metafacadeModelNamespaces a list of each namespace containing a metafacade model facade implementation.
*/
private void initializeMappings(final String[] metafacadeModelNamespaces)
{
ExceptionUtils.checkNull(
"modelTypes",
metafacadeModelNamespaces);
final Collection<MetafacadeMappings> metafacades = ComponentContainer.instance().findComponentsOfType(MetafacadeMappings.class);
// - we need to load up the allMetafacadeMappingInstances before we do
// anything else
for (final MetafacadeMappings mappings : metafacades)
{
for (final MetafacadeMapping mapping : mappings.mappings)
{
if (mapping.isMappingClassNamePresent())
{
Set<String> mappingClassNames = MetafacadeMappings.allMetafacadeMappingInstances.get(mapping.getMetafacadeClass());
if (mappingClassNames == null)
{
mappingClassNames = new HashSet<String>();
MetafacadeMappings.allMetafacadeMappingInstances.put(mapping.getMetafacadeClass(), mappingClassNames);
}
mappingClassNames.addAll(mapping.getMappingClassNames());
}
}
}
final List<String> modelNamespaces = new ArrayList<String>(Arrays.asList(metafacadeModelNamespaces));
try
{
final Namespaces namespaces = Namespaces.instance();
for (final String modelNamespace : metafacadeModelNamespaces)
{
if (modelNamespace != null)
{
// - remove the current model type so that we don't keep out the namespace
// that stores the metafacade model
modelNamespaces.remove(modelNamespace);
MetafacadeMappings modelMetafacadeMappings =
this.modelMetafacadeMappings.get(modelNamespace);
if (modelMetafacadeMappings == null)
{
modelMetafacadeMappings = MetafacadeMappings.newInstance();
// - set the namespace
modelMetafacadeMappings.setNamespace(modelNamespace);
this.modelMetafacadeMappings.put(
modelNamespace,
modelMetafacadeMappings);
}
for (final MetafacadeMappings mappings : metafacades)
{
final String namespace = mappings.getNamespace();
if (!modelNamespaces.contains(namespace))
{
// - if we have 'shared' mappings or only a single set available, they are copied
// to this mappings instance.
if (namespaces.isShared(namespace) || metafacades.size() == 1)
{
// - copy over any 'shared' mappings to this root instance
modelMetafacadeMappings.copyMappings(mappings);
// - set the metaclass pattern from the 'shared' or single
// instance of metafacades
final String metaclassPattern = mappings.metaclassPattern;
if (StringUtils.isNotBlank(metaclassPattern))
{
modelMetafacadeMappings.setMetaclassPattern(mappings.metaclassPattern);
}
}
else
{
// add all others as namespace mappings
modelMetafacadeMappings.addNamespaceMappings(
namespace,
mappings);
}
}
}
// - add the metafacade model namespace back
modelNamespaces.add(modelNamespace);
if (StringUtils.isBlank(modelMetafacadeMappings.getNamespace()))
{
throw new MetafacadeMappingsException(
"No shared metafacades found, please check your classpath, at least " +
"one set of metafacades must be marked as 'shared'");
}
if (StringUtils.isBlank(modelMetafacadeMappings.metaclassPattern))
{
throw new MetafacadeMappingsException("At least one set of metafacades marked as shared " +
"must have the 'metaclassPattern' attribute defined");
}
}
}
}
catch (final Throwable throwable)
{
throw new MetafacadeMappingsException(throwable);
}
this.getLogger().debug("initializeMappings " + " size=" + MetafacadeMappings.allMetafacadeMappingInstances.size());
}
/**
* Stores all metafacade mapping instances
*/
private static final Map<Class, Set<String>> allMetafacadeMappingInstances = new HashMap<Class, Set<String>>();
/**
* Stores every metafacade mapping instance, this is used from
* {@link MetafacadeUtils#getInheritedMappingClassNames(MetafacadeMapping)}.
*
* @return all metafacade mapping instances.
*/
static Map<Class, Set<String>> getAllMetafacadeMappingInstances()
{
return allMetafacadeMappingInstances;
}
/**
* The shared metafacade impls instance.
*/
private MetafacadeImpls metafacadeClasses = MetafacadeImpls.instance();
/**
* Stores the metafacadeMapping instances by model type.
*/
private Map<String, MetafacadeMappings> modelMetafacadeMappings = new LinkedHashMap<String, MetafacadeMappings>();
/**
* Should be used used instead of "this", retrieves the appropriate
* metafacade mappings instance based on the current model type.
*
* @param metafacadeModelNamespace the namespace that contains a metafacade model facade implementation.
* @return the {@link MetafacadeMappings} instance.
*/
public MetafacadeMappings getModelMetafacadeMappings(final String metafacadeModelNamespace)
{
final MetafacadeMappings instance =
this.modelMetafacadeMappings.get(metafacadeModelNamespace);
if (instance == null)
{
throw new MetafacadeMappingsException("Namespace '" + metafacadeModelNamespace +
"' is not a registered metafacade model namespace");
}
return instance;
}
/**
* Stores the namespace of the parent mappings.
*/
private String parentNamespace;
/**
* Gets the defaultMetafacadeClass, first looks for it in the namespace
* mapping, if it can't find it it then takes the default mappings, setting.
* @param namespace mapping to check for defaultMetafacadeClass
* @return Returns the defaultMetafacadeClass.
*/
final Class getDefaultMetafacadeClass(final String namespace)
{
Class defaultMetafacadeClass = null;
MetafacadeMappings mappings = this.getNamespaceMappings(namespace);
if (mappings != null)
{
defaultMetafacadeClass = mappings.defaultMetafacadeClass;
}
if (defaultMetafacadeClass == null)
{
defaultMetafacadeClass = this.defaultMetafacadeClass;
}
return defaultMetafacadeClass;
}
/**
* Sets the default metafacade class to use if no other is found for the
* mapping class.
*
* @param defaultMetafacadeClass the default metafacade class.
*/
public void setDefaultMetafacadeClass(final String defaultMetafacadeClass)
{
try
{
this.defaultMetafacadeClass = ClassUtils.loadClass(StringUtils.trimToEmpty(defaultMetafacadeClass));
}
catch (final Throwable throwable)
{
throw new MetafacadeMappingsException(throwable);
}
}
/**
* Retrieves all child {@link MetafacadeMapping} instances belonging to this
* metafacade mappings instance.
*
* @return the collection of {@link MetafacadeMapping} instances
*/
protected Collection<MetafacadeMapping> getMappings()
{
return this.mappings;
}
/**
* Registers the defined property references properties in the metafacade
* factory.
*
* @param propertyReferences the property references to register.
* @param metafacadeName the name of the metafacade under which to register
* the properties.
* @param namespace the namespace of the property reference.
*/
final void registerProperties(
final String namespace,
final Collection<String> propertyReferences,
final String metafacadeName)
{
final MetafacadeFactory factory = MetafacadeFactory.getInstance();
for (final String reference : propertyReferences)
{
final String value = Namespaces.instance().getPropertyValue(
namespace,
reference);
if (value != null)
{
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug(
"setting context property '" + reference + "' with value '" + value + "' for namespace '" +
namespace + "' on metafacade '" + metafacadeName + '\'');
}
}
factory.registerProperty(
namespace,
metafacadeName,
reference,
value);
}
}
/**
* Performs shutdown procedures for the factory. This should be called
* <strong>ONLY</code> when {@link MetafacadeFactory#shutdown()}is called.
*/
final void shutdown()
{
this.mappings.clear();
this.inProcessMappings.clear();
this.inProcessMetafacades.clear();
this.namespaceMetafacadeMappings.clear();
this.propertyReferences.clear();
this.mappingObjectHierarchyCache.clear();
this.mappingsByMetafacadeClass.clear();
this.contextHierarchyCache.clear();
this.reversedInterfaceArrayCache.clear();
for (final MetafacadeMappings metafacadeMappings : this.modelMetafacadeMappings.values())
{
metafacadeMappings.shutdown();
}
this.modelMetafacadeMappings.clear();
}
/**
* Returns the logger instance to be used for logging within this class.
*
* @return the plugin logger
*/
private Logger getLogger()
{
return AndroMDALogger.getNamespaceLogger(this.getNamespace());
}
/**
* @see Object#toString()
*/
public String toString()
{
return super.toString() + '[' + this.getNamespace() + ']';
}
}