/*
* 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 java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.Decorator;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyFactory.ClassLoaderProvider;
import javassist.util.proxy.ProxyObject;
import org.apache.webbeans.annotation.WebBeansAnnotation;
import org.apache.webbeans.component.InjectionTargetBean;
import org.apache.webbeans.component.OwbBean;
import org.apache.webbeans.component.ResourceBean;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.context.creational.CreationalContextImpl;
import org.apache.webbeans.decorator.WebBeansDecorator;
import org.apache.webbeans.exception.WebBeansConfigurationException;
import org.apache.webbeans.intercept.DependentScopedBeanInterceptorHandler;
import org.apache.webbeans.intercept.InterceptorData;
import org.apache.webbeans.intercept.InterceptorHandler;
import org.apache.webbeans.intercept.NormalScopedBeanInterceptorHandler;
import org.apache.webbeans.intercept.webbeans.WebBeansInterceptor;
import org.apache.webbeans.util.ClassUtil;
import org.apache.webbeans.util.OpenWebBeansClassLoaderProvider;
import org.apache.webbeans.util.SecurityUtil;
import org.apache.webbeans.util.WebBeansUtil;
public final class JavassistProxyFactory
{
public JavassistProxyFactory()
{
}
private ConcurrentMap<OwbBean<?>, Class<?>> buildInBeanProxyClasses = new ConcurrentHashMap<OwbBean<?>, Class<?>>();
private ConcurrentMap<OwbBean<?>, Class<?>> normalScopedBeanProxyClasses = new ConcurrentHashMap<OwbBean<?>, Class<?>>();
private ConcurrentMap<OwbBean<?>, Class<?>> dependentScopedBeanProxyClasses = new ConcurrentHashMap<OwbBean<?>, Class<?>>();
private ConcurrentMap<OwbBean<?>, Class<?>> interceptorProxyClasses = new ConcurrentHashMap<OwbBean<?>, Class<?>>();
private ConcurrentMap<ResourceBean<?, ?>, Class<?>> resourceBeanProxyClasses = new ConcurrentHashMap<ResourceBean<?,?>, Class<?>>();
// second level map is indexed on local interface
private ConcurrentMap<OwbBean<?>, ConcurrentMap<Class<?>, Class<?>>> ejbProxyClasses = new ConcurrentHashMap<OwbBean<?>, ConcurrentMap<Class<?>, Class<?>>>();
/**
* This map contains all configured special Scope->InterceptorHandler mappings.
* If no mapping is configured, a {@link org.apache.webbeans.intercept.NormalScopedBeanInterceptorHandler} will get created.
*/
private Map<String, Class<? extends InterceptorHandler>> interceptorHandlerClasses =
new ConcurrentHashMap<String, Class<? extends InterceptorHandler>>();
public Map<OwbBean<?>, Class<?>> getInterceptorProxyClasses()
{
return interceptorProxyClasses;
}
@Deprecated
public static JavassistProxyFactory getInstance()
{
return WebBeansContext.getInstance().getJavassistProxyFactory();
}
public void clear()
{
normalScopedBeanProxyClasses.clear();
dependentScopedBeanProxyClasses.clear();
interceptorProxyClasses.clear();
ejbProxyClasses.clear();
}
/**
* Provides the proxy for the given bean and interface, if defined
*
* @param bean the contextual representing the EJB
* @param iface the injected business local interface
* @return the proxy Class if one has been defined, else null
*/
public Class<?> getEjbBeanProxyClass(OwbBean<?> bean, Class<?> iface)
{
Class<?> proxyClass = null;
ConcurrentMap<Class<?>, Class<?>> typeToProxyClassMap = ejbProxyClasses.get(bean);
if (typeToProxyClassMap != null)
{
proxyClass = typeToProxyClassMap.get(iface);
}
return proxyClass;
}
public Class<?> getResourceBeanProxyClass(ResourceBean<?, ?> resourceBean)
{
Class<?> proxyClass = null;
try
{
proxyClass = this.resourceBeanProxyClasses.get(resourceBean);
if (proxyClass == null)
{
ProxyFactory fact = createProxyFactory(resourceBean);
proxyClass = getProxyClass(fact);
Class<?> oldClazz = this.resourceBeanProxyClasses.putIfAbsent(resourceBean, proxyClass);
if (oldClazz != null)
{
return oldClazz;
}
}
}
catch (Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return proxyClass;
}
/**
* Defines the proxy for the given bean and iface using callers factory. Due
* to races with the concurrentmap, this might sometimes create additional
* javassist-defined classes that are not used by the caller and not stored
* in the map. Synchronizing the entire method, and getEjbBeanProxyClass, on
* ejbProxyClasses is an alternative.
*
* @param bean the contextual representing the EJB
* @param iface the injected business local interface
* @param factory
* @return
*/
public Class<?> defineEjbBeanProxyClass(OwbBean<?> bean, Class<?> iface, ProxyFactory factory)
{
Class<?> proxyClass = null;
ConcurrentMap<Class<?>, Class<?>> typeToProxyClassMap = ejbProxyClasses.get(bean);
if (typeToProxyClassMap == null)
{
typeToProxyClassMap = new ConcurrentHashMap<Class<?>, Class<?>>();
ConcurrentMap<Class<?>, Class<?>> existingMap = ejbProxyClasses.putIfAbsent(bean, typeToProxyClassMap);
// use the map that beat us, because our new one definitely had no classes in it.
typeToProxyClassMap = (existingMap != null) ? existingMap : typeToProxyClassMap;
}
proxyClass = typeToProxyClassMap.get(iface);
if (proxyClass == null)
{
proxyClass = SecurityUtil.doPrivilegedCreateClass(factory);
typeToProxyClassMap.putIfAbsent(iface, proxyClass);
// don't care if we were beaten in updating the iface->proxyclass map
}
return proxyClass;
}
public Class<?> createAbstractDecoratorProxyClass(OwbBean<?> bean)
{
//Will only get called once while defining the bean, so no need to cache
Class<?> clazz = null;
try
{
ProxyFactory fact = createProxyFactory(bean);
clazz = SecurityUtil.doPrivilegedCreateClass(fact);
}
catch(Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return clazz;
}
public Object createNormalScopedBeanProxy(OwbBean<?> bean, CreationalContext<?> creationalContext)
{
Object result = null;
try
{
Class<?> proxyClass = normalScopedBeanProxyClasses.get(bean);
if (proxyClass == null)
{
ProxyFactory fact = createProxyFactory(bean);
proxyClass = getProxyClass(fact);
normalScopedBeanProxyClasses.putIfAbsent(bean, proxyClass);
}
result = proxyClass.newInstance();
if (!(bean instanceof WebBeansDecorator<?>) && !(bean instanceof WebBeansInterceptor<?>))
{
setInterceptorMethodHandler((ProxyObject) result, bean, creationalContext);
}
}
catch (Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return result;
}
/**
* This helper method will set the correct InterceptorHandler into our proxy.
* @param proxyObject
* @param bean
* @param creationalContext
*/
private void setInterceptorMethodHandler(ProxyObject proxyObject, OwbBean<?> bean, CreationalContext<?> creationalContext)
{
InterceptorHandler interceptorHandler = null;
String scopeClassName = bean.getScope().getName();
Class<? extends InterceptorHandler> interceptorHandlerClass = null;
if (!interceptorHandlerClasses.containsKey(scopeClassName))
{
String proxyMappingConfigKey = OpenWebBeansConfiguration.PROXY_MAPPING_PREFIX + scopeClassName;
String className = bean.getWebBeansContext().getOpenWebBeansConfiguration().getProperty(proxyMappingConfigKey);
if (className != null)
{
try
{
interceptorHandlerClass = (Class<? extends InterceptorHandler>) Class.forName(className, true, WebBeansUtil.getCurrentClassLoader());
}
catch (ClassNotFoundException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler "
+ className
+" cannot be found",
e);
}
}
else
{
// we need to explicitely store a class because ConcurrentHashMap will throw a NPE if value == null
interceptorHandlerClass = NormalScopedBeanInterceptorHandler.class;
}
interceptorHandlerClasses.put(scopeClassName, interceptorHandlerClass);
}
else
{
interceptorHandlerClass = interceptorHandlerClasses.get(scopeClassName);
}
if (interceptorHandlerClass.equals(NormalScopedBeanInterceptorHandler.class))
{
// this is faster that way...
interceptorHandler = new NormalScopedBeanInterceptorHandler(bean, creationalContext);
}
else
{
try
{
Constructor ct = interceptorHandlerClass.getConstructor(OwbBean.class, CreationalContext.class);
interceptorHandler = (InterceptorHandler) ct.newInstance(bean, creationalContext);
}
catch (NoSuchMethodException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler "
+ interceptorHandlerClass.getName()
+" has the wrong contructor",
e);
}
catch (InvocationTargetException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler "
+ interceptorHandlerClass.getName()
+" has the wrong contructor",
e);
}
catch (InstantiationException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler "
+ interceptorHandlerClass.getName()
+" has the wrong contructor",
e);
}
catch (IllegalAccessException e)
{
throw new WebBeansConfigurationException("Configured InterceptorHandler "
+ interceptorHandlerClass.getName()
+" has the wrong contructor",
e);
}
}
proxyObject.setHandler(interceptorHandler);
}
public Object createBuildInBeanProxy(OwbBean<?> bean)
{
Object result = null;
try
{
Class<?> proxyClass = buildInBeanProxyClasses.get(bean);
if (proxyClass == null)
{
ProxyFactory fact = createProxyFactory(bean);
proxyClass = getProxyClass(fact);
buildInBeanProxyClasses.putIfAbsent(bean, proxyClass);
}
result = proxyClass.newInstance();
}
catch (Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return result;
}
public Object createDependentScopedBeanProxy(OwbBean<?> bean, Object actualInstance, CreationalContext<?> creastionalContext)
{
Object result = null;
List<InterceptorData> interceptors = null;
List<Decorator<?>> decorators = null;
InjectionTargetBean<?> injectionTargetBean = null;
if(bean instanceof InjectionTargetBean<?>)
{
injectionTargetBean = (InjectionTargetBean<?>)bean;
interceptors = injectionTargetBean.getInterceptorStack();
decorators = injectionTargetBean.getDecoratorStack();
}
if(interceptors == null && decorators == null)
{
return actualInstance;
}
boolean notInInterceptorClassAndLifecycle = false;
if(interceptors != null)
{
Iterator<InterceptorData> its = interceptors.iterator();
while(its.hasNext())
{
InterceptorData id = its.next();
if(!id.isDefinedInInterceptorClass() && id.isLifecycleInterceptor())
{
continue;
}
notInInterceptorClassAndLifecycle = true;
break;
}
}
//No need to return proxy
if(!notInInterceptorClassAndLifecycle && decorators.isEmpty())
{
//Adding this dependent instance into creational context
//This occurs when no owner of this dependent instance
if(creastionalContext instanceof CreationalContextImpl)
{
//If this creational context is owned by itself, add it
//For example, getting it directly BeanManager#getReference(bean,creational context)
CreationalContextImpl<?> ccImpl = (CreationalContextImpl<?>)creastionalContext;
//Non contextual instance --> Bean --> Null
//See OWBInjector
if(ccImpl.getBean() != null)
{
if(ccImpl.getBean().equals(bean))
{
//Owner of the dependent is itself
ccImpl.addDependent(actualInstance, bean, actualInstance);
}
}
}
return actualInstance;
}
try
{
Class<?> proxyClass = dependentScopedBeanProxyClasses.get(bean);
if (proxyClass == null)
{
ProxyFactory fact = createProxyFactory(bean);
proxyClass = getProxyClass(fact);
dependentScopedBeanProxyClasses.putIfAbsent(bean, proxyClass);
}
result = proxyClass.newInstance();
if (!(bean instanceof WebBeansDecorator<?>) && !(bean instanceof WebBeansInterceptor<?>))
{
((ProxyObject)result).setHandler(new DependentScopedBeanInterceptorHandler(bean, actualInstance, creastionalContext));
}
}
catch (Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return result;
}
public Class<?> getProxyClass(ProxyFactory factory)
{
ClassLoaderProvider classLoaderProvider = ProxyFactory.classLoaderProvider;
Class<?> clazz = null;
try
{
clazz = SecurityUtil.doPrivilegedCreateClass(factory);
}
catch(RuntimeException e)
{
if(classLoaderProvider instanceof OpenWebBeansClassLoaderProvider)
{
((OpenWebBeansClassLoaderProvider)classLoaderProvider).useCurrentClassLoader();
}
//try again with updated class loader
clazz = SecurityUtil.doPrivilegedCreateClass(factory);
}
finally
{
if(classLoaderProvider instanceof OpenWebBeansClassLoaderProvider)
{
((OpenWebBeansClassLoaderProvider)classLoaderProvider).reset();
}
}
return clazz;
}
public ProxyFactory createProxyFactory(Bean<?> bean) throws Exception
{
Set<Type> types = bean.getTypes();
Set<Class<?>> interfaceList = new HashSet<Class<?>>();
Class<?> superClass = null;
for (Type generic : types)
{
Class<?> type = ClassUtil.getClazz(generic);
if (type.isInterface())
{
interfaceList.add(type);
}
else if ((superClass == null) || (superClass.isAssignableFrom(type) && type != Object.class))
{
superClass = type;
}
}
if (!interfaceList.contains(Serializable.class))
{
interfaceList.add(Serializable.class);
}
Class<?>[] interfaceArray = new Class<?>[interfaceList.size()];
interfaceArray = interfaceList.toArray(interfaceArray);
ProxyFactory fact = new ProxyFactory();
fact.setInterfaces(interfaceArray);
fact.setSuperclass(superClass);
return fact;
}
public static WebBeansAnnotation createNewAnnotationProxy(Class<? extends Annotation> annotationType)
{
WebBeansAnnotation result = null;
try
{
ProxyFactory pf = new ProxyFactory();
pf.setInterfaces(new Class<?>[] { annotationType, Annotation.class });
pf.setSuperclass(WebBeansAnnotation.class);
result = (WebBeansAnnotation) pf.create(new Class[] { Class.class }, new Object[] { annotationType });
((ProxyObject)result).setHandler(new WebBeansAnnotation(annotationType));
}
catch (Exception e)
{
WebBeansUtil.throwRuntimeExceptions(e);
}
return result;
}
/**
* @param o the object to check
* @return <code>true</code> if the given object is a proxy
*/
public static boolean isProxyInstance(Object o)
{
return o instanceof ProxyObject;
}
}