All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.weld.bean.proxy.InterceptedSubclassFactory Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed 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.jboss.weld.bean.proxy;

import static org.jboss.classfilewriter.util.DescriptorUtils.isPrimitive;
import static org.jboss.classfilewriter.util.DescriptorUtils.isWide;
import static org.jboss.classfilewriter.util.DescriptorUtils.makeDescriptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.enterprise.inject.spi.Bean;

import org.jboss.classfilewriter.AccessFlag;
import org.jboss.classfilewriter.ClassFile;
import org.jboss.classfilewriter.ClassMethod;
import org.jboss.classfilewriter.DuplicateMemberException;
import org.jboss.classfilewriter.code.BranchEnd;
import org.jboss.classfilewriter.code.CodeAttribute;
import org.jboss.classfilewriter.util.Boxing;
import org.jboss.classfilewriter.util.DescriptorUtils;
import org.jboss.weld.annotated.enhanced.MethodSignature;
import org.jboss.weld.annotated.enhanced.jlr.MethodSignatureImpl;
import org.jboss.weld.bean.proxy.InterceptionDecorationContext.Stack;
import org.jboss.weld.exceptions.WeldException;
import org.jboss.weld.interceptor.proxy.LifecycleMixin;
import org.jboss.weld.interceptor.util.proxy.TargetInstanceProxy;
import org.jboss.weld.logging.BeanLogger;
import org.jboss.weld.security.GetDeclaredMethodsAction;
import org.jboss.weld.util.bytecode.BytecodeUtils;
import org.jboss.weld.util.bytecode.MethodInformation;
import org.jboss.weld.util.bytecode.RuntimeMethodInformation;
import org.jboss.weld.util.reflection.Reflections;

/**
 * Factory for producing subclasses that are used by the combined interceptors and decorators stack.
 *
 * @author Marius Bogoevici
 */
public class InterceptedSubclassFactory extends ProxyFactory {

    // Default proxy class name suffix
    public static final String PROXY_SUFFIX = "Subclass";

    private static final String SUPER_DELEGATE_SUFFIX = "$$super";

    private static final String COMBINED_INTERCEPTOR_AND_DECORATOR_STACK_METHOD_HANDLER_CLASS_NAME = CombinedInterceptorAndDecoratorStackMethodHandler.class.getName();
    private static final String[] INVOKE_METHOD_PARAMETERS = new String[] { makeDescriptor(Stack.class), LJAVA_LANG_OBJECT, LJAVA_LANG_REFLECT_METHOD, LJAVA_LANG_REFLECT_METHOD, "[" + LJAVA_LANG_OBJECT  };

    protected static final String PRIVATE_METHOD_HANDLER_FIELD_NAME = "privateMethodHandler";

    private final Set enhancedMethodSignatures;
    private final Set interceptedMethodSignatures;

    private final Class proxiedBeanType;

    public InterceptedSubclassFactory(String contextId, Class proxiedBeanType, Set typeClosure, Bean bean, Set enhancedMethodSignatures, Set interceptedMethodSignatures) {
        this(contextId, proxiedBeanType, typeClosure, getProxyName(contextId, proxiedBeanType, typeClosure, bean), bean, enhancedMethodSignatures, interceptedMethodSignatures);
    }

    /**
     * Creates a new proxy factory when the name of the proxy class is already
     * known, such as during de-serialization
     *
     * @param proxiedBeanType          the super-class for this proxy class
     * @param typeClosure              the bean types of the bean
     * @param enhancedMethodSignatures a restricted set of methods that need to be intercepted
     */
    public InterceptedSubclassFactory(String contextId, Class proxiedBeanType, Set typeClosure, String proxyName, Bean bean,
            Set enhancedMethodSignatures, Set interceptedMethodSignatures) {
        super(contextId, proxiedBeanType, typeClosure, proxyName, bean, true);
        this.enhancedMethodSignatures = enhancedMethodSignatures;
        this.interceptedMethodSignatures = interceptedMethodSignatures;
        this.proxiedBeanType = proxiedBeanType;
    }

