/* * 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.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.Collections; import java.util.LinkedHashSet; 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.code.CodeAttribute; import org.jboss.classfilewriter.util.DescriptorUtils; import org.jboss.weld.exceptions.WeldException; import org.jboss.weld.injection.FieldInjectionPoint; import org.jboss.weld.injection.ParameterInjectionPoint; import org.jboss.weld.injection.attributes.WeldInjectionPointAttributes; import org.jboss.weld.interceptor.util.proxy.TargetInstanceProxy; import org.jboss.weld.logging.BeanLogger; import org.jboss.weld.security.GetDeclaredMethodAction; 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.bytecode.StaticMethodInformation; /** * This special proxy factory is mostly used for abstract decorators. When a * delegate field is injected, the abstract methods directly invoke the * corresponding method on the delegate. All other cases forward the calls to * the {@link BeanInstance} for further processing. * * @author David Allen * @author Stuart Douglas */ public class DecoratorProxyFactory<T> extends ProxyFactory<T> { public static final String PROXY_SUFFIX = "DecoratorProxy"; private static final String INIT_MH_METHOD_NAME = "_initMH"; private final WeldInjectionPointAttributes<?, ?> delegateInjectionPoint; private final Field delegateField; public DecoratorProxyFactory(String contextId, Class<T> proxyType, WeldInjectionPointAttributes<?, ?> delegateInjectionPoint, Bean<?> bean) { super(contextId, proxyType, Collections.<Type>emptySet(), bean); this.delegateInjectionPoint = delegateInjectionPoint; if (delegateInjectionPoint instanceof FieldInjectionPoint<?, ?>) { delegateField = (Field) ((FieldInjectionPoint<?, ?>) delegateInjectionPoint).getMember(); } else { delegateField = null; } } /** * calls _initMH on the method handler and then stores the result in the * methodHandler field as then new methodHandler */ private void addHandlerInitializerMethod(ClassFile proxyClassType, ClassMethod staticConstructor) throws Exception { ClassMethod classMethod = proxyClassType.addMethod(AccessFlag.PRIVATE, INIT_MH_METHOD_NAME, BytecodeUtils.VOID_CLASS_DESCRIPTOR, LJAVA_LANG_OBJECT); final CodeAttribute b = classMethod.getCodeAttribute(); b.aload(0); StaticMethodInformation methodInfo = new StaticMethodInformation(INIT_MH_METHOD_NAME, new Class[] { Object.class }, void.class, classMethod.getClassFile().getName()); invokeMethodHandler(classMethod, methodInfo, false, DEFAULT_METHOD_RESOLVER, staticConstructor); b.checkcast(MethodHandler.class); b.putfield(classMethod.getClassFile().getName(), METHOD_HANDLER_FIELD_NAME, DescriptorUtils.makeDescriptor(MethodHandler.class)); b.returnInstruction(); BeanLogger.LOG.createdMethodHandlerInitializerForDecoratorProxy(getBeanType()); } @Override protected void addAdditionalInterfaces(Set<Class<?>> interfaces) { interfaces.add(DecoratorProxy.class); } @Override protected void addMethodsFromClass(ClassFile proxyClassType, ClassMethod staticConstructor) { Method initializerMethod = null; int delegateParameterPosition = -1; if (delegateInjectionPoint instanceof ParameterInjectionPoint<?, ?>) { ParameterInjectionPoint<?, ?> parameterIP = (ParameterInjectionPoint<?, ?>) delegateInjectionPoint; if (parameterIP.getMember() instanceof Method) { initializerMethod = ((Method) parameterIP.getMember()); delegateParameterPosition = parameterIP.getAnnotated().getPosition(); } } try { if (delegateParameterPosition >= 0) { addHandlerInitializerMethod(proxyClassType, staticConstructor); } Class<?> cls = getBeanType(); Set<Method> methods = new LinkedHashSet<Method>(); decoratorMethods(cls, methods); for (Method method : methods) { MethodInformation methodInfo = new RuntimeMethodInformation(method); if (!method.getDeclaringClass().getName().equals("java.lang.Object") || method.getName().equals("toString")) { if ((delegateParameterPosition >= 0) && (initializerMethod.equals(method))) { createDelegateInitializerCode(proxyClassType.addMethod(method), methodInfo, delegateParameterPosition); } // exclude bridge methods if (Modifier.isAbstract(method.getModifiers())) { createAbstractMethodCode(proxyClassType.addMethod(method), methodInfo, staticConstructor); } } } } catch (Exception e) { throw new WeldException(e); } } private void decoratorMethods(Class<?> cls, Set<Method> all) { if (cls == null) { return; } all.addAll(Arrays.asList(AccessController.doPrivileged(new GetDeclaredMethodsAction(cls)))); decoratorMethods(cls.getSuperclass(), all); // by now we should have all declared methods, let's only add the missing ones for (Class<?> ifc : cls.getInterfaces()) { Method[] methods = ifc.getMethods(); for (Method m : methods) { boolean isEqual = false; for (Method a : all) { if (isEqual(m, a)) { isEqual = true; break; } } if (!isEqual) { all.add(m); } } } } // m is more generic than a private static boolean isEqual(Method m, Method a) { if (m.getName().equals(a.getName()) && m.getParameterTypes().length == a.getParameterTypes().length && m.getReturnType().isAssignableFrom(a.getReturnType())) { for (int i = 0; i < m.getParameterTypes().length; i++) { if (!(m.getParameterTypes()[i].isAssignableFrom(a.getParameterTypes()[i]))) { return false; } } return true; } return false; } @Override protected String getProxyNameSuffix() { return PROXY_SUFFIX; } private void createAbstractMethodCode(ClassMethod classMethod, MethodInformation method, ClassMethod staticConstructor) { if ((delegateField != null) && (!Modifier.isPrivate(delegateField.getModifiers()))) { // Call the corresponding method directly on the delegate final CodeAttribute b = classMethod.getCodeAttribute(); // load the delegate field b.aload(0); b.getfield(classMethod.getClassFile().getName(), delegateField.getName(), DescriptorUtils.makeDescriptor(delegateField.getType())); // load the parameters b.loadMethodParameters(); // invoke the delegate method b.invokeinterface(delegateField.getType().getName(), method.getName(), method.getDescriptor()); // return the value if applicable b.returnInstruction(); } else { if (!Modifier.isPrivate(method.getMethod().getModifiers())) { // if it is a parameter injection point we need to initialize the // injection point then handle the method with the method handler // this is slightly different to a normal method handler call, as we pass // in a TargetInstanceBytecodeMethodResolver. This resolver uses the // method handler to call getTargetClass to get the correct class type to // resolve the method with, and then resolves this method invokeMethodHandler(classMethod, method, true, TARGET_INSTANCE_BYTECODE_METHOD_RESOLVER, staticConstructor); } else { // if the delegate is private we need to use the method handler createInterceptorBody(classMethod, method, staticConstructor); } } } /** * When creates the delegate initializer code when the delegate is injected * into a method. * <p/> * super initializer method is called first, and then _initMH is called * * @param initializerMethodInfo * @param delegateParameterPosition * @return */ private void createDelegateInitializerCode(ClassMethod classMethod, MethodInformation initializerMethodInfo, int delegateParameterPosition) { final CodeAttribute b = classMethod.getCodeAttribute(); // we need to push all the parameters on the stack to call the corresponding // superclass arguments b.aload(0); // load this int localVariables = 1; int actualDelegateParameterPosition = 0; for (int i = 0; i < initializerMethodInfo.getMethod().getParameterTypes().length; ++i) { if (i == delegateParameterPosition) { // figure out the actual position of the delegate in the local // variables actualDelegateParameterPosition = localVariables; } Class<?> type = initializerMethodInfo.getMethod().getParameterTypes()[i]; BytecodeUtils.addLoadInstruction(b, DescriptorUtils.makeDescriptor(type), localVariables); if (type == long.class || type == double.class) { localVariables = localVariables + 2; } else { localVariables++; } } b.invokespecial(classMethod.getClassFile().getSuperclass(), initializerMethodInfo.getName(), initializerMethodInfo.getDescriptor()); // if this method returns a value it is now sitting on top of the stack // we will leave it there are return it later // now we need to call _initMH b.aload(0); // load this b.aload(actualDelegateParameterPosition); // load the delegate b.invokevirtual(classMethod.getClassFile().getName(), INIT_MH_METHOD_NAME, "(" + LJAVA_LANG_OBJECT + ")" + BytecodeUtils.VOID_CLASS_DESCRIPTOR); // return the object from the top of the stack that we got from calling // the superclass method earlier b.returnInstruction(); } protected class TargetInstanceBytecodeMethodResolver implements BytecodeMethodResolver { private static final String JAVA_LANG_CLASS_CLASS_NAME = "java.lang.Class"; public void getDeclaredMethod(ClassMethod classMethod, String declaringClass, String methodName, String[] parameterTypes, ClassMethod staticConstructor) { // get the correct class type to use to resolve the method MethodInformation methodInfo = new StaticMethodInformation("getTargetClass", new String[0], LJAVA_LANG_CLASS, TargetInstanceProxy.class.getName()); invokeMethodHandler(classMethod, methodInfo, false, DEFAULT_METHOD_RESOLVER, staticConstructor); CodeAttribute code = classMethod.getCodeAttribute(); code.checkcast("java/lang/Class"); // now we have the class on the stack code.ldc(methodName); // now we need to load the parameter types into an array code.iconst(parameterTypes.length); code.anewarray(JAVA_LANG_CLASS_CLASS_NAME); for (int i = 0; i < parameterTypes.length; ++i) { code.dup(); // duplicate the array reference code.iconst(i); // now load the class object String type = parameterTypes[i]; BytecodeUtils.pushClassType(code, type); // and store it in the array code.aastore(); } code.invokestatic(GetDeclaredMethodAction.class.getName(), "wrapException", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/security/PrivilegedAction;"); code.invokestatic(AccessController.class.getName(), "doPrivileged", "(Ljava/security/PrivilegedAction;)Ljava/lang/Object;"); code.checkcast(Method.class); } } private final TargetInstanceBytecodeMethodResolver TARGET_INSTANCE_BYTECODE_METHOD_RESOLVER = new TargetInstanceBytecodeMethodResolver(); }