/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.cmp.jdbc.metadata;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.w3c.dom.Element;
import org.jboss.deployment.DeploymentException;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.EntityMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.RelationMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
/**
* This immutable class contains information about the application
*
* @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
* @author <a href="sebastien.alborini@m4x.org">Sebastien Alborini</a>
* @author <a href="alex@jboss.org">Alexey Loubyansky</a>
* @version $Revision: 81030 $
*/
public final class JDBCApplicationMetaData
{
private final static Class JDBC_PM = org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager.class;
/**
* The class loader for this application. The class loader is used to
* load all classes used by this application.
*/
private final ClassLoader classLoader;
/**
* Application metadata loaded from the ejb-jar.xml file
*/
private final ApplicationMetaData applicationMetaData;
/**
* Map with user defined type mapping, e.g. enum mappings
*/
private final Map userTypeMappings;
/**
* Map of the type mappings by name.
*/
private final Map typeMappings = new HashMap();
/**
* Map of the entities managed by jbosscmp-jdbc by bean name.
*/
private final Map entities = new HashMap();
/**
* Collection of relations in this application.
*/
private final Collection relationships = new HashSet();
/**
* Map of the collection relationship roles for each entity by entity object.
*/
private final Map entityRoles = new HashMap();
/**
* Map of the dependent value classes by java class type.
*/
private final Map valueClasses = new HashMap();
/**
* Map from abstract schema name to entity name
*/
private final Map entitiesByAbstractSchemaName = new HashMap();
/**
* Map from entity interface(s) java type to entity name
*/
private final Map entitiesByInterface = new HashMap();
/**
* Map of the entity commands by name.
*/
private final Map entityCommands = new HashMap();
/**
* Constructs jdbc application meta data with the data from the
* applicationMetaData.
*
* @param applicationMetaData the application data loaded from
* the ejb-jar.xml file
* @param classLoader the ClassLoader used to load the classes
* of the application
* @throws DeploymentException if an problem occures while loading
* the classes or if data in the ejb-jar.xml is inconsistent
* with data from jbosscmp-jdbc.xml file
*/
public JDBCApplicationMetaData(ApplicationMetaData applicationMetaData, ClassLoader classLoader)
throws DeploymentException
{
// the classloader is the same for all the beans in the application
this.classLoader = classLoader;
this.applicationMetaData = applicationMetaData;
// create metadata for all jbosscmp-jdbc-managed cmp entities
// we do that here in case there is no jbosscmp-jdbc.xml
Iterator beans = applicationMetaData.getEnterpriseBeans();
while(beans.hasNext())
{
BeanMetaData bean = (BeanMetaData)beans.next();
// only take entities
if(bean.isEntity())
{
EntityMetaData entity = (EntityMetaData)bean;
// only take jbosscmp-jdbc-managed CMP entities
Class pm;
try
{
pm = classLoader.loadClass(entity.getContainerConfiguration().getPersistenceManager());
}
catch (ClassNotFoundException e)
{
throw new DeploymentException("Unable to load persistence manager", e);
}
if(entity.isCMP() &&
(JDBC_PM.isAssignableFrom(pm) || pm.getName().equals("org.jboss.ejb.plugins.cmp.jdbc2.JDBCStoreManager2")))
{
JDBCEntityMetaData jdbcEntity = new JDBCEntityMetaData(this, entity);
entities.put(entity.getEjbName(), jdbcEntity);
String schemaName = jdbcEntity.getAbstractSchemaName();
if(schemaName != null)
{
entitiesByAbstractSchemaName.put(schemaName, jdbcEntity);
}
Class remote = jdbcEntity.getRemoteClass();
if(remote != null)
{
entitiesByInterface.put(remote, jdbcEntity);
}
Class local = jdbcEntity.getLocalClass();
if(local != null)
{
entitiesByInterface.put(local, jdbcEntity);
}
// initialized the entity roles collection
entityRoles.put(entity.getEjbName(), new HashSet());
}
}
}
// relationships
Iterator iterator = applicationMetaData.getRelationships();
while(iterator.hasNext())
{
RelationMetaData relation = (RelationMetaData)iterator.next();
// Relationship metadata
JDBCRelationMetaData jdbcRelation =
new JDBCRelationMetaData(this, relation);
relationships.add(jdbcRelation);
// Left relationship-role metadata
JDBCRelationshipRoleMetaData left =
jdbcRelation.getLeftRelationshipRole();
Collection leftEntityRoles =
(Collection)entityRoles.get(left.getEntity().getName());
leftEntityRoles.add(left);
// Right relationship-role metadata
JDBCRelationshipRoleMetaData right =
jdbcRelation.getRightRelationshipRole();
Collection rightEntityRoles =
(Collection)entityRoles.get(right.getEntity().getName());
rightEntityRoles.add(right);
}
userTypeMappings = Collections.EMPTY_MAP;
}
/**
* Constructs application meta data with the data contained in the
* jboss-cmp xml element from a jbosscmp-jdbc xml file. Optional values
* of the xml element that are not present are loaded from the
* defalutValues parameter.
*
* @param element the xml Element which contains the metadata about
* this application
* @param defaultValues the JDBCApplicationMetaData which contains
* the values
* for optional elements of the element
* @throws DeploymentException if the xml element is not semantically correct
*/
public JDBCApplicationMetaData(Element element, JDBCApplicationMetaData defaultValues)
throws DeploymentException
{
// importXml will be called at least once: with standardjbosscmp-jdbc.xml
// it may be called a second time with user-provided jbosscmp-jdbc.xml
// we must ensure to set all defaults values in the first call
classLoader = defaultValues.classLoader;
applicationMetaData = defaultValues.applicationMetaData;
Element userTypeMaps = MetaData.getOptionalChild(element, "user-type-mappings");
if(userTypeMaps != null)
{
userTypeMappings = new HashMap();
Iterator iter = MetaData.getChildrenByTagName(userTypeMaps, "user-type-mapping");
while(iter.hasNext())
{
Element userTypeMappingEl = (Element)iter.next();
JDBCUserTypeMappingMetaData userTypeMapping = new JDBCUserTypeMappingMetaData(userTypeMappingEl);
userTypeMappings.put(userTypeMapping.getJavaType(), userTypeMapping);
}
}
else
userTypeMappings = defaultValues.getUserTypeMappings();
// type-mappings: (optional, always set in standardjbosscmp-jdbc.xml)
typeMappings.putAll(defaultValues.typeMappings);
Element typeMaps = MetaData.getOptionalChild(element, "type-mappings");
if(typeMaps != null)
{
for(Iterator i = MetaData.getChildrenByTagName(typeMaps, "type-mapping"); i.hasNext();)
{
Element typeMappingElement = (Element)i.next();
JDBCTypeMappingMetaData typeMapping =
new JDBCTypeMappingMetaData(typeMappingElement);
typeMappings.put(typeMapping.getName(), typeMapping);
}
}
// dependent-value-objects
valueClasses.putAll(defaultValues.valueClasses);
Element valueClassesElement =
MetaData.getOptionalChild(element, "dependent-value-classes");
if(valueClassesElement != null)
{
for(Iterator i = MetaData.getChildrenByTagName(
valueClassesElement, "dependent-value-class");
i.hasNext();)
{
Element valueClassElement = (Element)i.next();
JDBCValueClassMetaData valueClass =
new JDBCValueClassMetaData(valueClassElement, classLoader);
valueClasses.put(valueClass.getJavaType(), valueClass);
}
}
// entity-commands: (optional, always set in standardjbosscmp-jdbc.xml)
entityCommands.putAll(defaultValues.entityCommands);
Element entityCommandMaps = MetaData.getOptionalChild(
element, "entity-commands");
if(entityCommandMaps != null)
{
for(Iterator i =
MetaData.getChildrenByTagName(entityCommandMaps, "entity-command");
i.hasNext();)
{
Element entityCommandElement = (Element)i.next();
JDBCEntityCommandMetaData entityCommand =
new JDBCEntityCommandMetaData(entityCommandElement);
entityCommands.put(entityCommand.getCommandName(), entityCommand);
}
}
// reserved words: (optional, always set in standardjbosscmp-jdbc.xml)
// list of reserved words that should be escaped in table names
Element rWords = MetaData.getOptionalChild(element,"reserved-words");
if (rWords!=null)
{
for (Iterator i = MetaData.getChildrenByTagName(rWords,"word"); i.hasNext() ; )
{
Element rWord = (Element)i.next();
SQLUtil.addToRwords(MetaData.getElementContent(rWord));
}
}
// defaults: apply defaults for entities (optional, always
// set in standardjbosscmp-jdbc.xml)
entities.putAll(defaultValues.entities);
entitiesByAbstractSchemaName.putAll(
defaultValues.entitiesByAbstractSchemaName);
entitiesByInterface.putAll(defaultValues.entitiesByInterface);
Element defaults = MetaData.getOptionalChild(element, "defaults");
if(defaults != null)
{
ArrayList values = new ArrayList(entities.values());
for(Iterator i = values.iterator(); i.hasNext();)
{
JDBCEntityMetaData entityMetaData = (JDBCEntityMetaData)i.next();
// create the new metadata with the defaults applied
entityMetaData =
new JDBCEntityMetaData(this, defaults, entityMetaData);
// replace the old meta data with the new
entities.put(entityMetaData.getName(), entityMetaData);
String schemaName = entityMetaData.getAbstractSchemaName();
if(schemaName != null)
{
entitiesByAbstractSchemaName.put(schemaName, entityMetaData);
}
Class remote = entityMetaData.getRemoteClass();
if(remote != null)
{
entitiesByInterface.put(remote, entityMetaData);
}
Class local = entityMetaData.getLocalClass();
if(local != null)
{
entitiesByInterface.put(local, entityMetaData);
}
}
}
// enterprise-beans: apply entity specific configuration
// (only in jbosscmp-jdbc.xml)
Element enterpriseBeans =
MetaData.getOptionalChild(element, "enterprise-beans");
if(enterpriseBeans != null)
{
for(Iterator i =
MetaData.getChildrenByTagName(enterpriseBeans, "entity");
i.hasNext();)
{
Element beanElement = (Element)i.next();
// get entity by name, if not found, it is a config error
String ejbName =
MetaData.getUniqueChildContent(beanElement, "ejb-name");
JDBCEntityMetaData entityMetaData = getBeanByEjbName(ejbName);
if(entityMetaData == null)
{
throw new DeploymentException("Configuration found in " +
"jbosscmp-jdbc.xml for entity " + ejbName + " but bean " +
"is not a jbosscmp-jdbc-managed cmp entity in " +
"ejb-jar.xml");
}
entityMetaData =
new JDBCEntityMetaData(this, beanElement, entityMetaData);
entities.put(entityMetaData.getName(), entityMetaData);
String schemaName = entityMetaData.getAbstractSchemaName();
if(schemaName != null)
{
entitiesByAbstractSchemaName.put(schemaName, entityMetaData);
}
Class remote = entityMetaData.getRemoteClass();
if(remote != null)
{
entitiesByInterface.put(remote, entityMetaData);
}
Class local = entityMetaData.getLocalClass();
if(local != null)
{
entitiesByInterface.put(local, entityMetaData);
}
}
}
// defaults: apply defaults for relationships (optional, always
// set in standardjbosscmp-jdbc.xml)
if(defaults == null)
{
// no defaults just copy over the existing relationships and roles
relationships.addAll(defaultValues.relationships);
entityRoles.putAll(defaultValues.entityRoles);
}
else
{
// create a new empty role collection for each entity
for(Iterator i = entities.values().iterator(); i.hasNext();)
{
JDBCEntityMetaData entity = (JDBCEntityMetaData)i.next();
entityRoles.put(entity.getName(), new HashSet());
}
// for each relationship, apply defaults and store
for(Iterator i = defaultValues.relationships.iterator();
i.hasNext();)
{
JDBCRelationMetaData relationMetaData =
(JDBCRelationMetaData)i.next();
// create the new metadata with the defaults applied
relationMetaData =
new JDBCRelationMetaData(this, defaults, relationMetaData);
// replace the old metadata with the new
relationships.add(relationMetaData);
// store new left role
JDBCRelationshipRoleMetaData left =
relationMetaData.getLeftRelationshipRole();
Collection leftEntityRoles =
(Collection)entityRoles.get(left.getEntity().getName());
leftEntityRoles.add(left);
// store new right role
JDBCRelationshipRoleMetaData right =
relationMetaData.getRightRelationshipRole();
Collection rightEntityRoles =
(Collection)entityRoles.get(right.getEntity().getName());
rightEntityRoles.add(right);
}
}
// relationships: apply entity specific configuration
// (only in jbosscmp-jdbc.xml)
Element relationshipsElement =
MetaData.getOptionalChild(element, "relationships");
if(relationshipsElement != null)
{
// create a map of the relations by name (if it has a name)
Map relationByName = new HashMap();
for(Iterator i = relationships.iterator(); i.hasNext();)
{
JDBCRelationMetaData relation = (JDBCRelationMetaData)i.next();
if(relation.getRelationName() != null)
{
relationByName.put(relation.getRelationName(), relation);
}
}
for(Iterator i = MetaData.getChildrenByTagName(relationshipsElement, "ejb-relation"); i.hasNext();)
{
Element relationElement = (Element)i.next();
// get relation by name, if not found, it is a config error
String relationName = MetaData.getUniqueChildContent(relationElement, "ejb-relation-name");
JDBCRelationMetaData oldRelation = (JDBCRelationMetaData)relationByName.get(relationName);
if(oldRelation == null)
{
throw new DeploymentException("Configuration found in " +
"jbosscmp-jdbc.xml for relation " + relationName +
" but relation is not a jbosscmp-jdbc-managed relation " +
"in ejb-jar.xml");
}
// create new metadata with relation specific config applied
JDBCRelationMetaData newRelation =
new JDBCRelationMetaData(this, relationElement, oldRelation);
// replace the old metadata with the new
relationships.remove(oldRelation);
relationships.add(newRelation);
// replace the old left role with the new
JDBCRelationshipRoleMetaData newLeft =
newRelation.getLeftRelationshipRole();
Collection leftEntityRoles =
(Collection)entityRoles.get(newLeft.getEntity().getName());
leftEntityRoles.remove(oldRelation.getLeftRelationshipRole());
leftEntityRoles.add(newLeft);
// replace the old right role with the new
JDBCRelationshipRoleMetaData newRight =
newRelation.getRightRelationshipRole();
Collection rightEntityRoles =
(Collection)entityRoles.get(newRight.getEntity().getName());
rightEntityRoles.remove(oldRelation.getRightRelationshipRole());
rightEntityRoles.add(newRight);
}
}
}
/**
* Gets the type mapping with the specified name
* @param name the name for the type mapping
* @return the matching type mapping or null if not found
*/
public JDBCTypeMappingMetaData getTypeMappingByName(String name)
{
return (JDBCTypeMappingMetaData)typeMappings.get(name);
}
/**
* Gets the relationship roles for the entity with the specified name.
* @param entityName the name of the entity whos roles are returned
* @return an unmodifiable collection of JDBCRelationshipRoles
* of the specified entity
*/
public Collection getRolesForEntity(String entityName)
{
Collection roles = (Collection)entityRoles.get(entityName);
return Collections.unmodifiableCollection(roles);
}
/**
* Gets dependent value classes that are directly managed by the container.
* @returns an unmodifiable collection of JDBCValueClassMetaData
*/
public Collection getValueClasses()
{
return Collections.unmodifiableCollection(valueClasses.values());
}
/**
* Gets the classloader for this application which is used to load
* all classes.
* @return the ClassLoader for the application
*/
public ClassLoader getClassLoader()
{
return classLoader;
}
/**
* Gets the metadata for an entity bean by name.
* @param name the name of the entity meta data to return
* @return the entity meta data for the specified name
*/
public JDBCEntityMetaData getBeanByEjbName(String name)
{
return (JDBCEntityMetaData)entities.get(name);
}
/**
* Gets the entity command with the specified name
* @param name the name for the entity-command
* @return the matching entity command or null if not found
*/
public JDBCEntityCommandMetaData getEntityCommandByName(String name)
{
return (JDBCEntityCommandMetaData)entityCommands.get(name);
}
public Map getUserTypeMappings()
{
return Collections.unmodifiableMap(userTypeMappings);
}
}