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

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

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008, 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 java.io.ObjectStreamException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.spi.Bean;

import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.DuplicateMemberException;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.util.proxy.MethodHandler;
import org.jboss.weld.Container;
import org.jboss.weld.bean.proxy.util.SerializableClientProxy;
import org.jboss.weld.context.cache.RequestScopedBeanCache;
import org.jboss.weld.serialization.spi.ContextualStore;
import org.jboss.weld.util.bytecode.BytecodeUtils;
import org.jboss.weld.util.bytecode.DescriptorUtils;
import org.jboss.weld.util.bytecode.JumpMarker;
import org.jboss.weld.util.bytecode.JumpUtils;
import org.jboss.weld.util.bytecode.MethodInformation;
import org.jboss.weld.util.bytecode.MethodUtils;
import org.jboss.weld.util.bytecode.StaticMethodInformation;

/**
 * Proxy factory that generates client proxies, it uses optimizations that
 * are not valid for other proxy types.
 *
 * @author Stuart Douglas
 * @author Marius Bogoevici
 */
public class ClientProxyFactory extends ProxyFactory {

    private static final Set> CACHABLE_SCOPES;

    public static final String CLIENT_PROXY_SUFFIX = "ClientProxy";

    private static final String CACHE_FIELD = "BEAN_INSTANCE_CACHE";

    /**
     * It is possible although very unlikely that two different beans will end up with the same proxy class
     * (generally this will only happen in test situations where weld is being started/stopped multiple times
     * in the same class loader, such as during unit tests)
     *
     * To avoid this causing serialization problems we explicitly set the bean id on creation, and store it in this
     * field.
     *
     *
     */
    private static final String BEAN_ID_FIELD = "BEAN_ID_FIELD";

    private final String beanId;

    private volatile Field beanIdField;

    static {
        Set> scopes = new HashSet>();
        scopes.add(RequestScoped.class);
        scopes.add(ConversationScoped.class);
        scopes.add(SessionScoped.class);
        scopes.add(ApplicationScoped.class);
        CACHABLE_SCOPES = Collections.unmodifiableSet(scopes);
    }

    public ClientProxyFactory(Class proxiedBeanType, Set typeClosure, Bean bean) {
        super(proxiedBeanType, typeClosure, bean);
        beanId = Container.instance().services().get(ContextualStore.class).putIfAbsent(bean);
    }

