blob: 50339d0949659259bf4122230c843eaa822982ba [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.core.cmp.cmp2;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EntityContext;
import org.apache.xbean.asm.ClassWriter;
import org.apache.xbean.asm.FieldVisitor;
import org.apache.xbean.asm.Label;
import org.apache.xbean.asm.MethodVisitor;
import org.apache.xbean.asm.Opcodes;
import org.apache.xbean.asm.Type;
/**
* Code generate for CMP level 2 beans. This will
* generate the concrete class used to instantiate
* the bean instance.
*/
public class Cmp2Generator implements Opcodes {
private static final String UNKNOWN_PK_NAME = "OpenEJB_pk";
private static final Type UNKNOWN_PK_TYPE = Type.getType(Long.class);
private final String implClassName;
private final String beanClassName;
private final ClassWriter cw;
private final Map<String, CmpField> cmpFields = new LinkedHashMap<String, CmpField>();
private final Collection<CmrField> cmrFields = new ArrayList<CmrField>();
private final CmpField pkField;
private final Class primKeyClass;
private final List<Method> selectMethods = new ArrayList<Method>();
private final Class beanClass;
private final PostCreateGenerator postCreateGenerator;
private static final String DELETED = "openejb_deleted";
/**
* Constructor for a Cmp2Generator. This validates the
* initial EJB state information and prepares for the
* code generation process.
*
* @param cmpImplClass
* The name of the implementation class we're generating.
* @param beanClass The bean implementation class that is our starting
* point for code generation.
* @param pkField The name of the primary key field (optional if the
* primary key class is given).
* @param primKeyClass
* The optional primary key class for complex primary
* keys.
* @param cmpFields The list of fields that are managed using cmp.
*/
public Cmp2Generator(String cmpImplClass, Class beanClass, String pkField, Class<?> primKeyClass, String[] cmpFields) {
this.beanClass = beanClass;
beanClassName = Type.getInternalName(beanClass);
implClassName = cmpImplClass.replace('.', '/');
if (pkField == null && primKeyClass == null) {
throw new NullPointerException("Both pkField and primKeyClass are null for bean " + beanClassName);
}
this.primKeyClass = primKeyClass;
// for each of the defined cmp fields, we need to locate the getter method
// for the name and retrieve the type. This a) verifies that we have at least
// a getter defined and b) that we know the field return type. The created CmpField
// list will feed into the generation process.
for (String cmpFieldName : cmpFields) {
Method getter = getterMethod(cmpFieldName);
if (getter == null) {
throw new IllegalArgumentException("No such property " + cmpFieldName + " defined on bean class " + beanClassName);
}
// 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
// in the list we constructed above.
if (pkField != null) {
this.pkField = this.cmpFields.get(pkField);
if (this.pkField == null) {
throw new IllegalArgumentException("No such property " + pkField + " defined on bean class " + beanClassName);
}
} else {
this.pkField = null;
}
// build up the list of ejbSelectxxxx methods. These will be generated automatically.
for (Method method : beanClass.getMethods()) {
if (Modifier.isAbstract(method.getModifiers()) && method.getName().startsWith("ejbSelect")) {
addSelectMethod(method);
}
}
// The class writer will be used for all generator activies, while the
// postCreateGenerator will be used to add the ejbPostCreatexxxx methods as a
// last step.
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
postCreateGenerator = new PostCreateGenerator(beanClass, cw);
}
/**
* Add a field to the list of fields defined as CMR
* fields. Note that if the field is also defined
* as a CMP field, it will be removed from the normal
* CMP list.
*
* @param cmrField The new CMR field definition pulled from the
* EJB metadata.
*/
public void addCmrField(CmrField cmrField) {
if (cmpFields.get(cmrField.getName()) != null) {
cmpFields.remove(cmrField.getName());
}
cmrFields.add(cmrField);
}
/**
* Add a method to the list of ejbSelect methods that
* need to be processed.
*
* @param selectMethod
* The method that needs to be processed.
*/
public void addSelectMethod(Method selectMethod) {
selectMethods.add(selectMethod);
}
/**
* Perform the generation step for a CMP Entity Bean.
* This uses the accumulated meta data and the
* base bean class to generate a subclass with
* the automatically generated bits of OpenEJB infrastructure
* hooks.
*
* @return The class file byte array to be used for defining this
* class.
*/
public byte[] generate() {
// generate the class as super class of the base bean class. This class will also implment
// EntityBean and Cmp2Entity.
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, implClassName, null, beanClassName, new String[]{"org/apache/openejb/core/cmp/cmp2/Cmp2Entity", "javax/ejb/EntityBean"});
// public static Object deploymentInfo;
{
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "deploymentInfo", "Ljava/lang/Object;", null, null);
fv.visitEnd();
}
// private transient boolean deleted;
{
FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_TRANSIENT, DELETED, "Z", null, null);
fv.visitEnd();
}
if (Object.class.equals(primKeyClass)) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE, UNKNOWN_PK_NAME, UNKNOWN_PK_TYPE.getDescriptor(), null, null);
fv.visitEnd();
}
// Generate the set of cmp fields as private attributes.
// private ${cmpField.type} ${cmpField.name};
for (CmpField cmpField : cmpFields.values()) {
createField(cmpField);
}
// and create the corresponding CMR fields as well.
for (CmrField cmrField : cmrFields) {
createCmrFields(cmrField);
}
createConstructor();
// now for each of the CMP fields, generate the getter and setter methods
// from the abstract methods the bean author should have provided.
for (CmpField cmpField : cmpFields.values()) {
// public ${cmpField.type} get${cmpField.name}() {
// return this.${cmpField.name};
// }
createGetter(cmpField);
// public void setId(${cmpField.type} ${cmpField.name}) {
// this.${cmpField.name} = ${cmpField.name};
// }
createSetter(cmpField);
}
// and repeat this for the cmr fields.
for (CmrField cmrField : cmrFields) {
createCmrGetter(cmrField);
createCmrSetter(cmrField);
}
createSimplePrimaryKeyGetter();
// add the set of OpenEJB container management methods.
createOpenEJB_isDeleted();
createOpenEJB_deleted();
createOpenEJB_addCmr();
createOpenEJB_removeCmr();
// generate the select methods
for (Method selectMethod : selectMethods) {
createSelectMethod(selectMethod);
}
// now automatically generate any of the ejb* methods. According to the
// spec, the bean author should be responsble for these, but since these
// are frequently just nop stubs, we'll take responsibility for creating
// empty ones in the generated superclass.
if (!hasMethod(beanClass, "ejbActivate")) createEjbActivate();
if (!hasMethod(beanClass, "ejbPassivate")) createEjbPassivate();
if (!hasMethod(beanClass, "ejbLoad")) createEjbLoad();
if (!hasMethod(beanClass, "ejbStore")) createEjbStore();
if (!hasMethod(beanClass, "ejbRemove")) createEjbRemove();
if (!hasMethod(beanClass, "setEntityContext", EntityContext.class)) createSetEntityContext();
if (!hasMethod(beanClass, "unsetEntityContext")) createUnsetEntityContext();
// add on any post-create methods that might be required.
postCreateGenerator.generate();
cw.visitEnd();
// the class, in theory, is now complete. Return in byte[] form so this can
// be defined in the appropriate classloader instance.
return cw.toByteArray();
}
private boolean hasMethod(Class beanClass, String name, Class... args) {
try {
Method method = beanClass.getMethod(name, args);
return !Modifier.isAbstract(method.getModifiers());
} catch (NoSuchMethodException e) {
return false;
}
}
private void createConstructor() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, beanClassName, "<init>", "()V");
for (CmrField cmrField : cmrFields) {
initCmrFields(mv, cmrField);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void createOpenEJB_isDeleted() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "OpenEJB_isDeleted", "()Z", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, DELETED, "Z");
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void createOpenEJB_deleted() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "OpenEJB_deleted", "()V", null, null);
mv.visitCode();
/* if (deleted) return; */
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, DELETED, "Z");
Label notDeleted = new Label();
mv.visitJumpInsn(IFEQ, notDeleted);
mv.visitInsn(RETURN);
mv.visitLabel(notDeleted);
// deleted = true;
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ICONST_1);
mv.visitFieldInsn(PUTFIELD, implClassName, DELETED, "Z");
for (CmrField cmrField : cmrFields) {
// ${cmrField.accessor}.delete(${cmrField.name});
createOpenEJB_deleted(mv, cmrField);
}
// return;
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void createOpenEJB_addCmr() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "OpenEJB_addCmr", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", null, null);
mv.visitCode();
// if (deleted) return null;
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, DELETED, "Z");
Label notDeleted = new Label();
mv.visitJumpInsn(IFEQ, notDeleted);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
mv.visitLabel(notDeleted);
for (CmrField cmrField : cmrFields) {
// if ("${cmrField.name}".equals(name)) {
// ${cmrField.name}.add((${cmrField.type})value);
// return null;
// }
//
// OR
//
// if ("${cmrField.name}".equals(name)) {
// Object oldValue = ${cmrField.name};
// ${cmrField.name} = (${cmrField.type}) bean;
// return oldValue;
// }
createOpenEJB_addCmr(mv, cmrField);
}
// throw new IllegalArgumentException("Unknown cmr field " + name + " on entity bean of type " + getClass().getName());
mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
mv.visitLdcInsn("Unknown cmr field ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitLdcInsn(" on entity bean of type ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void createOpenEJB_removeCmr() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "OpenEJB_removeCmr", "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
mv.visitCode();
// if (deleted) return;
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, DELETED, "Z");
Label notDeleted = new Label();
mv.visitJumpInsn(IFEQ, notDeleted);
mv.visitInsn(RETURN);
mv.visitLabel(notDeleted);
for (CmrField cmrField : cmrFields) {
// if ("${cmrField.name}".equals(name)) {
// ${cmrField.name}.remove(value);
// return;
// }
//
// OR
//
// if ("${cmrField.name}".equals(name)) {
// ${cmrField.name} = null;
// return;
// }
createOpenEJB_removeCmr(mv, cmrField);
}
// throw new IllegalArgumentException("Unknown cmr field " + name + " on entity bean of type " + getClass().getName());
mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
mv.visitLdcInsn("Unknown cmr field ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitLdcInsn(" on entity bean of type ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Add a CMP field to the class. The field is created
* with private scope.
*
* @param cmpField The Cmp field defined in the metadata.
*/
private void createField(CmpField cmpField) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE,
cmpField.getName(),
cmpField.getDescriptor(),
null,
null);
fv.visitEnd();
}
/**
* Generate a concrete getter field for a CMP field.
* At this point, we're just generating a simple
* accessor for the field, given the type. The
* JPA engine when it makes this implementation class
* a managed class define whatever additional logic
* might be required.
*
* @param cmpField The CMP field backing this getter method.
*/
private void createGetter(CmpField cmpField) {
String methodName = cmpField.getGetterMethod().getName();
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "()" + cmpField.getDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmpField.getName(), cmpField.getDescriptor());
mv.visitInsn(cmpField.getType().getOpcode(IRETURN));
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate the getter name for a CMR property.
*
* @param propertyName
* The name of the CMR property.
*
* @return The string name of the getter method for the
* property.
*/
private static String getterName(String propertyName) {
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
}
/**
* Get the getter method for this CMP field. This
* will be either get<Name> or is<Name> depending on
* what abstract method is defined on the base bean
* class.
*
* @param propertyName The name of the CMP field.
*
* @return The name to be used for generating this method.
*/
private Method getterMethod(String propertyName) {
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.
return beanClass.getMethod(getterName, new Class[0]);
} catch (NoSuchMethodException e) {
}
// we're just going to assume this is the valid name. Other validation should already have been
// performed prior to this.
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.
return beanClass.getMethod(getterName, new Class[0]);
} catch (NoSuchMethodException e) {
}
return null;
}
/**
* Generate a concrete setter field for a CMP field.
* At this point, we're just generating a simple
* accessor for the field, given the type. The
* JPA engine when it makes this implementation class
* a managed class define whatever additional logic
* might be required.
*
* @param cmpField The CMP field backing this setter method.
*/
private void createSetter(CmpField cmpField) {
String methodName = setterName(cmpField.getName());
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "(" + cmpField.getDescriptor() + ")V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(cmpField.getType().getOpcode(ILOAD), 1);
mv.visitFieldInsn(PUTFIELD, implClassName, cmpField.getName(), cmpField.getDescriptor());
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private String setterName(String propertyName) {
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
}
/**
* Create a simple internal method for obtaining the
* primary key. There are 2 possibilities for handling
* the primary key here:
*
* 1) There is a defined primary key field. The
* contents of that field are returned.
*
* 2) The primary key is provided by the container.
* This is a long value stored in a private, generated
* field. This field is returned as a generated
* wrappered Long.
*
* 3) A primary key class has been provided. An instance
* of this class is instantiated, and code is generated
* that will copy all of the CMP fields from the EJB
* into the primary key instance.
*/
private void createSimplePrimaryKeyGetter() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "OpenEJB_getPrimaryKey", "()Ljava/lang/Object;", null, null);
mv.visitCode();
// the primary key is identifed as a field. We just return that value directly.
if (pkField != null) {
// push the pk field
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, pkField.getName(), pkField.getDescriptor());
// return the pk field (from the stack)
mv.visitInsn(pkField.getType().getOpcode(IRETURN));
} else if (Object.class.equals(primKeyClass)) {
// this is a container-generated primary key. It's a long value stored in
// a generated field. We return that value, wrappered in a Long instance.
// push the pk field
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, UNKNOWN_PK_NAME, UNKNOWN_PK_TYPE.getDescriptor());
// return the pk field (from the stack)
mv.visitInsn(UNKNOWN_PK_TYPE.getOpcode(IRETURN));
} else {
// We have a primary key class defined. For every field that matches one of the
// defined CMP fields, we generate code to copy that value into the corresponding
// field of the primary key class.
String pkImplName = primKeyClass.getName().replace('.', '/');
// new Pk();
mv.visitTypeInsn(NEW, pkImplName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, pkImplName, "<init>", "()V");
mv.visitVarInsn(ASTORE, 1);
mv.visitVarInsn(ALOAD, 1);
// copy each field from the ejb to the pk class
for (Field field : primKeyClass.getFields()) {
CmpField cmpField = cmpFields.get(field.getName());
// only process the cmp fields
if (cmpField == null) {
continue;
}
// verify types match... this should have been caught by the verifier, but
// check again since generated code is so hard to debug
if (!cmpField.getType().getClassName().equals(field.getType().getName())) {
throw new IllegalArgumentException("Primary key " + cmpField.getName() + " is type " + cmpField.getType().getClassName() + " but CMP field is type " + field.getType().getName());
}
// push the value from the cmp bean
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmpField.getName(), cmpField.getDescriptor());
// set matching field in the pk class to the value on the stack
mv.visitFieldInsn(PUTFIELD, pkImplName, cmpField.getName(), cmpField.getDescriptor());
mv.visitVarInsn(ALOAD, 1);
}
// return the Pk();
mv.visitInsn(ARETURN);
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Create the CMR fields defined for this object. This
* creates a pair of fields for each CMR field. The
* first field is the real field containing the object
* data. The second field will be an accessor object
* that's instantiated when the fields are first
* initialized. The accessor field gets created with
* the same name and "Cmr" concatenated to the end
* of the field name.
*
* @param cmrField The CMR field descriptor.
*/
private void createCmrFields(CmrField cmrField) {
FieldVisitor fv = cw.visitField(ACC_PRIVATE,
cmrField.getName(),
cmrField.getDescriptor(),
cmrField.getGenericSignature(),
null);
fv.visitEnd();
fv = cw.visitField(ACC_PRIVATE + ACC_TRANSIENT,
cmrField.getName() + "Cmr",
cmrField.getAccessorDescriptor(),
cmrField.getAccessorGenericSignature(),
null);
fv.visitEnd();
}
/**
* Initialize the CMR fields associated with a CMR
* definition. This initializes two fields per CMR
* defined field: 1) The CMR field itself (which might
* be initialized to an instance of a defined type) and 2)
* the appropriate CMD accessor that handles the
* different types of relationship.
*
* @param mv The method context we're initializing in.
* @param cmrField The CMR field to process.
*/
private void initCmrFields(MethodVisitor mv, CmrField cmrField) {
// this.${cmrField.name} = new ${cmrField.initialValueType}();
Type initialValueType = cmrField.getInitialValueType();
if (initialValueType != null) {
mv.visitVarInsn(ALOAD, 0);
mv.visitTypeInsn(NEW, initialValueType.getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, initialValueType.getInternalName(), "<init>", "()V");
mv.visitFieldInsn(PUTFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
}
// this.${cmrField.name}Cmr = new ${cmrField.accessorType}<${cmrField.type}, ${cmrField.proxyType}>(this,
// ${cmrField.name},
// ${cmrField.type},
// ${cmrField.relatedName});
mv.visitVarInsn(ALOAD, 0);
mv.visitTypeInsn(NEW, cmrField.getAccessorInternalName());
mv.visitInsn(DUP);
// arg0: EntityBean source = this
mv.visitVarInsn(ALOAD, 0);
// arg1: String sourceProperty - "b"
mv.visitLdcInsn(cmrField.getName());
// arg2: Class<Bean> relatedType = BBean_BBean
mv.visitLdcInsn(cmrField.getType());
// arg3: String relatedProperty
if (cmrField.getRelatedName() != null) {
mv.visitLdcInsn(cmrField.getRelatedName());
} else {
mv.visitInsn(ACONST_NULL);
}
// invoke
mv.visitMethodInsn(INVOKESPECIAL,
cmrField.getAccessorInternalName(),
"<init>",
"(Ljavax/ejb/EntityBean;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)V");
// bCmr = result
mv.visitFieldInsn(PUTFIELD,
implClassName,
cmrField.getName() + "Cmr",
cmrField.getAccessorDescriptor());
}
/**
* Create a getter method for the CMR field. This
* will used the accessor object initialized into the
* name + "Cmr" field that was already generated and
* initialized at object creation. The accessor
* object manages the object relationship.
*
* @param cmrField The field we're generating.
*/
private void createCmrGetter(CmrField cmrField) {
// a synthentic method essentially means this is a relationship with
// no back reference. We don't generate a getter method for this
if (cmrField.isSynthetic()) {
return;
}
String methodName = getterName(cmrField.getName());
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "()" + cmrField.getProxyDescriptor(), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName() + "Cmr", cmrField.getAccessorDescriptor());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
// return this.${cmrField.name}Cmr.get(this.${cmdField.name});
// this takes the value stored in the CMR field (which might be a single value or
// a Set or Collection), and hands it to the appropriate accessor.
mv.visitMethodInsn(INVOKEVIRTUAL, cmrField.getAccessorInternalName(), "get", cmrField.getCmrStyle().getGetterDescriptor());
// if the style is a single value, then we're going to need to cast this
// to the target class before returning.
if (cmrField.getCmrStyle() == CmrStyle.SINGLE) {
mv.visitTypeInsn(CHECKCAST, cmrField.getProxyType().getInternalName());
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate a setter method for a CMR field. The
* setter method will delegate the setting responsibility
* to the accessor object store the associated Cmr field.
*
* @param cmrField The field we're generating the setter for.
*/
private void createCmrSetter(CmrField cmrField) {
// a synthentic method essentially means this is a relationship with
// no back reference. We don't generate a getter method for this
if (cmrField.isSynthetic()) {
return;
}
String methodName = setterName(cmrField.getName());
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "(" + cmrField.getProxyDescriptor() + ")V", null, null);
mv.visitCode();
// if this is a Many relationship, the CMR field contains a Set value. The accessor
// will process the elements in the Set, removing any existing ones, and then populating
// the Set with the new values from the new value source.
if (cmrField.getCmrStyle() != CmrStyle.SINGLE) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName() + "Cmr", cmrField.getAccessorDescriptor());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, cmrField.getAccessorInternalName(), "set", cmrField.getCmrStyle().getSetterDescriptor());
mv.visitInsn(RETURN);
} else {
// this is a single value. We pass the existing value and the old value to
// the accessor, then must cast the accessor return value to the target type
// so we can store it in the real CMR field.
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName() + "Cmr", cmrField.getAccessorDescriptor());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, cmrField.getAccessorInternalName(), "set", cmrField.getCmrStyle().getSetterDescriptor());
mv.visitTypeInsn(CHECKCAST, cmrField.getType().getInternalName());
mv.visitFieldInsn(PUTFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitInsn(RETURN);
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate the OpenEJB_deleted() logic for a
* CMR field. This handles the cascade from the referencing
* object to the CMR fields for the object. This method generates the
* CMR logic inline inside the object OpenEJB_deleted() method.
*
* @param mv The method context we're operating within.
* @param cmrField The CMD field containing the deleted value.
*/
private void createOpenEJB_deleted(MethodVisitor mv, CmrField cmrField) {
// this.${cmrField.name}Cmr.deleted(this.${cmrField.name});
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName() + "Cmr", cmrField.getAccessorDescriptor());
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitMethodInsn(INVOKEVIRTUAL, cmrField.getAccessorInternalName(), "deleted", cmrField.getCmrStyle().getDeletedDescriptor());
}
/**
* Generate the OpenEJB_addCmr logic for an individual
* CMR field. Each CMR field has a test against the
* property name, which is passed to the wrappering
* addCmr method. This results in a series of
* if blocks for each defined CMD property.
*
* @param mv The method we're generating within.
* @param cmrField The CMR field definition.
*/
private void createOpenEJB_addCmr(MethodVisitor mv, CmrField cmrField) {
// if (${cmrField.name}.equals(arg1))
mv.visitLdcInsn(cmrField.getName());
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
// if not equal jump to end
Label end = new Label();
mv.visitJumpInsn(IFEQ, end);
// collection style relationship. Generate the code to add this object to the
// collection already anchored in the CMR field.
if (cmrField.getCmrStyle() != CmrStyle.SINGLE) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitVarInsn(ASTORE, 3);
mv.visitVarInsn(ALOAD, 3);
Label fieldNotNull = new Label();
mv.visitJumpInsn(IFNONNULL, fieldNotNull);
// lazy creation of the collection type if not already created.
mv.visitTypeInsn(NEW, cmrField.getInitialValueType().getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, cmrField.getInitialValueType().getInternalName(), "<init>", "()V");
mv.visitVarInsn(ASTORE, 3);
mv.visitLabel(fieldNotNull);
// ${cmrField.name}.add(arg2)
mv.visitVarInsn(ALOAD, 3);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEINTERFACE,
cmrField.getCmrStyle().getCollectionType().getInternalName(),
"add",
"(Ljava/lang/Object;)Z");
mv.visitInsn(POP);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 3);
// unconditionally set the CMR field to the collection. This is either the
// original one on entry, or a new one for first access.
mv.visitFieldInsn(PUTFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
// return null;
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
} else {
// push: this.${cmrField.name};
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
// this.${cmrField.name} = (${cmrField.type}) bean;
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, cmrField.getType().getInternalName());
mv.visitFieldInsn(PUTFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
// return pushed value above
mv.visitInsn(ARETURN);
}
// end of if statement
mv.visitLabel(end);
}
// private void createPrintln(MethodVisitor mv, String message) {
// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// mv.visitLdcInsn(message);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
// }
// private void createPrintField(MethodVisitor mv, String fieldName, String descriptor) {
// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// mv.visitLdcInsn(fieldName + "=");
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V");
//
// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// mv.visitVarInsn(ALOAD, 0);
// mv.visitFieldInsn(GETFIELD, implClassName, fieldName, descriptor);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V");
// }
/**
* Emit the remove logic for an individual CMR field.
* Like the addCmr logic, each field is guarded by an
* if test on the property name.
*
* @param mv
* @param cmrField
*/
private void createOpenEJB_removeCmr(MethodVisitor mv, CmrField cmrField) {
// if (${cmrField.name}.equals(arg1))
mv.visitLdcInsn(cmrField.getName());
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
// if not equal jump to end
Label end = new Label();
mv.visitJumpInsn(IFEQ, end);
// collection valued CMR field. Remove the object from the collection.
if (cmrField.getCmrStyle() != CmrStyle.SINGLE) {
// ${cmrField.name}.remove(arg2)
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEINTERFACE,
cmrField.getCmrStyle().getCollectionType().getInternalName(),
"remove",
"(Ljava/lang/Object;)Z");
mv.visitInsn(POP);
// return;
mv.visitInsn(RETURN);
} else {
// single valued, so just null out the field.
// this.${cmrField.name} = null;
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ACONST_NULL);
mv.visitFieldInsn(PUTFIELD, implClassName, cmrField.getName(), cmrField.getDescriptor());
// return;
mv.visitInsn(RETURN);
}
// end of if statement
mv.visitLabel(end);
}
/**
* Generate a concrete implementation of an abstract
* ejbSelectxxxx method.
*
* @param selectMethod
* The abstract definition for the method we're generating.
*/
private void createSelectMethod(Method selectMethod) {
Class<?> returnType = selectMethod.getReturnType();
Method executeMethod = EjbSelect.getSelectMethod(returnType);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, selectMethod.getName(), Type.getMethodDescriptor(selectMethod), null, getExceptionTypes(selectMethod));
mv.visitCode();
// push deploymentInfo
mv.visitFieldInsn(GETSTATIC, implClassName, "deploymentInfo", "Ljava/lang/Object;");
// push method signature
mv.visitLdcInsn(getSelectMethodSignature(selectMethod));
// push return type, but only if the executeMethod is not going to be for void or
// one of the primitives.
if (!returnType.isPrimitive()) {
mv.visitLdcInsn(returnType.getName());
}
// new Object[]
mv.visitIntInsn(BIPUSH, selectMethod.getParameterTypes().length);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
// object[i] = arg${i}
int i = 0;
for (Class<?> parameterType : selectMethod.getParameterTypes()) {
// push arguement i on stack
mv.visitInsn(DUP);
bipush(mv, i);
mv.visitVarInsn(Type.getType(parameterType).getOpcode(ILOAD), i + 1);
// convert argument on stack to an Object
Convert.toObjectFrom(mv, parameterType);
// store it into the array
mv.visitInsn(AASTORE);
if (long.class.equals(parameterType) || double.class.equals(parameterType)) {
// longs and doubles are double wide
i = i + 2;
} else {
i++;
}
}
// EjbSelect.execute_xxxx(deploymentInfo, signature, [returnType.] args[]);
mv.visitMethodInsn(INVOKESTATIC,
Type.getInternalName(executeMethod.getDeclaringClass()),
executeMethod.getName(),
Type.getMethodDescriptor(executeMethod));
// if this is a void type, we just return. Otherwise, the return type
// needs to match the type returned from the method call
if (!Void.TYPE.equals(returnType)) {
// if this is a non-primitive return type, then the returned
// object will need to be cast to the appropriate return type for
// the verifier. The primitive types all have the proper type on the
// stack already
if (!returnType.isPrimitive()) {
// convert return type
Convert.fromObjectTo(mv, returnType);
}
// And generate the appropriate return for the type
mv.visitInsn(Type.getReturnType(selectMethod).getOpcode(IRETURN));
} else {
// void return is just a RETURN.
mv.visitInsn(RETURN);
}
// close method
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private String getSelectMethodSignature(Method selectMethod) {
StringBuilder signature = new StringBuilder();
signature.append(selectMethod.getName());
if (selectMethod.getParameterTypes().length > 0) {
signature.append('(');
boolean first = true;
for (Class<?> parameterType : selectMethod.getParameterTypes()) {
if (!first) signature.append(',');
signature.append(parameterType.getCanonicalName());
first = false;
}
signature.append(')');
}
return signature.toString();
}
private static String[] getExceptionTypes(Method method) {
List<String> types = new ArrayList<String>(method.getExceptionTypes().length);
for (Class<?> exceptionType : method.getExceptionTypes()) {
types.add(Type.getInternalName(exceptionType));
}
return types.toArray(new String[types.size()]);
}
private static void bipush(MethodVisitor mv, int i) {
switch(i) {
case -1:
mv.visitInsn(ICONST_M1);
break;
case 0:
mv.visitInsn(ICONST_0);
break;
case 1:
mv.visitInsn(ICONST_1);
break;
case 2:
mv.visitInsn(ICONST_2);
break;
case 3:
mv.visitInsn(ICONST_3);
break;
case 4:
mv.visitInsn(ICONST_4);
break;
case 5:
mv.visitInsn(ICONST_5);
break;
default:
mv.visitIntInsn(BIPUSH, i);
}
}
/**
* Helper class to handle common type conversions
* in generated code.
*/
private static class Convert {
/**
* Generate code to performing boxing of primitive types
* into a wrapper class instance.
*
* @param mv The method currently being emitted.
* @param from The class we're converting from.
*/
public static void toObjectFrom(MethodVisitor mv, Class from) {
// we only handler boxing for the primitive types.
if (from.isPrimitive()) {
Convert conversion = getConversion(from);
// the only conversion that will be trouble here is void.
if (conversion == null)
{
throw new NullPointerException("conversion is null " + from.getName() + " " + from.isPrimitive());
}
conversion.primitiveToObject(mv);
}
}
/**
* Handle a conversion from one object type to another. If
* There are 3 possible conversions:
*
* 1) The to class is Object. This can be handled
* without conversion. This option is a NOP.
* 2) The to class is a reference type (non-primitive). This conversion
* is a cast operation (which might fail at run time).
* 3) The to class is a primitive type. This is
* an unboxing operation.
*
* @param mv The method currently being constructed.
* @param to The target class for the conversion.
*/
public static void fromObjectTo(MethodVisitor mv, Class to) {
if (to.equals(Object.class)) {
// direct assignment will work
} else if (!to.isPrimitive()) {
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(to));
} else {
Convert conversion = getConversion(to);
{
if (conversion == null)
throw new NullPointerException("unsupported conversion for EJB select return type " + to.getName());
}
conversion.objectToPrimitive(mv);
}
}
private static Map<Class, Convert> conversionsByPrimitive = new HashMap<Class, Convert>();
public static Convert getConversion(Class primitive) {
if (!primitive.isPrimitive()) {
throw new IllegalArgumentException(primitive.getName() + " is not a primitive class");
}
return conversionsByPrimitive.get(primitive);
}
public static final Convert BOOLEAN = new Convert(boolean.class, Boolean.class, "booleanValue");
public static final Convert CHAR = new Convert(char.class, Character.class, "charValue");
public static final Convert BYTE = new Convert(byte.class, Byte.class, "byteValue");
public static final Convert SHORT = new Convert(short.class, Short.class, "shortValue");
public static final Convert INT = new Convert(int.class, Integer.class, "intValue");
public static final Convert LONG = new Convert(long.class, Long.class, "longValue");
public static final Convert FLOAT = new Convert(float.class, Float.class, "floatValue");
public static final Convert DOUBLE = new Convert(double.class, Double.class, "doubleValue");
private Type objectType;
private final Method toPrimitive;
private final Method toObject;
private Convert(Class primitiveClass, Class objectClass, String toPrimitiveMethodName) {
objectType = Type.getType(objectClass);
try {
toObject = objectClass.getMethod("valueOf", primitiveClass);
toPrimitive = objectClass.getMethod(toPrimitiveMethodName);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
conversionsByPrimitive.put(primitiveClass, this);
}
public void primitiveToObject(MethodVisitor mv) {
mv.visitMethodInsn(INVOKESTATIC, objectType.getInternalName(), toObject.getName(), Type.getMethodDescriptor(toObject));
}
public void objectToPrimitive(MethodVisitor mv) {
mv.visitTypeInsn(CHECKCAST, objectType.getInternalName());
mv.visitMethodInsn(INVOKEVIRTUAL, objectType.getInternalName(), toPrimitive.getName(), Type.getMethodDescriptor(toPrimitive));
}
}
public void createEjbActivate() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "ejbActivate", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
public void createEjbLoad() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "ejbLoad", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
public void createEjbPassivate() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "ejbPassivate", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
public void createEjbRemove() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "ejbRemove", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
public void createEjbStore() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "ejbStore", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
public void createSetEntityContext() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setEntityContext", "(Ljavax/ejb/EntityContext;)V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 2);
mv.visitEnd();
}
public void createUnsetEntityContext() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "unsetEntityContext", "()V", null, null);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
}
}