blob: 760a94ac408b8c68f4718d39389190d70a5c0ce9 [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.aries.jax.rs.whiteboard.internal.cxf;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.aries.jax.rs.whiteboard.internal.Whiteboard.SUPPORTED_EXTENSION_INTERFACES;
import static org.apache.aries.jax.rs.whiteboard.internal.utils.Utils.canonicalize;
import static org.apache.cxf.jaxrs.provider.ProviderFactory.DEFAULT_FILTER_NAME_BINDING;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
import org.apache.aries.component.dsl.CachingServiceReference;
import org.apache.aries.component.dsl.OSGi;
import org.apache.aries.jax.rs.whiteboard.ApplicationClasses;
import org.apache.aries.jax.rs.whiteboard.internal.AriesJaxrsServiceRuntime;
import org.apache.aries.jax.rs.whiteboard.internal.ServiceReferenceRegistry;
import org.apache.aries.jax.rs.whiteboard.internal.introspection.Proxies;
import org.apache.aries.jax.rs.whiteboard.internal.utils.ServiceTuple;
import org.apache.cxf.Bus;
import org.apache.cxf.common.util.ClassHelper;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
import org.apache.cxf.jaxrs.ext.ContextProvider;
import org.apache.cxf.jaxrs.ext.ResourceContextProvider;
import org.apache.cxf.jaxrs.impl.ConfigurableImpl;
import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider;
import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.apache.cxf.jaxrs.model.ApplicationInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.provider.ProviderFactory.ProviderInfoClassComparator;
import org.apache.cxf.jaxrs.provider.ServerConfigurableFactory;
import org.apache.cxf.jaxrs.sse.SseContextProvider;
import org.apache.cxf.jaxrs.sse.SseEventSinkContextProvider;
import org.apache.cxf.jaxrs.utils.AnnotationUtils;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
import org.apache.cxf.message.Message;
public class CxfJaxrsServiceRegistrator {
public Map<String, ?> getProperties() {
return _properties;
}
public CxfJaxrsServiceRegistrator(
Bus bus, ServiceTuple<Application> applicationTuple,
Map<String, ?> properties,
AriesJaxrsServiceRuntime ariesJaxrsServiceRuntime) {
_bus = bus;
_applicationTuple = applicationTuple;
_properties = Collections.unmodifiableMap(new HashMap<>(properties));
_ariesJaxrsServiceRuntime = ariesJaxrsServiceRuntime;
Comparator<ServiceTuple<?>> comparing = Comparator.comparing(
ServiceTuple::getCachingServiceReference);
_providers = new TreeSet<>(comparing);
_erroredProviders = new ArrayList<>();
_erroredServices = new ArrayList<>();
_serviceReferenceRegistry = new ServiceReferenceRegistry();
}
public synchronized void add(ResourceProvider resourceProvider) {
_services.add(resourceProvider);
try {
rewire();
}
catch (Exception e) {
remove(resourceProvider);
throw e;
}
}
public synchronized void addProvider(ServiceTuple<?> tuple) {
_providers.add(tuple);
try {
rewire();
}
catch (Exception e) {
removeProvider(tuple);
throw e;
}
}
public void enable() {
_enabled = true;
try {
rewire();
}
catch (Exception e) {
ArrayList<ServiceTuple<?>> providers = new ArrayList<>();
ArrayList<ResourceProvider> services = new ArrayList<>();
for (ServiceTuple<?> provider : _providers) {
providers.add(provider);
try {
doRewire(providers, services);
}
catch (Exception ex) {
providers.remove(provider);
_erroredProviders.add(provider);
}
}
for (ResourceProvider service : _services) {
services.add(service);
try {
doRewire(providers, services);
}
catch (Exception ex) {
services.remove(service);
_erroredServices.add(service);
}
}
_enabled = false;
for (ServiceTuple<?> erroredProvider : _erroredProviders) {
CachingServiceReference<?> cachingServiceReference =
erroredProvider.getCachingServiceReference();
_providers.remove(erroredProvider);
_ariesJaxrsServiceRuntime.addErroredExtension(
cachingServiceReference);
_serviceReferenceRegistry.unregister(cachingServiceReference);
}
for (ResourceProvider erroredService : _erroredServices) {
_services.remove(erroredService);
_ariesJaxrsServiceRuntime.addErroredEndpoint(
((ServiceReferenceResourceProvider)erroredService).
getImmutableServiceReference());
}
_enabled = true;
rewire();
}
}
public void close() {
if (!_enabled) {
return;
}
_enabled = false;
if (_server != null) {
_server.destroy();
}
if (_bus != null) {
_bus.shutdown(false);
}
}
public <T> T createEndpoint(Application app, Class<T> endpointType) {
// final JAXRSServerFactoryBean bean = ResourceUtils.createApplication(app, false, false, false, null);
final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
Set<Object> singletons = app.getSingletons();
if (!singletons.isEmpty() && singletons.stream().map(Object::getClass).count() < singletons.size()) {
throw new IllegalArgumentException("More than one instance of the same singleton class is available: " + singletons);
}
final List<Class<?>> resourceClasses = new ArrayList<>();
final List<Object> providers = new ArrayList<>();
final List<org.apache.cxf.feature.Feature> features = new ArrayList<>();
final Map<Class<?>, ResourceProvider> map = new HashMap<>();
// Note, app.getClasses() returns a list of per-request classes
// or singleton provider classes
for (Class<?> cls : app.getClasses()) {
if (!cls.isInterface() && !Modifier.isAbstract(cls.getModifiers())) {
if (isProvider(cls)) {
providers.add(ResourceUtils.createProviderInstance(cls));
} else if (org.apache.cxf.feature.Feature.class.isAssignableFrom(cls)) {
features.add(ResourceUtils.createFeatureInstance((Class<? extends org.apache.cxf.feature.Feature>) cls));
} else {
resourceClasses.add(cls);
/* todo: support singleton provider otherwise perfs can be a shame
if (useSingletonResourceProvider) {
map.put(cls, new SingletonResourceProvider(ResourceUtils.createProviderInstance(cls)));
} else {
*/
map.put(cls, new PerRequestResourceProvider(cls));
}
}
}
// we can get either a provider or resource class here
for (final Object o : singletons) {
if (isProvider(o.getClass())) {
providers.add(o);
} else if (o instanceof org.apache.cxf.feature.Feature) {
features.add((org.apache.cxf.feature.Feature) o);
} else {
final Class<?> unwrapped = Proxies.unwrap(o.getClass());
resourceClasses.add(unwrapped);
map.put(unwrapped, new SingletonResourceProvider(o));
}
}
String address = "/";
ApplicationPath appPath = ResourceUtils.locateApplicationPath(app.getClass());
if (appPath != null) {
address = appPath.value();
}
if (!address.startsWith("/")) {
address = "/" + address;
}
// todo resolve conflicts between @ApplicationPath and @JaxrsApplicationBase (if same value -> use only one?)
bean.setAddress(address);
bean.setStaticSubresourceResolution(false);
bean.setResourceClasses(resourceClasses);
bean.setProviders(providers);
bean.getFeatures().addAll(features);
for (Map.Entry<Class<?>, ResourceProvider> entry : map.entrySet()) {
bean.setResourceProvider(entry.getKey(), entry.getValue());
}
Map<String, Object> appProps = app.getProperties();
if (appProps != null) {
bean.getProperties(true).putAll(appProps);
}
bean.setApplication(app);
if (_bus != null) {
bean.setBus(_bus);
}
if (JAXRSServerFactoryBean.class.isAssignableFrom(endpointType)) {
return endpointType.cast(bean);
}
bean.setApplication(app);
bean.setStart(false);
final Server server = bean.create();
return endpointType.cast(server);
}
private boolean isProvider(Class<?> cls) {
return cls.isAnnotationPresent(Provider.class) || SUPPORTED_EXTENSION_INTERFACES.values().stream().anyMatch(it -> it.isAssignableFrom(cls));
}
public Bus getBus() {
return _bus;
}
public Iterable<Class<?>> getStaticResourceClasses() {
if (_jaxRsServerFactoryBean == null) {
return Collections.emptyList();
}
JAXRSServiceFactoryBean serviceFactory =
_jaxRsServerFactoryBean.getServiceFactory();
List<ClassResourceInfo> classResourceInfo =
serviceFactory.getClassResourceInfo();
ArrayList<Class<?>> classes = new ArrayList<>();
for (ClassResourceInfo resourceInfo : classResourceInfo) {
if (resourceInfo == null) {
continue;
}
ResourceProvider resourceProvider =
resourceInfo.getResourceProvider();
if (resourceProvider == null ||
!ServiceReferenceResourceProvider.class.isAssignableFrom(
resourceProvider.getClass())) {
classes.add(resourceInfo.getResourceClass());
}
}
return classes;
}
public void registerExtension(
CachingServiceReference<?> serviceReference) {
_serviceReferenceRegistry.register(serviceReference);
}
public synchronized void remove(ResourceProvider resourceProvider) {
if (_erroredServices.remove(resourceProvider)) {
_ariesJaxrsServiceRuntime.removeErroredEndpoint(
((ServiceReferenceResourceProvider)resourceProvider).
getImmutableServiceReference());
}
_services.remove(resourceProvider);
rewire();
}
public synchronized void removeProvider(ServiceTuple<?> tuple) {
if (_erroredProviders.remove(tuple)) {
_ariesJaxrsServiceRuntime.removeErroredExtension(
tuple.getCachingServiceReference());
}
_providers.remove(tuple);
rewire();
}
public synchronized void rewire() {
doRewire(_providers, _services);
}
public void unregisterExtension(
CachingServiceReference<?> serviceReference) {
_serviceReferenceRegistry.unregister(serviceReference);
}
public OSGi<CachingServiceReference<?>> waitForExtension(
String extensionDependency) {
return _serviceReferenceRegistry.waitFor(extensionDependency);
}
private ArrayList<ServiceTuple<?>> _erroredProviders;
private ArrayList<ResourceProvider> _erroredServices;
@SuppressWarnings("serial")
private synchronized void doRewire(
Collection<ServiceTuple<?>> providers,
Collection<ResourceProvider> services) {
if (!_enabled) {
return;
}
if (!_applicationTuple.isAvailable()) {
_applicationTuple.dispose();
return;
}
if (_server != null) {
_server.destroy();
_applicationTuple.refresh();
for (ServiceTuple<?> provider : providers) {
provider.refresh();
}
}
Application application = _applicationTuple.getService();
if (application == null) {
return;
}
if (_services.isEmpty() &&
application.getSingletons().isEmpty() &&
application.getClasses().isEmpty()) {
return;
}
_jaxRsServerFactoryBean = createEndpoint(
application, JAXRSServerFactoryBean.class);
_jaxRsServerFactoryBean.setInvoker(new PromiseAwareJAXRSInvoker());
_jaxRsServerFactoryBean.setBus(_bus);
_bus.setExtension(
context -> {
ConfigurableImpl<FeatureContext> configurable =
new ConfigurableImpl<>(context, RuntimeType.SERVER);
configurable.property(
"osgi.jaxrs.application.serviceProperties", _properties);
return configurable;
},
ServerConfigurableFactory.class);
_jaxRsServerFactoryBean.setStart(false);
List<org.apache.cxf.feature.Feature> features = new ArrayList<>();
for (ServiceTuple<?> provider : providers) {
CachingServiceReference<?> cachingServiceReference =
provider.getCachingServiceReference();
if (!provider.isAvailable()) {
continue;
}
Object service = provider.getService();
if (service == null) {
continue;
}
if (service instanceof Feature ||
service instanceof DynamicFeature) {
_jaxRsServerFactoryBean.setProvider(service);
continue;
}
else if (service instanceof org.apache.cxf.feature.Feature) {
features.add((org.apache.cxf.feature.Feature)service);
continue;
}
Class<?> realClass = ClassHelper.getRealClass(getBus(), service);
Class<?>[] interfaces = Arrays.stream(canonicalize(
cachingServiceReference.getProperty("objectClass")))
.filter(SUPPORTED_EXTENSION_INTERFACES::containsKey)
.map(SUPPORTED_EXTENSION_INTERFACES::get)
.toArray(Class[]::new);
Map<Class<?>, Integer> classesWithPriorities=
Arrays.stream(interfaces).collect(
toMap(
c -> c,
__ -> AnnotationUtils.getBindingPriority(realClass))
);
_jaxRsServerFactoryBean.setProvider(
new ServiceReferenceFilterProviderInfo<>(
cachingServiceReference, realClass, realClass, service,
getBus(), getFilterNameBindings(getBus(), service), false,
classesWithPriorities));
}
_jaxRsServerFactoryBean.setProvider(new SseEventSinkContextProvider());
_jaxRsServerFactoryBean.setProvider(new SseContextProvider());
_jaxRsServerFactoryBean.setProvider(new ContextProvider<ApplicationClasses>() {
@Override
public ApplicationClasses createContext(Message message) {
return () -> {
return Stream.concat(
StreamSupport.stream(getStaticResourceClasses().spliterator(), false),
_services.stream().map(ResourceProvider::getResourceClass)
).collect(toSet());
};
}
});
if (!features.isEmpty()) {
features.addAll(_jaxRsServerFactoryBean.getFeatures());
_jaxRsServerFactoryBean.setFeatures(features);
}
for (ResourceProvider resourceProvider: services) {
if (resourceProvider instanceof
PrototypeServiceReferenceResourceProvider) {
PrototypeServiceReferenceResourceProvider provider =
(PrototypeServiceReferenceResourceProvider)resourceProvider;
if (!provider.isAvailable()) {
continue;
}
}
_jaxRsServerFactoryBean.setResourceProvider(resourceProvider);
}
if (_jaxRsServerFactoryBean.getResourceClasses().isEmpty()) {
return;
}
ComparableResourceComparator comparableResourceComparator =
new ComparableResourceComparator();
_jaxRsServerFactoryBean.setResourceComparator(
comparableResourceComparator);
ProviderInfoClassComparator providerInfoClassComparator =
new ProviderInfoClassComparator(Object.class);
_jaxRsServerFactoryBean.setProviderComparator(
new ServiceReferenceProviderInfoComparator(
providerInfoClassComparator)
);
_server = _jaxRsServerFactoryBean.create();
Endpoint endpoint = _server.getEndpoint();
ApplicationInfo applicationInfo = (ApplicationInfo)endpoint.get(
Application.class.getName());
applicationInfo.setOverridingProps(new HashMap<String, Object>() {{
put("osgi.jaxrs.application.serviceProperties", _properties);
}});
endpoint.put(
"org.apache.cxf.jaxrs.resource.context.provider",
createResourceContextProvider(
_jaxRsServerFactoryBean.getServiceFactory()));
_server.start();
}
private ResourceContextProvider createResourceContextProvider(
JAXRSServiceFactoryBean jaxrsServiceFactoryBean) {
ComparableResourceComparator comparableResourceComparator =
new ComparableResourceComparator();
List<ClassResourceInfo> classResourceInfos =
jaxrsServiceFactoryBean.getClassResourceInfo().stream().sorted(
(cri1, cri2) -> comparableResourceComparator.compare(
cri1, cri2, null)
).collect(
Collectors.toList()
);
HashMap<Class, ResourceProvider> map = new HashMap<>();
for (ClassResourceInfo classResourceInfo : classResourceInfos) {
map.put(
classResourceInfo.getResourceClass(),
classResourceInfo.getResourceProvider());
}
return map::get;
}
private final ServiceTuple<Application> _applicationTuple;
private final Bus _bus;
private final Collection<ServiceTuple<?>> _providers;
private final Collection<ResourceProvider> _services = new ArrayList<>();
private volatile boolean _enabled = false;
private JAXRSServerFactoryBean _jaxRsServerFactoryBean;
private Map<String, Object> _properties;
private AriesJaxrsServiceRuntime _ariesJaxrsServiceRuntime;
private ServiceReferenceRegistry _serviceReferenceRegistry;
private Server _server;
private static Set<String> getFilterNameBindings(
Bus bus, Object provider) {
Class<?> pClass = ClassHelper.getRealClass(bus, provider);
Set<String> names = AnnotationUtils.getNameBindings(
pClass.getAnnotations());
if (names.isEmpty()) {
names = Collections.singleton(DEFAULT_FILTER_NAME_BINDING);
}
return names;
}
}