package org.mobicents.slee.container.deployment.profile.jpa; import java.beans.Introspector; import java.io.Serializable; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.CtNewMethod; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.AnnotationMemberValue; import javassist.bytecode.annotation.ArrayMemberValue; import javassist.bytecode.annotation.BooleanMemberValue; import javassist.bytecode.annotation.MemberValue; import javassist.bytecode.annotation.StringMemberValue; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import javax.slee.SLEEException; import org.apache.log4j.Logger; import org.hibernate.annotations.Cascade; import org.mobicents.slee.container.component.ProfileSpecificationComponent; import org.mobicents.slee.container.component.deployment.ClassPool; import org.mobicents.slee.container.component.deployment.jaxb.descriptors.ProfileSpecificationDescriptorImpl; import org.mobicents.slee.container.component.deployment.jaxb.descriptors.profile.MProfileCMPInterface; import org.mobicents.slee.container.component.profile.ProfileAttribute; import org.mobicents.slee.container.component.profile.ProfileEntity; import org.mobicents.slee.container.deployment.profile.ClassGeneratorUtils; import org.mobicents.slee.container.deployment.profile.SleeProfileClassCodeGenerator; /** * * Generates the concrete profile entity and attribute array value classes, which hold the profile persistent attributes * * <br>Project: mobicents * <br>11:16:57 AM Mar 23, 2009 * <br> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> * @author martins */ public class ConcreteProfileEntityGenerator { private static final Logger logger = Logger.getLogger(ConcreteProfileEntityGenerator.class); private ProfileSpecificationComponent profileComponent; private final JPAProfileEntityFramework jpaProfileDataSource; private final int profileCombination; public ConcreteProfileEntityGenerator(ProfileSpecificationComponent profileComponent,JPAProfileEntityFramework jpaProfileDataSource) { this.profileComponent = profileComponent; this.jpaProfileDataSource = jpaProfileDataSource; this.profileCombination = SleeProfileClassCodeGenerator.checkCombination(profileComponent); logger.info( "Profile combination for " + profileComponent.getProfileSpecificationID() + " = " + this.profileCombination ); ClassGeneratorUtils.setClassPool( this.profileComponent.getClassPool().getClassPool() ); } /** * Generates the concrete profile entity class and a profile entity array * attr value class for each attribute that is an array */ public void generateClasses() { try { ProfileSpecificationDescriptorImpl profileDescriptor = profileComponent.getDescriptor(); String deployDir = profileComponent.getDeploymentDir().getAbsolutePath(); MProfileCMPInterface cmpInterface = profileDescriptor.getProfileCMPInterface(); // define the concrete profile entity class name String concreteProfileEntityClassName = cmpInterface.getProfileCmpInterfaceName() + "_PE"; // create javassist class CtClass concreteProfileEntityClass = ClassGeneratorUtils.createClass(concreteProfileEntityClassName, new String[]{cmpInterface.getProfileCmpInterfaceName(), Serializable.class.getName()}); // set inheritance ClassGeneratorUtils.createInheritanceLink(concreteProfileEntityClass, ProfileEntity.class.getName()); // annotate with @Entity ClassGeneratorUtils.addAnnotation( Entity.class.getName(), new LinkedHashMap<String, Object>(), concreteProfileEntityClass ); // annotate the @IdClass LinkedHashMap<String, Object> idClassMVs = new LinkedHashMap<String, Object>(); idClassMVs.put( "value", JPAProfileId.class ); ClassGeneratorUtils.addAnnotation( IdClass.class.getName(), idClassMVs, concreteProfileEntityClass ); // add the table name to map it to ProfileSpecification ID LinkedHashMap<String, Object> tableMVs1 = new LinkedHashMap<String, Object>(); tableMVs1.put( "name", "SLEE_PE_"+profileComponent.getProfileCmpInterfaceClass().getSimpleName() + "_" + Math.abs(profileComponent.getComponentID().hashCode()) ); ClassGeneratorUtils.addAnnotation( Table.class.getName(), tableMVs1, concreteProfileEntityClass ); // override @id & @basic getter methods String getProfileNameMethodSrc = "public String getProfileName() { return super.getProfileName(); }"; CtMethod getProfileNameMethod = CtNewMethod.make(getProfileNameMethodSrc, concreteProfileEntityClass); ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getProfileNameMethod); concreteProfileEntityClass.addMethod(getProfileNameMethod); String getTableNameMethodSrc = "public String getTableName() { return super.getTableName(); }"; CtMethod getTableNameMethod = CtNewMethod.make(getTableNameMethodSrc, concreteProfileEntityClass); ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getTableNameMethod); concreteProfileEntityClass.addMethod(getTableNameMethod); // generate the getters/setters in the profile entity // gather the fieldNames of array type attributes ClassPool pool = profileComponent.getClassPool(); CtClass cmpInterfaceClass = pool.get(cmpInterface.getProfileCmpInterfaceName()); CtClass listClass = pool.get(List.class.getName()); Map<String,Class<?>> profileEntityArrayAttrValueClassMap = new HashMap<String, Class<?>>(); for(CtMethod method : cmpInterfaceClass.getMethods()) { if(!method.getDeclaringClass().getName().equals(Object.class.getName()) && method.getName().startsWith( "get" )) { String fieldName = Introspector.decapitalize(method.getName().replaceFirst( "get", "" )); boolean array = method.getReturnType().isArray(); CtClass returnType = array ? listClass : method.getReturnType(); CtField genField = ClassGeneratorUtils.addField( returnType, fieldName, concreteProfileEntityClass ); String pojoCmpAccessorSufix = ClassGeneratorUtils.getPojoCmpAccessorSufix(genField.getName()); // create the getter CtMethod ctMethod = CtNewMethod.getter( "get" + pojoCmpAccessorSufix, genField ); ProfileAttribute profileAttribute = profileComponent.getProfileAttributes().get(fieldName); concreteProfileEntityClass.addMethod(ctMethod); if (array) { // we need to generate a class for this attribute, to hold the one to many relation Class<?> profileAttributeArrayValueClass = generateProfileAttributeArrayValueClass(concreteProfileEntityClass,fieldName,profileAttribute.isUnique()); profileEntityArrayAttrValueClassMap.put(fieldName, profileAttributeArrayValueClass); // add the annotations of one to many association with array attr value class LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); map.put("targetEntity", profileAttributeArrayValueClass); // FIXME see comment on generation of avv, it is possible to work wituout a join table but needs more work // THE MAPPEDBY IS REQUIRED FOR THE RELATION WITHOUT A JOIN TABLE map.put("mappedBy", "owner"); map.put("cascade",new CascadeType[]{CascadeType.ALL}); ClassGeneratorUtils.addAnnotation( OneToMany.class.getName(), map, ctMethod); // we need to add a special hibernate annotation because the jpa cascade delete only deletes from join table, not the orphan row at the PEAAV table map = new LinkedHashMap<String, Object>(); map.put("value",new org.hibernate.annotations.CascadeType[]{org.hibernate.annotations.CascadeType.DELETE_ORPHAN}); ClassGeneratorUtils.addAnnotation( Cascade.class.getName(), map, ctMethod); // make setter from src /*String setterSrc = "public void set"+ pojoCmpAccessorSufix + "("+List.class.getName()+" value) {" + " System.out.println(\"PEAAV setter: "+genField.getName()+" = \"+this."+genField.getName()+"+\" value = \"+value);" + " if (this."+genField.getName()+" != null) { " + " this."+genField.getName()+".clear(); " + " if (value != null) { " + " for ("+Iterator.class.getName()+" i = value.iterator(); i.hasNext();) { " + " " + profileAttributeArrayValueClass.getName()+" otherArrayValue = ("+profileAttributeArrayValueClass.getName()+") i.next(); " + " " + profileAttributeArrayValueClass.getName()+" thisArrayValue = new "+profileAttributeArrayValueClass.getName()+"(); " + " thisArrayValue.setString( otherArrayValue.getString() ); " + " thisArrayValue.setSerializable( ("+Serializable.class.getName()+") "+ProfileEntity.class.getName()+".makeDeepCopy(otherArrayValue.getSerializable()) ); " + " this."+genField.getName()+".add(thisArrayValue); " + " }" + " }" + " } else { " + " this."+genField.getName()+" = value; " + " }" + "}"; ctMethod = CtMethod.make(setterSrc, concreteProfileEntityClass); concreteProfileEntityClass.addMethod(ctMethod); */ } else { // not an array, just add column annotation with or without unique constraint LinkedHashMap<String,Object> getterAnnotationMemberValues = new LinkedHashMap<String, Object>(); if (profileAttribute.isUnique()) { getterAnnotationMemberValues.put("unique", true); } ClassGeneratorUtils.addAnnotation(Column.class.getName(), getterAnnotationMemberValues, ctMethod); } // add usual setter ctMethod = CtNewMethod.setter( "set" + pojoCmpAccessorSufix, genField ); concreteProfileEntityClass.addMethod(ctMethod); } } jpaProfileDataSource.setProfileEntityArrayAttrValueClassMap(profileEntityArrayAttrValueClassMap); // write and load profile entity class if (logger.isDebugEnabled()) logger.debug( "Writing PROFILE ENTITY CONCRETE CLASS ( "+concreteProfileEntityClass.getName()+" ) to: " + deployDir ); concreteProfileEntityClass.writeFile( deployDir ); jpaProfileDataSource.setProfileEntityClass(Thread.currentThread().getContextClassLoader().loadClass(concreteProfileEntityClass.getName())); concreteProfileEntityClass.defrost(); } catch ( Throwable e ) { throw new SLEEException(e.getMessage(),e); } } /** * Generates a class that extends {@link ProfileEntityArrayAttributeValue} for a specific entity attribute of array type value * @param concreteProfileEntityClass * @param profileAttributeName * @return */ private Class<?> generateProfileAttributeArrayValueClass(CtClass concreteProfileEntityClass, String profileAttributeName, boolean unique) { CtClass concreteArrayValueClass = null; try { // define the concrete profile attribute array value class name String concreteArrayValueClassName = profileComponent.getProfileCmpInterfaceClass().getName() + "PEAAV_"+ClassGeneratorUtils.capitalize(profileAttributeName); // create javassist class concreteArrayValueClass = ClassGeneratorUtils.createClass(concreteArrayValueClassName, new String[]{Serializable.class.getName()}); // set inheritance ClassGeneratorUtils.createInheritanceLink(concreteArrayValueClass, ProfileEntityArrayAttributeValue.class.getName()); // annotate class with @Entity ClassGeneratorUtils.addAnnotation( Entity.class.getName(), new LinkedHashMap<String, Object>(), concreteArrayValueClass ); // generate a random table name addTableAnnotationToPEAAV("SLEE_PEAAV_"+profileComponent.getProfileCmpInterfaceClass().getSimpleName() + "_" + Math.abs(profileComponent.getComponentID().hashCode()) + profileAttributeName,unique,concreteArrayValueClass); // override @id String getIdNameMethodSrc = "public long getId() { return super.getId(); }"; CtMethod getIdNameMethod = CtNewMethod.make(getIdNameMethodSrc, concreteArrayValueClass); ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getIdNameMethod); ClassGeneratorUtils.addAnnotation( GeneratedValue.class.getName(), new LinkedHashMap<String, Object>(), getIdNameMethod); concreteArrayValueClass.addMethod(getIdNameMethod); // override getter methods String getSerializableMethodSrc = "public "+Serializable.class.getName()+" getSerializable() { return super.getSerializable(); }"; CtMethod getSerializableMethod = CtNewMethod.make(getSerializableMethodSrc, concreteArrayValueClass); LinkedHashMap<String,Object> map = new LinkedHashMap<String, Object>(); map.put("name", "serializable"); //if (unique)map.put("unique", true); ClassGeneratorUtils.addAnnotation(Column.class.getName(), map, getSerializableMethod); concreteArrayValueClass.addMethod(getSerializableMethod); String getStringMethodSrc = "public String getString() { return super.getString(); }"; CtMethod getStringMethod = CtNewMethod.make(getStringMethodSrc, concreteArrayValueClass); map = new LinkedHashMap<String, Object>(); map.put("name", "string"); //if (unique)map.put("unique", true); ClassGeneratorUtils.addAnnotation(Column.class.getName(), map, getStringMethod); concreteArrayValueClass.addMethod(getStringMethod); // FIXME add join columns here or in profile entity class to make // the relation without a join table, atm if this is changed, the // inserts on this table go with profile and table name as null %) // THE PROFILENTITY FIELD IN AAV CLASS IS REQUIRED FOR THE RELATION WITH PROFILE ENTITY CLASS WITHOUT A JOIN TABLE // add join column regarding the relation from array attr value to profile entity CtField ctField = ClassGeneratorUtils.addField(concreteProfileEntityClass, "owner", concreteArrayValueClass); ClassGeneratorUtils.generateSetter(ctField,null); CtMethod getter = ClassGeneratorUtils.generateGetter(ctField,null); //ClassGeneratorUtils.addAnnotation(ManyToOne.class.getName(), new LinkedHashMap<String, Object>(), getter); // ---- ConstPool cp = getter.getMethodInfo().getConstPool(); AnnotationsAttribute attr = (AnnotationsAttribute) getter.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag); if (attr == null) { attr = new AnnotationsAttribute(cp,AnnotationsAttribute.visibleTag); } Annotation manyToOne = new Annotation(ManyToOne.class.getName(), cp); manyToOne.addMemberValue("optional", new BooleanMemberValue(false,cp)); attr.addAnnotation(manyToOne); Annotation joinColumns = new Annotation(JoinColumns.class.getName(), cp); Annotation joinColumn1 = new Annotation(JoinColumn.class.getName(), cp); joinColumn1.addMemberValue("name", new StringMemberValue("owner_tableName", cp)); joinColumn1.addMemberValue("referencedColumnName", new StringMemberValue("tableName", cp)); Annotation joinColumn2 = new Annotation(JoinColumn.class.getName(), cp); joinColumn2.addMemberValue("name", new StringMemberValue("owner_profileName", cp)); joinColumn2.addMemberValue("referencedColumnName", new StringMemberValue("profileName", cp)); ArrayMemberValue joinColumnsMemberValue = new ArrayMemberValue(cp); joinColumnsMemberValue.setValue(new MemberValue[] { new AnnotationMemberValue(joinColumn1,cp), new AnnotationMemberValue(joinColumn2,cp)}); joinColumns.addMemberValue("value", joinColumnsMemberValue); attr.addAnnotation(joinColumns); getter.getMethodInfo().addAttribute(attr); // generate concrete setProfileEntity method String setProfileEntityMethodSrc = "public void setProfileEntity("+ProfileEntity.class.getName()+" profileEntity){ setOwner(("+concreteProfileEntityClass.getName()+")profileEntity); }"; CtMethod setProfileEntityMethod = CtMethod.make(setProfileEntityMethodSrc, concreteArrayValueClass); concreteArrayValueClass.addMethod(setProfileEntityMethod); // write and load the attr array value class String deployDir = profileComponent.getDeploymentDir().getAbsolutePath(); if (logger.isDebugEnabled()) { logger.debug( "Writing PROFILE ATTR ARRAY VALUE CONCRETE CLASS ( "+concreteArrayValueClass.getName()+" ) to: " + deployDir ); } concreteArrayValueClass.writeFile( deployDir ); return Thread.currentThread().getContextClassLoader().loadClass(concreteArrayValueClass.getName()); } catch (Throwable e) { throw new SLEEException(e.getMessage(),e); } finally { if (concreteArrayValueClass != null) { concreteArrayValueClass.defrost(); } } } private void addTableAnnotationToPEAAV(String tableName, boolean unique, CtClass ctClass) { if (logger.isDebugEnabled()) { logger.debug("Adding PEAAV table with name "+tableName+" for "+profileComponent+", attribute is unique? "+unique); } ConstPool cp = ctClass.getClassFile().getConstPool(); AnnotationsAttribute attr = (AnnotationsAttribute) ctClass.getClassFile().getAttribute(AnnotationsAttribute.visibleTag); if (attr == null) { attr = new AnnotationsAttribute(cp,AnnotationsAttribute.visibleTag); } Annotation table = new Annotation(Table.class.getName(), cp); table.addMemberValue("name", new StringMemberValue(tableName,cp)); if (unique) { ArrayMemberValue columnNamesMemberValue = new ArrayMemberValue(cp); columnNamesMemberValue.setValue(new MemberValue[] { new StringMemberValue("owner_tableName",cp) , new StringMemberValue("string",cp) }); Annotation uniqueContraint = new Annotation(UniqueConstraint.class.getName(), cp); uniqueContraint.addMemberValue("columnNames", columnNamesMemberValue); ArrayMemberValue uniqueConstraintsMemberValue = new ArrayMemberValue(cp); uniqueConstraintsMemberValue.setValue(new MemberValue[] { new AnnotationMemberValue(uniqueContraint,cp)}); table.addMemberValue("uniqueConstraints", uniqueConstraintsMemberValue); } attr.addAnnotation(table); ctClass.getClassFile().addAttribute(attr); } }