    @Override
    public void addInterfacesFromTypeClosure(Set typeClosure, Class proxiedBeanType) {
        for (Class c : proxiedBeanType.getInterfaces()) {
            addInterface(c);
        }
    }

    /**
     * Returns a suffix to append to the name of the proxy class. The name
     * already consists of _$$_Weld, to which the suffix is added.
     * This allows the creation of different types of proxies for the same class.
     *
     * @return a name suffix
     */
    protected String getProxyNameSuffix() {
        return PROXY_SUFFIX;
    }

    @Override
    protected void addMethods(ClassFile proxyClassType, ClassMethod staticConstructor) {
        // Add all class methods for interception
        addMethodsFromClass(proxyClassType, staticConstructor);

        // Add special proxy methods
        addSpecialMethods(proxyClassType, staticConstructor);

    }

    @Override
    protected void addMethodsFromClass(ClassFile proxyClassType, final ClassMethod staticConstructor) {
        try {

            final Set finalMethods = new HashSet();
            final Set processedBridgeMethods = new HashSet();

            // Add all methods from the class hierarchy
            Class cls = getBeanType();
            while (cls != null) {
                Set declaredBridgeMethods = new HashSet();
                for (Method method : AccessController.doPrivileged(new GetDeclaredMethodsAction(cls))) {

                    final MethodSignatureImpl methodSignature = new MethodSignatureImpl(method);

                    if (!Modifier.isFinal(method.getModifiers()) && !method.isBridge() && enhancedMethodSignatures.contains(methodSignature)
                            && !finalMethods.contains(methodSignature)
                            && !bridgeMethodsContainsMethod(processedBridgeMethods, methodSignature, method.getGenericReturnType(), Modifier.isAbstract(method.getModifiers()))) {
                        try {
                            final MethodInformation methodInfo = new RuntimeMethodInformation(method);

                            if (interceptedMethodSignatures.contains(methodSignature)) {
                                // create delegate-to-super method
                                createDelegateMethod(proxyClassType, method, methodInfo);

                                // this method is intercepted
                                // override a subclass method to delegate to method handler
                                ClassMethod classMethod = proxyClassType.addMethod(method);
                                addConstructedGuardToMethodBody(classMethod);
                                createForwardingMethodBody(classMethod, methodInfo, staticConstructor);
                                BeanLogger.LOG.addingMethodToProxy(method);
                            } else {
                                // this method is not intercepted
                                // we still need to override and push InterceptionDecorationContext stack to prevent full interception
                                ClassMethod classMethod = proxyClassType.addMethod(method);
                                new RunWithinInterceptionDecorationContextGenerator(classMethod, this) {

                                    @Override
                                    void doWork(CodeAttribute b, ClassMethod classMethod) {
                                        if (Modifier.isPrivate(classMethod.getAccessFlags())) {
                                            // Weld cannot use invokespecial to invoke a private method from the superclass
                                            invokePrivateMethodHandler(b, classMethod, methodInfo, staticConstructor);
                                        } else {
                                            // build the bytecode that invokes the super class method directly
                                            b.aload(0);
                                            // create the method invocation
                                            b.loadMethodParameters();
                                            b.invokespecial(methodInfo.getDeclaringClass(), methodInfo.getName(), methodInfo.getDescriptor());
                                        }
                                        // leave the result on top of the stack
                                    }

                                    @Override
                                    void doReturn(CodeAttribute b, ClassMethod method) {
                                        // assumes doWork() result is on top of the stack
                                        b.returnInstruction();
                                    }
                                }.runStartIfNotOnTop();
                            }


                        } catch (DuplicateMemberException e) {
                            // do nothing. This will happen if superclass methods have
                            // been overridden
                        }
                    } else {
                        if (Modifier.isFinal(method.getModifiers())) {
                            finalMethods.add(methodSignature);
                        }
                        if (method.isBridge()) {
                            declaredBridgeMethods.add(new BridgeMethod(methodSignature, method.getGenericReturnType()));
                        }
                    }
                }
                processedBridgeMethods.addAll(declaredBridgeMethods);
                cls = cls.getSuperclass();
            }
            for (Class c : getAdditionalInterfaces()) {
                for (Method method : c.getMethods()) {
                    MethodSignature signature = new MethodSignatureImpl(method);
                    // For interfaces we do not consider return types when going through processed bridge methods
                    if (enhancedMethodSignatures.contains(signature) && !bridgeMethodsContainsMethod(processedBridgeMethods, signature, null, Modifier.isAbstract(method.getModifiers()))) {
                        try {
                            MethodInformation methodInfo = new RuntimeMethodInformation(method);
                            if (interceptedMethodSignatures.contains(signature) && Reflections.isDefault(method)) {
                                createDelegateMethod(proxyClassType, method, methodInfo);

                                // this method is intercepted
                                // override a subclass method to delegate to method handler
                                ClassMethod classMethod = proxyClassType.addMethod(method);
                                addConstructedGuardToMethodBody(classMethod);
                                createForwardingMethodBody(classMethod, methodInfo, staticConstructor);
                                BeanLogger.LOG.addingMethodToProxy(method);
                            } else {
                                if (Reflections.isDefault(method)) {
                                    createDelegateMethod(proxyClassType, method, methodInfo);
                                } else {
                                    final ClassMethod classMethod = proxyClassType.addMethod(method);
                                    createSpecialMethodBody(classMethod, methodInfo, staticConstructor);
                                    BeanLogger.LOG.addingMethodToProxy(method);
                                }
                            }
                        } catch (DuplicateMemberException e) {
                        }
                    }
                    if (method.isBridge()) {
                        processedBridgeMethods.add(new BridgeMethod(signature, method.getGenericReturnType()));
                    }
                }
            }
        } catch (Exception e) {
            throw new WeldException(e);
        }
    }

