package org.jboss.resteasy.cdi;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Method;
import javax.decorator.Decorator;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import javax.enterprise.inject.spi.ProcessSessionBean;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.enterprise.util.AnnotationLiteral;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.cdi.i18n.LogMessages;
import org.jboss.resteasy.cdi.i18n.Messages;
import org.jboss.resteasy.util.GetRestful;

This Extension handles default scopes for discovered JAX-RS components. It also observes ProcessInjectionTarget event and wraps InjectionTargets representing JAX-RS components within JaxrsInjectionTarget. Furthermore, it builds the sessionBeanInterface map which maps Session Bean classes to a local interface. This map is used in CdiInjectorFactory during lookup of Sesion Bean JAX-RS components.
Author:Jozef Hartinger
/** * This Extension handles default scopes for discovered JAX-RS components. It * also observes ProcessInjectionTarget event and wraps InjectionTargets * representing JAX-RS components within JaxrsInjectionTarget. Furthermore, it * builds the sessionBeanInterface map which maps Session Bean classes to a * local interface. This map is used in CdiInjectorFactory during lookup of * Sesion Bean JAX-RS components. * * @author Jozef Hartinger * */
public class ResteasyCdiExtension implements Extension { private static boolean active; private BeanManager beanManager; private static final String JAVAX_EJB_STATELESS = "javax.ejb.Stateless"; private static final String JAVAX_EJB_SINGLETON = "javax.ejb.Singleton"; private final List<Class> providers = new ArrayList<Class>(); private final List<Class> resources = new ArrayList<Class>(); // Scope literals public static final Annotation requestScopedLiteral = new AnnotationLiteral<RequestScoped>() { private static final long serialVersionUID = 3381824686081435817L; }; public static final Annotation applicationScopedLiteral = new AnnotationLiteral<ApplicationScoped>() { private static final long serialVersionUID = -8211157243671012820L; }; public static boolean isCDIActive() { return active; } private Map<Class<?>, Type> sessionBeanInterface = new HashMap<Class<?>, Type>();
Obtain BeanManager reference for future use.
Params:
  • event – event
  • beanManager – bean manager
/** * Obtain BeanManager reference for future use. * * @param event event * @param beanManager bean manager */
public void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery event, BeanManager beanManager) { this.beanManager = beanManager; active = true; }
Set a default scope for each CDI bean which is a JAX-RS Resource.
Params:
  • event – event
  • beanManager – bean manager
Type parameters:
  • <T> – type