    @Override
    public T create(BeanInstance beanInstance) {
        try {
            final T instance = super.create(beanInstance);
            if (beanIdField == null) {
                final Field f = instance.getClass().getDeclaredField(BEAN_ID_FIELD);
                f.setAccessible(true);
                beanIdField = f;
            }
            beanIdField.set(instance, beanId);
            return instance;
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void addFields(ClassFile proxyClassType, Bytecode initialValueBytecode) {
        super.addFields(proxyClassType, initialValueBytecode);
        if (CACHABLE_SCOPES.contains(getBean().getScope())) {
            try {
                FieldInfo sfield = new FieldInfo(proxyClassType.getConstPool(), CACHE_FIELD, "Ljava/lang/ThreadLocal;");
                sfield.setAccessFlags(AccessFlag.TRANSIENT | AccessFlag.PRIVATE);
                proxyClassType.addField(sfield);
                initialValueBytecode.addAload(0);
                initialValueBytecode.addNew(ThreadLocal.class.getName());
                initialValueBytecode.add(Opcode.DUP);
                initialValueBytecode.addInvokespecial(ThreadLocal.class.getName(), "", "()V");
                initialValueBytecode.addPutfield(proxyClassType.getName(), CACHE_FIELD, "Ljava/lang/ThreadLocal;");
            } catch (DuplicateMemberException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            FieldInfo beanIdField = new FieldInfo(proxyClassType.getConstPool(), BEAN_ID_FIELD, "Ljava/lang/String;");
            beanIdField.setAccessFlags(AccessFlag.VOLATILE | AccessFlag.PRIVATE);
            proxyClassType.addField(beanIdField);
        } catch (DuplicateMemberException e) {
            throw new RuntimeException(e);
        }
    }

    protected void addSerializationSupport(ClassFile proxyClassType) {
        try {
            Class[] exceptions = new Class[]{ObjectStreamException.class};
            Bytecode writeReplaceBody = createWriteReplaceBody(proxyClassType);
            MethodInformation writeReplaceInfo = new StaticMethodInformation("writeReplace", new Class[]{}, Object.class, proxyClassType.getName());
            proxyClassType.addMethod(MethodUtils.makeMethod(writeReplaceInfo, exceptions, writeReplaceBody, proxyClassType.getConstPool()));
        } catch (DuplicateMemberException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * creates serialization code that returns a SerializableClientProxy
     */
    private Bytecode createWriteReplaceBody(ClassFile proxyClassType) {
        Bytecode b = new Bytecode(proxyClassType.getConstPool());
        b.addNew(SerializableClientProxy.class.getName());
        b.add(Opcode.DUP);
        b.add(Opcode.ALOAD_0);
        b.addGetfield(proxyClassType.getName(), BEAN_ID_FIELD, "Ljava/lang/String;");
        b.addInvokespecial(SerializableClientProxy.class.getName(), "", "(Ljava/lang/String;)V");
        b.add(Opcode.ARETURN);
        b.setMaxLocals(1);
        return b;
    }


    /**
     * Calls methodHandler.invoke with a null method parameter in order to
     * get the underlying instance. The invocation is then forwarded to
     * this instance with generated bytecode.
     */
    protected Bytecode createForwardingMethodBody(ClassFile file, MethodInformation methodInfo) throws NotFoundException {
        Method method = methodInfo.getMethod();
        // we can only use bytecode based invocation for some methods
        // at the moment we restrict it solely to public methods with public
        // return and parameter types
        boolean bytecodeInvocationAllowed = Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getReturnType().getModifiers());
        for (Class paramType : method.getParameterTypes()) {
            if (!Modifier.isPublic(paramType.getModifiers())) {
                bytecodeInvocationAllowed = false;
                break;
            }
        }
        if (!bytecodeInvocationAllowed) {
            return createInterceptorBody(file, methodInfo);
        }
        Bytecode b = new Bytecode(file.getConstPool());
        int localCount = MethodUtils.calculateMaxLocals(method) + 1;

        // create a new interceptor invocation context whenever we invoke a method on a client proxy
        // we use a try-catch block in order to make sure that endInterceptorContext() is invoked regardless whether
        // the method has succeeded or not
        int start = b.currentPc();
        b.addInvokestatic("org.jboss.weld.bean.proxy.InterceptionDecorationContext", "startInterceptorContext", "()V");

        final Class scope = getBean().getScope();

        if (CACHABLE_SCOPES.contains(scope)) {
            loadCachableBeanInstance(file, methodInfo, b);
        } else {
            loadBeanInstance(file, methodInfo, b);
        }
        //now we should have the target bean instance on top of the stack
        // we need to dup it so we still have it to compare to the return value
        b.add(Opcode.DUP);

        //lets create the method invocation
        String methodDescriptor = methodInfo.getDescriptor();
        BytecodeUtils.loadParameters(b, methodDescriptor);
        if (method.getDeclaringClass().isInterface()) {
            b.addInvokeinterface(methodInfo.getDeclaringClass(), methodInfo.getName(), methodDescriptor, method.getParameterTypes().length + 1);
        } else {
            b.addInvokevirtual(methodInfo.getDeclaringClass(), methodInfo.getName(), methodDescriptor);
        }

        // end the interceptor context, everything was fine
        b.addInvokestatic("org.jboss.weld.bean.proxy.InterceptionDecorationContext", "endInterceptorContext", "()V");

        // jump over the catch block
        b.addOpcode(Opcode.GOTO);
        JumpMarker gotoEnd = JumpUtils.addJumpInstruction(b);

        // create catch block
        b.addExceptionHandler(start, b.currentPc(), b.currentPc(), 0);
        b.addInvokestatic("org.jboss.weld.bean.proxy.InterceptionDecorationContext", "endInterceptorContext", "()V");
        b.add(Opcode.ATHROW);

        // update the correct address to jump over the catch block
        gotoEnd.mark();

        // if this method returns a primitive we just return
        if (method.getReturnType().isPrimitive()) {
            BytecodeUtils.addReturnInstruction(b, methodInfo.getReturnType());
        } else {
            // otherwise we have to check that the proxy is not returning 'this;
            // now we need to check if the proxy has return 'this' and if so return
            // an
            // instance of the proxy.
            // currently we have result, beanInstance on the stack.
            b.add(Opcode.DUP_X1);
            // now we have result, beanInstance, result
            // we need to compare result and beanInstance

            // first we need to build up the inner conditional that just returns
            // the
            // result
            b.add(Opcode.IF_ACMPEQ);
            JumpMarker returnInstruction = JumpUtils.addJumpInstruction(b);
            BytecodeUtils.addReturnInstruction(b, methodInfo.getReturnType());
            returnInstruction.mark();

            // now add the case where the proxy returns 'this';
            b.add(Opcode.ALOAD_0);
            b.addCheckcast(methodInfo.getMethod().getReturnType().getName());
            BytecodeUtils.addReturnInstruction(b, methodInfo.getReturnType());
        }
        if (b.getMaxLocals() < localCount) {
            b.setMaxLocals(localCount);
        }
        return b;
    }

    /**
     * If the bean is part of a well known scope then this code caches instances in a thread local for the life of the
     * request, as a performance enhancement.
     */
    private void loadCachableBeanInstance(ClassFile file, MethodInformation methodInfo, Bytecode b) {
        //first we need to see if the scope is active
        b.addInvokestatic(RequestScopedBeanCache.class.getName(), "isActive", "()Z");
        //if it is not active we just get the bean directly


        b.add(Opcode.IFEQ);
        final JumpMarker returnInstruction = JumpUtils.addJumpInstruction(b);
        //get the bean from the cache
        b.addAload(0);
        b.addGetfield(file.getName(), CACHE_FIELD, "Ljava/lang/ThreadLocal;");
        b.addInvokevirtual(ThreadLocal.class.getName(), "get", "()Ljava/lang/Object;");
        b.add(Opcode.DUP);
        b.add(Opcode.IFNULL);
        final JumpMarker createNewInstance = JumpUtils.addJumpInstruction(b);
        //so we have a not-null bean instance in the cache
        b.addCheckcast(methodInfo.getDeclaringClass());
        b.add(Opcode.GOTO);
        final JumpMarker loadedFromCache = JumpUtils.addJumpInstruction(b);
        createNewInstance.mark();
        //we need to get a bean instance and cache it
        //first clear the null off the top of the stack
        b.add(Opcode.POP);
        loadBeanInstance(file, methodInfo, b);
        b.add(Opcode.DUP);
        b.addAload(0);
        b.addGetfield(file.getName(), CACHE_FIELD, "Ljava/lang/ThreadLocal;");
        b.add(Opcode.DUP_X1);
        b.add(Opcode.SWAP);
        b.addInvokevirtual(ThreadLocal.class.getName(), "set", "(Ljava/lang/Object;)V");
        b.addInvokestatic(RequestScopedBeanCache.class.getName(), "addItem", "(Ljava/lang/ThreadLocal;)V");
        b.add(Opcode.GOTO);
        final JumpMarker endOfIfStatement = JumpUtils.addJumpInstruction(b);
        returnInstruction.mark();
        loadBeanInstance(file, methodInfo, b);
        endOfIfStatement.mark();
        loadedFromCache.mark();
    }

    private void loadBeanInstance(ClassFile file, MethodInformation methodInfo, Bytecode b) {
        b.add(Opcode.ALOAD_0);
        b.addGetfield(file.getName(), "methodHandler", DescriptorUtils.classToStringRepresentation(MethodHandler.class));
        //pass null arguments to methodHandler.invoke
        b.add(Opcode.ALOAD_0);
        b.add(Opcode.ACONST_NULL);
        b.add(Opcode.ACONST_NULL);
        b.add(Opcode.ACONST_NULL);

        // now we have all our arguments on the stack
        // lets invoke the method
        b.addInvokeinterface(MethodHandler.class.getName(), "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", 5);

        b.addCheckcast(methodInfo.getDeclaringClass());
    }

    /**
     * Client proxies use the following hashCode:
     * MyProxyName.class.hashCode()
     */
    @Override
    protected MethodInfo generateHashCodeMethod(ClassFile proxyClassType) {
        MethodInfo method = new MethodInfo(proxyClassType.getConstPool(), "hashCode", "()I");
        method.setAccessFlags(AccessFlag.PUBLIC);
        Bytecode b = new Bytecode(proxyClassType.getConstPool());
        // MyProxyName.class.hashCode()
        int classLocation = proxyClassType.getConstPool().addClassInfo(proxyClassType.getName());
        b.addLdc(classLocation);
        // now we have the class object on top of the stack
        b.addInvokevirtual("java.lang.Object", "hashCode", "()I");
        // now we have the hashCode
        b.add(Opcode.IRETURN);
        b.setMaxLocals(1);
        b.setMaxStack(1);
        method.setCodeAttribute(b.toCodeAttribute());
        return method;
    }

    /**
     * Client proxies are equal to other client proxies for the same bean.
     * 

* The corresponding java code: * return other instanceof MyProxyClassType.class * */ @Override protected MethodInfo generateEqualsMethod(ClassFile proxyClassType) { MethodInfo method = new MethodInfo(proxyClassType.getConstPool(), "equals", "(Ljava/lang/Object;)Z"); method.setAccessFlags(AccessFlag.PUBLIC); Bytecode b = new Bytecode(proxyClassType.getConstPool()); b.addAload(1); b.addInstanceof(proxyClassType.getName()); b.add(Opcode.IRETURN); b.setMaxLocals(2); b.setMaxStack(1); method.setCodeAttribute(b.toCodeAttribute()); return method; } @Override protected String getProxyNameSuffix() { return CLIENT_PROXY_SUFFIX; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy