blob: bf96a16627869677a1ee279fad6dd95d28e48f4a [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.openejb.server.rest;
import org.apache.openejb.AppContext;
import org.apache.openejb.BeanContext;
import org.apache.openejb.BeanType;
import org.apache.openejb.Injection;
import org.apache.openejb.assembler.classic.AppInfo;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.EjbJarInfo;
import org.apache.openejb.assembler.classic.EnterpriseBeanInfo;
import org.apache.openejb.assembler.classic.IdPropertiesInfo;
import org.apache.openejb.assembler.classic.ParamValueInfo;
import org.apache.openejb.assembler.classic.ServiceInfo;
import org.apache.openejb.assembler.classic.ServletInfo;
import org.apache.openejb.assembler.classic.WebAppInfo;
import org.apache.openejb.assembler.classic.event.AssemblerAfterApplicationCreated;
import org.apache.openejb.assembler.classic.event.AssemblerBeforeApplicationDestroyed;
import org.apache.openejb.assembler.classic.util.PojoUtil;
import org.apache.openejb.assembler.classic.util.ServiceConfiguration;
import org.apache.openejb.core.CoreContainerSystem;
import org.apache.openejb.core.WebContext;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.observer.Observes;
import org.apache.openejb.server.SelfManaging;
import org.apache.openejb.server.ServerService;
import org.apache.openejb.server.ServiceException;
import org.apache.openejb.server.ServiceManager;
import org.apache.openejb.server.httpd.BasicAuthHttpListenerWrapper;
import org.apache.openejb.server.httpd.HttpListener;
import org.apache.openejb.server.httpd.HttpListenerRegistry;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.xbean.finder.MetaAnnotatedClass;
import javax.naming.Context;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@SuppressWarnings("UnusedDeclaration")
public abstract class RESTService implements ServerService, SelfManaging {
public static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_RS, RESTService.class);
private static final boolean OLD_WEBSERVICE_DEPLOYMENT = SystemInstance.get().getOptions().get("openejb.webservice.old-deployment", false);
public static final String OPENEJB_USE_APPLICATION_PROPERTY = "openejb.jaxrs.application";
private static final String APPLICATION_DEPLOYMENT = SystemInstance.get().getOptions().get(OPENEJB_USE_APPLICATION_PROPERTY, "true");
public static final String OPENEJB_JAXRS_PROVIDERS_AUTO_PROP = "openejb.jaxrs.providers.auto";
private static final String IP = "n/a";
private static final int PORT = -1;
public static final String NOPATH_PREFIX = "http://nopath/";
private final Set<AppInfo> deployedApplications = new HashSet<>();
private final Set<WebAppInfo> deployedWebApps = new HashSet<>();
private Assembler assembler;
private CoreContainerSystem containerSystem;
private RsRegistry rsRegistry;
private final List<DeployedService> services = new ArrayList<>();
private String virtualHost = "localhost";
private String auth = "NONE";
private String realm = "PropertiesLogin";
protected boolean enabled = true;
private final String wildcard = SystemInstance.get().getProperty("openejb.rest.wildcard", ".*"); // embedded = regex, tomee = servlet
/**
* Deployment of JAX-RS services starts in response to a AfterApplicationCreated event
* after normal deployment is done
* @param appInfo the ear (real or auto-created) in which the webapp is contained
* @param webApp the webapp containing EJB or Pojo rest services to deploy
*/
public void afterApplicationCreated(final AppInfo appInfo, final WebAppInfo webApp) {
if ("false".equalsIgnoreCase(appInfo.properties.getProperty("openejb.jaxrs.on", "true"))) {
return;
}
final WebContext webContext = containerSystem.getWebContextByHost(webApp.moduleId, webApp.host != null ? webApp.host : virtualHost);
if (webContext == null) {
return;
}
if (!deployedWebApps.add(webApp)) {
return;
}
final Map<String, EJBRestServiceInfo> restEjbs = getRestEjbs(appInfo, webApp.moduleId);
final ClassLoader classLoader = getClassLoader(webContext.getClassLoader());
final Collection<Injection> injections = webContext.getInjections();
final WebBeansContext owbCtx;
if (webContext.getWebbeansContext() != null) {
owbCtx = webContext.getWebbeansContext();
} else {
owbCtx = webContext.getAppContext().getWebBeansContext();
}
Context context = webContext.getJndiEnc();
if (context == null) { // usually true since it is set in org.apache.tomee.catalina.TomcatWebAppBuilder.afterStart() and lookup(comp) fails
context = webContext.getAppContext().getAppJndiContext();
}
final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
final Collection<Object> additionalProviders = new HashSet<>();
addAppProvidersIfNeeded(appInfo, webApp, classLoader, additionalProviders);
Collection<IdPropertiesInfo> pojoConfigurations = null; // done lazily
try {
boolean deploymentWithApplication = "true".equalsIgnoreCase(appInfo.properties.getProperty(OPENEJB_USE_APPLICATION_PROPERTY, APPLICATION_DEPLOYMENT));
if (deploymentWithApplication) {
Class<?> appClazz;
for (final String app : webApp.restApplications) {
Application application;
boolean appSkipped = false;
String prefix = "/";
try {
appClazz = classLoader.loadClass(app);
application = Application.class.cast(appClazz.newInstance());
if (owbCtx != null && owbCtx.getBeanManagerImpl().isInUse()) {
try {
webContext.inject(application);
} catch (final Exception e) {
// not important since not required by the spec
}
}
} catch (final Exception e) {
throw new OpenEJBRestRuntimeException("can't create class " + app, e);
}
application = "true".equalsIgnoreCase(
appInfo.properties.getProperty("openejb.cxf-rs.cache-application",
SystemInstance.get().getOptions().get("openejb.cxf-rs.cache-application", "true")))
?
new InternalApplication(application) /* caches singletons and classes */ :
application;
final Set<Class<?>> classes = new HashSet<>(application.getClasses());
final Set<Object> singletons = application.getSingletons();
if (classes.size() + singletons.size() == 0) {
appSkipped = true;
} else {
for (final Class<?> clazz : classes) {
if (isProvider(clazz)) {
additionalProviders.add(clazz);
} else if (!hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
if (PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()) != null) {
deploymentWithApplication = false;
logOldDeploymentUsage(clazz.getName());
}
}
}
if (deploymentWithApplication) { // don't do it if we detected we should use old deployment
for (final Object o : singletons) {
final Class<?> clazz = o.getClass();
if (isProvider(clazz)) {
additionalProviders.add(o);
} else if (!hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
if (PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()) != null) {
deploymentWithApplication = false;
logOldDeploymentUsage(clazz.getName());
}
}
}
}
}
if (deploymentWithApplication) { // don't do it if we detected we should use old deployment
final String path = appPrefix(webApp, appClazz);
if (path != null) {
prefix += path + wildcard;
} else {
prefix += wildcard;
}
}
if (deploymentWithApplication) { // don't do it if we detected we should use old deployment
if (appSkipped || application == null) {
application = !InternalApplication.class.isInstance(application) ? new InternalApplication(application) : application;
for (final String clazz : webApp.restClass) {
try {
final Class<?> loaded = classLoader.loadClass(clazz);
if (!isProvider(loaded)) {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
if (PojoUtil.findConfiguration(pojoConfigurations, loaded.getName()) != null) {
deploymentWithApplication = false;
logOldDeploymentUsage(loaded.getName());
break;
}
application.getClasses().add(loaded);
} else {
additionalProviders.add(loaded);
}
} catch (final Exception e) {
throw new OpenEJBRestRuntimeException("can't load class " + clazz, e);
}
}
if (deploymentWithApplication) {
addEjbToApplication(application, restEjbs);
if (!prefix.endsWith(wildcard)) {
prefix += wildcard;
}
}
}
if (!application.getClasses().isEmpty() || !application.getSingletons().isEmpty()) {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
deployApplication(appInfo, webApp.contextRoot, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations, application, prefix);
}
}
if (!deploymentWithApplication) {
fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
}
}
if (webApp.restApplications.isEmpty()) {
final Application application = new InternalApplication(null);
for (final String clazz : webApp.restClass) {
try {
final Class<?> loaded = classLoader.loadClass(clazz);
if (!isProvider(loaded)) {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
if (PojoUtil.findConfiguration(pojoConfigurations, loaded.getName()) != null) {
deploymentWithApplication = false;
logOldDeploymentUsage(loaded.getName());
break;
}
application.getClasses().add(loaded);
} else {
additionalProviders.add(loaded);
}
} catch (final Exception e) {
throw new OpenEJBRestRuntimeException("can't load class " + clazz, e);
}
}
addEjbToApplication(application, restEjbs);
if (deploymentWithApplication) {
if (!application.getClasses().isEmpty() || !application.getSingletons().isEmpty()) {
final String path = appPrefix(webApp, application.getClass());
final String prefix;
if (path != null) {
prefix = "/" + path + wildcard;
} else {
prefix = "/" + wildcard;
}
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
deployApplication(appInfo, webApp.contextRoot, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations, application, prefix);
}
} else {
fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
}
}
} else {
fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
}
} finally {
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
private void addAppProvidersIfNeeded(final AppInfo appInfo, final WebAppInfo webApp, final ClassLoader classLoader, final Collection<Object> additionalProviders) {
if (useDiscoveredProviders(appInfo, webApp.restApplications.size() == 0)) {
final Set<String> jaxRsProviders = new HashSet<>(webApp.jaxRsProviders);
jaxRsProviders.addAll(appInfo.jaxRsProviders);
additionalProviders.addAll(appProviders(jaxRsProviders, classLoader));
}
}
private void addEjbToApplication(final Application application, final Map<String, EJBRestServiceInfo> restEjbs) {
for (final Map.Entry<String, EJBRestServiceInfo> ejb : restEjbs.entrySet()) {
application.getClasses().add(ejb.getValue().context.getBeanClass());
}
}
private void fullServletDeployment(final AppInfo appInfo, final WebAppInfo webApp, final WebContext webContext,
final Map<String, EJBRestServiceInfo> restEjbs, final ClassLoader classLoader,
final Collection<Injection> injections, final WebBeansContext owbCtx,
final Context context, final Collection<Object> additionalProviders,
final Collection<IdPropertiesInfo> initPojoConfigurations) {
// The spec says:
//
// "The resources and providers that make up a JAX-RS application are configured via an application-supplied
// subclass of Application. An implementation MAY provide alternate mechanisms for locating resource
// classes and providers (e.g. runtime class scanning) but use of Application is the only portable means of
// configuration."
//
// The choice here is to deploy using the Application if it exists or to use the scanned classes
// if there is no Application.
//
// Like this providing an Application subclass user can totally control deployed services.
Collection<IdPropertiesInfo> pojoConfigurations = null;
boolean useApp = false;
String appPrefix = webApp.contextRoot;
for (final String app : webApp.restApplications) {
appPrefix = webApp.contextRoot; // if multiple application classes reinit it
if (!appPrefix.endsWith("/")) {
appPrefix += "/";
}
final Application appInstance;
final Class<?> appClazz;
try {
appClazz = classLoader.loadClass(app);
appInstance = Application.class.cast(appClazz.newInstance());
if (owbCtx.getBeanManagerImpl().isInUse()) {
try {
webContext.inject(appInstance);
} catch (final Exception e) {
// not important since not required by the spec
}
}
} catch (final Exception e) {
throw new OpenEJBRestRuntimeException("can't create class " + app, e);
}
final String path = appPrefix(webApp, appClazz);
if (path != null) {
appPrefix += path;
}
final Set<Class<?>> classes = appInstance.getClasses();
final Set<Object> singletons = appInstance.getSingletons();
// look for providers
for (final Class<?> clazz : classes) {
if (isProvider(clazz)) {
additionalProviders.add(clazz);
}
}
for (final Object obj : singletons) {
if (obj != null && isProvider(obj.getClass())) {
additionalProviders.add(obj);
}
}
for (final Object o : singletons) {
if (o == null || additionalProviders.contains(o)) {
continue;
}
if (hasEjbAndIsNotAManagedBean(restEjbs, o.getClass().getName())) {
// no more a singleton if the ejb is not a singleton...but it is a weird case
deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(o.getClass().getName()).context, additionalProviders, appInfo.services);
} else {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
deploySingleton(appInfo.appId, webApp.contextRoot, appPrefix, o, appInstance, classLoader, additionalProviders,
new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, o.getClass().getName()), appInfo.services));
}
}
for (final Class<?> clazz : classes) {
if (additionalProviders.contains(clazz)) {
continue;
}
if (hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(clazz.getName()).context, additionalProviders, appInfo.services);
} else {
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, clazz, appInstance, classLoader, injections, context, owbCtx, additionalProviders,
new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()), appInfo.services));
}
}
useApp = useApp || classes.size() + singletons.size() > 0;
LOGGER.info("REST application deployed: " + app);
}
if (!useApp) {
if (webApp.restApplications.isEmpty() || webApp.restApplications.size() > 1) {
appPrefix = webApp.contextRoot;
} // else keep application prefix
final Set<String> restClasses = new HashSet<>(webApp.restClass);
restClasses.addAll(webApp.ejbRestServices);
for (final String clazz : restClasses) {
if (restEjbs.containsKey(clazz)) {
final BeanContext ctx = restEjbs.get(clazz).context;
if (hasEjbAndIsNotAManagedBean(restEjbs, clazz)) {
deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(clazz).context, additionalProviders, appInfo.services);
} else {
deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, ctx.getBeanClass(), null, ctx.getClassLoader(), ctx.getInjections(), context,
owbCtx, additionalProviders, new ServiceConfiguration(ctx.getProperties(), appInfo.services));
}
} else {
try {
final Class<?> loadedClazz = classLoader.loadClass(clazz);
pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, loadedClazz, null, classLoader, injections, context, owbCtx,
additionalProviders,
new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, loadedClazz.getName()), appInfo.services));
} catch (final ClassNotFoundException e) {
throw new OpenEJBRestRuntimeException("can't find class " + clazz, e);
}
}
}
}
}
protected static void logOldDeploymentUsage(final String clazz) {
LOGGER.info("Using deployment by endpoint instead of by application for JAXRS deployment because an old configuration (by class/ejb) was found on " + clazz);
}
private void deployApplication(final AppInfo appInfo, final String contextRoot, final Map<String, EJBRestServiceInfo> restEjbs, final ClassLoader classLoader, final Collection<Injection> injections, final WebBeansContext owbCtx, final Context context, final Collection<Object> additionalProviders, final Collection<IdPropertiesInfo> pojoConfigurations, final Application application, final String prefix) {
// get configuration
Properties configuration;
if (InternalApplication.class.equals(application.getClass())) {
final Application original = InternalApplication.class.cast(application).getOriginal();
if (original == null) {
configuration = PojoUtil.findConfiguration(pojoConfigurations, "jaxrs-application");
} else {
configuration = PojoUtil.findConfiguration(pojoConfigurations, original.getClass().getName());
}
} else {
configuration = PojoUtil.findConfiguration(pojoConfigurations, application.getClass().getName());
}
if (configuration == null) { // try a constant (common in half of cases)
configuration = PojoUtil.findConfiguration(pojoConfigurations, "jaxrs-application");
}
if (configuration != null) {
LOGGER.info("Registered JAX-RS Configuration: " + configuration);
}
final String base = getAddress(contextRoot);
final String nopath;
if (base.endsWith("/") && prefix.startsWith("/")) {
nopath = base + prefix.substring(1);
} else {
nopath = base + prefix;
}
final RsHttpListener listener = createHttpListener();
final String host = findHost(contextRoot, appInfo.webApps);
final RsRegistry.AddressInfo address = rsRegistry.createRsHttpListener(appInfo.appId, contextRoot, listener, classLoader, nopath.substring(NOPATH_PREFIX.length() - 1), host, auth, realm);
services.add(new DeployedService(address.complete, contextRoot, application.getClass().getName(), appInfo.appId));
listener.deployApplication(application, address.complete.substring(0, address.complete.length() - wildcard.length()), nopath.substring(NOPATH_PREFIX.length(), nopath.length() - wildcard.length()), additionalProviders, restEjbs, // app config
classLoader, injections, context, owbCtx, // injection/webapp context
new ServiceConfiguration(configuration, appInfo.services)); // deployment config
}
private String findHost(final String context, final Collection<WebAppInfo> webs) {
for (final WebAppInfo web : webs) {
if (context.equals(web.contextRoot)) {
return web.host != null ? web.host : virtualHost;
}
}
return virtualHost;
}
private static String appPrefix(final WebAppInfo info, final Class<?> appClazz) {
StringBuilder builder = null;
// no annotation, try servlets
for (final ServletInfo s : info.servlets) {
if (s.mappings.isEmpty()) {
continue;
}
String mapping = null;
final String name = appClazz.getName();
if (name.equals(s.servletClass) || name.equals(s.servletName) || "javax.ws.rs.core.Application ".equals(s.servletName)) {
mapping = s.mappings.iterator().next();
} else {
for (final ParamValueInfo pvi : s.initParams) {
if ("javax.ws.rs.Application".equals(pvi.name) || Application.class.getName().equals(pvi.name)) {
mapping = s.mappings.iterator().next();
break;
}
}
}
if (mapping != null) {
if (mapping.endsWith("/*")) {
mapping = mapping.substring(0, mapping.length() - 2);
}
if (mapping.startsWith("/")) {
mapping = mapping.substring(1);
}
builder = new StringBuilder();
builder.append(mapping);
break;
}
}
if (builder != null) { // https://issues.apache.org/jira/browse/CXF-5702
return builder.toString();
}
// annotation
final ApplicationPath path = appClazz.getAnnotation(ApplicationPath.class);
if (path != null) {
String appPath = path.value();
if (appPath.endsWith("*")) {
appPath = appPath.substring(0, appPath.length() - 1);
}
builder = new StringBuilder();
if (appPath.startsWith("/")) {
builder.append(appPath.substring(1));
} else {
builder.append(appPath);
}
}
if (builder == null) {
return null;
}
return builder.toString();
}
private static <T> boolean isProvider(final Class<T> clazz) {
return MessageBodyReader.class.isAssignableFrom(clazz) ||
MessageBodyWriter.class.isAssignableFrom(clazz) ||
ParamConverter.class.isAssignableFrom(clazz) ||
ContainerRequestFilter.class.isAssignableFrom(clazz) ||
ContainerResponseFilter.class.isAssignableFrom(clazz) ||
ReaderInterceptor.class.isAssignableFrom(clazz) ||
WriterInterceptor.class.isAssignableFrom(clazz) ||
ParamConverterProvider.class.isAssignableFrom(clazz) ||
ContextResolver.class.isAssignableFrom(clazz) ||
Feature.class.isAssignableFrom(clazz) ||
new MetaAnnotatedClass<>(clazz).isAnnotationPresent(Provider.class);
}
private boolean hasEjbAndIsNotAManagedBean(final Map<String, EJBRestServiceInfo> restEjbs, final String clazz) {
return restEjbs.containsKey(clazz) && !BeanType.MANAGED.equals(restEjbs.get(clazz).context.getComponentType());
}
private boolean useDiscoveredProviders(final AppInfo appInfo) {
return useDiscoveredProviders(appInfo, true);
}
private boolean useDiscoveredProviders(final AppInfo appInfo, final boolean defaultValue) {
final String value = appInfo.properties.getProperty(OPENEJB_JAXRS_PROVIDERS_AUTO_PROP);
if (value != null) {
return "true".equalsIgnoreCase(value.trim());
}
return SystemInstance.get().getOptions().get(OPENEJB_JAXRS_PROVIDERS_AUTO_PROP, defaultValue);
}
private Collection<Object> appProviders(final Collection<String> jaxRsProviders, final ClassLoader classLoader) {
final Collection<Object> additionalProviders = new HashSet<>();
for (final String name : jaxRsProviders) {
try {
final Class<?> providerClass = classLoader.loadClass(name);
if (providerClass.getAnnotation(Deprecated.class) != null) {
continue;
}
additionalProviders.add(providerClass);
} catch (final ClassNotFoundException e) {
LOGGER.warning("can't load '" + name + "'", e);
}
}
return additionalProviders;
}
public void afterApplicationCreated(@Observes final AssemblerAfterApplicationCreated event) {
if (!enabled)
return;
final AppInfo appInfo = event.getApp();
if ("false".equalsIgnoreCase(appInfo.properties.getProperty("openejb.jaxrs.on", "true"))) {
return;
}
quickCheckIfOldDeploymentShouldBeUsedFromEjbConfig(appInfo);
if (deployedApplications.add(appInfo)) {
if (appInfo.webApps.size() == 0) {
final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
final ClassLoader appClassLoader = getClassLoader(containerSystem.getAppContext(appInfo.appId).getClassLoader());
Thread.currentThread().setContextClassLoader(appClassLoader);
try {
final Map<String, EJBRestServiceInfo> restEjbs = getRestEjbs(appInfo, null);
if (restEjbs.isEmpty()) {
return;
}
final Collection<Object> providers;
if (useDiscoveredProviders(appInfo)) {
providers = appProviders(appInfo.jaxRsProviders, appClassLoader);
} else {
providers = new ArrayList<>();
}
if ("true".equalsIgnoreCase(appInfo.properties.getProperty(OPENEJB_USE_APPLICATION_PROPERTY, APPLICATION_DEPLOYMENT))) {
final Application application = new InternalApplication(null);
addEjbToApplication(application, restEjbs);
// merge configurations at app level since a single deployment is available
final List<IdPropertiesInfo> pojoConfigurations = new ArrayList<>();
BeanContext comp = null;
for (final EjbJarInfo ejbJar : appInfo.ejbJars) {
for (final EnterpriseBeanInfo bean : ejbJar.enterpriseBeans) {
if (comp != null) {
break;
}
if (bean.ejbClass.equals(BeanContext.Comp.class.getName())) {
comp = containerSystem.getBeanContext(bean.ejbDeploymentId);
break;
}
}
if (ejbJar.pojoConfigurations != null) {
pojoConfigurations.addAll(ejbJar.pojoConfigurations);
}
}
if (appInfo.pojoConfigurations != null) {
pojoConfigurations.addAll(appInfo.pojoConfigurations);
}
final Map.Entry<String, EJBRestServiceInfo> next = restEjbs.entrySet().iterator().next();
if (comp == null) {
comp = next.getValue().context;
}
deployApplication(appInfo, next.getValue().path, restEjbs, comp.getClassLoader(), comp.getInjections(),
containerSystem.getAppContext(appInfo.appId).getWebBeansContext(), comp.getJndiContext(),
providers, pojoConfigurations, application, wildcard);
} else {
for (final Map.Entry<String, EJBRestServiceInfo> ejb : restEjbs.entrySet()) {
final BeanContext ctx = ejb.getValue().context;
if (BeanType.MANAGED.equals(ctx.getComponentType())) {
deployPojo(appInfo.appId, "", ejb.getValue().path, ctx.getBeanClass(), null, ctx.getClassLoader(), ctx.getInjections(),
ctx.getJndiContext(),
containerSystem.getAppContext(appInfo.appId).getWebBeansContext(),
providers, new ServiceConfiguration(ctx.getProperties(), appInfo.services));
} else {
deployEJB(appInfo.appId, "", ejb.getValue().path, ctx, providers, appInfo.services);
}
}
}
} finally {
Thread.currentThread().setContextClassLoader(oldLoader);
}
} else {
for (final WebAppInfo webApp : appInfo.webApps) {
afterApplicationCreated(appInfo, webApp);
}
}
}
}
private void quickCheckIfOldDeploymentShouldBeUsedFromEjbConfig(final AppInfo appInfo) {
// if forced don't update anything
if (appInfo.properties.getProperty(OPENEJB_USE_APPLICATION_PROPERTY) != null) {
return;
}
for (final EjbJarInfo ejbJar : appInfo.ejbJars) {
for (final EnterpriseBeanInfo bean : ejbJar.enterpriseBeans) {
if (bean.restService) {
final BeanContext beanContext = containerSystem.getBeanContext(bean.ejbDeploymentId);
if (beanContext == null) { // ear
continue;
}
if (containsJaxRsConfiguration(beanContext.getProperties())) {
appInfo.properties.setProperty(OPENEJB_USE_APPLICATION_PROPERTY, "false");
logOldDeploymentUsage(bean.ejbClass);
return; // no need to look further
}
}
}
}
}
protected abstract boolean containsJaxRsConfiguration(final Properties properties);
protected Map<String, EJBRestServiceInfo> getRestEjbs(final AppInfo appInfo, final String webapp) {
final Map<String, BeanContext> beanContexts = new HashMap<>();
for (final EjbJarInfo ejbJar : appInfo.ejbJars) {
if (ejbJar.webapp && webapp != null && !ejbJar.moduleId.equals(webapp)) {
continue;
}
for (final EnterpriseBeanInfo bean : ejbJar.enterpriseBeans) {
if (bean.restService) {
final BeanContext beanContext = containerSystem.getBeanContext(bean.ejbDeploymentId);
if (beanContext == null) {
continue;
}
beanContexts.put(bean.ejbClass, beanContext);
}
}
}
final Map<String, EJBRestServiceInfo> restEjbs = new HashMap<>();
for (final WebAppInfo webApp : appInfo.webApps) {
for (final String ejb : webApp.ejbRestServices) {
if (beanContexts.containsKey(ejb)) {
restEjbs.put(ejb, new EJBRestServiceInfo(webApp.contextRoot, beanContexts.get(ejb)));
} // else ear probably
}
}
for (final Map.Entry<String, BeanContext> ejbs : beanContexts.entrySet()) {
final String clazz = ejbs.getKey();
if (!restEjbs.containsKey(clazz)) {
// null is important, it means there is no webroot path in standalone
String context = null;
if (!OLD_WEBSERVICE_DEPLOYMENT) {
if (appInfo.appId != null && !appInfo.appId.isEmpty()) {
context = appInfo.appId;
} else {
context = ejbs.getValue().getModuleName();
}
}
restEjbs.put(clazz, new EJBRestServiceInfo(context, beanContexts.get(clazz)));
}
}
beanContexts.clear();
return restEjbs;
}
private void deploySingleton(final String appId, final String web, final String contextRoot, final Object o, final Application appInstance, final ClassLoader classLoader,
final Collection<Object> additionalProviders, final ServiceConfiguration configuration) {
final String nopath = getAddress(contextRoot, o.getClass());
final RsHttpListener listener = createHttpListener();
final RsRegistry.AddressInfo address = rsRegistry.createRsHttpListener(appId, web, listener, classLoader, nopath.substring(NOPATH_PREFIX.length() - 1), virtualHost, auth, realm);
services.add(new DeployedService(address.complete, web, o.getClass().getName(), appId));
listener.deploySingleton(contextRoot, getFullContext(address.base, contextRoot), o, appInstance, additionalProviders, configuration);
LOGGER.info("deployed REST singleton: " + o);
}
private void deployPojo(final String appId, final String web, final String contextRoot, final Class<?> loadedClazz, final Application app,
final ClassLoader classLoader, final Collection<Injection> injections,
final Context context, final WebBeansContext owbCtx, final Collection<Object> additionalProviders, final ServiceConfiguration config) {
if (loadedClazz.isInterface()) {
return;
}
final String nopath = getAddress(contextRoot, loadedClazz);
final RsHttpListener listener = createHttpListener();
final RsRegistry.AddressInfo address = rsRegistry.createRsHttpListener(appId, web, listener, classLoader, nopath.substring(NOPATH_PREFIX.length() - 1), virtualHost, auth, realm);
services.add(new DeployedService(address.complete, contextRoot, loadedClazz.getName(), appId));
listener.deployPojo(classLoader, contextRoot, getFullContext(address.base, contextRoot), loadedClazz, app, injections, context, owbCtx,
additionalProviders, config);
LOGGER.info("REST Service: " + address.complete + " -> Pojo " + loadedClazz.getName());
}
private void deployEJB(final String appId, final String web, final String context, final BeanContext beanContext, final Collection<Object> additionalProviders,
final Collection<ServiceInfo> serviceInfos) {
final String nopath = getAddress(context, beanContext.getBeanClass());
final RsHttpListener listener = createHttpListener();
final RsRegistry.AddressInfo address = rsRegistry.createRsHttpListener(appId, web, listener, beanContext.getClassLoader(), nopath.substring(NOPATH_PREFIX.length() - 1), virtualHost, auth, realm);
services.add(new DeployedService(address.complete, context, beanContext.getBeanClass().getName(), appId));
listener.deployEJB(context, getFullContext(address.base, context), beanContext,
additionalProviders, new ServiceConfiguration(beanContext.getProperties(), serviceInfos));
LOGGER.info("REST Service: " + address.complete + " -> EJB " + beanContext.getEjbName());
}
/**
* It creates the service container (http listener).
*
* @return the service container
*/
protected abstract RsHttpListener createHttpListener();
private static String getFullContext(final String address, final String context) {
if (context == null) {
return address;
}
if (context.isEmpty() && address.contains("/")) {
return address.substring(0, address.lastIndexOf("/"));
}
// context can get the app path too
// so keep only web context without /
String webCtx = context;
while (webCtx.startsWith("/")) {
webCtx = webCtx.substring(1);
}
// get root path ending with /
try {
final URL url = new URL(address);
final int port = url.getPort();
if (port > 0) {
return url.getProtocol() + "://" + url.getHost() + ":" + port + "/" + webCtx;
} else {
return url.getProtocol() + "://" + url.getHost() + "/" + webCtx;
}
} catch (final MalformedURLException e) {
throw new OpenEJBRestRuntimeException("bad url: " + address, e);
}
}
private Class<?> findPath(final Class<?> clazz) {
Class<?> usedClass = clazz;
while (usedClass.getAnnotation(Path.class) == null && usedClass.getSuperclass() != null) {
usedClass = usedClass.getSuperclass();
}
return usedClass;
}
private String getAddress(final String context, final Class<?> clazz) {
String root = getAddress(context);
Class<?> usedClass = findPath(clazz);
if (usedClass == null || Object.class.equals(usedClass)) { // try interfaces
final Class<?>[] itfs = clazz.getInterfaces();
if (itfs != null) {
for (final Class<?> c : itfs) {
usedClass = findPath(c);
if (usedClass.getAnnotation(Path.class) != null) {
break;
}
}
}
}
if (usedClass == null || usedClass.getAnnotation(Path.class) == null) {
throw new IllegalArgumentException("no @Path annotation on " + clazz.getName());
}
String builtUrl = null;
try {
builtUrl = UriBuilder.fromUri(new URI(root)).path(usedClass).build().toURL().toString();
return replaceParams(builtUrl); // pathparam at class level
} catch (final IllegalArgumentException iae) {
if (builtUrl != null) {
return builtUrl;
}
// try to do it manually with @Path on the class
Class<?> current = usedClass;
while (current != null && !Object.class.equals(current)) {
final Path path = current.getAnnotation(Path.class);
if (path != null) {
String classPath = path.value();
if (classPath.startsWith("/")) {
classPath = classPath.substring(1);
}
if (!root.endsWith("/")) {
root = root + "/";
}
return replaceParams(root + classPath);
}
current = current.getSuperclass();
}
throw new OpenEJBRestRuntimeException("can't built the service mapping for service '" + usedClass.getName() + "'", iae);
} catch (final MalformedURLException e) {
throw new OpenEJBRestRuntimeException("url is malformed", e);
} catch (final URISyntaxException e) {
throw new OpenEJBRestRuntimeException("uri syntax is not correct", e);
}
}
private String getAddress(final String context) {
String root = NOPATH_PREFIX;
if (context != null) {
if (context.startsWith("/")) {
root += context.substring(1);
} else {
root += context;
}
}
return root;
}
// this mean not really conflicting mappings (rest/servlet and so on) can be conflicting
// a good solution is to handle a unique rest servlet managing the routing
private String replaceParams(final String url) {
final String managedUrl = url.replaceAll("\\{[^}]*\\}.*", wildcard);
if (managedUrl.endsWith(wildcard)) {
return managedUrl;
}
return managedUrl + "/" + wildcard;
}
private void undeployRestObject(final String appId, final String context) {
HttpListener listener = rsRegistry.removeListener(appId, context);
if (listener != null) {
if (BasicAuthHttpListenerWrapper.class.isInstance(listener)) {
listener = BasicAuthHttpListenerWrapper.class.cast(listener).getHttpListener();
}
checkUndeploy(listener);
}
}
private void checkUndeploy(final HttpListener listener) {
if (RsHttpListener.class.isInstance(listener)) {
RsHttpListener.class.cast(listener).undeploy();
}
}
private static ClassLoader getClassLoader(final ClassLoader classLoader) {
ClassLoader cl = classLoader;
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
}
if (cl == null) {
cl = RESTService.class.getClassLoader();
}
return cl;
}
public void undeploy(@Observes final AssemblerBeforeApplicationDestroyed event) {
final AppInfo app = event.getApp();
final boolean removed = deployedApplications.remove(app);
if (removed) {
for (final WebAppInfo webApp : app.webApps) {
final List<DeployedService> toRemove = new ArrayList<>();
for (final DeployedService service : services) {
if (service.isInWebApp(app.appId, webApp)) {
undeployRestObject(app.appId, service.address);
toRemove.add(service);
}
}
services.removeAll(toRemove);
deployedWebApps.remove(webApp);
}
}
}
@Override
public void start() throws ServiceException {
SystemInstance.get().setComponent(RESTService.class, this);
beforeStart();
containerSystem = (CoreContainerSystem) SystemInstance.get().getComponent(ContainerSystem.class);
assembler = SystemInstance.get().getComponent(Assembler.class);
if (assembler != null) {
SystemInstance.get().addObserver(this);
for (final AppInfo appInfo : assembler.getDeployedApplications()) {
final AppContext appContext = containerSystem.getAppContext(appInfo.appId);
afterApplicationCreated(new AssemblerAfterApplicationCreated(appInfo, appContext, null));
}
}
}
protected void beforeStart() {
rsRegistry = SystemInstance.get().getComponent(RsRegistry.class);
if (rsRegistry == null && SystemInstance.get().getComponent(HttpListenerRegistry.class) != null) {
rsRegistry = new RsRegistryImpl();
}
}
@Override
public void stop() throws ServiceException {
if (assembler != null) {
SystemInstance.get().removeObserver(this);
for (final AppInfo appInfo : new ArrayList<>(deployedApplications)) {
undeploy(new AssemblerBeforeApplicationDestroyed(appInfo, null));
}
}
for (final DeployedService service : services) {
undeployRestObject(service.appId, service.address);
}
}
@Override
public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException {
throw new UnsupportedOperationException(getClass().getName() + " cannot be invoked directly");
}
@Override
public void service(final Socket socket) throws ServiceException, IOException {
throw new UnsupportedOperationException(getClass().getName() + " cannot be invoked directly");
}
@Override
public String getIP() {
return IP;
}
@Override
public int getPort() {
return PORT;
}
@Override
public void init(final Properties props) throws Exception {
if (props == null) {
return;
}
virtualHost = props.getProperty("virtualHost", "localhost");
auth = props.getProperty("auth", "NONE");
realm = props.getProperty("realm", "PropertiesLogin");
enabled = ServiceManager.isEnabled(props);
}
public String getVirtualHost() {
return virtualHost;
}
public void setVirtualHost(final String virtualHost) {
this.virtualHost = virtualHost;
}
public String getAuth() {
return auth;
}
public void setAuth(final String auth) {
this.auth = auth;
}
public String getRealm() {
return realm;
}
public void setRealm(final String realm) {
this.realm = realm;
}
// look WebServiceHelperImpl before updating it
public static class DeployedService {
public String address;
public String webapp;
public String origin;
public String appId;
public DeployedService(final String address, final String webapp, final String origin, final String appId) {
this.address = address;
this.webapp = webapp;
this.origin = origin;
this.appId = appId;
}
public boolean isInWebApp(final String appId, final WebAppInfo webApp) { // we support paralell deployments so we need app (versionned) + webapp check
final boolean appTest = !(appId == null && this.appId != null) && !(appId != null && !appId.equals(this.appId));
return appTest && cleanWeb(webapp).equals(cleanWeb(webApp.contextRoot));
}
private String cleanWeb(final String s) {
if (s == null) {
return "";
}
if (s.startsWith("/")) {
return cleanWeb(s.substring(1));
}
final int i = s.indexOf("##");
if (i > 0) {
return cleanWeb(s.substring(0, i));
}
if ("ROOT".equals(s)) {
return "";
}
return s;
}
}
public List<DeployedService> getServices() {
return services;
}
public String getWildcard() {
return wildcard;
}
}