/** * Set a default scope for each CDI bean which is a JAX-RS Resource. * * @param <T> type * @param event event * @param beanManager bean manager */
public <T> void observeResources(@WithAnnotations({Path.class}) @Observes ProcessAnnotatedType<T> event, BeanManager beanManager) { setBeanManager(beanManager); AnnotatedType<T> annotatedType = event.getAnnotatedType(); if(!annotatedType.getJavaClass().isInterface() && !isSessionBean(annotatedType) // This check is redundant for CDI 1.1 containers but required for CDI 1.0 && GetRestful.isRootResource(annotatedType.getJavaClass()) && !annotatedType.isAnnotationPresent(Decorator.class)) { LogMessages.LOGGER.debug(Messages.MESSAGES.discoveredCDIBeanJaxRsResource(annotatedType.getJavaClass().getCanonicalName())); event.setAnnotatedType(wrapAnnotatedType(annotatedType, requestScopedLiteral)); this.resources.add(annotatedType.getJavaClass()); } }
Set a default scope for each CDI bean which is a JAX-RS Provider.
Params:
  • event – event
  • beanManager – bean manager
Type parameters:
  • <T> – type
/** * Set a default scope for each CDI bean which is a JAX-RS Provider. * * @param <T> type * @param event event * @param beanManager bean manager */
public <T> void observeProviders(@WithAnnotations({Provider.class}) @Observes ProcessAnnotatedType<T> event, BeanManager beanManager) { setBeanManager(beanManager); AnnotatedType<T> annotatedType = event.getAnnotatedType(); if(!annotatedType.getJavaClass().isInterface() && !isSessionBean(annotatedType) // This check is redundant for CDI 1.1 containers but required for CDI 1.0 && annotatedType.isAnnotationPresent(Provider.class) && !isUnproxyableClass(annotatedType.getJavaClass())) { LogMessages.LOGGER.debug(Messages.MESSAGES.discoveredCDIBeanJaxRsProvider(annotatedType.getJavaClass().getCanonicalName())); event.setAnnotatedType(wrapAnnotatedType(annotatedType, applicationScopedLiteral)); this.providers.add(annotatedType.getJavaClass()); } }
Set a default scope for each CDI bean which is a JAX-RS Application subclass.
Params:
  • event – event
  • beanManager – bean manager
Type parameters:
  • <T> – type
/** * Set a default scope for each CDI bean which is a JAX-RS Application subclass. * * @param <T> type * @param event event * @param beanManager bean manager */
public <T extends Application> void observeApplications(@Observes ProcessAnnotatedType<T> event, BeanManager beanManager) { setBeanManager(beanManager); AnnotatedType<T> annotatedType = event.getAnnotatedType(); if(!isSessionBean(annotatedType)) { LogMessages.LOGGER.debug(Messages.MESSAGES.discoveredCDIBeanApplication(annotatedType.getJavaClass().getCanonicalName())); event.setAnnotatedType(wrapAnnotatedType(annotatedType, applicationScopedLiteral)); } } protected <T> AnnotatedType<T> wrapAnnotatedType(AnnotatedType<T> type, Annotation scope) { if (Utils.isScopeDefined(type, beanManager)) { LogMessages.LOGGER.debug(Messages.MESSAGES.beanHasScopeDefined(type.getJavaClass())); return type; // leave it as it is } else { LogMessages.LOGGER.debug(Messages.MESSAGES.beanDoesNotHaveScopeDefined(type.getJavaClass(), scope)); return new JaxrsAnnotatedType<T>(type, scope); } }
Wrap InjectionTarget of JAX-RS components within JaxrsInjectionTarget which takes care of JAX-RS property injection.
Params:
  • event – event
Type parameters:
  • <T> – type
/** * Wrap InjectionTarget of JAX-RS components within JaxrsInjectionTarget * which takes care of JAX-RS property injection. * * @param <T> type * @param event event */
public <T> void observeInjectionTarget(@Observes ProcessInjectionTarget<T> event) { if (event.getAnnotatedType() == null) { // check for resin's bug http://bugs.caucho.com/view.php?id=3967 LogMessages.LOGGER.warn(Messages.MESSAGES.annotatedTypeNull()); return; } if (Utils.isJaxrsComponent(event.getAnnotatedType().getJavaClass())) { event.setInjectionTarget(wrapInjectionTarget(event)); } } protected <T> InjectionTarget<T> wrapInjectionTarget(ProcessInjectionTarget<T> event) { return new JaxrsInjectionTarget<T>(event.getInjectionTarget(), event.getAnnotatedType().getJavaClass()); }
Observes ProcessSessionBean events and creates a (Bean class -> Local interface) map for Session beans with local interfaces. This map is necessary since RESTEasy identifies a bean class as JAX-RS components while CDI requires a local interface to be used for lookup.
Params:
  • event – event
Type parameters:
  • <T> – type
/** * Observes ProcessSessionBean events and creates a (Bean class {@literal ->} Local * interface) map for Session beans with local interfaces. This map is * necessary since RESTEasy identifies a bean class as JAX-RS components * while CDI requires a local interface to be used for lookup. * * @param <T> type * @param event event * */
public <T> void observeSessionBeans(@Observes ProcessSessionBean<T> event) { Bean<Object> sessionBean = event.getBean(); if (Utils.isJaxrsComponent(sessionBean.getBeanClass())) { addSessionBeanInterface(sessionBean); } } private void addSessionBeanInterface(Bean<?> bean) { for (Type type : bean.getTypes()) { if ((type instanceof Class<?>) && ((Class<?>) type).isInterface()) { Class<?> clazz = (Class<?>) type; if (Utils.isJaxrsAnnotatedClass(clazz)) { sessionBeanInterface.put(bean.getBeanClass(), type); LogMessages.LOGGER.debug(Messages.MESSAGES.typeWillBeUsedForLookup(type, bean.getBeanClass())); return; } } } LogMessages.LOGGER.debug(Messages.MESSAGES.noLookupInterface(bean.getBeanClass())); } public Map<Class<?>, Type> getSessionBeanInterface() { return sessionBeanInterface; } private boolean isSessionBean(AnnotatedType<?> annotatedType) { for (Annotation annotation : annotatedType.getAnnotations()) { Class<?> annotationType = annotation.annotationType(); if (annotationType.getName().equals(JAVAX_EJB_STATELESS) || annotationType.getName().equals(JAVAX_EJB_SINGLETON)) { LogMessages.LOGGER.debug(Messages.MESSAGES.beanIsSLSBOrSingleton(annotatedType.getJavaClass())); return true; // Do not modify scopes of SLSBs and Singletons } } return false; } private void setBeanManager(BeanManager beanManager) { if (this.beanManager == null) { // this may happen if Solder Config receives BBD first this.beanManager = beanManager; } } public List<Class> getProviders() { return providers; } public List<Class> getResources() { return resources; }
Check for select case of unproxyable bean type. (see CDI 2.0 spec, section 3.11)
Params:
  • clazz –
Returns:
/** * Check for select case of unproxyable bean type. * (see CDI 2.0 spec, section 3.11) * @param clazz * @return */
private boolean isUnproxyableClass(Class clazz) { // Unproxyable bean type: classes which are declared final, // or expose final methods, // or have no non-private no-args constructor return isFinal(clazz) || hasNonPrivateNonStaticFinalMethod(clazz) || hasNoNonPrivateNoArgsConstructor(clazz); } private boolean isFinal(Class clazz) { return Modifier.isFinal(clazz.getModifiers()); } // Adapted from weld-core-impl:3.0.5.Final's Reflections.getNonPrivateNonStaticFinalMethod() private boolean hasNonPrivateNonStaticFinalMethod(Class<?> type) { for (Class<?> clazz = type; clazz != null && clazz != Object.class; clazz = clazz.getSuperclass()) { for (Method method : clazz.getDeclaredMethods()) { if (isFinal(method) && !isPrivate(method) && !isStatic(method)) { return true; } } } return false; } private boolean hasNoNonPrivateNoArgsConstructor(Class<?> clazz) { Constructor<?> constructor; try { constructor = clazz.getConstructor(); } catch (NoSuchMethodException exception) { return true; } // Note: this probably can only be private if the provider also has // a non-private @Context constructor, which is unlikely but possible. return isPrivate(constructor); } private boolean isFinal(Member member) { return Modifier.isFinal(member.getModifiers()); } private boolean isPrivate(Member member) { return Modifier.isPrivate(member.getModifiers()); } private boolean isStatic(Member member) { return Modifier.isStatic(member.getModifiers()); } }