Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/AnnotatedTypeWrapper.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/AnnotatedTypeWrapper.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/AnnotatedTypeWrapper.java (revision 0) @@ -0,0 +1,80 @@ +package org.jboss.resteasy.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.enterprise.inject.spi.AnnotatedConstructor; +import javax.enterprise.inject.spi.AnnotatedField; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedType; + +/** + * This wrapper allows additional metadata to be added during bootstrap. + * + * @author Jozef Hartinger + * + * @see org.jboss.resteasy.cdi.stereotype.ApplicationScopedJaxrsComponent + * @see org.jboss.resteasy.cdi.stereotype.RequestScopedJaxrsComponent + * + */ +public class AnnotatedTypeWrapper implements AnnotatedType +{ + + private AnnotatedType delegate; + private Set annotations = new HashSet(); + + public AnnotatedTypeWrapper(AnnotatedType delegate, Annotation scope) + { + this.delegate = delegate; + this.annotations.addAll(delegate.getAnnotations()); + this.annotations.add(scope); + } + + public Set> getConstructors() + { + return delegate.getConstructors(); + } + + public Set> getFields() + { + return delegate.getFields(); + } + + public Class getJavaClass() + { + return delegate.getJavaClass(); + } + + public Set> getMethods() + { + return delegate.getMethods(); + } + + public T getAnnotation(Class annotationType) + { + return delegate.getAnnotation(annotationType); + } + + public Set getAnnotations() + { + return Collections.unmodifiableSet(annotations); + } + + public Type getBaseType() + { + return delegate.getBaseType(); + } + + public Set getTypeClosure() + { + return delegate.getTypeClosure(); + } + + public boolean isAnnotationPresent(Class annotationType) + { + return delegate.isAnnotationPresent(annotationType); + } +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiConstructorInjector.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiConstructorInjector.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiConstructorInjector.java (revision 0) @@ -0,0 +1,82 @@ +package org.jboss.resteasy.cdi; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.ws.rs.WebApplicationException; + +import org.jboss.resteasy.spi.ApplicationException; +import org.jboss.resteasy.spi.ConstructorInjector; +import org.jboss.resteasy.spi.Failure; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This ConstructorInjector implementation uses CDI's BeanManager to obtain + * a contextual instance of a bean. + * + * @author Jozef Hartinger + * + */ +public class CdiConstructorInjector implements ConstructorInjector +{ + + private BeanManager manager; + private Class clazz; + private static final Logger log = LoggerFactory.getLogger(CdiConstructorInjector.class); + + public CdiConstructorInjector(Class clazz, BeanManager manager) + { + this.clazz = clazz; + this.manager = manager; + } + + public Object construct() + { + Set> beans = manager.getBeans(clazz); + + if (beans.size() > 1) + { + Set> modifiableBeans = new HashSet>(); + modifiableBeans.addAll(beans); + // Ambiguous dependency may occur if a resource has subclasses + // Therefore we remove those beans + for (Iterator> iterator = modifiableBeans.iterator(); iterator.hasNext();) + { + if (!iterator.next().getBeanClass().equals(clazz)) + { + // remove Beans that have clazz in their type closure but not as a base class + iterator.remove(); + } + } + beans = modifiableBeans; + } + + log.debug("Beans found for class {} : {}", clazz.getCanonicalName(), beans); + + Bean bean = manager.resolve(beans); + CreationalContext context = manager.createCreationalContext(bean); + return manager.getReference(bean, clazz, context); + } + + public Object construct(HttpRequest request, HttpResponse response) throws Failure, WebApplicationException, ApplicationException + { + return construct(); + } + + public Object[] injectableArguments() + { + return new Object[0]; + } + + public Object[] injectableArguments(HttpRequest request, HttpResponse response) throws Failure + { + return injectableArguments(); + } +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/RequestScopedJaxrsComponent.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/RequestScopedJaxrsComponent.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/RequestScopedJaxrsComponent.java (revision 0) @@ -0,0 +1,20 @@ +package org.jboss.resteasy.cdi.stereotype; + +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.TYPE; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Stereotype; + +import org.jboss.resteasy.cdi.JaxrsFieldInjectionInterceptorBinding; + +@Target( { TYPE } ) +@Retention(RUNTIME) +@RequestScoped +@JaxrsFieldInjectionInterceptorBinding +@Stereotype +public @interface RequestScopedJaxrsComponent +{ +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/ApplicationScopedJaxrsComponent.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/ApplicationScopedJaxrsComponent.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/stereotype/ApplicationScopedJaxrsComponent.java (revision 0) @@ -0,0 +1,20 @@ +package org.jboss.resteasy.cdi.stereotype; + +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.TYPE; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Stereotype; + +import org.jboss.resteasy.cdi.JaxrsFieldInjectionInterceptorBinding; + +@Target( { TYPE } ) +@Retention(RUNTIME) +@ApplicationScoped +@JaxrsFieldInjectionInterceptorBinding +@Stereotype +public @interface ApplicationScopedJaxrsComponent +{ +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiInjectorFactory.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiInjectorFactory.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/CdiInjectorFactory.java (revision 0) @@ -0,0 +1,64 @@ +package org.jboss.resteasy.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import javax.enterprise.inject.spi.BeanManager; +import org.jboss.resteasy.core.ValueInjector; +import org.jboss.resteasy.spi.ConstructorInjector; +import org.jboss.resteasy.spi.InjectorFactory; +import org.jboss.resteasy.spi.MethodInjector; +import org.jboss.resteasy.spi.PropertyInjector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Jozef Hartinger + * + */ +public class CdiInjectorFactory implements InjectorFactory +{ + private static final Logger log = LoggerFactory.getLogger(CdiInjectorFactory.class); + private InjectorFactory delegate; + private BeanManager manager; + + public CdiInjectorFactory(InjectorFactory delegate, BeanManager manager) + { + this.manager = manager; + this.delegate = delegate; + } + + public ConstructorInjector createConstructor(Constructor constructor) + { + if (manager.getBeans(constructor.getDeclaringClass()).isEmpty()) + { + log.debug("No CDI beans found for {}. Using default ConstructorInjector.", constructor.getDeclaringClass()); + return delegate.createConstructor(constructor); + } + else + { + return new CdiConstructorInjector(constructor.getDeclaringClass(), manager); + } + } + + public MethodInjector createMethodInjector(Class root, Method method) + { + return delegate.createMethodInjector(root, method); + } + + public PropertyInjector createPropertyInjector(Class resourceClass) + { + // TODO This is currently called twice for CDI beans. Firstly by JaxrsFieldInjectionInterceptor + // and the other time by RESTEasy's injection mechanism. + return delegate.createPropertyInjector(resourceClass); + } + + public ValueInjector createParameterExtractor(Class injectTargetClass, AccessibleObject injectTarget, Class type, Type genericType, Annotation[] annotations) + { + return delegate.createParameterExtractor(injectTargetClass, injectTarget, type, genericType, annotations); + } +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptorBinding.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptorBinding.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptorBinding.java (revision 0) @@ -0,0 +1,24 @@ +package org.jboss.resteasy.cdi; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.TYPE; + +import javax.interceptor.InterceptorBinding; + +/** + * Interceptor binding for JaxrsFieldInjectionInterceptor. This interceptor binding + * is added to JAX-RS components by default. + * @author Jozef Hartinger + * + * @see JaxrsFieldInjectionInterceptor + */ +@InterceptorBinding +@Target( { TYPE } ) +@Retention(RUNTIME) +@Inherited +public @interface JaxrsFieldInjectionInterceptorBinding +{ +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptor.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptor.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/JaxrsFieldInjectionInterceptor.java (revision 0) @@ -0,0 +1,50 @@ +package org.jboss.resteasy.cdi; + +import javax.annotation.PostConstruct; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.jboss.resteasy.spi.PropertyInjector; +import org.jboss.resteasy.spi.ResteasyProviderFactory; + +/** + * RESTEasy property injection does not work for CDI proxies. It injects these proxies + * instead of an underlying objects. Therefore this interceptor , which performs JAX-RS + * property injection, is needed. + * + * @author Jozef Hartinger + */ +@Interceptor +@JaxrsFieldInjectionInterceptorBinding +public class JaxrsFieldInjectionInterceptor +{ + @PostConstruct + public void intercept(InvocationContext ctx) + { + Object target = ctx.getTarget(); + ResteasyProviderFactory factory = ResteasyProviderFactory.getInstance(); + PropertyInjector injector = factory.getInjectorFactory().createPropertyInjector(target.getClass()); + + HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class); + HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + + if ((request != null) && (response != null)) + { + injector.inject(request, response, target); + } + else + { + injector.inject(target); + } + try + { + ctx.proceed(); + } + catch (Exception e) + { + throw new RuntimeException(); + } + } +} Index: resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/Bootstrap.java =================================================================== --- resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/Bootstrap.java (revision 0) +++ resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/Bootstrap.java (revision 0) @@ -0,0 +1,90 @@ +package org.jboss.resteasy.cdi; + +import java.lang.annotation.Annotation; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; +import javax.enterprise.util.AnnotationLiteral; +import javax.ws.rs.core.Application; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.cdi.stereotype.ApplicationScopedJaxrsComponent; +import org.jboss.resteasy.cdi.stereotype.RequestScopedJaxrsComponent; +import org.jboss.resteasy.spi.InjectorFactory; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.jboss.resteasy.util.GetRestful; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This Extension registers CdiInjectorFactory with RESTEasy and adds + * a default scoped and interceptor binding to discovered JAX-RS resources, + * providers and Application subclasses. + * + * @author Jozef Hartinger + * + */ +public class Bootstrap implements Extension +{ + public static final Logger log = LoggerFactory.getLogger(Bootstrap.class); + + // Stereotypes + public static final Annotation requestScopedJaxrsComponentStereotype = new AnnotationLiteral() + { + private static final long serialVersionUID = -4530734209482668662L; + }; + public static final Annotation applicationScopedJaxrsComponentStereotype = new AnnotationLiteral() + { + private static final long serialVersionUID = -5697607605643346819L; + }; + + /** + * Register CdiInjectorFactory with RESTEasy + * + */ + public void observeAfterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager manager) + { + log.debug("Registering CdiInjectorFactory."); + ResteasyProviderFactory providerFactory = ResteasyProviderFactory.getInstance(); + InjectorFactory delegate = providerFactory.getInjectorFactory(); + + InjectorFactory injectorFactory = new CdiInjectorFactory(delegate, manager); + providerFactory.setInjectorFactory(injectorFactory); + } + + /** + * Add a default scope and an interceptor binding for each CDI bean which is + * a JAX-RS Resource, Provider or Application subclass. + * + */ + public void observeResources(@Observes ProcessAnnotatedType event) + { + AnnotatedType type = event.getAnnotatedType(); + + Provider providerAnnotation = type.getAnnotation(Provider.class); + + if (providerAnnotation != null) + { + log.debug("Discovered CDI bean which is a JAX-RS provider {}. Adding @ApplicationScopedJaxrsComponent stereotype.", type.getJavaClass().getCanonicalName()); + event.setAnnotatedType(wrapAnnotatedType(type, applicationScopedJaxrsComponentStereotype)); + } + else if (GetRestful.isRootResource(type.getJavaClass())) + { + log.debug("Discovered CDI bean which is a JAX-RS resource {}. Adding @RequestScopedJaxrsComponent stereotype.", type.getJavaClass().getCanonicalName()); + event.setAnnotatedType(wrapAnnotatedType(type, requestScopedJaxrsComponentStereotype)); + } + else if (Application.class.isAssignableFrom(type.getJavaClass())) + { + log.debug("Discovered CDI bean which is javax.ws.rs.core.Application subclass {}. Adding @ApplicationScopedJaxrsComponent stereotype.", type.getJavaClass().getCanonicalName()); + event.setAnnotatedType(wrapAnnotatedType(type, applicationScopedJaxrsComponentStereotype)); + } + } + + protected AnnotatedType wrapAnnotatedType(AnnotatedType type, Annotation stereotype) + { + return new AnnotatedTypeWrapper(type, stereotype); + } +} Index: resteasy-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension =================================================================== --- resteasy-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension (revision 0) +++ resteasy-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension (revision 0) @@ -0,0 +1 @@ +org.jboss.resteasy.cdi.Bootstrap \ No newline at end of file Index: resteasy-cdi/src/main/resources/META-INF/beans.xml =================================================================== --- resteasy-cdi/src/main/resources/META-INF/beans.xml (revision 0) +++ resteasy-cdi/src/main/resources/META-INF/beans.xml (revision 0) @@ -0,0 +1,13 @@ + + + + + org.jboss.resteasy.cdi.JaxrsFieldInjectionInterceptor + + Index: resteasy-cdi/pom.xml =================================================================== --- resteasy-cdi/pom.xml (revision 0) +++ resteasy-cdi/pom.xml (revision 0) @@ -0,0 +1,46 @@ + + 4.0.0 + + org.jboss.resteasy + resteasy-jaxrs-all + 2.0-beta-2-SNAPSHOT + + resteasy-cdi + RESTEasy CDI integration + jar + + + org.jboss.resteasy + jaxrs-api + ${project.version} + + + + org.slf4j + slf4j-api + provided + + + + javax.servlet + servlet-api + provided + + + + javax.enterprise + cdi-api + provided + 1.0 + + + + org.jboss.resteasy + resteasy-jaxrs + ${project.version} + + + + +