/*
* 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.Collection;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jboss.deployment.DeploymentException;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.RelationshipRoleMetaData;
import org.w3c.dom.Element;
/**
* Imutable class which represents one ejb-relationship-role element found in
* the ejb-jar.xml file's ejb-relation elements.
*
* @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version $Revision: 81030 $
*/
public final class JDBCRelationshipRoleMetaData
{
/** Relation to which this role belongs. */
private final JDBCRelationMetaData relationMetaData;
/** Role name */
private final String relationshipRoleName;
/** Is the multiplicity one? If not, multiplicity is many. */
private final boolean multiplicityOne;
/** Should this role have a foreign key constraint? */
private final boolean foreignKeyConstraint;
/** Should this entity be deleted when related entity is deleted. */
private final boolean cascadeDelete;
/** Should the cascade-delete be batched. */
private final boolean batchCascadeDelete;
/** The entity that has this role. */
private final JDBCEntityMetaData entity;
/** Name of the entity's cmr field for this role. */
private final String cmrFieldName;
/** true if this side is navigable */
private final boolean navigable;
/** Type of the cmr field (i.e., collection or set) */
private final String cmrFieldType;
private boolean genIndex;
/** Type of the cmr field (i.e., collection or set) */
private final JDBCReadAheadMetaData readAhead;
/** The other role in this relationship. */
private JDBCRelationshipRoleMetaData relatedRole;
/** The key fields used by this role by field name. */
private Map keyFields;
public JDBCRelationshipRoleMetaData(JDBCRelationMetaData relationMetaData,
JDBCApplicationMetaData application,
RelationshipRoleMetaData role)
throws DeploymentException
{
this.relationMetaData = relationMetaData;
relationshipRoleName = role.getRelationshipRoleName();
multiplicityOne = role.isMultiplicityOne();
cascadeDelete = role.isCascadeDelete();
batchCascadeDelete = false;
foreignKeyConstraint = false;
readAhead = null;
String fieldName = loadCMRFieldName(role);
if(fieldName == null)
{
cmrFieldName = generateNonNavigableCMRName(role);
navigable = false;
}
else
{
cmrFieldName = fieldName;
navigable = true;
}
cmrFieldType = role.getCMRFieldType();
// get the entity for this role
entity = application.getBeanByEjbName(role.getEntityName());
if(entity == null)
{
throw new DeploymentException("Entity: " + role.getEntityName() +
" not found for relation: " + role.getRelationMetaData().getRelationName());
}
}
public JDBCRelationshipRoleMetaData(JDBCRelationMetaData relationMetaData,
JDBCApplicationMetaData application,
Element element,
JDBCRelationshipRoleMetaData defaultValues)
throws DeploymentException
{
this.relationMetaData = relationMetaData;
this.entity = application.getBeanByEjbName(defaultValues.getEntity().getName());
relationshipRoleName = defaultValues.getRelationshipRoleName();
multiplicityOne = defaultValues.isMultiplicityOne();
cascadeDelete = defaultValues.isCascadeDelete();
cmrFieldName = defaultValues.getCMRFieldName();
navigable = defaultValues.isNavigable();
cmrFieldType = defaultValues.getCMRFieldType();
// foreign key constraint? If not provided, keep default.
String fkString = MetaData.getOptionalChildContent(element, "fk-constraint");
if(fkString != null)
{
foreignKeyConstraint = Boolean.valueOf(fkString).booleanValue();
}
else
{
foreignKeyConstraint = defaultValues.hasForeignKeyConstraint();
}
// read-ahead
Element readAheadElement = MetaData.getOptionalChild(element, "read-ahead");
if(readAheadElement != null)
{
readAhead = new JDBCReadAheadMetaData(readAheadElement, entity.getReadAhead());
}
else
{
readAhead = entity.getReadAhead();
}
batchCascadeDelete = MetaData.getOptionalChild(element, "batch-cascade-delete") != null;
if(batchCascadeDelete)
{
if(!cascadeDelete)
throw new DeploymentException(
relationMetaData.getRelationName() + '/' + relationshipRoleName
+ " has batch-cascade-delete in jbosscmp-jdbc.xml but has no cascade-delete in ejb-jar.xml"
);
if(relationMetaData.isTableMappingStyle())
{
throw new DeploymentException(
"Relationship " + relationMetaData.getRelationName()
+ " with relation-table-mapping style was setup for batch cascade-delete."
+ " Batch cascade-delete supported only for foreign key mapping style."
);
}
}
}
public void init(JDBCRelationshipRoleMetaData relatedRole)
throws DeploymentException
{
init(relatedRole, null);
}
public void init(JDBCRelationshipRoleMetaData relatedRole, Element element)
throws DeploymentException
{
this.relatedRole = relatedRole;
if(element == null || "defaults".equals(element.getTagName()))
{
keyFields = loadKeyFields();
}
else
{
keyFields = loadKeyFields(element);
}
}
private static String loadCMRFieldName(RelationshipRoleMetaData role)
{
return role.getCMRFieldName();
}
private static String generateNonNavigableCMRName(RelationshipRoleMetaData role)
{
RelationshipRoleMetaData relatedRole = role.getRelatedRoleMetaData();
return relatedRole.getEntityName() + "_" + relatedRole.getCMRFieldName();
}
/**
* Gets the relation to which this role belongs.
*/
public JDBCRelationMetaData getRelationMetaData()
{
return relationMetaData;
}
/**
* Gets the name of this role.
*/
public String getRelationshipRoleName()
{
return relationshipRoleName;
}
/**
* Should this role use a foreign key constraint.
* @return true if the store mananager will execute an ALTER TABLE ADD
* CONSTRAINT statement to add a foreign key constraint.
*/
public boolean hasForeignKeyConstraint()
{
return foreignKeyConstraint;
}
/**
* Checks if the multiplicity is one.
*/
public boolean isMultiplicityOne()
{
return multiplicityOne;
}
/**
* Checks if the multiplicity is many.
*/
public boolean isMultiplicityMany()
{
return !multiplicityOne;
}
/**
* Should this entity be deleted when related entity is deleted.
*/
public boolean isCascadeDelete()
{
return cascadeDelete;
}
public boolean isBatchCascadeDelete()
{
return batchCascadeDelete;
}
/**
* Gets the name of the entity that has this role.
*/
public JDBCEntityMetaData getEntity()
{
return entity;
}
/**
* Gets the name of the entity's cmr field for this role.
*/
public String getCMRFieldName()
{
return cmrFieldName;
}
public boolean isNavigable()
{
return navigable;
}
/**
* Gets the type of the cmr field (i.e., collection or set)
*/
private String getCMRFieldType()
{
return cmrFieldType;
}
/**
* Gets the related role's jdbc meta data.
*/
public JDBCRelationshipRoleMetaData getRelatedRole()
{
return relationMetaData.getOtherRelationshipRole(this);
}
/**
* Gets the read ahead meta data
*/
public JDBCReadAheadMetaData getReadAhead()
{
return readAhead;
}
/**
* Gets the key fields of this role.
* @return an unmodifiable collection of JDBCCMPFieldMetaData objects
*/
public Collection getKeyFields()
{
return Collections.unmodifiableCollection(keyFields.values());
}
public boolean isIndexed()
{
return genIndex;
}
/**
* Loads the key fields for this role based on the primary keys of the
* this entity.
*/
private Map loadKeyFields()
{
// with foreign key mapping, foreign key fields are no added if
// - it is the many side of one-to-many relationship
// - it is the one side of one-to-one relationship and related side is not navigable
if(relationMetaData.isForeignKeyMappingStyle())
{
if(isMultiplicityMany())
return Collections.EMPTY_MAP;
else
if(getRelatedRole().isMultiplicityOne() && !getRelatedRole().isNavigable())
return Collections.EMPTY_MAP;
}
// get all of the pk fields
ArrayList pkFields = new ArrayList();
for(Iterator i = entity.getCMPFields().iterator(); i.hasNext();)
{
JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) i.next();
if(cmpField.isPrimaryKeyMember())
{
pkFields.add(cmpField);
}
}
// generate a new key field for each pk field
Map fields = new HashMap(pkFields.size());
for(Iterator i = pkFields.iterator(); i.hasNext();)
{
JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) i.next();
String columnName;
if(relationMetaData.isTableMappingStyle())
{
if(entity.equals(relatedRole.getEntity()))
columnName = getCMRFieldName();
else
columnName = entity.getName();
}
else
{
columnName = relatedRole.getCMRFieldName();
}
if(pkFields.size() > 1)
{
columnName += "_" + cmpField.getFieldName();
}
cmpField = new JDBCCMPFieldMetaData(
entity,
cmpField,
columnName,
false,
relationMetaData.isTableMappingStyle(),
relationMetaData.isReadOnly(),
relationMetaData.getReadTimeOut(),
relationMetaData.isTableMappingStyle());
fields.put(cmpField.getFieldName(), cmpField);
}
return Collections.unmodifiableMap(fields);
}
/**
* Loads the key fields for this role based on the primary keys of the
* this entity and the override data from the xml element.
*/
private Map loadKeyFields(Element element)
throws DeploymentException
{
Element keysElement = MetaData.getOptionalChild(element, "key-fields");
// no field overrides, we're done
if(keysElement == null)
{
return loadKeyFields();
}
// load overrides
Iterator iter = MetaData.getChildrenByTagName(keysElement, "key-field");
// if key-fields element empty, no key should be used
if(!iter.hasNext())
{
return Collections.EMPTY_MAP;
}
else
if(relationMetaData.isForeignKeyMappingStyle() && isMultiplicityMany())
{
throw new DeploymentException("Role: " + relationshipRoleName + " with multiplicity many using " +
"foreign-key mapping is not allowed to have key-fields");
}
// load the default field values
Map defaultFields = getPrimaryKeyFields();
// load overrides
Map fields = new HashMap(defaultFields.size());
while(iter.hasNext())
{
Element keyElement = (Element) iter.next();
String fieldName = MetaData.getUniqueChildContent(keyElement, "field-name");
JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) defaultFields.remove(fieldName);
if(cmpField == null)
{
throw new DeploymentException(
"Role '" + relationshipRoleName + "' on Entity Bean '" +
entity.getName() + "' : CMP field for key not found: field " +
"name='" + fieldName + "'");
}
String isIndexedtmp = MetaData.getOptionalChildContent(keyElement, "dbindex");
boolean isIndexed;
if(isIndexedtmp != null)
isIndexed = true;
else
isIndexed = false;
genIndex = isIndexed;
cmpField = new JDBCCMPFieldMetaData(
entity,
keyElement,
cmpField,
false,
relationMetaData.isTableMappingStyle(),
relationMetaData.isReadOnly(),
relationMetaData.getReadTimeOut(),
relationMetaData.isTableMappingStyle());
fields.put(cmpField.getFieldName(), cmpField);
}
// all fields must be overriden
if(!defaultFields.isEmpty())
{
throw new DeploymentException("Mappings were not provided for all " +
"fields: unmaped fields=" + defaultFields.keySet() +
" in role=" + relationshipRoleName);
}
return Collections.unmodifiableMap(fields);
}
/**
* Returns the primary key fields of the entity mapped by field name.
*/
private Map getPrimaryKeyFields()
{
Map pkFields = new HashMap();
for(Iterator cmpFieldsIter = entity.getCMPFields().iterator(); cmpFieldsIter.hasNext();)
{
JDBCCMPFieldMetaData cmpField = (JDBCCMPFieldMetaData) cmpFieldsIter.next();
if(cmpField.isPrimaryKeyMember())
pkFields.put(cmpField.getFieldName(), cmpField);
}
return pkFields;
}
}