MetafacadeFactory.java
package org.andromda.core.metafacade;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.andromda.core.common.AndroMDALogger;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.profile.Profile;
import org.apache.commons.collections.keyvalue.MultiKey;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/**
* The factory in charge of constructing Metafacade instances. In order for a
* metafacade (i.e. a facade around a meta model element) to be constructed, it
* must be constructed through this factory.
*
* @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
* @author Chad Brandon
* @author Peter Friese
* @author Bob Fields
*/
public final class MetafacadeFactory
implements Serializable
{
private static final long serialVersionUID = 34L;
/**
* Caches the registered properties used within metafacades.
*/
private final Map<String, Map<String, Map<String, Object>>> metafacadeNamespaces = new LinkedHashMap<String, Map<String, Map<String, Object>>>();
/**
* The shared instance of this factory.
*/
private static MetafacadeFactory instance = null;
private MetafacadeFactory()
{
// make sure that nobody instantiates it
}
/**
* Returns the facade factory singleton.
*
* @return the only instance
*/
public static MetafacadeFactory getInstance()
{
if (instance == null)
{
instance = new MetafacadeFactory();
}
return instance;
}
/**
* The metafacade cache for this factory.
*/
private final MetafacadeCache cache = MetafacadeCache.newInstance();
/**
* The metafacade mappings instance for this factory.
*/
private final MetafacadeMappings mappings = MetafacadeMappings.newInstance();
/**
* Performs any initialization required by the factory (i.e. discovering all
* <code>metafacade</code> mappings, etc).
*/
public void initialize()
{
this.mappings.initialize();
}
/**
* The shared profile instance.
*/
private final Profile profile = Profile.instance();
/**
* The namespace that is currently active (i.e. being used) within the factory
*/
private String namespace;
/**
* Sets the active namespace. The AndroMDA core and each cartridge have their own namespace for metafacade
* registration.
*
* @param namespace the name of the active namespace.
*/
public void setNamespace(final String namespace)
{
this.namespace = namespace;
this.profile.setNamespace(namespace);
this.cache.setNamespace(this.namespace);
}
/**
* Returns the name of the active namespace.
*
* @return String the namespace name
*/
public String getNamespace()
{
if (this.namespace == null)
{
throw new MetafacadeFactoryException("This metafacade factory's namespace must be populated before " +
"metafacade construction can occur");
}
return this.namespace;
}
/**
* Returns a metafacade for a mappingObject, depending on its
* <code>mappingClass</code> and (optionally) its <code>stereotypes</code>
* and <code>context</code>.
*
* @param mappingObject the object used to map the metafacade (a meta model
* object or a metafacade itself).
* @param context the name of the context the meta model element is
* registered under.
* @return the new metafacade
*/
public MetafacadeBase createMetafacade(
final Object mappingObject,
final String context)
{
return this.createMetafacade(
mappingObject,
context,
null);
}
/**
* Creates a metafacade given the <code>mappingObject</code>,
* <code>contextName</code> and <code>metafacadeClass</code>.
*
* @param mappingObject the object used to map the metafacade (a meta model
* object or a metafacade itself).
* @param context the name of the context the meta model element (if the
* mappObject is a meta model element) is registered under.
* @param metafacadeClass if not null, it contains the name of the
* metafacade class to be used. This is used ONLY when instantiating
* super metafacades in an inheritance chain. The final metafacade
* will NEVER have a metafacadeClass specified (it will ALWAYS be
* null).
* @return the new metafacade
*/
private MetafacadeBase createMetafacade(
final Object mappingObject,
final String context,
Class metafacadeClass)
{
final String methodName = "MetafacadeFactory.createMetafacade";
ExceptionUtils.checkNull(
"mappingObject",
mappingObject);
// - register the namespace properties (if they haven't been)
this.registerNamespaceProperties();
// if the mappingObject is REALLY a metafacade, just return it
if (mappingObject instanceof MetafacadeBase)
{
return (MetafacadeBase)mappingObject;
}
try
{
final Collection<String> stereotypes = this.getModel().getStereotypeNames(mappingObject);
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug("mappingObject stereotypes --> '" + stereotypes + '\'');
}
MetafacadeMapping mapping = null;
if (metafacadeClass == null)
{
final MetafacadeMappings modelMetafacadeMappings = this.getModelMetafacadeMappings();
mapping = modelMetafacadeMappings.getMetafacadeMapping(
mappingObject,
this.getNamespace(),
context,
stereotypes);
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug("mappingObject getModelMetafacadeMappings for " + mappingObject + " namespace " + this.getNamespace() + " context " + context);
}
if (mapping != null)
{
metafacadeClass = mapping.getMetafacadeClass();
}
else
{
// get the default since no mapping was found.
metafacadeClass = modelMetafacadeMappings.getDefaultMetafacadeClass(this.getNamespace());
if (this.getLogger().isDebugEnabled())
{
this.getLogger().warn(
"Meta object model class '" + mappingObject.getClass() +
"' has no corresponding meta facade class, default is being used --> '" + metafacadeClass +
'\'');
}
}
}
if (metafacadeClass == null)
{
throw new MetafacadeMappingsException(methodName + " metafacadeClass was not retrieved from mappings" +
" or specified as an argument in this method for mappingObject --> '" + mappingObject + '\'');
}
final MetafacadeBase metafacade = this.getMetafacade(
metafacadeClass,
mappingObject,
context,
mapping);
// IMPORTANT: initialize each metafacade ONLY once (otherwise we
// get stack overflow errors)
if (metafacade != null && !metafacade.isInitialized())
{
metafacade.setInitialized();
metafacade.initialize();
}
return metafacade;
}
catch (final Throwable throwable)
{
final String message =
"Failed to construct a meta facade of type '" + metafacadeClass + "' with mappingObject of type --> '" +
mappingObject.getClass() + '\'';
this.getLogger().error(message);
throw new MetafacadeFactoryException(message, throwable);
}
}
/**
* Gets the model metafacade mappings (the mappings that correspond
* to the current metafacade model namespace).
*
* @return the model metafacade mappings.
*/
private MetafacadeMappings getModelMetafacadeMappings()
{
return this.mappings.getModelMetafacadeMappings(this.metafacadeModelNamespace);
}
/**
* Validates all metafacades for the current namespace
* and collects the messages in the internal validation messages
* collection.
*
* @see #getValidationMessages()
*/
public void validateAllMetafacades()
{
for (final MetafacadeBase metafacadeBase : this.getAllMetafacades())
{
metafacadeBase.validate(this.validationMessages);
}
}
/**
* Creates a metafacade from the passed in <code>mappingObject</code>, and
* <code>mapping</code> instance.
*
* @param mappingObject the mapping object for which to create the
* metafacade.
* @param mapping the mapping from which to create the metafacade
* @return the metafacade, or null if it can't be created.
*/
protected MetafacadeBase createMetafacade(
final Object mappingObject,
final MetafacadeMapping mapping)
{
try
{
return this.getMetafacade(
mapping.getMetafacadeClass(),
mappingObject,
mapping.getContext(),
mapping);
}
catch (final Throwable throwable)
{
final String message =
"Failed to construct a meta facade of type '" + mapping.getMetafacadeClass() +
"' with mappingObject of type --> '" + mapping.getMappingClassNames() + '\'';
this.getLogger().error(message);
throw new MetafacadeFactoryException(message, throwable);
}
}
/**
* Retrieves (if one has already been constructed) or constructs a new
* <code>metafacade</code> from the given <code>metafacadeClass</code>
* and <code>mappingObject</code>.
*
* @param metafacadeClass the metafacade class.
* @param mappingObject the object to which the metafacade is mapped.
* @param context the context to which the metafacade applies
* @param mapping the optional MetafacadeMapping instance from which the
* metafacade is mapped.
* @return the new (or cached) metafacade.
* @throws Exception if any error occurs during metafacade creation
*/
private MetafacadeBase getMetafacade(
final Class metafacadeClass,
final Object mappingObject,
final String context,
final MetafacadeMapping mapping)
throws Exception
{
MetafacadeBase metafacade = this.cache.get(
mappingObject,
metafacadeClass);
if (metafacade == null)
{
final MultiKey key = new MultiKey(mappingObject, metafacadeClass);
if (!this.metafacadesInCreation.contains(key))
{
this.metafacadesInCreation.add(
key);
if (mapping != null && mapping.isContextRoot())
{
metafacade = MetafacadeUtils.constructMetafacade(
metafacadeClass,
mappingObject,
null);
// set whether or not this metafacade is a context root
metafacade.setContextRoot(mapping.isContextRoot());
}
else
{
metafacade = MetafacadeUtils.constructMetafacade(
metafacadeClass,
mappingObject,
context);
}
this.metafacadesInCreation.remove(key);
this.cache.add(
mappingObject,
metafacade);
}
}
if (metafacade != null)
{
// if the requested metafacadeClass is different from the one in the mapping, contextRoot should be reset
if (mapping != null && !mapping.getMetafacadeClass().equals(metafacadeClass))
{
metafacade.setContextRoot(false);
metafacade.resetMetafacadeContext(context);
}
// we need to set some things each time
// we change a metafacade's namespace
final String metafacadeNamespace = metafacade.getMetafacadeNamespace();
if (metafacadeNamespace == null || !metafacadeNamespace.equals(this.getNamespace()))
{
// assign the logger and active namespace
metafacade.setNamespace(this.getNamespace());
metafacade.setLogger(this.getLogger());
}
}
return metafacade;
}
/**
* Stores the metafacades being created, so that we don't get stuck in
* endless recursion during creation.
*/
private final Collection<MultiKey> metafacadesInCreation = new ArrayList<MultiKey>();
/**
* Returns a metafacade for a mappingObject, depending on its <code>mappingClass</code>.
*
* @param mappingObject the object which is used to map to the metafacade
* @return MetafacadeBase the facade object (not yet attached to mappingClass object)
*/
public MetafacadeBase createMetafacade(final Object mappingObject)
{
return this.createMetafacade(
mappingObject,
null,
null);
}
/**
* Create a facade implementation object for a mappingObject. The facade
* implementation object must be found in a way that it implements the
* interface <code>interfaceName</code>.
*
* @param interfaceName the name of the interface that the implementation
* object has to implement
* @param mappingObject the mappingObject for which a facade shall be
* created
* @param context the context in which this metafacade will be created.
* @return MetafacadeBase the metafacade
*/
public MetafacadeBase createFacadeImpl(
final String interfaceName,
final Object mappingObject,
final String context)
{
ExceptionUtils.checkEmpty(
"interfaceName",
interfaceName);
ExceptionUtils.checkNull(
"mappingObject",
mappingObject);
Class metafacadeClass = null;
try
{
metafacadeClass = this.metafacadeImpls.getMetafacadeImplClass(interfaceName);
return this.createMetafacade(
mappingObject,
context,
metafacadeClass);
}
catch (final Throwable throwable)
{
final String message =
"Failed to construct a meta facade of type '" + metafacadeClass + "' with mappingObject of type --> '" +
mappingObject.getClass().getName() + '\'';
this.getLogger().error(message);
throw new MetafacadeFactoryException(message, throwable);
}
}
/**
* Returns a metafacade for each mappingObject, contained within the
* <code>mappingObjects</code> collection depending on its
* <code>mappingClass</code> and (optionally) its <code>stereotypes</code>,
* and <code>contextName</code>.
*
* @param mappingObjects the meta model element.
* @param contextName the name of the context the meta model element is
* registered under.
* @return the Collection of newly created Metafacades.
*/
protected Collection<MetafacadeBase> createMetafacades(
final Collection mappingObjects,
final String contextName)
{
final Collection<MetafacadeBase> metafacades = new ArrayList<MetafacadeBase>();
if (mappingObjects != null && !mappingObjects.isEmpty())
{
for (final Object mappingObject : mappingObjects)
{
if (this.getLogger().isDebugEnabled())
{
this.getLogger().debug("MetafacadeFactory createMetafacade for namespace " + this.getNamespace() + " model " + this.getModel() + " contextName " + contextName + " mappingObject " + mappingObject);
}
metafacades.add(this.createMetafacade(
mappingObject,
contextName,
null));
}
}
return metafacades;
}
/**
* Returns a metafacade for each mappingObject, contained within the
* <code>mappingObjects</code> collection depending on its
* <code>mappingClass</code>.
*
* @param mappingObjects the objects used to map the metafacades (can be a
* meta model element or an actual metafacade itself).
* @return Collection of metafacades
*/
public Collection<MetafacadeBase> createMetafacades(final Collection mappingObjects)
{
return this.createMetafacades(
mappingObjects,
null);
}
/**
* The model facade which provides access to the underlying meta model.
*/
private ModelAccessFacade model;
/**
* Gets the model which provides access to the underlying model and is used
* to construct metafacades.
*
* @return the model access facade.
*/
public ModelAccessFacade getModel()
{
if (this.model == null)
{
throw new MetafacadeFactoryException("This metafacade factory's model must be populated before " +
"metafacade construction can occur");
}
return this.model;
}
/**
* The shared metafacade impls instance.
*/
private MetafacadeImpls metafacadeImpls = MetafacadeImpls.instance();
/**
* Stores the namespace that contains the metafacade model implementation.
*/
private String metafacadeModelNamespace;
/**
* The model access facade instance (provides access to the meta model).
*
* @param model the model
* @param metafacadeModelNamespace the namespace that contains the metafacade facade implementation.
*/
public void setModel(
final ModelAccessFacade model,
final String metafacadeModelNamespace)
{
this.metafacadeModelNamespace = metafacadeModelNamespace;
// - set the model type as the namespace for the metafacade impls so we have
// access to the correct metafacade classes
this.metafacadeImpls.setMetafacadeModelNamespace(metafacadeModelNamespace);
this.model = model;
}
/**
* Gets the correct logger based on whether or not an namespace logger should be used
*
* @return the logger
*/
final Logger getLogger()
{
return AndroMDALogger.getNamespaceLogger(this.getNamespace());
}
/**
* Registers a property with the specified <code>name</code> in the given
* <code>namespace</code>.
*
* @param namespace the namespace in which the property is stored.
* @param metafacadeName the name of the metafacade under which the property is registered
* @param name the name of the property
* @param value to give the property
*/
final void registerProperty(
final String namespace,
final String metafacadeName,
final String name,
final Object value)
{
ExceptionUtils.checkEmpty(
"name",
name);
Map<String, Map<String, Object>> metafacadeNamespace = this.metafacadeNamespaces.get(namespace);
if (metafacadeNamespace == null)
{
metafacadeNamespace = new LinkedHashMap<String, Map<String, Object>>();
this.metafacadeNamespaces.put(
namespace,
metafacadeNamespace);
}
Map<String, Object> propertyNamespace = metafacadeNamespace.get(metafacadeName);
if (propertyNamespace == null)
{
propertyNamespace = new LinkedHashMap<String, Object>();
metafacadeNamespace.put(
metafacadeName,
propertyNamespace);
}
propertyNamespace.put(
name,
value);
}
/**
* Registers a property with the specified <code>name</code> in the namespace
* that is currently set within the factory.
*
* @param metafacadeName the name of the metafacade under which the property is registered
* @param name the name of the property
* @param value to give the property
*/
final void registerProperty(
final String metafacadeName,
final String name,
final Object value)
{
this.registerProperty(
this.getNamespace(),
metafacadeName,
name,
value);
}
/**
* Gets the metafacade's property namespace (or returns null if hasn't be registered).
*
* @param metafacade the metafacade
* @return the metafacade's namespace
*/
private Map<String, Object> getMetafacadePropertyNamespace(final MetafacadeBase metafacade)
{
Map<String, Object> metafacadeNamespace = null;
if (metafacade != null)
{
Map<String, Map<String, Object>> namespace = this.metafacadeNamespaces.get(this.getNamespace());
if (namespace != null)
{
metafacadeNamespace = namespace.get(metafacade.getMetafacadeName());
}
}
return metafacadeNamespace;
}
/**
* Returns true if this property is registered under the given
* <code>namespace</code>, false otherwise.
*
* @param metafacade the metafacade to search.
* @param name the name of the property.
* @return true if the property is registered, false otherwise.
*/
final boolean isPropertyRegistered(
final MetafacadeBase metafacade,
final String name)
{
final Map<String, Object> propertyNamespace = this.getMetafacadePropertyNamespace(metafacade);
return propertyNamespace != null && propertyNamespace.containsKey(name);
}
/**
* Finds the first property having the given <code>namespaces</code>, or
* <code>null</code> if the property can <strong>NOT </strong> be found.
*
* @param metafacade the metafacade to search.
* @param name the name of the property to find.
* @return the property or null if it can't be found.
*/
private Object findProperty(
final MetafacadeBase metafacade,
final String name)
{
final Map<String, Object> propertyNamespace = this.getMetafacadePropertyNamespace(metafacade); //final Map<String, Map>
return propertyNamespace != null ? propertyNamespace.get(name) : null;
}
/**
* Gets the registered property registered under the <code>namespace</code>
* with the <code>name</code>
*
* @param metafacade the metafacade to search
* @param name the name of the property to check.
* @return the registered property
*/
final Object getRegisteredProperty(
final MetafacadeBase metafacade,
final String name)
{
final String methodName = "MetafacadeFactory.getRegisteredProperty";
final Object registeredProperty = this.findProperty(
metafacade,
name);
if (registeredProperty == null && !this.isPropertyRegistered(
metafacade,
name))
{
throw new MetafacadeFactoryException(methodName + " - no property '" + name +
"' registered under metafacade '" + metafacade.getMetafacadeName() + "' for namespace '" + this.getNamespace() +
'\'');
}
return registeredProperty;
}
/**
* The validation messages that have been collected during the
* execution of this factory.
*/
private final Collection<ModelValidationMessage> validationMessages = new LinkedHashSet<ModelValidationMessage>();
/**
* Gets the list of all validation messages collection during model processing.
*
* @return Returns the validationMessages.
* @see #validateAllMetafacades()
*/
public List<ModelValidationMessage> getValidationMessages()
{
return new ArrayList<ModelValidationMessage>(this.validationMessages);
}
/**
* Stores the collection of all metafacades for
* each namespace.
*/
private final Map<String, Collection<MetafacadeBase>> allMetafacades = new LinkedHashMap<String, Collection<MetafacadeBase>>();
/**
* <p>
* Gets all metafacades for the entire model for the
* current namespace set within the factory.
* </p>
* <p>
* <strong>NOTE:</strong> The model package filter is applied
* before returning the results (if defined within the factory).
* </p>
*
* @return all metafacades
*/
public Collection<MetafacadeBase> getAllMetafacades()
{
final String namespace = this.getNamespace();
Collection<MetafacadeBase> metafacades = null;
if (this.getModel() != null)
{
metafacades = allMetafacades.get(namespace);
if (metafacades == null)
{
metafacades = this.createMetafacades(this.getModel().getModelElements());
allMetafacades.put(
namespace,
metafacades);
}
if (metafacades != null)
{
metafacades = new ArrayList<MetafacadeBase>(metafacades);
}
}
return metafacades;
}
/**
* Caches the metafacades by stereotype.
*/
private final Map<String, Map<String, Collection<MetafacadeBase>>> metafacadesByStereotype
= new LinkedHashMap<String, Map<String, Collection<MetafacadeBase>>>();
/**
* <p>
* Gets all metafacades for the entire model having the given
* stereotype.
* </p>
* <p>
* <strong>NOTE:</strong> The model package filter is applied
* before returning the results (if defined within the factory).
* </p>
*
* @param stereotype the stereotype by which to perform the search.
* @return the metafacades having the given <code>stereotype</code>.
*/
public Collection<MetafacadeBase> getMetafacadesByStereotype(final String stereotype)
{
final String namespace = this.getNamespace();
Collection<MetafacadeBase> metafacades = null;
if (this.getModel() != null)
{
Map<String, Collection<MetafacadeBase>> stereotypeMetafacades = this.metafacadesByStereotype.get(namespace);
if (stereotypeMetafacades == null)
{
stereotypeMetafacades = new LinkedHashMap<String, Collection<MetafacadeBase>>();
}
metafacades = stereotypeMetafacades.get(stereotype);
if (metafacades == null)
{
metafacades = this.createMetafacades(this.getModel().findByStereotype(stereotype));
stereotypeMetafacades.put(
stereotype,
metafacades);
this.metafacadesByStereotype.put(
namespace,
stereotypeMetafacades);
}
if (metafacades != null)
{
metafacades = new ArrayList<MetafacadeBase>(metafacades);
}
}
return metafacades;
}
/**
* Performs shutdown procedures for the factory. This should be called <strong>ONLY</strong> when model processing has
* completed.
*/
public void shutdown()
{
this.clearCaches();
this.metafacadeNamespaces.clear();
this.mappings.shutdown();
this.model = null;
MetafacadeFactory.instance = null;
// - shutdown the profile instance
this.profile.shutdown();
}
/**
* Registers all namespace properties (if required).
*/
private void registerNamespaceProperties()
{
// - only register them if they already aren't registered
if (this.metafacadeNamespaces.isEmpty())
{
if (StringUtils.isNotBlank(this.metafacadeModelNamespace))
{
final MetafacadeMappings modelMappings = this.getModelMetafacadeMappings();
if (modelMappings != null)
{
modelMappings.registerAllProperties();
}
}
}
}
/**
* Entirely resets all the internal resources within this factory instance (such
* as the caches, etc).
*/
public void reset()
{
// - refresh the profile
this.profile.refresh();
// - clear out the namespace properties so we can re-register them next run
this.metafacadeNamespaces.clear();
// - re-register the namespace properties (if we're running again)
this.registerNamespaceProperties();
// - clear out the rest of the factory's caches
this.clearCaches();
}
/**
* Clears out the factory's internal caches (other
* than namespace properties, which can be cleared by
* calling {@link org.andromda.core.configuration.Namespaces#clear()}.
*/
public void clearCaches()
{
this.validationMessages.clear();
this.allMetafacades.clear();
this.metafacadesByStereotype.clear();
this.cache.clear();
this.metafacadesInCreation.clear();
}
}