org.mobicents.slee.container.deployment.profile.jpa.ConcreteProfileGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.mobicents.slee.container.deployment.profile.jpa.ConcreteProfileGenerator.java

Source

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

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.ClassPool;
import org.mobicents.slee.container.component.profile.ProfileAttribute;
import org.mobicents.slee.container.component.profile.ProfileConcreteClassInfo;
import org.mobicents.slee.container.component.profile.ProfileSpecificationComponent;
import org.mobicents.slee.container.component.profile.ProfileSpecificationDescriptor;
import org.mobicents.slee.container.component.profile.cmp.ProfileCMPInterfaceDescriptor;
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.ProfileObjectImpl;
import org.mobicents.slee.container.util.ObjectCloner;

/**
 * 
 * Generates the ProfileConcrete impl for a specific Profile Specification.
 * 
 * <br>
 * Project: restcomm <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.
             */

            ProfileSpecificationDescriptor profileDescriptor = profileComponent.getDescriptor();

            if (logger.isTraceEnabled())
                logger.trace("Profile combination for " + profileComponent.getProfileSpecificationID() + " = "
                        + this.profileCombination);

            String deployDir = profileComponent.getDeploymentDir().getAbsolutePath();

            ProfileCMPInterfaceDescriptor 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() });
                }
            }

            generateProfileConcreteClassInfo(profileConcreteClass);

            // add profile object field and getter/setter
            CtField fProfileObject = ClassGeneratorUtils.addField(
                    ClassGeneratorUtils.getClass(ProfileObjectImpl.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.isTraceEnabled())
                logger.trace("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.isTraceEnabled())
                        logger.trace("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.isTraceEnabled())
                        logger.trace("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 if (pType.isArray())
                            pTypes[i++] = org.apache.commons.lang.ClassUtils.getClass(pType.getName());
                        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
        ProfileCMPInterfaceDescriptor 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();
        }
    }

    public boolean isNumericOrStringArray(CtClass ctClass) {
        boolean result = false;
        if (ctClass.isArray()) {
            try {
                String typeName = ctClass.getComponentType().getName();
                result = typeName.equals(Boolean.class.getName()) || typeName.equals(Character.class.getName())
                        || typeName.equals(Byte.class.getName()) || typeName.equals(Short.class.getName())
                        || typeName.equals(Integer.class.getName()) || typeName.equals(Long.class.getName())
                        || typeName.equals(Float.class.getName()) || typeName.equals(Double.class.getName())
                        || typeName.equals(String.class.getName());
            } catch (NotFoundException e) {
            }
        }

        return result;
    }

    private void generateCMPGetter(CtMethod method, CtClass classToBeInstrumented) throws Exception {

        // see issue #23: [Profiles] getting correct fieldName from method of ProfileCMP interface
        String fieldName = ClassGeneratorUtils.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 ? ObjectCloner.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 (isPrimitive || isNumericOrStringArray(method.getReturnType())) {
                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.isTraceEnabled()) {
            logger.trace("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 {

        // see issue #23: [Profiles] getting correct fieldName from method of ProfileCMP interface
        String fieldName = ClassGeneratorUtils.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() + ")"
                + ObjectCloner.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.isTraceEnabled()) {
            logger.trace("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 = profileComponent.getProfileConcreteClassInfo();

        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;
                }
            }
        }
    }
}