blob: c652284e947c6103ed7c0be6c1ca90eebe77c842 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.webbeans.proxy;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Provider;
import java.io.ObjectStreamException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.webbeans.component.OwbBean;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.exception.ProxyGenerationException;
import org.apache.webbeans.exception.WebBeansConfigurationException;
import org.apache.webbeans.intercept.NormalScopedBeanInterceptorHandler;
import org.apache.webbeans.util.ClassUtil;
import org.apache.webbeans.util.ExceptionUtil;
import org.apache.webbeans.util.WebBeansUtil;
import org.apache.xbean.asm7.ClassWriter;
import org.apache.xbean.asm7.MethodVisitor;
import org.apache.xbean.asm7.Opcodes;
import org.apache.xbean.asm7.Type;
/**
* This factory creates proxies which delegate the
* method invocations 1:1 to an instance which gets
* resolved via a {@link javax.inject.Provider}.
*/
public class NormalScopeProxyFactory extends AbstractProxyFactory
{
/** the name of the field which stores the {@link Provider} for the Contextual Instance */
public static final String FIELD_INSTANCE_PROVIDER = "owbContextualInstanceProvider";
/** the Method[] for all protected methods. We need to invoke them via reflection. */
public static final String FIELD_PROTECTED_METHODS = "owbProtectedMethods";
/**
* Caches the proxy classes for each bean.
* We need this to prevent filling up the ClassLoaders by
*/
private ConcurrentMap<Bean<?>, Class<?>> cachedProxyClasses = new ConcurrentHashMap<>();
public NormalScopeProxyFactory(WebBeansContext webBeansContext)
{
super(webBeansContext);
}
@Override
protected Class getMarkerInterface()
{
return OwbNormalScopeProxy.class;
}
public static <T> T unwrapInstance(T proxyInstance)
{
if (proxyInstance instanceof OwbNormalScopeProxy)
{
try
{
Field internalInstanceField = proxyInstance.getClass().getDeclaredField(FIELD_INSTANCE_PROVIDER);
internalInstanceField.setAccessible(true);
Provider<T> provider = (Provider<T>) internalInstanceField.get(proxyInstance);
return provider.get();
}
catch (Exception e)
{
throw ExceptionUtil.throwAsRuntimeException(e);
}
}
return proxyInstance;
}
/**
* @return the internal instance which gets proxied.
*/
public Provider getInstanceProvider(OwbNormalScopeProxy proxyInstance)
{
try
{
Field internalInstanceField = proxyInstance.getClass().getDeclaredField(FIELD_INSTANCE_PROVIDER);
internalInstanceField.setAccessible(true);
return (Provider) internalInstanceField.get(proxyInstance);
}
catch (Exception e)
{
throw ExceptionUtil.throwAsRuntimeException(e);
}
}
public <T> T createNormalScopeProxy(Bean<T> bean)
{
ClassLoader classLoader;
if (bean.getBeanClass() != null)
{
classLoader = getProxyClassLoader(bean.getBeanClass());
}
else if (OwbBean.class.isInstance(bean) && OwbBean.class.cast(bean).getReturnType() != null)
{
classLoader = getProxyClassLoader(OwbBean.class.cast(bean).getReturnType());
}
else
{
classLoader = WebBeansUtil.getCurrentClassLoader();
}
Class<T> classToProxy;
if (bean instanceof OwbBean)
{
classToProxy = ((OwbBean<T>) bean).getReturnType();
}
else
{
// TODO: that might be wrong sometimes
classToProxy = (Class<T>) bean.getBeanClass();
}
Class<? extends T> proxyClass = (Class<? extends T>) cachedProxyClasses.get(bean);
if (proxyClass == null)
{
proxyClass = createProxyClass(bean, classLoader, classToProxy);
}
return createProxyInstance(proxyClass, getInstanceProvider(classLoader, bean));
}
public Provider getInstanceProvider(ClassLoader classLoader, Bean<?> bean)
{
String scopeClassName = bean.getScope().getName();
Class<? extends Provider> instanceProviderClass = null;
String proxyMappingConfigKey = OpenWebBeansConfiguration.PROXY_MAPPING_PREFIX + scopeClassName;
String className = webBeansContext.getOpenWebBeansConfiguration().getProperty(proxyMappingConfigKey);
if (className == null || NormalScopedBeanInterceptorHandler.class.getName().equals(className))
{
return new NormalScopedBeanInterceptorHandler(webBeansContext.getBeanManagerImpl(), bean);
}
try
{
instanceProviderClass = (Class<? extends Provider>) Class.forName(className, true, classLoader);
Constructor<?> ct = instanceProviderClass.getConstructor(BeanManager.class, Bean.class);
return (Provider) ct.newInstance(webBeansContext.getBeanManagerImpl(), bean);
}
catch (NoSuchMethodException nsme)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler " + className +" has wrong constructor" , nsme);
}
catch (ClassNotFoundException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler " + className +" cannot be found", e);
}
catch (InvocationTargetException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler " + className +" creation exception", e);
}
catch (InstantiationException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler " + className +" creation exception", e);
}
catch (IllegalAccessException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler " + className +" creation exception", e);
}
}
public synchronized <T> Class<T> createProxyClass(Bean<T> bean, ClassLoader classLoader, Class<T> classToProxy)
{
Class<T> proxyClass = (Class<T>) cachedProxyClasses.get(bean);
if (proxyClass == null)
{
proxyClass = createProxyClass(classLoader, classToProxy);
cachedProxyClasses.putIfAbsent(bean, proxyClass);
}
return proxyClass;
}
@Override
protected void createSerialisation(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName)
{
String[] exceptionTypeNames = {Type.getType(ObjectStreamException.class).getInternalName()};
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "writeReplace", "()Ljava/lang/Object;", null, exceptionTypeNames);
// fill method body
mv.visitCode();
// load the contextual instance Provider
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassFileName, FIELD_INSTANCE_PROVIDER, Type.getDescriptor(Provider.class));
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
/**
* @param classLoader to use for creating the class in
* @param classToProxy the class for which a subclass will get generated
* @param <T>
* @return the proxy class
*/
public <T> Class<T> createProxyClass(ClassLoader classLoader, Class<T> classToProxy)
throws ProxyGenerationException
{
String proxyClassName = getUnusedProxyClassName(
classLoader,
(classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) + "$$OwbNormalScopeProxy");
Method[] nonInterceptedMethods;
Method[] interceptedMethods = null;
if (classToProxy.isInterface())
{
nonInterceptedMethods = classToProxy.getMethods();
}
else
{
List<Method> methods = new ArrayList<>();
List<Method> protectedMethods = new ArrayList<>();
for (Method method : ClassUtil.getNonPrivateMethods(classToProxy, true))
{
if (unproxyableMethod(method))
{
continue;
}
if (Modifier.isProtected(method.getModifiers()))
{
protectedMethods.add(method);
}
else
{
methods.add(method);
}
}
nonInterceptedMethods = methods.toArray(new Method[methods.size()]);
interceptedMethods = protectedMethods.toArray(new Method[protectedMethods.size()]);
}
Class<T> clazz = createProxyClass(classLoader, proxyClassName, classToProxy, interceptedMethods, nonInterceptedMethods);
if (interceptedMethods != null && interceptedMethods.length > 0)
{
try
{
Field protectedMethodsField = clazz.getDeclaredField(FIELD_PROTECTED_METHODS);
protectedMethodsField.setAccessible(true);
protectedMethodsField.set(null, interceptedMethods);
}
catch (Exception e)
{
throw new ProxyGenerationException(e);
}
}
return clazz;
}
public <T> T createProxyInstance(Class<T> proxyClass, Provider provider)
throws ProxyGenerationException
{
try
{
T proxy = unsafe.unsafeNewInstance(proxyClass);
Field delegateField = proxy.getClass().getDeclaredField(FIELD_INSTANCE_PROVIDER);
delegateField.setAccessible(true);
delegateField.set(proxy, provider);
return proxy;
}
catch (Exception e)
{
throw new ProxyGenerationException(e);
}
}
@Override
protected void createConstructor(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName, Constructor<?> ignored)
throws ProxyGenerationException
{
try
{
Constructor superDefaultCt;
String parentClassFileName;
if (classToProxy.isInterface())
{
parentClassFileName = Type.getInternalName(Object.class);
superDefaultCt = Object.class.getConstructor(null);
}
else
{
parentClassFileName = classFileName;
superDefaultCt = classToProxy.getDeclaredConstructor(null);
}
String descriptor = Type.getConstructorDescriptor(superDefaultCt);
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, parentClassFileName, "<init>", descriptor, false);
// the instance provider field
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassFileName, FIELD_INSTANCE_PROVIDER, Type.getDescriptor(Provider.class));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
catch (NoSuchMethodException e)
{
throw new ProxyGenerationException(e);
}
}
@Override
protected void createInstanceVariables(ClassWriter cw, Class<?> classToProxy, String classFileName)
{
// variable #1, the Provider<?> for the Contextual Instance
cw.visitField(Opcodes.ACC_PRIVATE,
FIELD_INSTANCE_PROVIDER, Type.getDescriptor(Provider.class), null, null).visitEnd();
// variable #2, the Method[] for all protected methods
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
FIELD_PROTECTED_METHODS, Type.getDescriptor(Method[].class), null, null).visitEnd();
}
/**
* In the NormalScope proxying case this is used for all the protected methods
* as they need to get invoked via reflection.
*/
@Override
protected void delegateInterceptedMethods(ClassLoader classLoader, ClassWriter cw, String proxyClassFileName,
Class<?> classToProxy, Method[] interceptedMethods)
throws ProxyGenerationException
{
if (interceptedMethods == null)
{
return;
}
for (int i = 0; i < interceptedMethods.length; i++)
{
Method proxiedMethod = interceptedMethods[i];
generateDelegationMethod(cw, proxiedMethod, i, classToProxy, proxyClassFileName);
}
}
@Override
protected void delegateNonInterceptedMethods(ClassLoader classLoader, ClassWriter cw, String proxyClassFileName,
Class<?> classToProxy, Method[] noninterceptedMethods)
throws ProxyGenerationException
{
for (Method delegatedMethod : noninterceptedMethods)
{
if (isIgnoredMethod(delegatedMethod))
{
return;
}
String methodDescriptor = Type.getMethodDescriptor(delegatedMethod);
//X TODO handle generic exception types?
Class[] exceptionTypes = delegatedMethod.getExceptionTypes();
String[] exceptionTypeNames = new String[exceptionTypes.length];
for (int i = 0; i < exceptionTypes.length; i++)
{
exceptionTypeNames[i] = Type.getType(exceptionTypes[i]).getInternalName();
}
int targetModifiers = delegatedMethod.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC | MODIFIER_VARARGS);
MethodVisitor mv = cw.visitMethod(targetModifiers, delegatedMethod.getName(), methodDescriptor, null, exceptionTypeNames);
// fill method body
mv.visitCode();
// load the contextual instance Provider
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassFileName, FIELD_INSTANCE_PROVIDER, Type.getDescriptor(Provider.class));
// invoke the get() method on the Provider
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Provider.class), "get", "()Ljava/lang/Object;", true);
// and convert the Object to the target class type
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(classToProxy));
// now calculate the parameters
int offset = 1;
for (Class<?> aClass : delegatedMethod.getParameterTypes())
{
Type type = Type.getType(aClass);
mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
offset += type.getSize();
}
// and finally invoke the target method on the provided Contextual Instance
Type declaringClass = Type.getType(delegatedMethod.getDeclaringClass());
boolean interfaceMethod = Modifier.isInterface(delegatedMethod.getDeclaringClass().getModifiers());
mv.visitMethodInsn(interfaceMethod ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
declaringClass.getInternalName(), delegatedMethod.getName(), methodDescriptor, interfaceMethod);
generateReturn(mv, delegatedMethod);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
}
private boolean isIgnoredMethod(final Method delegatedMethod)
{
return "writeReplace".equals(delegatedMethod.getName());
}
private void generateDelegationMethod(ClassWriter cw, Method method, int methodIndex, Class<?> classToProxy, String proxyClassFileName)
{
if (isIgnoredMethod(method))
{
return;
}
Class<?> returnType = method.getReturnType();
Class<?>[] parameterTypes = method.getParameterTypes();
int modifiers = method.getModifiers();
// push the method definition
int modifier = modifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_VARARGS);
MethodVisitor mv = cw.visitMethod(modifier, method.getName(), Type.getMethodDescriptor(method), null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
// add the Method from the static array as first parameter
mv.visitFieldInsn(Opcodes.GETSTATIC, proxyClassFileName, FIELD_PROTECTED_METHODS, Type.getDescriptor(Method[].class));
// push the methodIndex of the current method
mv.visitIntInsn(Opcodes.BIPUSH, methodIndex);
// and now load the Method from the array
mv.visitInsn(Opcodes.AALOAD);
// now invoke the get() on the contextual instance Provider<T>
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassFileName, FIELD_INSTANCE_PROVIDER, Type.getDescriptor(Provider.class));
// invoke the get() method on the Provider
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Provider.class), "get", "()Ljava/lang/Object;", true);
// prepare the parameter array as Object[] and store it on the stack
pushMethodParameterArray(mv, parameterTypes);
// this invokes NormalScopeProxyFactory.delegateProtectedMethod
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(NormalScopeProxyFactory.class), "delegateProtectedMethod",
"(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
// cast the result
mv.visitTypeInsn(Opcodes.CHECKCAST, getCastType(returnType));
if (returnType.isPrimitive() && (!Void.TYPE.equals(returnType)))
{
// get the primitive value
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getWrapperType(returnType), getPrimitiveMethod(returnType),
"()" + Type.getDescriptor(returnType), false);
}
mv.visitInsn(getReturnInsn(returnType));
// finish this method
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* This method get invoked via generated ASM code.
* It delegates to the underlying protected Method so we don't need to do
* all the reflection stuff in our generated bytecode.
* This is needed as we cannot invoke instanceProvider.get().targetMethod() directly
* if targetMethod is protected. Please see Java LangSpec 6.6.2 about the complex
* rules for calling 'protected' methods.
*
* @see #generateDelegationMethod(org.apache.xbean.asm7.ClassWriter, java.lang.reflect.Method, int, Class, String)
*/
@SuppressWarnings("unused")
public static Object delegateProtectedMethod(Method method, Object instance, Object[] params)
{
try
{
if (!method.isAccessible())
{
method.setAccessible(true);
}
return method.invoke(instance, params);
}
catch (InvocationTargetException ite)
{
throw ExceptionUtil.throwAsRuntimeException(ite.getCause());
}
catch (Exception e)
{
throw ExceptionUtil.throwAsRuntimeException(e);
}
}
}