blob: dd51a8345c44f82cf18acb875d0a4f26699eb2eb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.config;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.ejb.EJBLocalObject;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.core.cmp.CmpUtil;
import org.apache.openejb.core.cmp.jpa.JpaCmpEngine;
import org.apache.openejb.jee.*;
import org.apache.openejb.jee.jpa.*;
import org.apache.openejb.jee.jpa.unit.Persistence;
import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
import org.apache.openejb.jee.jpa.unit.TransactionType;
import org.apache.openejb.jee.oejb3.EjbDeployment;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.Strings;
public class CmpJpaConversion implements DynamicDeployer {
private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP_CONFIG, CmpJpaConversion.class);
private static final String CMP_PERSISTENCE_UNIT_NAME = "cmp";
// A specific set of fields that get marked as transient in the superclass mappings
private static final Set<String> ENHANCED_FIELDS = Collections.unmodifiableSet(new TreeSet<String>(Arrays.asList(
"pcInheritedFieldCount",
"pcFieldNames",
"pcFieldTypes",
"pcFieldFlags",
"pcPCSuperclass",
"pcStateManager",
"class$Ljava$lang$String",
"class$Ljava$lang$Integer",
"class$Lcom$sun$ts$tests$common$ejb$wrappers$CMP11Wrapper",
"pcDetachedState",
"serialVersionUID"
)));
public AppModule deploy(AppModule appModule) throws OpenEJBException {
if (!hasCmpEntities(appModule)) return appModule;
// todo scan existing persistence module for all entity mappings and don't generate mappings for them
// create mappings if no mappings currently exist
EntityMappings cmpMappings = appModule.getCmpMappings();
if (cmpMappings == null) {
cmpMappings = new EntityMappings();
cmpMappings.setVersion("1.0");
appModule.setCmpMappings(cmpMappings);
}
// we process this one jar-file at a time...each contributing to the
// app mapping data
for (EjbModule ejbModule : appModule.getEjbModules()) {
EjbJar ejbJar = ejbModule.getEjbJar();
// scan for CMP entity beans and merge the data into the collective set
for (EnterpriseBean enterpriseBean : ejbJar.getEnterpriseBeans()) {
if (isCmpEntity(enterpriseBean)) {
processEntityBean(ejbModule, cmpMappings, (EntityBean) enterpriseBean);
}
}
// if there are relationships defined in this jar, get a list of the defined
// entities and process the relationship maps.
Relationships relationships = ejbJar.getRelationships();
if (relationships != null) {
Map<String, Entity> entitiesByEjbName = new TreeMap<String,Entity>();
for (Entity entity : cmpMappings.getEntity()) {
entitiesByEjbName.put(entity.getEjbName(), entity);
}
for (EjbRelation relation : relationships.getEjbRelation()) {
processRelationship(entitiesByEjbName, relation);
}
}
// Let's warn the user about any declarations we didn't end up using
// so there can be no misunderstandings.
EntityMappings userMappings = getUserEntityMappings(ejbModule);
for (Entity mapping : userMappings.getEntity()) {
logger.warning("openejb-cmp-orm.xml mapping ignored: module="+ejbModule.getModuleId()+": <entity class=\""+mapping.getClazz()+"\">");
}
for (MappedSuperclass mapping : userMappings.getMappedSuperclass()) {
logger.warning("openejb-cmp-orm.xml mapping ignored: module="+ejbModule.getModuleId()+": <mapped-superclass class=\""+mapping.getClazz()+"\">");
}
}
if (!cmpMappings.getEntity().isEmpty()) {
PersistenceUnit persistenceUnit = getCmpPersistenceUnit(appModule);
persistenceUnit.getMappingFile().add("META-INF/openejb-cmp-generated-orm.xml");
for (Entity entity : cmpMappings.getEntity()) {
persistenceUnit.getClazz().add(entity.getClazz());
}
}
// TODO: This should not be necessary, but having an empty <attributes/> tag
// causes some of the unit tests to fail. Not sure why. Should be fixed.
for (Entity entity : appModule.getCmpMappings().getEntity()) {
if (entity.getAttributes() != null && entity.getAttributes().isEmpty()){
entity.setAttributes(null);
}
}
return appModule;
}
private PersistenceUnit getCmpPersistenceUnit(AppModule appModule) {
// search for the cmp persistence unit
PersistenceUnit persistenceUnit = null;
for (PersistenceModule persistenceModule : appModule.getPersistenceModules()) {
Persistence persistence = persistenceModule.getPersistence();
for (PersistenceUnit unit : persistence.getPersistenceUnit()) {
if (CMP_PERSISTENCE_UNIT_NAME.equals(unit.getName())) {
persistenceUnit = unit;
break;
}
}
}
// if not found create one
if (persistenceUnit == null) {
persistenceUnit = new PersistenceUnit();
persistenceUnit.setName(CMP_PERSISTENCE_UNIT_NAME);
persistenceUnit.setTransactionType(TransactionType.JTA);
// Don't set default values here, let the autoconfig do that
// persistenceUnit.setJtaDataSource("java:openejb/Resource/Default JDBC Database");
// persistenceUnit.setNonJtaDataSource("java:openejb/Resource/Default Unmanaged JDBC Database");
// todo paramterize this
Properties properties = new Properties();
properties.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true, Indexes=false, IgnoreErrors=true)");
// properties.setProperty("openjpa.DataCache", "false");
properties.setProperty("openjpa.Log", "DefaultLevel=INFO");
persistenceUnit.setProperties(properties);
Persistence persistence = new Persistence();
persistence.setVersion("1.0");
persistence.getPersistenceUnit().add(persistenceUnit);
PersistenceModule persistenceModule = new PersistenceModule(getPersistenceModuleId(appModule), persistence);
appModule.getPersistenceModules().add(persistenceModule);
}
return persistenceUnit;
}
private String getPersistenceModuleId(AppModule appModule) {
if (appModule.getModuleId() != null) return appModule.getModuleId();
for (EjbModule ejbModule: appModule.getEjbModules()) {
return ejbModule.getModuleId();
}
throw new IllegalStateException("Comp must be in an ejb module, this one has none: " + appModule);
}
/**
* Test if a module contains CMP entity beans that will
* need a JPA mapping generated.
*
* @param appModule The source application module.
*
* @return true if the module contains any entity beans
* using container managed persistence.
*/
private boolean hasCmpEntities(AppModule appModule) {
for (EjbModule ejbModule : appModule.getEjbModules()) {
for (EnterpriseBean bean : ejbModule.getEjbJar().getEnterpriseBeans()) {
if (isCmpEntity(bean)) return true;
}
}
return false;
}
/**
* Tests if an EJB is an entity bean using container
* managed persistence.
*
* @param bean The source bean.
*
* @return True if all of the conditions for a CMP bean are met.
*/
private static boolean isCmpEntity(EnterpriseBean bean) {
return bean instanceof EntityBean && ((EntityBean) bean).getPersistenceType() == PersistenceType.CONTAINER;
}
private void processRelationship(Map<String, Entity> entitiesByEjbName, EjbRelation relation) throws OpenEJBException {
List<EjbRelationshipRole> roles = relation.getEjbRelationshipRole();
// if we don't have two roles, the relation is bad so we skip it
if (roles.size() != 2) {
return;
}
// get left entity
EjbRelationshipRole leftRole = roles.get(0);
RelationshipRoleSource leftRoleSource = leftRole.getRelationshipRoleSource();
String leftEjbName = leftRoleSource == null ? null : leftRoleSource.getEjbName();
Entity leftEntity = entitiesByEjbName.get(leftEjbName);
// get right entity
EjbRelationshipRole rightRole = roles.get(1);
RelationshipRoleSource rightRoleSource = rightRole.getRelationshipRoleSource();
String rightEjbName = rightRoleSource == null ? null : rightRoleSource.getEjbName();
Entity rightEntity = entitiesByEjbName.get(rightEjbName);
// neither left or right have a mapping which is fine
if (leftEntity == null && rightEntity == null) {
return;
}
// left not found?
if (leftEntity == null) {
throw new OpenEJBException("Role source " + leftEjbName + " defined in relationship role " +
relation.getEjbRelationName() + "::" + leftRole.getEjbRelationshipRoleName() + " not found");
}
// right not found?
if (rightEntity == null) {
throw new OpenEJBException("Role source " + rightEjbName + " defined in relationship role " +
relation.getEjbRelationName() + "::" + rightRole.getEjbRelationshipRoleName() + " not found");
}
final Attributes rightAttributes = rightEntity.getAttributes();
Map<String, RelationField> rightRelationships = rightAttributes.getRelationshipFieldMap();
final Attributes leftAttributes = leftEntity.getAttributes();
Map<String, RelationField> leftRelationships = leftAttributes.getRelationshipFieldMap();
String leftFieldName = null;
boolean leftSynthetic = false;
if (leftRole.getCmrField() != null) {
leftFieldName = leftRole.getCmrField().getCmrFieldName();
} else {
leftFieldName = rightEntity.getName() + "_" + rightRole.getCmrField().getCmrFieldName();
leftSynthetic = true;
}
boolean leftIsOne = leftRole.getMultiplicity() == Multiplicity.ONE;
String rightFieldName = null;
boolean rightSynthetic = false;
if (rightRole.getCmrField() != null) {
rightFieldName = rightRole.getCmrField().getCmrFieldName();
} else {
rightFieldName = leftEntity.getName() + "_" + leftRole.getCmrField().getCmrFieldName();
rightSynthetic = true;
}
boolean rightIsOne = rightRole.getMultiplicity() == Multiplicity.ONE;
if (leftIsOne && rightIsOne) {
//
// one-to-one
//
// left
OneToOne leftOneToOne = null;
leftOneToOne = new OneToOne();
leftOneToOne.setName(leftFieldName);
leftOneToOne.setSyntheticField(leftSynthetic);
setCascade(rightRole, leftOneToOne);
addRelationship(leftOneToOne, leftRelationships, leftAttributes.getOneToOne());
// right
OneToOne rightOneToOne = null;
rightOneToOne = new OneToOne();
rightOneToOne.setName(rightFieldName);
rightOneToOne.setSyntheticField(rightSynthetic);
rightOneToOne.setMappedBy(leftFieldName);
setCascade(leftRole, rightOneToOne);
addRelationship(rightOneToOne, rightRelationships, rightAttributes.getOneToOne());
// link
leftOneToOne.setRelatedField(rightOneToOne);
rightOneToOne.setRelatedField(leftOneToOne);
} else if (leftIsOne && !rightIsOne) {
//
// one-to-many
//
// left
OneToMany leftOneToMany = null;
leftOneToMany = new OneToMany();
leftOneToMany.setName(leftFieldName);
leftOneToMany.setSyntheticField(leftSynthetic);
leftOneToMany.setMappedBy(rightFieldName);
setCascade(rightRole, leftOneToMany);
addRelationship(leftOneToMany, leftRelationships, leftAttributes.getOneToMany());
// right
ManyToOne rightManyToOne = null;
rightManyToOne = new ManyToOne();
rightManyToOne.setName(rightFieldName);
rightManyToOne.setSyntheticField(rightSynthetic);
setCascade(leftRole, rightManyToOne);
addRelationship(rightManyToOne, rightRelationships, rightAttributes.getManyToOne());
// link
leftOneToMany.setRelatedField(rightManyToOne);
rightManyToOne.setRelatedField(leftOneToMany);
} else if (!leftIsOne && rightIsOne) {
//
// many-to-one
//
// left
ManyToOne leftManyToOne = null;
leftManyToOne = new ManyToOne();
leftManyToOne.setName(leftFieldName);
leftManyToOne.setSyntheticField(leftSynthetic);
setCascade(rightRole, leftManyToOne);
addRelationship(leftManyToOne, leftRelationships, leftAttributes.getManyToOne());
// right
OneToMany rightOneToMany = null;
rightOneToMany = new OneToMany();
rightOneToMany.setName(rightFieldName);
rightOneToMany.setSyntheticField(rightSynthetic);
rightOneToMany.setMappedBy(leftFieldName);
setCascade(leftRole, rightOneToMany);
addRelationship(rightOneToMany, rightRelationships, rightAttributes.getOneToMany());
// link
leftManyToOne.setRelatedField(rightOneToMany);
rightOneToMany.setRelatedField(leftManyToOne);
} else if (!leftIsOne && !rightIsOne) {
//
// many-to-many
//
// left
ManyToMany leftManyToMany = null;
leftManyToMany = new ManyToMany();
leftManyToMany.setName(leftFieldName);
leftManyToMany.setSyntheticField(leftSynthetic);
setCascade(rightRole, leftManyToMany);
addRelationship(leftManyToMany, leftRelationships, leftAttributes.getManyToMany());
// right
ManyToMany rightManyToMany = null;
rightManyToMany = new ManyToMany();
rightManyToMany.setName(rightFieldName);
rightManyToMany.setSyntheticField(rightSynthetic);
rightManyToMany.setMappedBy(leftFieldName);
setCascade(leftRole, rightManyToMany);
addRelationship(rightManyToMany, rightRelationships, rightAttributes.getManyToMany());
// link
leftManyToMany.setRelatedField(rightManyToMany);
rightManyToMany.setRelatedField(leftManyToMany);
}
}
private <R extends RelationField> R addRelationship(R relationship, Map<String, RelationField> existing, List<R> relationships) {
R r = null;
try {
r = (R) existing.get(relationship.getKey());
} catch (ClassCastException e) {
return relationship;
}
if (r == null){
r = relationship;
relationships.add(relationship);
}
return r;
}
/**
* Generate the CMP mapping data for an individual
* EntityBean.
*
* @param ejbModule The module containing the bean.
* @param entityMappings
* The accumulated set of entity mappings.
* @param bean The been we're generating the mapping for.
*/
private void processEntityBean(EjbModule ejbModule, EntityMappings entityMappings, EntityBean bean) {
// try to add a new persistence-context-ref for cmp
if (!addPersistenceContextRef(bean)) {
// Bean already has a persistence-context-ref for cmp
// which means it has a mapping, so skip this bean
return;
}
// get the real bean class
Class ejbClass = loadClass(ejbModule.getClassLoader(), bean.getEjbClass());
// and generate a name for the subclass that will be generated and handed to the JPA
// engine as the managed class.
String jpaEntityClassName = CmpUtil.getCmpImplClassName(bean.getAbstractSchemaName(), ejbClass.getName());
// We don't use this mapping directly, instead we pull entries from it
// the reason being is that we intend to support mappings that aren't
// exactly correct. i.e. users should be able to write mappings completely
// ignorant of the fact that we subclass. The fact that we subclass means
// these user supplied mappings might need to be adjusted as the jpa orm.xml
// file is extremely subclass/supperclass aware and mappings specified in it
// need to be spot on.
EntityMappings userMappings = getUserEntityMappings(ejbModule);
// Look for any existing mapped superclass mappings. We check the entire inheritance
// chain of the bean looking for any that have user defined mappings.
for (Class clazz = ejbClass; clazz != null; clazz = clazz.getSuperclass()){
MappedSuperclass mappedSuperclass = removeMappedSuperclass(userMappings, clazz.getName());
// We're going to assume that if they bothered to map a superclass
// that the mapping is correct. Copy it from their mappings to ours
if (mappedSuperclass != null) {
entityMappings.getMappedSuperclass().add(mappedSuperclass);
}
}
// Look for an existing mapping using the openejb generated subclass name
Entity entity = removeEntity(userMappings, jpaEntityClassName);
// DMB: For the first iteration, we're not going to allow
// anything other than the ugly mapping file we generate.
// So if they supplied an entity, it better be correct
// because we are going to ignore all other xml metadata.
if (entity != null) {
// XmlMetadataComplete is an OpenEJB specific flag that
// tells all other legacy descriptor converters to keep
// their hands off.
entity.setXmlMetadataComplete(true);
entityMappings.getEntity().add(entity);
return;
}
// This section is an in progress TODO
// if (entity == null){
// entity = removeEntity(userMappings, ejbClass.getName());
// // OVERWRITE: class: impl class name
// if (entity != null) {
// entity.setClazz(jpaEntityClassName);
//
// if (Modifier.isAbstract(ejbClass.getModifiers())){
// // This is a CMP2 bean and we allowed the user to
// // define it via the orm.xml file as an <entity>
// // We need to split this definition. We need
// // an <entity> definition for the generated subclass
// // and a <mapped-superclass> for the bean class
//
//
// }
// }
// }
if (entity == null){
entity = new Entity(jpaEntityClassName);
}
// Aggressively add an "Attributes" instance so we don't
// have to check for null everywhere.
if (entity.getAttributes() == null){
entity.setAttributes(new Attributes());
}
// add the entity
entityMappings.getEntity().add(entity);
// OVERWRITE: description: contains the name of the entity bean
entity.setDescription(ejbModule.getModuleId() + "#" + bean.getEjbName());
// PRESERVE has queries: name: the name of the entity in queries
String entityName = bean.getAbstractSchemaName();
entity.setName(entityName);
entity.setEjbName(bean.getEjbName());
ClassLoader classLoader = ejbModule.getClassLoader();
Collection<MappedSuperclass> mappedSuperclasses;
if (bean.getCmpVersion() == CmpVersion.CMP2) {
// perform the 2.x class mapping. This really just identifies the primary key and
// other cmp fields that will be generated for the concrete class and identify them
// to JPA.
mappedSuperclasses = mapClass2x(entity, bean, classLoader);
} else {
// map the cmp class, but if we are using a mapped super class,
// generate attribute-override instead of id and basic
mappedSuperclasses = mapClass1x(bean.getEjbClass(), entity, bean, classLoader);
}
// if we have superclass mappings to process, add those to the
// configuration. f
if (mappedSuperclasses != null) {
// now that things are mapped out, add the superclass mappings to the entity mappings
// that will get passed to the JPA engine.
for (MappedSuperclass mappedSuperclass : mappedSuperclasses) {
entityMappings.getMappedSuperclass().add(mappedSuperclass);
}
}
// process queries
for (Query query : bean.getQuery()) {
NamedQuery namedQuery = new NamedQuery();
QueryMethod queryMethod = query.getQueryMethod();
// todo deployment id could change in one of the later conversions... use entity name instead, but we need to save it off
StringBuilder name = new StringBuilder();
name.append(entityName).append(".").append(queryMethod.getMethodName());
if (queryMethod.getMethodParams() != null && !queryMethod.getMethodParams().getMethodParam().isEmpty()) {
name.append('(');
boolean first = true;
for (String methodParam : queryMethod.getMethodParams().getMethodParam()) {
if (!first) name.append(",");
name.append(methodParam);
first = false;
}
name.append(')');
}
namedQuery.setName(name.toString());
namedQuery.setQuery(query.getEjbQl());
entity.getNamedQuery().add(namedQuery);
}
// todo: there should be a common interface between ejb query object and openejb query object
OpenejbJar openejbJar = ejbModule.getOpenejbJar();
EjbDeployment ejbDeployment = openejbJar.getDeploymentsByEjbName().get(bean.getEjbName());
if (ejbDeployment != null) {
for (org.apache.openejb.jee.oejb3.Query query : ejbDeployment.getQuery()) {
NamedQuery namedQuery = new NamedQuery();
org.apache.openejb.jee.oejb3.QueryMethod queryMethod = query.getQueryMethod();
// todo deployment id could change in one of the later conversions... use entity name instead, but we need to save it off
StringBuilder name = new StringBuilder();
name.append(entityName).append(".").append(queryMethod.getMethodName());
if (queryMethod.getMethodParams() != null && !queryMethod.getMethodParams().getMethodParam().isEmpty()) {
name.append('(');
boolean first = true;
for (String methodParam : queryMethod.getMethodParams().getMethodParam()) {
if (!first) name.append(",");
name.append(methodParam);
first = false;
}
name.append(')');
}
namedQuery.setName(name.toString());
namedQuery.setQuery(query.getObjectQl());
entity.getNamedQuery().add(namedQuery);
}
}
}
private Entity removeEntity(EntityMappings userMappings, String className) {
Entity entity;
entity = userMappings.getEntityMap().get(className);
if (entity != null){
userMappings.getEntityMap().remove(entity.getKey());
}
return entity;
}
/**
* Check the user-defined entity mappings for a superclass
* mapping for a given named class. If the user mappings exist,
* remove them from the user list and return them
* for inclusion in our generated mappings.
*
* @param userMappings
* The current user mapping set.
* @param className The name of the class of interest.
*
* @return Returns the superclass mapping for the named class, or
* null if the class is not in the mapping set.
*/
private MappedSuperclass removeMappedSuperclass(EntityMappings userMappings, String className) {
MappedSuperclass mappedSuperclass;
mappedSuperclass = userMappings.getMappedSuperclassMap().get(className);
if (mappedSuperclass != null){
userMappings.getMappedSuperclassMap().remove(mappedSuperclass.getKey());
}
return mappedSuperclass;
}
private EntityMappings getUserEntityMappings(EjbModule ejbModule) {
Object o = ejbModule.getAltDDs().get("openejb-cmp-orm.xml");
if (o instanceof EntityMappings) {
return (EntityMappings) o;
}
return new EntityMappings();
}
/**
* Generate the JPA mapping for a CMP 2.x bean. Since
* the field accessors are all defined as abstract methods
* and the fields will not be defined in the implementation
* class, we don't need to deal with mapped superclasses.
* All of the fields and concrete methods will be
* implemented by the generated subclass, so from
* a JPA standpoint, there are no mapped superclasses
* required.
*
* @param mapping The mapping information we're updating.
* @param bean The entity bean meta data
* @param classLoader
* The classloader for resolving class references and
* primary key classes.
*/
private Collection<MappedSuperclass> mapClass2x(Mapping mapping, EntityBean bean, ClassLoader classLoader) {
Set<String> allFields = new TreeSet<String>();
// get an acculated set of the CMP fields.
for (CmpField cmpField : bean.getCmpField()) {
allFields.add(cmpField.getFieldName());
}
Class<?> beanClass = null;
try {
beanClass = classLoader.loadClass(bean.getEjbClass());
} catch (ClassNotFoundException e) {
// class was already loaded in validation phase, so this should succeed
// if it does fail, just return null from here
return null;
}
// build a map from the field name to the super class that contains that field.
// If this is a strictly CMP 2.x class, this is generally an empty map. However,
// we support some migration steps toward EJB3, so this can be defined completely
// or partially as a POJO with concrete fields and accessors. This allows us to
// locate and generate the mappings
Map<String, MappedSuperclass> superclassByField = mapFields(beanClass, allFields);
// Add the cmp-field declarations for all the cmp fields that
// weren't explicitly declared in the ejb-jar.xml.
// we can identify these by looking for abstract methods that match
// the get<Name> or is<Name> pattern.
for (Method method : beanClass.getMethods()) {
if (!Modifier.isAbstract(method.getModifiers())) continue;
if (method.getParameterTypes().length != 0) continue;
if (method.getReturnType().equals(Void.TYPE)) continue;
// Skip relationships: anything of type EJBLocalObject or Collection
if (EJBLocalObject.class.isAssignableFrom(method.getReturnType())) continue;
if (Collection.class.isAssignableFrom(method.getReturnType())) continue;
if (Map.class.isAssignableFrom(method.getReturnType())) continue;
String name = method.getName();
if (name.startsWith("get")){
name = name.substring("get".length(), name.length());
} else if (name.startsWith("is")){
// Only add this if the return type from an "is" method
// boolean.
if (method.getReturnType() == Boolean.TYPE) {
name = name.substring("is".length(), name.length());
}
else {
// not an acceptable "is" method.
continue;
}
} else continue;
// the property needs the first character lowercased. Generally,
// we'll have this field already in our list, but it might have been
// omitted from the meta data.
name = Strings.lcfirst(name);
if (!allFields.contains(name)){
allFields.add(name);
bean.addCmpField(name);
}
}
//
// id: the primary key
//
Set<String> primaryKeyFields = new HashSet<String>();
if (bean.getPrimkeyField() != null) {
String fieldName = bean.getPrimkeyField();
MappedSuperclass superclass = superclassByField.get(fieldName);
// this need not be here...for CMP 2.x, these are generally autogenerated fields.
if (superclass != null) {
// ok, add this field to the superclass mapping
superclass.addField(new Id(fieldName));
// the direct mapping is an over ride
mapping.addField(new AttributeOverride(fieldName));
}
else {
// this is a normal generated field, it will be in the main class mapping.
mapping.addField(new Id(fieldName));
}
primaryKeyFields.add(fieldName);
} else if ("java.lang.Object".equals(bean.getPrimKeyClass())) {
// the automatically generated keys use a special property name
// and will always be in the generated superclass.
String fieldName = "OpenEJB_pk";
Id field = new Id(fieldName);
field.setGeneratedValue(new GeneratedValue(GenerationType.AUTO));
mapping.addField(field);
primaryKeyFields.add(fieldName);
} else if (bean.getPrimKeyClass() != null) {
Class<?> pkClass = null;
try {
pkClass = classLoader.loadClass(bean.getPrimKeyClass());
MappedSuperclass idclass = null;
// now validate the primary class fields against the bean cmp fields
// to make sure everything maps correctly.
for (java.lang.reflect.Field pkField : pkClass.getFields()) {
String pkFieldName = pkField.getName();
int modifiers = pkField.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && allFields.contains(pkFieldName)) {
// see if the bean field is concretely defined in one of the superclasses
MappedSuperclass superclass = superclassByField.get(pkFieldName);
if (superclass != null) {
// ok, we have an override that needs to be specified at the main class level.
superclass.addField(new Id(pkFieldName));
mapping.addField(new AttributeOverride(pkFieldName));
idclass = resolveIdClass(idclass, superclass, beanClass);
}
else {
// this field will be autogenerated
mapping.addField(new Id(pkFieldName));
}
primaryKeyFields.add(pkFieldName);
}
}
// if we've located an ID class, set it as such
if (idclass != null) {
idclass.setIdClass(new IdClass(bean.getPrimKeyClass()));
}
else {
// do this for the toplevel mapping
mapping.setIdClass(new IdClass(bean.getPrimKeyClass()));
}
} catch (ClassNotFoundException e) {
throw (IllegalStateException)new IllegalStateException("Could not find entity primary key class " + bean.getPrimKeyClass()).initCause(e);
}
}
//
// basic: cmp-fields
// This again, adds all of the additional cmp-fields to the mapping
//
for (CmpField cmpField : bean.getCmpField()) {
// only add entries for cmp fields that are not part of the primary key
if (!primaryKeyFields.contains(cmpField.getFieldName())) {
String fieldName = cmpField.getFieldName();
// this will be here if we've already processed this
MappedSuperclass superclass = superclassByField.get(fieldName);
// if this field is defined by one of the superclasses, then
// we need to provide a mapping for this.
if (superclass != null) {
// we need to mark this as being in one of the superclasses
superclass.addField(new Basic(fieldName));
mapping.addField(new AttributeOverride(fieldName));
}
else {
// directly generated.
mapping.addField(new Basic(fieldName));
}
}
}
// all of the fields should now be identified by type, so return a set of
// the field mappings
return new HashSet<MappedSuperclass>(superclassByField.values());
}
/**
* Create the class mapping for a CMP 1.x entity bean.
* Since the fields for 1.x persistence are defined
* in the objects directly, we need to create superclass
* mappings for each of the defined fields to identify
* which classes implement each of the managed fields.
*
* @param ejbClassName
* The name of the class we're processing.
* @param mapping The mappings we're going to generate.
* @param bean The bean metadata for the ejb.
* @param classLoader
* The classloader used to load the bean class for
* inspection.
*
* @return The set of mapped superclasses used in this
* bean mapping.
*/
private Collection<MappedSuperclass> mapClass1x(String ejbClassName, Mapping mapping, EntityBean bean, ClassLoader classLoader) {
Class ejbClass = loadClass(classLoader, ejbClassName);
// build a set of all field names
Set<String> allFields = new TreeSet<String>();
for (CmpField cmpField : bean.getCmpField()) {
allFields.add(cmpField.getFieldName());
}
// build a map from the field name to the super class that contains that field
Map<String, MappedSuperclass> superclassByField = mapFields(ejbClass, allFields);
//
// id: the primary key
//
Set<String> primaryKeyFields = new HashSet<String>();
if (bean.getPrimkeyField() != null) {
String fieldName = bean.getPrimkeyField();
MappedSuperclass superclass = superclassByField.get(fieldName);
if (superclass == null) {
throw new IllegalStateException("Primary key field " + fieldName + " is not defined in class " + ejbClassName + " or any super classes");
}
superclass.addField(new Id(fieldName));
mapping.addField(new AttributeOverride(fieldName));
primaryKeyFields.add(fieldName);
} else if ("java.lang.Object".equals(bean.getPrimKeyClass())) {
// a primary field type of Object is an automatically generated
// pk field. Mark it as such and add it to the mapping.
String fieldName = "OpenEJB_pk";
Id field = new Id(fieldName);
field.setGeneratedValue(new GeneratedValue(GenerationType.AUTO));
mapping.addField(field);
} else if (bean.getPrimKeyClass() != null) {
// we have a primary key class. We need to define the mappings between the key class fields
// and the bean's managed fields.
Class<?> pkClass = null;
try {
pkClass = classLoader.loadClass(bean.getPrimKeyClass());
MappedSuperclass superclass = null;
MappedSuperclass idclass = null;
for (java.lang.reflect.Field pkField : pkClass.getFields()) {
String fieldName = pkField.getName();
int modifiers = pkField.getModifiers();
// the primary key fields must be public, non-static, must be defined as a CMP field,
// AND must also exist in the class hierarchy (not enforced by mapFields());
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && allFields.contains(fieldName)) {
superclass = superclassByField.get(fieldName);
if (superclass == null) {
throw new IllegalStateException("Primary key field " + fieldName + " is not defined in class " + ejbClassName + " or any super classes");
}
// these are defined ast ID fields because they are part of the primary key
superclass.addField(new Id(fieldName));
mapping.addField(new AttributeOverride(fieldName));
primaryKeyFields.add(fieldName);
idclass = resolveIdClass(idclass, superclass, ejbClass);
}
}
// if we've located an ID class, set it as such
if (idclass != null) {
idclass.setIdClass(new IdClass(bean.getPrimKeyClass()));
}
} catch (ClassNotFoundException e) {
throw (IllegalStateException)new IllegalStateException("Could not find entity primary key class " + bean.getPrimKeyClass()).initCause(e);
}
}
//
// basic: cmp-fields
//
for (CmpField cmpField : bean.getCmpField()) {
String fieldName = cmpField.getFieldName();
// all of the primary key fields have been processed, so only handle whatever is left over
if (!primaryKeyFields.contains(fieldName)) {
MappedSuperclass superclass = superclassByField.get(fieldName);
if (superclass == null) {
throw new IllegalStateException("CMP field " + fieldName + " is not defined in class " + ejbClassName + " or any super classes");
}
superclass.addField(new Basic(fieldName));
mapping.addField(new AttributeOverride(fieldName));
}
}
// all of the fields should now be identified by type, so return a set of
// the field mappings
return new HashSet<MappedSuperclass>(superclassByField.values());
}
/**
* Handle the potential situation where the fields
* of a complex primary key are defined at different
* levels of the class hierarchy. We want to define
* the idClass as the most derived class (i.e., the one
* that will contain ALL of the defined fields).
*
* In practice, most ejbs will define all of the
* primary key fields at the same subclass level, so
* this should return quickly.
*
* @param idclass The currently defined id class (will be null if
* this is the first call).
* @param current The current superclass being processed.
* @param ejbClass The ejbClass we're creating the mapping for.
*
* @return Either idClass or current, depending on which is
* the most derived of the classes.
*/
private MappedSuperclass resolveIdClass(MappedSuperclass idclass, MappedSuperclass current, Class ejbClass)
{
// None identified yet? Just use the one we just found
if (idclass == null) {
return current;
}
String idClassName = idclass.getClazz();
String currentClassName = current.getClazz();
// defined at the same level (common). Just keep the same id class
if (idClassName.equals(currentClassName)) {
return idclass;
}
// we have a split across the hiearchy, we need to figure out which of the classes is
// the most derived
for (Class clazz = ejbClass; clazz != null; clazz = clazz.getSuperclass()) {
String name = clazz.getName();
// if we find the current one first, return it
if (name.equals(currentClassName)) {
return current;
}
else if (name.equals(idClassName)) {
// keeping the same highest level
return idclass;
}
}
// this should never happen, but keep the same one if we ever reach here
return idclass;
}
private static Class loadClass(ClassLoader classLoader, String className) {
Class ejbClass = null;
try {
ejbClass = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
return ejbClass;
}
/**
* Build a mapping between a bean's CMP fields and the
* particular subclass in the inheritance hierarchy that
* defines the field.
*
* @param clazz The bean implementation class.
* @param persistantFields
* The set of container-managed fields.
*
* @return A map of fieldname-to-defining class relationships.
*/
private Map<String, MappedSuperclass> mapFields(Class clazz, Set<String> persistantFields) {
persistantFields = new TreeSet<String>(persistantFields);
Map<String,MappedSuperclass> fields = new TreeMap<String,MappedSuperclass>();
// spin down the class hierarchy until we've either processed all of the fields
// or we've reached the Object class.
while (!persistantFields.isEmpty() && !clazz.equals(Object.class)) {
// This is a single target for the relationship mapping for each
// class in the hierarchy.
MappedSuperclass superclass = new MappedSuperclass(clazz.getName());
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
if (!field.isSynthetic()) {
String fieldName = field.getName();
// if this is one of bean's persistence fields, create the mapping
if (persistantFields.contains(fieldName)) {
fields.put(fieldName, superclass);
persistantFields.remove(fieldName);
} else if (!ENHANCED_FIELDS.contains(fieldName)){
// these are fields we need to identify as transient for the persistence engine.
Transient transientField = new Transient(fieldName);
superclass.addField(transientField);
}
}
}
clazz = clazz.getSuperclass();
}
return fields;
}
/**
* Add a persistence context reference for the CMP
* persistence contexts to this EntityBean definition.
*
* @param bean The bean we're updating.
*
* @return Returns true if the context was added. Returns false if
* the bean already is associated with the CMP persistence
* context.
*/
private boolean addPersistenceContextRef(EntityBean bean) {
// if a ref is already defined, skip this bean
if (bean.getPersistenceContextRefMap().containsKey("java:" + JpaCmpEngine.CMP_PERSISTENCE_CONTEXT_REF_NAME)) return false;
PersistenceContextRef persistenceContextRef = new PersistenceContextRef();
persistenceContextRef.setName("java:" + JpaCmpEngine.CMP_PERSISTENCE_CONTEXT_REF_NAME);
persistenceContextRef.setPersistenceUnitName(CMP_PERSISTENCE_UNIT_NAME);
bean.getPersistenceContextRef().add(persistenceContextRef);
return true;
}
private void setCascade(EjbRelationshipRole role, RelationField field) {
if (role.getCascadeDelete()) {
CascadeType cascadeType = new CascadeType();
cascadeType.setCascadeAll(true);
field.setCascade(cascadeType);
}
}
}