package org.mobicents.slee.container.deployment.profile.jpa;
import java.beans.Introspector;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.CtPrimitiveType;
import javassist.NotFoundException;
import javax.slee.Address;
import javax.slee.SLEEException;
import javax.slee.profile.Profile;
import javax.slee.profile.ProfileManagement;
import org.apache.log4j.Logger;
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.ProfileConcreteClassInfo;
import org.mobicents.slee.container.component.profile.ProfileEntity;
import org.mobicents.slee.container.deployment.ClassUtils;
import org.mobicents.slee.container.deployment.profile.ClassGeneratorUtils;
import org.mobicents.slee.container.deployment.profile.SleeProfileClassCodeGenerator;
import org.mobicents.slee.container.profile.ProfileCmpHandler;
import org.mobicents.slee.container.profile.ProfileConcrete;
import org.mobicents.slee.container.profile.ProfileObject;
/**
*
* Generates the ProfileConcrete impl for a specific Profile Specification.
*
* <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>
*/
@SuppressWarnings("deprecation")
public class ConcreteProfileGenerator {
private static final Logger logger = Logger
.getLogger(ConcreteProfileGenerator.class);
private final ProfileSpecificationComponent profileComponent;
private final int profileCombination;
public ConcreteProfileGenerator(
ProfileSpecificationComponent profileComponent) {
this.profileComponent = profileComponent;
this.profileCombination = SleeProfileClassCodeGenerator
.checkCombination(profileComponent);
ClassGeneratorUtils.setClassPool(this.profileComponent.getClassPool()
.getClassPool());
}
@SuppressWarnings("unchecked")
public Class<?> generateConcreteProfile() {
Class<?> clazz = null;
try {
/*
* 10.12 Profile concrete class A Profile concrete class is
* implemented by the SLEE when a Profile Specification is deployed.
* The Profile concrete class extends the Profile abstract class and
* implements the Profile CMP methods.
*
* The following rules apply to the Profile concrete class
* implemented by the SLEE: - If a Profile abstract class is
* defined, then the SLEE implemented Profile concrete class extends
* the Profile abstract class and implements the Profile CMP
* methods.
*
* - If a Profile abstract class is not defined, then the SLEE
* implemented Profile concrete class provides an implementation of
* the Profile CMP interface.
*/
ProfileSpecificationDescriptorImpl profileDescriptor = profileComponent
.getDescriptor();
if (logger.isDebugEnabled())
logger.debug("Profile combination for "
+ profileComponent.getProfileSpecificationID() + " = "
+ this.profileCombination);
String deployDir = profileComponent.getDeploymentDir()
.getAbsolutePath();
MProfileCMPInterface cmpInterface = profileDescriptor
.getProfileCMPInterface();
String concreteClassName = cmpInterface
.getProfileCmpInterfaceName()
+ "Impl";
// Create the Impl class
CtClass profileConcreteClass = ClassGeneratorUtils.createClass(
concreteClassName, new String[] {
cmpInterface.getProfileCmpInterfaceName(),
ProfileConcrete.class.getName() });
// If this is combination 3 or 4, the the concrete class extends the
// Concrete Profile Management Abstract Class
if (profileCombination >= 3) {
if (profileDescriptor.getProfileAbstractClass() != null) {
ClassGeneratorUtils.createInheritanceLink(
profileConcreteClass, profileDescriptor
.getProfileAbstractClass()
.getProfileAbstractClassName());
}
if (profileDescriptor.getProfileManagementInterface() != null) {
ClassGeneratorUtils.createInterfaceLinks(
profileConcreteClass,
new String[] { profileDescriptor
.getProfileManagementInterface()
.getProfileManagementInterfaceName() });
}
}
generateProfileConcreteClassInfo(profileConcreteClass);
// add profile object field and getter/setter
CtField fProfileObject = ClassGeneratorUtils
.addField(ClassGeneratorUtils.getClass(ProfileObject.class
.getName()), "profileObject", profileConcreteClass);
ClassGeneratorUtils.generateGetterAndSetter(fProfileObject, null);
// CMP fields getters and setters
generateCMPAccessors(profileConcreteClass);
generateConstructors(profileConcreteClass);
// Profile Management methods for JAIN SLEE 1.1
Map<String, CtMethod> profileManagementMethods = new HashMap<String, CtMethod>();
// only add Profile Management methods for JAIN SLEE 1.0 that are implemented by SLEE
for (Object object : ClassUtils
.getInterfaceMethodsFromInterface(ClassGeneratorUtils
.getClass(ProfileManagement.class.getName())).entrySet()) {
Entry entry = (Entry) object;
CtMethod ctMethod = (CtMethod) entry.getValue();
if (ctMethod.getName().equals("markProfileDirty") || ctMethod.getName().equals("isProfileDirty") || ctMethod.getName().equals("isProfileValid")) {
profileManagementMethods.put((String) entry.getKey(),ctMethod);
}
}
// Check for a Profile Management Interface
Class<?> profileManagementInterface = this.profileComponent
.getProfileManagementInterfaceClass();
if (profileManagementInterface != null) {
profileManagementMethods
.putAll(ClassUtils
.getInterfaceMethodsFromInterface(ClassGeneratorUtils
.getClass(profileManagementInterface
.getName())));
}
Class<?> profileLocalInterface = this.profileComponent
.getProfileLocalInterfaceClass();
if (profileLocalInterface != null) {
profileManagementMethods.putAll(ClassUtils
.getAbstractMethodsFromClass((ClassGeneratorUtils
.getClass(profileLocalInterface.getName()))));
}
Map<String, CtMethod> cmpInterfaceMethods = ClassUtils
.getInterfaceMethodsFromInterface(ClassGeneratorUtils
.getClass(this.profileComponent
.getProfileCmpInterfaceClass().getName()));
generateManagementHandlerDelegationMethods(profileConcreteClass,
profileManagementMethods, cmpInterfaceMethods);
if (profileComponent.getUsageParametersInterface() != null) {
generateDefaultUsageParameterGetter(profileConcreteClass);
generateNamedUsageParameterGetter(profileConcreteClass);
}
profileConcreteClass.getClassFile().setVersionToJava5();
if (logger.isDebugEnabled())
logger.debug("Writing PROFILE CONCRETE CLASS to: " + deployDir);
profileConcreteClass.writeFile(deployDir);
clazz = Thread.currentThread().getContextClassLoader().loadClass(
profileConcreteClass.getName());
profileConcreteClass.defrost();
} catch (Exception e) {
throw new SLEEException(e.getMessage(), e);
}
return clazz;
}
private void generateConstructors(CtClass profileConcreteClass) {
ClassGeneratorUtils.generateDefaultConstructor(profileConcreteClass);
}
/**
* Create a named usage parameter getter.
*
* @param profileConcreteClass
* @throws SLEEException
*/
private void generateNamedUsageParameterGetter(CtClass profileConcreteClass) {
String methodName = "getUsageParameterSet";
for (CtMethod ctMethod : profileConcreteClass.getMethods()) {
if (ctMethod.getName().equals(methodName)) {
try {
// copy method, we can't just add body becase it is in super
// class and does not sees profileObject field
CtMethod ctMethodCopy = CtNewMethod.copy(ctMethod, profileConcreteClass, null);
// create the method body
String methodBody = "{ return ($r)"
+ ClassGeneratorUtils.MANAGEMENT_HANDLER
+ ".getUsageParameterSet(profileObject,$1); }";
if (logger.isDebugEnabled()) {
logger.debug("Implemented method " + methodName
+ " , body = " + methodBody);
}
ctMethodCopy.setBody(methodBody);
profileConcreteClass.addMethod(ctMethodCopy);
} catch (CannotCompileException e) {
throw new SLEEException(e.getMessage(), e);
}
}
}
}
private void generateDefaultUsageParameterGetter(
CtClass profileConcreteClass) {
String methodName = "getDefaultUsageParameterSet";
for (CtMethod ctMethod : profileConcreteClass.getMethods()) {
if (ctMethod.getName().equals(methodName)) {
try {
// copy method, we can't just add body becase it is in super
// class and does not sees profileObject field
CtMethod ctMethodCopy = CtNewMethod.copy(ctMethod, profileConcreteClass, null);
// create the method body
String methodBody = "{ return ($r)"
+ ClassGeneratorUtils.MANAGEMENT_HANDLER
+ ".getDefaultUsageParameterSet(profileObject); }";
if (logger.isDebugEnabled()) {
logger.debug("Implemented method " + methodName
+ " , body = " + methodBody);
}
ctMethodCopy.setBody(methodBody);
profileConcreteClass.addMethod(ctMethodCopy);
} catch (CannotCompileException e) {
throw new SLEEException(e.getMessage(), e);
}
}
}
}
private void generateManagementHandlerDelegationMethods(CtClass profileConcreteClass,
Map<String, CtMethod> methods,
Map<String, CtMethod> cmpInterfaceMethods) {
// boolean useInterceptor = true;
Class<?> abstractClass = this.profileComponent
.getProfileAbstractClass();
Iterator<Map.Entry<String, CtMethod>> mm = methods.entrySet()
.iterator();
Set<String> implementedMethods = new HashSet<String>();
implementedMethods.addAll(cmpInterfaceMethods.keySet());
while (mm.hasNext()) {
String interceptor = ClassGeneratorUtils.MANAGEMENT_HANDLER;
Map.Entry<String, CtMethod> entry = mm.next();
CtMethod method = entry.getValue();
// We should use key, but ClassUtils has different behaviors... go
// safe!
String methodKey = method.getName() + method.getSignature();
if (!implementedMethods.add(methodKey)) {
// This was already implemented
continue;
}
if (abstractClass != null) {
try {
int i = 0;
Class<?>[] pTypes = new Class[method.getParameterTypes().length];
for (CtClass pType : method.getParameterTypes()) {
if (pType.isPrimitive())
pTypes[i++] = ((Class<?>) Class.forName(
((CtPrimitiveType) pType).getWrapperName())
.getField("TYPE").get(null));
else
pTypes[i++] = Class.forName(pType.getClassFile()
.getName());
}
Method m = abstractClass
.getMethod(method.getName(), pTypes);
interceptor = Modifier.isAbstract(m.getModifiers()) ? interceptor
: "super";
} catch (Exception e) {
if (!(e instanceof NoSuchMethodException))
throw new SLEEException(
"Problem with Business method generation: "
+ method.getName(), e);
// else ignore... we are using default interceptor.
}
}
try {
ClassGeneratorUtils.generateDelegateMethod(
profileConcreteClass,method, interceptor,
true);
} catch (Exception e) {
throw new SLEEException(e.getMessage(), e);
}
}
}
private void generateCMPAccessors(CtClass profileConcreteClass)
throws Exception {
// Get the CMP interface to generate the getters/setters
MProfileCMPInterface cmpInterface = profileComponent.getDescriptor()
.getProfileCMPInterface();
ClassPool pool = profileComponent.getClassPool();
CtClass cmpInterfaceClass = pool.get(cmpInterface
.getProfileCmpInterfaceName());
CtClass objectClass = pool.get(Object.class.getName());
for (CtMethod method : cmpInterfaceClass.getMethods()) {
if (!method.getDeclaringClass().equals(objectClass)) {
// ignoring methods from Object class
if (method.getName().startsWith("get")) {
generateCMPGetter(method, profileConcreteClass);
} else if (method.getName().startsWith("set")) {
generateCMPSetter(method, profileConcreteClass);
} else {
throw new SLEEException(
"unexpected method name in cmp interface "
+ method.getName());
}
}
}
}
private boolean isPrimitiveOrPrimitiveArray(CtClass ctClass) {
if (ctClass.isArray()) {
try {
return ctClass.getComponentType().isPrimitive();
} catch (NotFoundException e) {
throw new SLEEException(e.getMessage(), e);
}
} else {
return ctClass.isPrimitive();
}
}
private void generateCMPGetter(CtMethod method,
CtClass classToBeInstrumented) throws Exception {
String fieldName = Introspector.decapitalize(method.getName()
.replaceFirst("get", ""));
boolean isPrimitive = isPrimitiveOrPrimitiveArray(method
.getReturnType());
// 1. invoke profile entity getter
String profileEntityGetterInvocationBody = "(("
+ profileComponent.getProfileEntityFramework()
.getProfileEntityClass().getName()
+ ")profileObject.getProfileEntity()).get"
+ ClassGeneratorUtils.getPojoCmpAccessorSufix(fieldName) + "()";
// 2. create the open and close "return" code - do a deep copy on the
// result if the return type is not a primitive, this ensures no refs
// with original value
String returnOpenBody = "return ($r) "
+ (!isPrimitive ? ProfileEntity.class.getName()
+ ".makeDeepCopy(" : "");
String returnCloseBody = !isPrimitive ? ");" : ";";
// 3. lets add the code between the profile entity getter invocation and
// the return clause - if the return type is an array then we need to
// convert it from the attr array value list
String methodBody = null;
if (method.getReturnType().isArray()) {
if (isPrimitiveOrPrimitiveArray(method.getReturnType())
|| method.getReturnType().getComponentType().getName()
.equals(String.class.getName())
|| method.getReturnType().getComponentType().getName()
.equals(Address.class.getName())) {
methodBody = returnOpenBody
+ ProfileAttributeArrayValueUtils.class.getName()
+ ".to"
+ method.getReturnType().getComponentType()
.getSimpleName() + "Array("
+ profileEntityGetterInvocationBody + ")"
+ returnCloseBody;
} else {
methodBody = List.class.getName() + " list = "
+ profileEntityGetterInvocationBody + ";"
+ returnOpenBody
+ ProfileAttributeArrayValueUtils.class.getName()
+ ".toSerializableArray( new "
+ method.getReturnType().getComponentType().getName()
+ "[list.size()] , list)" + returnCloseBody;
}
} else {
methodBody = returnOpenBody + profileEntityGetterInvocationBody
+ returnCloseBody;
}
;
// add final method wrappers
methodBody = "{" + ProfileCmpHandler.class.getName()
+ ".beforeGetCmpField(profileObject);" + " try { " + methodBody
+ " } finally { " + ProfileCmpHandler.class.getName()
+ ".afterGetCmpField(profileObject); }" + "}";
if (logger.isDebugEnabled()) {
logger.debug("Adding method named " + method.getName()
+ ", with source : " + methodBody + ", into: "
+ classToBeInstrumented);
}
CtMethod methodCopy = CtNewMethod.copy(method, classToBeInstrumented,
null);
methodCopy.setBody(methodBody);
classToBeInstrumented.addMethod(methodCopy);
}
private void generateCMPSetter(CtMethod method,
CtClass classToBeInstrumented) throws Exception {
String fieldName = Introspector.decapitalize(method.getName()
.replaceFirst("set", ""));
ProfileAttribute profileAttribute = profileComponent
.getProfileAttributes().get(fieldName);
JPAProfileEntityFramework profileEntityFramework = (JPAProfileEntityFramework) profileComponent
.getProfileEntityFramework();
String pojoCmpAccessorSufix = ClassGeneratorUtils
.getPojoCmpAccessorSufix(fieldName);
// define the string of the object to store in the profile entity
// if it is not a primitive do a deep copy of the object
String objectToStore = !profileAttribute.isPrimitive() ? "("
+ method.getParameterTypes()[0].getName() + ")"
+ ProfileEntity.class.getName() + ".makeDeepCopy($1)" : "$1";
// if it is an array convert it to a list
if (profileAttribute.getType().isArray()) {
objectToStore = ProfileAttributeArrayValueUtils.class.getName()
+ ".toProfileAttributeArrayValueList( "
+ profileEntityFramework
.getProfileEntityArrayAttrValueClassMap().get(
profileAttribute.getName()).getName()
+ ".class , profileEntity, profileEntity.get"
+ pojoCmpAccessorSufix + "() , "
+ (profileAttribute.isUnique() ? true : false) + " , "
+ objectToStore + ")";
}
String methodBody = "{" + ProfileCmpHandler.class.getName()
+ ".beforeSetCmpField(profileObject);" + " try {" + " "
+ profileEntityFramework.getProfileEntityClass().getName()
+ " profileEntity = ("
+ profileEntityFramework.getProfileEntityClass().getName()
+ ")profileObject.getProfileEntity();"
+ " profileEntity.set" + pojoCmpAccessorSufix + "("
+ objectToStore + ");" + " }" + " finally {"
+ ProfileCmpHandler.class.getName()
+ ".afterSetCmpField(profileObject);" + " };" + "}";
if (logger.isDebugEnabled()) {
logger.debug("Adding method named " + method.getName()
+ ", with source : " + methodBody + ", into: "
+ classToBeInstrumented);
}
CtMethod methodCopy = CtNewMethod.copy(method, classToBeInstrumented,
null);
methodCopy.setBody(methodBody);
classToBeInstrumented.addMethod(methodCopy);
}
/**
* Generates info that indicates if a method from {@link Profile} interface
* should be invoked or not, in runtime. Note that all methods from {@link ProfileManagement}
* that we are interested are all contained in {@link Profile} interface.
*/
private void generateProfileConcreteClassInfo(CtClass profileConcreteClass) {
final ClassPool pool = profileComponent.getClassPool();
CtClass profileClass = null;
try {
profileClass = pool.get(Profile.class.getName());
} catch (NotFoundException e) {
throw new SLEEException(e.getMessage(), e);
}
ProfileConcreteClassInfo profileConcreteClassInfo = new ProfileConcreteClassInfo();
for (CtMethod method : profileClass.getDeclaredMethods()) {
for (CtMethod profileConcreteMethod : profileConcreteClass
.getMethods()) {
if (profileConcreteMethod.getName().equals(
method.getName())
&& profileConcreteMethod.getSignature().equals(
method.getSignature())) {
// match, save info
profileConcreteClassInfo.setInvokeInfo(profileConcreteMethod
.getMethodInfo().getName(), !profileConcreteMethod
.isEmpty());
break;
}
}
}
profileComponent.setProfileConcreteClassInfo(profileConcreteClassInfo);
}
}