Author: rickmcguire
Date: Wed Jul 30 11:55:37 2008
New Revision: 681175
URL: http://svn.apache.org/viewvc?rev=681175&view=rev
Log:
OPENEJB-877 Allow pojo-style attributes in cmp2.x beans as a migratiion path
toward EJB3.
Added:
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-pojo-ejb-jar.xml
(with props)
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-pojo-openejb-jar.xml
(with props)
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-pojo-orm.xml
(with props)
openejb/trunk/openejb3/itests/openejb-itests-beans/src/main/java/org/apache/openejb/test/entity/cmp/BasicCmp2PojoBean.java
(with props)
Modified:
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/CmpJpaConversion.java
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/cmp/cmp2/Cmp2Generator.java
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/OpenEjb2ConversionTest.java
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/SunCmpConversionTest.java
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-ejb-jar.xml
Modified:
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/CmpJpaConversion.java
URL:
http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/CmpJpaConversion.java?rev=681175&r1=681174&r2=681175&view=diff
==============================================================================
---
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/CmpJpaConversion.java
(original)
+++
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/CmpJpaConversion.java
Wed Jul 30 11:55:37 2008
@@ -92,7 +92,8 @@
}
}
-
+ // 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) {
@@ -176,6 +177,15 @@
return persistenceUnit;
}
+ /**
+ * 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()) {
@@ -185,6 +195,14 @@
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;
}
@@ -475,12 +493,23 @@
ClassLoader classLoader = ejbModule.getClassLoader();
+ Collection<MappedSuperclass> mappedSuperclasses;
if (bean.getCmpVersion() == CmpVersion.CMP2) {
- mapClass2x(entity, bean, classLoader);
+ // 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
- Collection<MappedSuperclass> mappedSuperclasses =
mapClass1x(bean.getEjbClass(), entity, bean, classLoader);
-
+ // 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);
}
@@ -509,6 +538,7 @@
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());
@@ -579,62 +609,113 @@
return new EntityMappings();
}
- private void mapClass2x(Mapping mapping, EntityBean bean, ClassLoader
classLoader) {
+ /**
+ * 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;
- // Add the cmp-field declarations for all the cmp fields that
- // weren't explicitly declared in the ejb-jar.xml
try {
- Class<?> beanClass = classLoader.loadClass(bean.getEjbClass());
- 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;
-
- name = Strings.lcfirst(name);
- if (!allFields.contains(name)){
- allFields.add(name);
- bean.addCmpField(name);
- }
- }
+ beanClass = classLoader.loadClass(bean.getEjbClass());
} catch (ClassNotFoundException e) {
- // class was already loaded in validation phase
+ // 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();
- Field field = new Id(fieldName);
- mapping.addField(field);
+ 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));
@@ -644,16 +725,36 @@
Class<?> pkClass = null;
try {
pkClass = classLoader.loadClass(bean.getPrimKeyClass());
- mapping.setIdClass(new IdClass(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)) {
- Field field = new Id(pkFieldName);
- mapping.addField(field);
+ // 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);
}
@@ -661,15 +762,33 @@
//
// 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())) {
- Field field = new Basic(cmpField.getFieldName());
- mapping.addField(field);
+ 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
@@ -840,6 +959,7 @@
return ejbClass;
}
+
/**
* Build a mapping between a bean's CMP fields and the
* particular subclass in the inheritance hierarchy that
Modified:
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/cmp/cmp2/Cmp2Generator.java
URL:
http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/cmp/cmp2/Cmp2Generator.java?rev=681175&r1=681174&r2=681175&view=diff
==============================================================================
---
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/cmp/cmp2/Cmp2Generator.java
(original)
+++
openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/core/cmp/cmp2/Cmp2Generator.java
Wed Jul 30 11:55:37 2008
@@ -94,10 +94,27 @@
if (getter == null) {
throw new IllegalArgumentException("No such property " +
cmpFieldName + " defined on bean class " + beanClassName);
}
-
- Type type = Type.getType(getter.getReturnType());
- CmpField cmpField = new CmpField(cmpFieldName, type, getter);
- this.cmpFields.put(cmpFieldName, cmpField);
+ // if this is an abstract method, then it's one we have to
generate
+ if (Modifier.isAbstract(getter.getModifiers())) {
+
+ Type type = Type.getType(getter.getReturnType());
+ CmpField cmpField = new CmpField(cmpFieldName, type, getter);
+ this.cmpFields.put(cmpFieldName, cmpField);
+ }
+ else {
+ // the getter is non-abstract. We only allow this if the
class that
+ // defines the getter also has a private field with a matching
type.
+ try {
+ Field field =
getter.getDeclaringClass().getDeclaredField(cmpFieldName);
+ // if this is a private field, then just continue. We
don't need to generate
+ // any code for this
+ if (Modifier.isPrivate(field.getModifiers())) {
+ continue;
+ }
+ } catch (NoSuchFieldException e) {
+ }
+ throw new IllegalArgumentException("No such property " +
cmpFieldName + " defined on bean class " + beanClassName);
+ }
}
// if a pkField is defined, it MUST be a CMP field. Make sure it
really exists
@@ -484,11 +501,7 @@
String getterName = "get" + propertyName.substring(0, 1).toUpperCase()
+ propertyName.substring(1);
try {
// check to see if we have the getter as an abstract class. This
might be an "is" method.
- Method method = beanClass.getMethod(getterName, new Class[0]);
- if (Modifier.isAbstract(method.getModifiers())) {
- // this is a getter
- return method;
- }
+ return beanClass.getMethod(getterName, new Class[0]);
} catch (NoSuchMethodException e) {
}
@@ -497,11 +510,7 @@
getterName = "is" + propertyName.substring(0, 1).toUpperCase() +
propertyName.substring(1);
try {
// check to see if we have the getter as an abstract class. This
might be an "is" method.
- Method method = beanClass.getMethod(getterName, new Class[0]);
- if (Modifier.isAbstract(method.getModifiers())) {
- // this is a getter
- return method;
- }
+ return beanClass.getMethod(getterName, new Class[0]);
} catch (NoSuchMethodException e) {
}
return null;
Modified:
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/OpenEjb2ConversionTest.java
URL:
http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/OpenEjb2ConversionTest.java?rev=681175&r1=681174&r2=681175&view=diff
==============================================================================
---
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/OpenEjb2ConversionTest.java
(original)
+++
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/OpenEjb2ConversionTest.java
Wed Jul 30 11:55:37 2008
@@ -41,6 +41,10 @@
public void testItests22() throws Exception {
convert("convert/oej2/cmp/itest-2.2/itest-2.2-");
}
+
+ public void testItests22Pojo() throws Exception {
+ convert("convert/oej2/cmp/itest-2.2/itest-2.2-pojo-");
+ }
public void testDaytrader() throws Exception {
convert("convert/oej2/cmp/daytrader/daytrader-");
Modified:
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/SunCmpConversionTest.java
URL:
http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/SunCmpConversionTest.java?rev=681175&r1=681174&r2=681175&view=diff
==============================================================================
---
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/SunCmpConversionTest.java
(original)
+++
openejb/trunk/openejb3/container/openejb-core/src/test/java/org/apache/openejb/config/SunCmpConversionTest.java
Wed Jul 30 11:55:37 2008
@@ -53,7 +53,7 @@
// public void testDaytrader() throws Exception {
// convert("convert/oej2/cmp/daytrader/daytrader-");
-// }
+// }
//
public void testOneToOne() throws Exception {
convert("convert/oej2/cmp/onetoone/simplepk/");
Modified:
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-ejb-jar.xml
URL:
http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-ejb-jar.xml?rev=681175&r1=681174&r2=681175&view=diff
==============================================================================
---
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-ejb-jar.xml
(original)
+++
openejb/trunk/openejb3/container/openejb-core/src/test/resources/convert/oej2/cmp/itest-2.2/itest-2.2-ejb-jar.xml
Wed Jul 30 11:55:37 2008
@@ -379,6 +379,7 @@
</ejb-ql>
</query>
</entity>
+
<!--
########################################################