    private boolean bridgeMethodsContainsMethod(Set processedBridgeMethods, MethodSignature signature, Type returnType, boolean isMethodAbstract) {
        for (BridgeMethod bridgeMethod : processedBridgeMethods) {
            if (bridgeMethod.signature.equals(signature)) {
                // method signature is equal (name and params) but return type can still differ
                if (returnType != null) {
                    if (bridgeMethod.returnType.equals(Object.class) || isMethodAbstract) {
                        // bridge method with matching signature has Object as return type
                        // or the method we compare against is abstract meaning the bridge overrides it
                        // both cases are a match
                        return true;
                    } else {
                        // in all other cases we compare return types
                        return bridgeMethod.returnType.equals(returnType);
                    }
                }
                return true;
            }
        }
        return false;
    }

    protected void createForwardingMethodBody(ClassMethod classMethod, MethodInformation method, ClassMethod staticConstructor) {
        createInterceptorBody(classMethod, method, true, staticConstructor);
    }

    /**
     * Creates the given method on the proxy class where the implementation
     * forwards the call directly to the method handler.
     * 

* the generated bytecode is equivalent to: *

* return (RetType) methodHandler.invoke(this,param1,param2); * * @param methodInfo any JLR method * @param delegateToSuper * @return the method byte code */ protected void createInterceptorBody(ClassMethod method, MethodInformation methodInfo, boolean delegateToSuper, ClassMethod staticConstructor) { invokeMethodHandler(method, methodInfo, true, DEFAULT_METHOD_RESOLVER, delegateToSuper, staticConstructor); } private void createDelegateToSuper(ClassMethod classMethod, MethodInformation method) { createDelegateToSuper(classMethod, method, classMethod.getClassFile().getSuperclass()); } private void createDelegateToSuper(ClassMethod classMethod, MethodInformation method, String className) { CodeAttribute b = classMethod.getCodeAttribute(); // first generate the invokespecial call to the super class method b.aload(0); b.loadMethodParameters(); b.invokespecial(className, method.getName(), method.getDescriptor()); b.returnInstruction(); } /** * calls methodHandler.invoke for a given method * * @param methodInfo declaring class of the method * @param addReturnInstruction set to true you want to return the result of * @param bytecodeMethodResolver The method resolver * @param addProceed */ protected void invokeMethodHandler(ClassMethod method, MethodInformation methodInfo, boolean addReturnInstruction, BytecodeMethodResolver bytecodeMethodResolver, boolean addProceed, ClassMethod staticConstructor) { // now we need to build the bytecode. The order we do this in is as // follows: // load methodHandler // dup the methodhandler // invoke isDisabledHandler on the method handler to figure out of this is // a self invocation. // load this // load the method object // load the proceed method that invokes the superclass version of the // current method // create a new array the same size as the number of parameters // push our parameter values into the array // invokeinterface the invoke method // add checkcast to cast the result to the return type, or unbox if // primitive // add an appropriate return instruction final CodeAttribute b = method.getCodeAttribute(); b.aload(0); getMethodHandlerField(method.getClassFile(), b); if (addProceed) { b.dup(); // get the Stack b.invokestatic(InterceptionDecorationContext.class.getName(), "getStack", "()" + DescriptorUtils.makeDescriptor(Stack.class)); // this is a self invocation optimisation // test to see if this is a self invocation, and if so invokespecial the // superclass method directly // do not optimize in the case of default methods if (!Reflections.isDefault(methodInfo.getMethod())) { b.dupX1(); // Handler, Stack -> Stack, Handler, Stack b.invokevirtual(COMBINED_INTERCEPTOR_AND_DECORATOR_STACK_METHOD_HANDLER_CLASS_NAME, "isDisabledHandler", "(" + DescriptorUtils.makeDescriptor(Stack.class) + ")" + BytecodeUtils.BOOLEAN_CLASS_DESCRIPTOR); b.iconst(0); BranchEnd invokeSuperDirectly = b.ifIcmpeq(); // now build the bytecode that invokes the super class method b.pop2(); // pop Stack and Handler b.aload(0); // create the method invocation b.loadMethodParameters(); b.invokespecial(methodInfo.getDeclaringClass(), methodInfo.getName(), methodInfo.getDescriptor()); b.returnInstruction(); b.branchEnd(invokeSuperDirectly); } } else { b.aconstNull(); } b.aload(0); bytecodeMethodResolver.getDeclaredMethod(method, methodInfo.getDeclaringClass(), methodInfo.getName(), methodInfo.getParameterTypes(), staticConstructor); if (addProceed) { if (Modifier.isPrivate(method.getAccessFlags())) { // If the original method is private we can't use WeldSubclass.method$$super() as proceed bytecodeMethodResolver.getDeclaredMethod(method, methodInfo.getDeclaringClass(), methodInfo.getName(), methodInfo.getParameterTypes(), staticConstructor); } else { bytecodeMethodResolver.getDeclaredMethod(method, method.getClassFile().getName(), methodInfo.getName() + SUPER_DELEGATE_SUFFIX, methodInfo.getParameterTypes(), staticConstructor); } } else { b.aconstNull(); } b.iconst(methodInfo.getParameterTypes().length); b.anewarray(Object.class.getName()); int localVariableCount = 1; for (int i = 0; i < methodInfo.getParameterTypes().length; ++i) { String typeString = methodInfo.getParameterTypes()[i]; b.dup(); // duplicate the array reference b.iconst(i); // load the parameter value BytecodeUtils.addLoadInstruction(b, typeString, localVariableCount); // box the parameter if necessary Boxing.boxIfNessesary(b, typeString); // and store it in the array b.aastore(); if (isWide(typeString)) { localVariableCount = localVariableCount + 2; } else { localVariableCount++; } } // now we have all our arguments on the stack // lets invoke the method b.invokeinterface(StackAwareMethodHandler.class.getName(), INVOKE_METHOD_NAME, LJAVA_LANG_OBJECT, INVOKE_METHOD_PARAMETERS); if (addReturnInstruction) { // now we need to return the appropriate type if (methodInfo.getReturnType().equals(BytecodeUtils.VOID_CLASS_DESCRIPTOR)) { b.returnInstruction(); } else if (isPrimitive(methodInfo.getReturnType())) { Boxing.unbox(b,method.getReturnType()); b.returnInstruction(); } else { b.checkcast(BytecodeUtils.getName(methodInfo.getReturnType())); b.returnInstruction(); } } } /** * Adds methods requiring special implementations rather than just * delegation. * * @param proxyClassType the Javassist class description for the proxy type */ protected void addSpecialMethods(ClassFile proxyClassType, ClassMethod staticConstructor) { try { // Add special methods for interceptors for (Method method : LifecycleMixin.class.getMethods()) { BeanLogger.LOG.addingMethodToProxy(method); MethodInformation methodInfo = new RuntimeMethodInformation(method); createInterceptorBody(proxyClassType.addMethod(method), methodInfo, false, staticConstructor); } Method getInstanceMethod = TargetInstanceProxy.class.getMethod("getTargetInstance"); Method getInstanceClassMethod = TargetInstanceProxy.class.getMethod("getTargetClass"); generateGetTargetInstanceBody(proxyClassType.addMethod(getInstanceMethod)); generateGetTargetClassBody(proxyClassType.addMethod(getInstanceClassMethod)); Method setMethodHandlerMethod = ProxyObject.class.getMethod("setHandler", MethodHandler.class); generateSetMethodHandlerBody(proxyClassType.addMethod(setMethodHandlerMethod)); Method getMethodHandlerMethod = ProxyObject.class.getMethod("getHandler"); generateGetMethodHandlerBody(proxyClassType.addMethod(getMethodHandlerMethod)); } catch (Exception e) { throw new WeldException(e); } } private static void generateGetTargetInstanceBody(ClassMethod method) { final CodeAttribute b = method.getCodeAttribute(); b.aload(0); b.returnInstruction(); } private static void generateGetTargetClassBody(ClassMethod method) { final CodeAttribute b = method.getCodeAttribute(); BytecodeUtils.pushClassType(b, method.getClassFile().getSuperclass()); b.returnInstruction(); } @Override public Class getBeanType() { return proxiedBeanType; } @Override protected Class getMethodHandlerType() { return CombinedInterceptorAndDecoratorStackMethodHandler.class; } @SuppressWarnings("unchecked") private void createDelegateMethod(ClassFile proxyClassType, Method method, MethodInformation methodInformation) { int modifiers = (method.getModifiers() | AccessFlag.SYNTHETIC | AccessFlag.PRIVATE) & ~AccessFlag.PUBLIC & ~AccessFlag.PROTECTED; ClassMethod delegatingMethod = proxyClassType.addMethod(modifiers, method.getName() + SUPER_DELEGATE_SUFFIX, DescriptorUtils.makeDescriptor(method.getReturnType()), DescriptorUtils.parameterDescriptors(method.getParameterTypes())); delegatingMethod.addCheckedExceptions((Class[]) method.getExceptionTypes()); createDelegateToSuper(delegatingMethod, methodInformation); } private void invokePrivateMethodHandler(CodeAttribute b, ClassMethod classMethod, MethodInformation methodInfo, ClassMethod staticConstructor) { try { classMethod.getClassFile().addField(AccessFlag.PRIVATE, PRIVATE_METHOD_HANDLER_FIELD_NAME, MethodHandler.class); } catch (DuplicateMemberException ignored) { } // 1. Load private method handler b.aload(0); b.getfield(classMethod.getClassFile().getName(), PRIVATE_METHOD_HANDLER_FIELD_NAME, DescriptorUtils.makeDescriptor(MethodHandler.class)); // 2. Load this b.aload(0); // 3. Load method DEFAULT_METHOD_RESOLVER.getDeclaredMethod(classMethod, methodInfo.getDeclaringClass(), methodInfo.getName(), methodInfo.getParameterTypes(), staticConstructor); // 4. No proceed method b.aconstNull(); // 5. Load method params b.iconst(methodInfo.getParameterTypes().length); b.anewarray(Object.class.getName()); int localVariableCount = 1; for (int i = 0; i < methodInfo.getParameterTypes().length; ++i) { String typeString = methodInfo.getParameterTypes()[i]; b.dup(); // duplicate the array reference b.iconst(i); // load the parameter value BytecodeUtils.addLoadInstruction(b, typeString, localVariableCount); // box the parameter if necessary Boxing.boxIfNessesary(b, typeString); // and store it in the array b.aastore(); if (isWide(typeString)) { localVariableCount = localVariableCount + 2; } else { localVariableCount++; } } // Invoke PrivateMethodHandler b.invokeinterface(MethodHandler.class.getName(), INVOKE_METHOD_NAME, LJAVA_LANG_OBJECT, new String[] { LJAVA_LANG_OBJECT, LJAVA_LANG_REFLECT_METHOD, LJAVA_LANG_REFLECT_METHOD, "[" + LJAVA_LANG_OBJECT }); if (methodInfo.getReturnType().equals(BytecodeUtils.VOID_CLASS_DESCRIPTOR)) { // No-op } else if (isPrimitive(methodInfo.getReturnType())) { Boxing.unbox(b, methodInfo.getReturnType()); } else { b.checkcast(BytecodeUtils.getName(methodInfo.getReturnType())); } } /** * If the given instance represents a proxy and its class is synthetic and its class name ends with {@value #PROXY_SUFFIX}, attempt to find the * {@value #PRIVATE_METHOD_HANDLER_FIELD_NAME} field and set its value to {@link PrivateMethodHandler#INSTANCE}. * * @param instance */ public static void setPrivateMethodHandler(T instance) { if (instance instanceof ProxyObject && instance.getClass().isSynthetic() && instance.getClass().getName().endsWith(PROXY_SUFFIX) && SecurityActions.hasDeclaredField(instance.getClass(), PRIVATE_METHOD_HANDLER_FIELD_NAME)) { try { Field privateMethodHandlerField = SecurityActions.getDeclaredField(instance.getClass(), PRIVATE_METHOD_HANDLER_FIELD_NAME); SecurityActions.ensureAccessible(privateMethodHandlerField); privateMethodHandlerField.set(instance, PrivateMethodHandler.INSTANCE); } catch (NoSuchFieldException ignored) { } catch (IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException(e); } } } private static class BridgeMethod { private final Type returnType; private final MethodSignature signature; public BridgeMethod(MethodSignature signature, Type returnType) { this.signature = signature; this.returnType = returnType; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((returnType == null) ? 0 : returnType.hashCode()); result = prime * result + ((signature == null) ? 0 : signature.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof BridgeMethod)) { return false; } BridgeMethod other = (BridgeMethod) obj; if (returnType == null) { if (other.returnType != null) { return false; } } else if (!returnType.equals(other.returnType)) { return false; } if (signature == null) { if (other.signature != null) { return false; } } else if (!signature.equals(other.signature)) { return false; } return true; } @Override public String toString() { return new StringBuilder().append("method ").append(returnType).append(" ").append(signature.getMethodName()) .append(Arrays.toString(signature.getParameterTypes()).replace('[', '(').replace(']', ')')).toString(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy