package org.mobicents.slee.container.deployment.profile; import java.lang.reflect.Array; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMethod; import javassist.CtNewConstructor; import javassist.CtNewMethod; import javassist.CtPrimitiveType; import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ClassFile; import javassist.bytecode.ConstPool; import javassist.bytecode.FieldInfo; import javassist.bytecode.MethodInfo; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.AnnotationMemberValue; import javassist.bytecode.annotation.ArrayMemberValue; import javassist.bytecode.annotation.BooleanMemberValue; import javassist.bytecode.annotation.ByteMemberValue; import javassist.bytecode.annotation.CharMemberValue; import javassist.bytecode.annotation.ClassMemberValue; import javassist.bytecode.annotation.DoubleMemberValue; import javassist.bytecode.annotation.EnumMemberValue; import javassist.bytecode.annotation.FloatMemberValue; import javassist.bytecode.annotation.IntegerMemberValue; import javassist.bytecode.annotation.LongMemberValue; import javassist.bytecode.annotation.MemberValue; import javassist.bytecode.annotation.ShortMemberValue; import javassist.bytecode.annotation.StringMemberValue; import org.apache.log4j.Logger; import org.mobicents.slee.container.deployment.ClassUtils; import org.mobicents.slee.container.profile.ProfileCallRecorderTransactionData; import org.mobicents.slee.container.profile.ProfileCmpHandler; import org.mobicents.slee.container.profile.ProfileManagementHandler; import org.mobicents.slee.container.security.Utility; /** * * ClassGeneratorUtils.java * * <br>Project: mobicents * <br>9:22:57 AM Mar 26, 2009 * <br> * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a> * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a> */ public class ClassGeneratorUtils { private static final Logger logger = Logger.getLogger(ClassGeneratorUtils.class); private static ClassPool classPool = null; public static final String _PLO_PO_ALLOCATION = "";//"allocateProfileObject();"; public static final String CMP_HANDLER = ProfileCmpHandler.class.getName(); public static final String MANAGEMENT_HANDLER = ProfileManagementHandler.class.getName(); /** * Creates a class with the desired name and linked to the mentioned interfaces. * * @param className * @param interfaces * @return * @throws Exception */ public static CtClass createClass(String className, String[] interfaces) throws Exception { if(className == null) { throw new NullPointerException("Class name cannot be null"); } CtClass clazz = classPool.makeClass(className); if(interfaces != null && interfaces.length > 0) { clazz.setInterfaces( classPool.get( interfaces ) ); } return clazz; } /** * Gets the desired class from the pool. * * @param className * @return * @throws Exception */ public static CtClass getClass(String className) throws Exception { return classPool.get(className); } /** * Create the links with possible interfaces * * @param concreteClass * @param interfaces */ public static void createInterfaceLinks(CtClass concreteClass, String[] interfaceNames) { if(interfaceNames != null && interfaceNames.length > 0) { try { for(String interfaceName : interfaceNames) { boolean found = false; for(CtClass existingInterfaces : concreteClass.getInterfaces()) { if(existingInterfaces.getName().equals(interfaceName)) found = true; } if(!found) concreteClass.addInterface(classPool.get(interfaceName)); } } catch ( NotFoundException e ) { e.printStackTrace(); } } } /** * Create the inheritance link with the absract class provided by the developer * * @param concreteClass the concrete class to which to add the inheritance link * @param superClass the superClass to set */ public static void createInheritanceLink(CtClass concreteClass, String superClassName) { if(superClassName != null && superClassName.length() >= 0) { try { concreteClass.setSuperclass(classPool.get(superClassName)); } catch ( CannotCompileException e ) { e.printStackTrace(); } catch ( NotFoundException e ) { e.printStackTrace(); } } } /** * * @param concreteClass * @return */ public static CtConstructor generateDefaultConstructor(CtClass concreteClass) { CtConstructor constructor = null; try { constructor = CtNewConstructor.defaultConstructor(concreteClass); concreteClass.addConstructor(constructor); } catch ( CannotCompileException e ) { e.printStackTrace(); } return constructor; } /** * * @param concreteClass * @param parameterClasses * @param parameterNames * @return */ public static CtConstructor generateConstructorWithParameters(CtClass concreteClass, Class<?>[] parameterClasses, String[] parameterNames, boolean[] isTransient) { CtConstructor constructor = null; CtClass[] parameters = new CtClass[parameterClasses.length]; String constructorBody = "{"; for (int i = 0; i < parameterClasses.length; i++) { try { CtField ctField = null; parameters[i] = classPool.get(parameterClasses[i].getName()); try { ctField = concreteClass.getField(parameterNames[i]); } catch (NotFoundException nfe) { ctField = new CtField(parameters[i], parameterNames[i], concreteClass); if (ctField.getName().equals("java.lang.Object")) { ctField.setModifiers(Modifier.PUBLIC); } else { ctField.setModifiers(Modifier.PRIVATE); } concreteClass.addField(ctField); } if(isTransient[i]) { addAnnotation( "javax.persistence.Transient", null, ctField ); } } catch (Exception cce) { cce.printStackTrace(); } constructorBody += "this." + parameterNames[i] + "=$" + (i+1) + ";"; } constructorBody += "}"; try { constructor = CtNewConstructor.make(parameters, new CtClass[]{}, constructorBody, concreteClass); concreteClass.addConstructor( constructor ); } catch (CannotCompileException e) { e.printStackTrace(); } return constructor; } /** * Adds a field of the desired type to the declaring class. * * @param fieldType * @param fieldName * @param declaringClass * @return * @throws CannotCompileException */ public static CtField addField(CtClass fieldType, String fieldName, CtClass declaringClass) throws CannotCompileException { return addField( fieldType, fieldName, declaringClass, Modifier.PRIVATE ); } /** * Adds a field of the desired type to the declaring class. * * @param fieldType * @param fieldName * @param declaringClass * @return * @throws CannotCompileException */ public static CtField addField(CtClass fieldType, String fieldName, CtClass declaringClass, int modifier) throws CannotCompileException { return addField( fieldType, fieldName, declaringClass, modifier, null ); } /** * Adds a field of the desired type to the declaring class. * * @param fieldType * @param fieldName * @param declaringClass * @param modifier * @return * @throws CannotCompileException */ public static CtField addField(CtClass fieldType, String fieldName, CtClass declaringClass, int modifier, String initializerExpr) throws CannotCompileException { CtField field = new CtField( fieldType, decapitalize(fieldName), declaringClass ); field.setModifiers(modifier); if(initializerExpr != null) { declaringClass.addField(field, CtField.Initializer.byExpr(initializerExpr)); } else { declaringClass.addField(field); } return field; } /** * Generates a getter for the field (get<FieldName>) and adds it to the declaring class. * * @param field * @param interceptorAccess * @return * @throws NotFoundException * @throws CannotCompileException */ public static CtMethod generateGetter(CtField field, String interceptorAccess) throws NotFoundException, CannotCompileException { String getterName = "get" + capitalize(field.getName()); CtMethod getter = CtNewMethod.getter( getterName, field ); if(interceptorAccess != null) getter.setBody( interceptorAccess + "." + getterName + "($$);" ); field.getDeclaringClass().addMethod(getter); return getter; } /** * Generates a setter for the field (get<FieldName>) and adds it to the declaring class. * * @param field * @return * @throws NotFoundException * @throws CannotCompileException */ public static CtMethod generateSetter(CtField field, String interceptorAccess) throws NotFoundException, CannotCompileException { String setterName = "set" + capitalize(field.getName()); CtMethod setter = CtNewMethod.setter( setterName, field ); if(interceptorAccess != null) setter.setBody( interceptorAccess + "." + setterName + "($$);" ); field.getDeclaringClass().addMethod(setter); return setter; } /** * Generates getter and setter for the field (get/set<FieldName>) and adds them to the declaring class. * * @param field * @throws NotFoundException * @throws CannotCompileException */ public static void generateGetterAndSetter(CtField field, String interceptorAccess) throws NotFoundException, CannotCompileException { generateGetter(field, interceptorAccess); generateSetter(field, interceptorAccess); } /** * Retrieves the sufix to add to set/get and obtain the profile cmp acessors method names in the profile pojo * @param fieldName * @return */ public static String getPojoCmpAccessorSufix(String fieldName) { return "C" + fieldName; } /** * Adds the selected annotation to the Object, along with the specified memberValues. * * @param annotation the FQDN of the annotation * @param memberValues the member values HashMap (name=value) * @param toAnnotate the object to be annotated */ public static void addAnnotation(String annotation, LinkedHashMap<String, Object> memberValues, Object toAnnotate) { if(toAnnotate instanceof CtClass) { CtClass classToAnnotate = (CtClass) toAnnotate; ClassFile cf = classToAnnotate.getClassFile(); ConstPool cp = cf.getConstPool(); AnnotationsAttribute attr = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag); if(attr == null) { attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); } Annotation a = new Annotation(annotation, cp); if(memberValues != null) { addMemberValuesToAnnotation(a, cp, memberValues); } attr.addAnnotation( a ); cf.addAttribute( attr ); } else if(toAnnotate instanceof CtMethod) { CtMethod methodToAnnotate = (CtMethod) toAnnotate; MethodInfo mi = methodToAnnotate.getMethodInfo(); ConstPool cp = mi.getConstPool(); AnnotationsAttribute attr = (AnnotationsAttribute) mi.getAttribute(AnnotationsAttribute.visibleTag); if(attr == null) { attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); } Annotation a = new Annotation(annotation, cp); if(memberValues != null) { addMemberValuesToAnnotation(a, cp, memberValues); } attr.addAnnotation( a ); mi.addAttribute( attr ); } else if(toAnnotate instanceof CtField) { CtField fieldToAnnotate = (CtField) toAnnotate; FieldInfo fi = fieldToAnnotate.getFieldInfo(); ConstPool cp = fi.getConstPool(); AnnotationsAttribute attr = (AnnotationsAttribute) fi.getAttribute(AnnotationsAttribute.visibleTag); if(attr == null) { attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); } Annotation a = new Annotation(annotation, cp); if(memberValues != null) { addMemberValuesToAnnotation(a, cp, memberValues); } attr.addAnnotation( a ); fi.addAttribute( attr ); } else { throw new UnsupportedOperationException("Unknown object type: " + toAnnotate.getClass()); } } public static Map<?,?> getInterfaceMethodsFromInterface(String interfaceClassName) { HashMap<String, CtMethod> interfaceMethods = new HashMap<String, CtMethod>(); try { CtClass interfaceClass = classPool.get(interfaceClassName); CtMethod[] methods = interfaceClass.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { interfaceMethods.put(ClassUtils.getMethodKey(methods[i]), methods[i]); } interfaceMethods.putAll(ClassUtils.getSuperClassesAbstractMethodsFromInterface(interfaceClass)); } catch (Exception e) { e.printStackTrace(); } return interfaceMethods; } public static void instrumentBussinesMethod(CtClass concreteClass, CtMethod method, String interceptorAccess) throws Exception { boolean hasReturnValue = method.getReturnType().toString().equals("void"); String body = "{" + _PLO_PO_ALLOCATION + "System.out.println(\"Calling " + method.getName() + "\");" + "Thread t = Thread.currentThread();"+ "ClassLoader oldClassLoader = t.getContextClassLoader();"+ "t.setContextClassLoader(this.profileObject.getProfileSpecificationComponent().getClassLoader());"+ "System.out.println(\"profileManagementHandler[\" + this.profileManagementHandler + \"]\");" + "try {"; if(hasReturnValue) body += "Object result = " + interceptorAccess + "." + method.getName() + "($$);"; else body += interceptorAccess + "." + method.getName() + "($$);"; body += hasReturnValue ? "return ($r)result;" : "return;"; body+= "}" + "catch(java.lang.RuntimeException re)"+ "{"+ " try {"+ " this.sleeTransactionManager.rollback();"+ " throw new javax.slee.TransactionRolledbackLocalException(\"ProfileLocalObject invocation results in RuntimeException, rolling back.\",re);"+ " }" + " catch (Exception e) {"+ " e.printStackTrace();"+ " throw new javax.slee.SLEEException(\"System level failure.\",e);"+ " }"+ "}" + "finally"+ "{"+ " if(this.getProfileObject().getProfileContext().getRollbackOnly()){" + " try {"+ " this.sleeTransactionManager.rollback();"+ " }" + " catch (Exception e) {"+ " e.printStackTrace();"+ " throw new javax.slee.SLEEException(\"System level failure.\",e);"+ " }"+ "}" + " t.setContextClassLoader(oldClassLoader);"+ "}"+ "}"; if(logger.isDebugEnabled()) { logger.info("Instrumented method, name:"+method.getName()+", with body:\n"+body); } CtMethod newMethod = CtNewMethod.make(method.getReturnType(), method.getName(), method.getParameterTypes(), method.getExceptionTypes(), body, concreteClass); newMethod.setModifiers(method.getModifiers() & ~Modifier.ABSTRACT); concreteClass.addMethod(newMethod); } public static void generateDelegateMethod(CtClass classToBeInstrumented, CtMethod method, String interceptorAccess, boolean recordTxData) throws CannotCompileException, NotFoundException, IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException { // FIXME: should we add check for concrete methods from profileManagementAbstractClass and do clone? if (logger.isDebugEnabled()) { logger.debug("About to instrument: " + method.getName() + ", into: " + classToBeInstrumented.getName()); } method = CtNewMethod.copy(method, classToBeInstrumented, null); method.setModifiers(method.getModifiers() & ~Modifier.ABSTRACT); String retStatement = null; String retType = null; try { if(method.getReturnType() != CtClass.voidType) { retStatement = "return ($r) result;"; } } catch (NotFoundException e) { throw e; } boolean hasImpl = interceptorAccess.equals("super"); String body = "{ " +ClassLoader.class.getName()+ " cl = "+Utility.class.getName()+".switchSafelyClassLoader(null,profileObject);" + //ClassLoader.class.getName()+ " cl = "+Thread.class.getName()+".currentThread().getContextClassLoader();" + ///Thread.class.getName()+".currentThread().setContextClassLoader(profileObject.getProfileTable().getProfileSpecificationComponent().getClassLoader());"+ " try {" + (recordTxData ? ProfileCallRecorderTransactionData.class.getName() + ".addProfileCall(profileObject);" : ""); if(retStatement != null) { if(method.getReturnType().isPrimitive()) retType = ((Class<?>)Class.forName( ((CtPrimitiveType)method.getReturnType()).getWrapperName() ).getField( "TYPE" ).get( null )).getName(); else retType = Class.forName(method.getReturnType().getClassFile().getName()).getName(); } body += (retStatement != null ? retType + " result = " : "") + interceptorAccess + "." + method.getName() + "(" + (hasImpl ? "" : "profileObject, ") + "$$);"; body += retStatement != null ? retStatement : ""; body += " }" + " finally {" + Utility.class.getName()+".switchSafelyClassLoader(cl,null);" + //Thread.class.getName()+".currentThread().setContextClassLoader(cl);" + (recordTxData ? ProfileCallRecorderTransactionData.class.getName() + ".removeProfileCall(profileObject);" : "") + " }" + "}"; if (logger.isDebugEnabled()) { logger.debug("About to instrumented: " + method.getName() + ", body: " + body); } method.setBody(body); classToBeInstrumented.addMethod(method); } private static MemberValue getMemberValue(Object mvValue,ConstPool cp) { MemberValue mv = null; if(mvValue instanceof MemberValue){ mv = (MemberValue) mvValue; } else if(mvValue instanceof String) { mv = new StringMemberValue((String)mvValue, cp); } else if(mvValue instanceof Annotation) { mv = new AnnotationMemberValue((Annotation)mvValue, cp); } else if(mvValue instanceof Boolean) { mv = new BooleanMemberValue((Boolean)mvValue, cp); } else if(mvValue instanceof Byte) { mv = new ByteMemberValue((Byte)mvValue, cp); } else if(mvValue instanceof Character) { mv = new CharMemberValue((Character)mvValue, cp); } else if(mvValue instanceof Class) { mv = new ClassMemberValue(((Class<?>)mvValue).getName(), cp); } else if(mvValue instanceof Double) { mv = new DoubleMemberValue((Double)mvValue, cp); } else if(mvValue instanceof Enum) { EnumMemberValue emv = new EnumMemberValue(cp); emv.setType(((Enum<?>)mvValue).getClass().getName()); emv.setValue(((Enum<?>)mvValue).name()); mv = emv; } else if(mvValue instanceof Float) { mv = new FloatMemberValue((Float)mvValue, cp); } else if(mvValue instanceof Integer) { IntegerMemberValue imv = new IntegerMemberValue(cp); imv.setValue(((Integer)mvValue).intValue()); mv = imv; } else if(mvValue instanceof Long) { mv = new LongMemberValue((Long)mvValue, null); } else if(mvValue instanceof Short) { mv = new ShortMemberValue((Short)mvValue, null); } else if(mvValue.getClass().isArray() ) { ArrayMemberValue amv = new ArrayMemberValue(cp); MemberValue[] elements = new MemberValue[Array.getLength(mvValue)]; for(int i=0;i<elements.length;i++) { elements[i] = getMemberValue(Array.get(mvValue, i), cp); } amv.setValue(elements); mv = amv; } else { throw new UnsupportedOperationException("Unknown object type: " + mvValue.getClass()); } return mv; } /** * Private method to add member values to annotation * * @param annotation * @param cp * @param memberValues */ private static void addMemberValuesToAnnotation(Annotation annotation, ConstPool cp, LinkedHashMap<String, Object> memberValues) { // Get the member value object for(String mvName : memberValues.keySet()) { Object mvValue = memberValues.get(mvName); MemberValue mv = getMemberValue(mvValue, cp); annotation.addMemberValue( mvName, mv ); } } /** * * @param s * @return */ public static String capitalize(String s) { return s.length() > 0 ? s.substring(0, 1).toUpperCase() + s.substring(1) : s; } /** * * @param s * @return */ public static String decapitalize(String s) { return s.length() > 0 ? s.substring(0, 1).toLowerCase() + s.substring(1) : s; } public static void setClassPool(ClassPool classPool) { ClassGeneratorUtils.classPool = classPool; } }