/**
* 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.cxf.cdi;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeShutdown;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessBean;
import javax.enterprise.inject.spi.ProcessProducerField;
import javax.enterprise.inject.spi.ProcessProducerMethod;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.extension.ExtensionManagerBus;
import org.apache.cxf.cdi.extension.JAXRSServerFactoryCustomizationExtension;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
/**
* Apache CXF portable CDI extension to support initialization of JAX-RS resources.
*/
public class JAXRSCdiResourceExtension implements Extension {
private boolean hasBus;
private Bus bus;
private final Set< Bean< ? > > applicationBeans = new LinkedHashSet< Bean< ? > >();
private final List< Bean< ? > > serviceBeans = new ArrayList< Bean< ? > >();
private final List< Bean< ? > > providerBeans = new ArrayList< Bean< ? > >();
private final List< Bean< ? extends Feature > > featureBeans = new ArrayList< Bean< ? extends Feature > >();
private final List< CreationalContext< ? > > disposableCreationalContexts =
new ArrayList< CreationalContext< ? > >();
/**
* Holder of the classified resource classes, converted to appropriate instance
* representations.
*/
private static class ClassifiedClasses {
private List< Object > providers = new ArrayList<>();
private List< Feature > features = new ArrayList<>();
private List< CdiResourceProvider > resourceProviders = new ArrayList<>();
public void addProviders(final Collection< Object > others) {
this.providers.addAll(others);
}
public void addFeatures(final Collection< Feature > others) {
this.features.addAll(others);
}
public void addResourceProvider(final CdiResourceProvider other) {
this.resourceProviders.add(other);
}
public List< Object > getProviders() {
return providers;
}
public List< Feature > getFeatures() {
return features;
}
public List<CdiResourceProvider> getResourceProviders() {
return resourceProviders;
}
}
@SuppressWarnings("unchecked")
public <T> void collect(@Observes final ProcessBean< T > event) {
if (event.getAnnotated().isAnnotationPresent(ApplicationPath.class)) {
applicationBeans.add(event.getBean());
} else if (event.getAnnotated().isAnnotationPresent(Path.class)) {
serviceBeans.add(event.getBean());
} else if (event.getAnnotated().isAnnotationPresent(Provider.class)) {
providerBeans.add(event.getBean());
} else if (event.getBean().getTypes().contains(javax.ws.rs.core.Feature.class)) {
providerBeans.add((Bean< ? extends Feature >)event.getBean());
} else if (event.getBean().getTypes().contains(Feature.class)) {
featureBeans.add((Bean< ? extends Feature >)event.getBean());
} else if (CdiBusBean.CXF.equals(event.getBean().getName())
&& Bus.class.isAssignableFrom(event.getBean().getBeanClass())) {
hasBus = true;
}
}
public <T, X> void collect(@Observes final ProcessProducerField< T, X > event) {
final Type baseType = event.getAnnotatedProducerField().getBaseType();
processProducer(event, baseType);
}
public <T, X> void collect(@Observes final ProcessProducerMethod< T, X > event) {
final Type baseType = event.getAnnotatedProducerMethod().getBaseType();
processProducer(event, baseType);
}
public void load(@Observes final AfterDeploymentValidation event, final BeanManager beanManager) {
// no need of creational context, it only works for app scoped instances anyway
final Bean<?> busBean = beanManager.resolve(beanManager.getBeans(CdiBusBean.CXF));
bus = (Bus)beanManager.getReference(
busBean,
Bus.class,
beanManager.createCreationalContext(busBean));
for (final Bean< ? > application: applicationBeans) {
final Application instance = (Application)beanManager.getReference(
application,
application.getBeanClass(),
createCreationalContext(beanManager, application)
);
// If there is an application without any singletons and classes defined, we will
// create a server factory bean with all services and providers discovered.
if (instance.getSingletons().isEmpty() && instance.getClasses().isEmpty()) {
final JAXRSServerFactoryBean factory = createFactoryInstance(instance,
loadServices(beanManager, Collections.<Class<?>>emptySet()),
loadProviders(beanManager, Collections.<Class<?>>emptySet()),
loadFeatures(beanManager, Collections.<Class<?>>emptySet()));
customize(beanManager, factory);
factory.init();
} else {
// If there is an application with any singletons or classes defined, we will
// create a server factory bean with only application singletons and classes.
final JAXRSServerFactoryBean factory = createFactoryInstance(instance, beanManager);
customize(beanManager, factory);
factory.init();
}
}
}
public void injectBus(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) {
if (!hasBus) {
final AnnotatedType< ExtensionManagerBus > busAnnotatedType =
beanManager.createAnnotatedType(ExtensionManagerBus.class);
final InjectionTarget<ExtensionManagerBus> busInjectionTarget =
beanManager.createInjectionTarget(busAnnotatedType);
event.addBean(new CdiBusBean(busInjectionTarget));
}
if (applicationBeans.isEmpty() && !serviceBeans.isEmpty()) {
final DefaultApplicationBean applicationBean = new DefaultApplicationBean();
applicationBeans.add(applicationBean);
event.addBean(applicationBean);
}
}
/**
* Releases created CreationalContext instances
*/
public void release(@Observes final BeforeShutdown event) {
for (final CreationalContext<?> disposableCreationalContext: disposableCreationalContexts) {
disposableCreationalContext.release();
}
}
/**
* Create the JAXRSServerFactoryBean from the application and all discovered service and provider instances.
* @param application application instance
* @param services all discovered services
* @param providers all discovered providers
* @return JAXRSServerFactoryBean instance
*/
private JAXRSServerFactoryBean createFactoryInstance(final Application application, final List< ? > services,
final List< ? > providers, final List< ? extends Feature > features) {
final JAXRSServerFactoryBean instance =
ResourceUtils.createApplication(application, false, false, false, bus);
instance.setServiceBeans(new ArrayList<>(services));
instance.setProviders(providers);
instance.setProviders(loadExternalProviders());
instance.setFeatures(features);
return instance;
}
/**
* Create the JAXRSServerFactoryBean from the objects declared by application itself.
* @param application application instance
* @return JAXRSServerFactoryBean instance
*/
private JAXRSServerFactoryBean createFactoryInstance(final Application application, final BeanManager beanManager) {
final JAXRSServerFactoryBean instance =
ResourceUtils.createApplication(application, false, false, false, bus);
final ClassifiedClasses classified = classes2singletons(application, beanManager);
instance.setProviders(classified.getProviders());
instance.getFeatures().addAll(classified.getFeatures());
for (final CdiResourceProvider resourceProvider: classified.getResourceProviders()) {
instance.setResourceProvider(resourceProvider.getResourceClass(), resourceProvider);
}
return instance;
}
/**
* JAX-RS application has defined singletons as being classes of any providers, resources and features.
* In the JAXRSServerFactoryBean, those should be split around several method calls depending on instance
* type. At the moment, only the Feature is CXF-specific and should be replaced by JAX-RS Feature implementation.
* @param application the application instance
* @return classified instances of classes by instance types
*/
private ClassifiedClasses classes2singletons(final Application application, final BeanManager beanManager) {
final ClassifiedClasses classified = new ClassifiedClasses();
// now loop through the classes
Set<Class<?>> classes = application.getClasses();
if (!classes.isEmpty()) {
classified.addProviders(loadProviders(beanManager, classes));
classified.addFeatures(loadFeatures(beanManager, classes));
for (final Bean< ? > bean: serviceBeans) {
if (classes.contains(bean.getBeanClass())) {
classified.addResourceProvider(new CdiResourceProvider(beanManager, bean));
}
}
}
return classified;
}
/**
* Load external providers from service loader
* @return loaded external providers
*/
@SuppressWarnings("rawtypes")
private List< Object > loadExternalProviders() {
final List< Object > providers = new ArrayList< Object >();
final ServiceLoader< MessageBodyWriter > writers = ServiceLoader.load(MessageBodyWriter.class);
for (final MessageBodyWriter< ? > writer: writers) {
providers.add(writer);
}
final ServiceLoader< MessageBodyReader > readers = ServiceLoader.load(MessageBodyReader.class);
for (final MessageBodyReader< ? > reader: readers) {
providers.add(reader);
}
return providers;
}
/**
* Gets the references for all discovered JAX-RS resources
* @param beanManager bean manager instance
* @param limitedClasses not null, if empty ignored. the set of classes to consider as providers
* @return the references for all discovered JAX-RS resources
*/
private List< Object > loadProviders(final BeanManager beanManager, Collection<Class<?>> limitedClasses) {
return loadBeans(beanManager, limitedClasses, providerBeans);
}
/**
* Gets the references for all discovered JAX-RS providers
* @param beanManager bean manager instance
* @param limitedClasses not null, if empty ignored. the set of classes to consider as providers
* @return the references for all discovered JAX-RS providers
*/
private List< Object > loadServices(final BeanManager beanManager, Collection<Class<?>> limitedClasses) {
return loadBeans(beanManager, limitedClasses, serviceBeans);
}
/**
* Gets references for all beans of a given type
* @param beanManager bean manager instance
* @param limitedClasses not null, if empty ignored. the set of classes to consider as providers
* @param beans the collection of beans to go through
* @return the references for all discovered JAX-RS providers
*/
private List< Object > loadBeans(final BeanManager beanManager, Collection<Class<?>> limitedClasses,
Collection<Bean<?>> beans) {
final List< Object > instances = new ArrayList<>();
for (final Bean< ? > bean: beans) {
if (limitedClasses.isEmpty() || limitedClasses.contains(bean.getBeanClass())) {
instances.add(
beanManager.getReference(
bean,
Object.class,
createCreationalContext(beanManager, bean)
)
);
}
}
return instances;
}
/**
* Gets the references for all discovered CXF-specific features
* @param beanManager bean manager instance
* @return the references for all discovered CXF-specific features
*/
private List< Feature > loadFeatures(final BeanManager beanManager, Collection<Class<?>> limitedClasses) {
final List< Feature > features = new ArrayList<>();
for (final Bean< ? extends Feature > bean: featureBeans) {
if (limitedClasses.isEmpty() || limitedClasses.contains(bean.getBeanClass())) {
features.add(
(Feature) beanManager.getReference(
bean,
Feature.class,
createCreationalContext(beanManager, bean)
)
);
}
}
return features;
}
/**
* Look and apply the available JAXRSServerFactoryBean extensions to customize its
* creation (f.e. add features, providers, assign transport, ...)
* @param beanManager bean manager
* @param bean JAX-RS server factory bean about to be created
*/
private void customize(final BeanManager beanManager, final JAXRSServerFactoryBean bean) {
final Collection<Bean<?>> extensionBeans = beanManager.getBeans(JAXRSServerFactoryCustomizationExtension.class);
for (final Bean<?> extensionBean: extensionBeans) {
final JAXRSServerFactoryCustomizationExtension extension =
(JAXRSServerFactoryCustomizationExtension)beanManager.getReference(
extensionBean,
extensionBean.getBeanClass(),
beanManager.createCreationalContext(extensionBean)
);
extension.customize(bean);
}
}
/**
* Creates and collects the CreationalContext instances for future releasing.
* @param beanManager bean manager instance
* @param bean bean instance to create CreationalContext for
* @return CreationalContext instance
*/
private<T> CreationalContext< T > createCreationalContext(final BeanManager beanManager, Bean< T > bean) {
final CreationalContext< T > creationalContext = beanManager.createCreationalContext(bean);
if (!(bean instanceof DefaultApplicationBean)) {
disposableCreationalContexts.add(creationalContext);
}
return creationalContext;
}
/**
* Extracts relevant beans from producers.
* @param event process bean event
* @param baseType base type of the producer
*/
private <T> void processProducer(final ProcessBean<T> event, final Type baseType) {
if (baseType instanceof Class<?>) {
final Class<?> clazz = (Class<?>)baseType;
if (clazz.isAnnotationPresent(Path.class)) {
serviceBeans.add(event.getBean());
} else if (clazz.isAnnotationPresent(Provider.class)) {
providerBeans.add(event.getBean());
} else if (clazz.isAnnotationPresent(ApplicationPath.class)) {
applicationBeans.add(event.getBean());
}
}
}
}