/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.jaxrs.deployment;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.jboss.as.controller.PathElement;
import org.jboss.as.jaxrs.DeploymentRestResourcesDefintion;
import org.jboss.as.jaxrs.JaxrsAnnotations;
import org.jboss.as.jaxrs.JaxrsExtension;
import org.jboss.as.jaxrs.logging.JaxrsLogger;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentResourceSupport;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.annotation.CompositeIndex;
import org.jboss.as.web.common.WarMetaData;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.FilterMetaData;
import org.jboss.metadata.web.spec.ServletMetaData;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrapClasses;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import static org.jboss.as.jaxrs.logging.JaxrsLogger.JAXRS_LOGGER;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN_PROVIDERS;
import static org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters.RESTEASY_SCAN_RESOURCES;
/**
* Processor that finds jax-rs classes in the deployment
*
* @author Stuart Douglas
*/
public class JaxrsScanningProcessor implements DeploymentUnitProcessor {
private static final DotName DECORATOR = DotName.createSimple("javax.decorator.Decorator");
public static final DotName APPLICATION = DotName.createSimple(Application.class.getName());
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
if (!JaxrsDeploymentMarker.isJaxrsDeployment(deploymentUnit)) {
return;
}
final DeploymentUnit parent = deploymentUnit.getParent() == null ? deploymentUnit : deploymentUnit.getParent();
final Map<ModuleIdentifier, ResteasyDeploymentData> deploymentData;
if (deploymentUnit.getParent() == null) {
deploymentData = Collections.synchronizedMap(new HashMap<ModuleIdentifier, ResteasyDeploymentData>());
deploymentUnit.putAttachment(JaxrsAttachments.ADDITIONAL_RESTEASY_DEPLOYMENT_DATA, deploymentData);
} else {
deploymentData = parent.getAttachment(JaxrsAttachments.ADDITIONAL_RESTEASY_DEPLOYMENT_DATA);
}
final ModuleIdentifier moduleIdentifier = deploymentUnit.getAttachment(Attachments.MODULE_IDENTIFIER);
ResteasyDeploymentData resteasyDeploymentData = new ResteasyDeploymentData();
final WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
try {
if (warMetaData == null) {
resteasyDeploymentData.setScanAll(true);
scan(deploymentUnit, module.getClassLoader(), resteasyDeploymentData);
deploymentData.put(moduleIdentifier, resteasyDeploymentData);
} else {
scanWebDeployment(deploymentUnit, warMetaData.getMergedJBossWebMetaData(), module.getClassLoader(), resteasyDeploymentData);
scan(deploymentUnit, module.getClassLoader(), resteasyDeploymentData);
// When BootStrap classes are present and no Application subclass declared
// must check context param for Application subclass declaration
if (resteasyDeploymentData.getScannedResourceClasses().isEmpty() &&
!resteasyDeploymentData.isDispatcherCreated() &&
hasBootClasses(warMetaData.getMergedJBossWebMetaData())) {
checkOtherParams(deploymentUnit, warMetaData.getMergedJBossWebMetaData(), module.getClassLoader(), resteasyDeploymentData);
}
}
deploymentUnit.putAttachment(JaxrsAttachments.RESTEASY_DEPLOYMENT_DATA, resteasyDeploymentData);
List<String> rootRestClasses = new ArrayList<>(resteasyDeploymentData.getScannedResourceClasses());
Collections.sort(rootRestClasses);
for(String cls: rootRestClasses) {
addManagement(deploymentUnit, cls);
}
} catch (ModuleLoadException e) {
throw new DeploymentUnitProcessingException(e);
}
}
private void addManagement(DeploymentUnit deploymentUnit, String componentClass) {
try {
final DeploymentResourceSupport deploymentResourceSupport = deploymentUnit.getAttachment(org.jboss.as.server.deployment.Attachments.DEPLOYMENT_RESOURCE_SUPPORT);
deploymentResourceSupport.getDeploymentSubModel(JaxrsExtension.SUBSYSTEM_NAME, PathElement.pathElement(DeploymentRestResourcesDefintion.REST_RESOURCE_NAME, componentClass));
} catch (Exception e) {
JaxrsLogger.JAXRS_LOGGER.failedToRegisterManagementViewForRESTResources(componentClass, e);
}
}
private void checkOtherParams(final DeploymentUnit du,
final JBossWebMetaData webdata,
final ClassLoader classLoader,
final ResteasyDeploymentData resteasyDeploymentData)
throws DeploymentUnitProcessingException{
HashSet<String> appClazzList = new HashSet<>();
List<ParamValueMetaData> contextParamList = webdata.getContextParams();
if (contextParamList !=null) {
for(ParamValueMetaData param: contextParamList) {
if ("javax.ws.rs.core.Application".equals(param.getParamName())) {
appClazzList.add(param.getParamValue());
}
}
}
if (webdata.getServlets() != null) {
for (ServletMetaData servlet : webdata.getServlets()) {
List<ParamValueMetaData> initParamList = servlet.getInitParam();
if (initParamList != null) {
for(ParamValueMetaData param: initParamList) {
if ("javax.ws.rs.core.Application".equals(param.getParamName())) {
appClazzList.add(param.getParamValue());
}
}
}
}
}
processDeclaredApplicationClasses(du, appClazzList, webdata, classLoader, resteasyDeploymentData);
}
private void processDeclaredApplicationClasses(final DeploymentUnit du,
final Set<String> appClazzList,
final JBossWebMetaData webdata,
final ClassLoader classLoader,
final ResteasyDeploymentData resteasyDeploymentData)
throws DeploymentUnitProcessingException {
final CompositeIndex index = du.getAttachment(Attachments.COMPOSITE_ANNOTATION_INDEX);
List<AnnotationInstance> resources = index.getAnnotations(JaxrsAnnotations.PATH.getDotName());
Map<String, ClassInfo> resourceMap = new HashMap<>(resources.size());
if (resources != null) {
for (AnnotationInstance a: resources) {
if (a.target() instanceof ClassInfo) {
resourceMap.put(((ClassInfo)a.target()).name().toString(),
(ClassInfo)a.target());
}
}
}
for (String clazzName: appClazzList) {
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(clazzName);
} catch (ClassNotFoundException e) {
throw new DeploymentUnitProcessingException(e);
}
if (Application.class.isAssignableFrom(clazz)) {
try {
Application appClazz = (Application) clazz.newInstance();
Set<Class<?>> declClazzs = appClazz.getClasses();
Set<Object> declSingletons = appClazz.getSingletons();
HashSet<Class<?>> clazzSet = new HashSet<>();
if (declClazzs != null) {
clazzSet.addAll(declClazzs);
}
if (declSingletons != null) {
for (Object obj : declSingletons) {
clazzSet.add((Class) obj);
}
}
Set<String> scannedResourceClasses = resteasyDeploymentData.getScannedResourceClasses();
for (Class<?> cClazz : clazzSet) {
if (cClazz.isAnnotationPresent(javax.ws.rs.Path.class)) {
final ClassInfo info = resourceMap.get(cClazz.getName());
if (info != null) {
if (info.annotations().containsKey(DECORATOR)) {
//we do not add decorators as resources
//we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
continue;
}
if (!Modifier.isInterface(info.flags())) {
scannedResourceClasses.add(info.name().toString());
}
}
}
}
} catch (Exception e) {
JAXRS_LOGGER.cannotLoadApplicationClass(e);
}
}
}
}
@Override
public void undeploy(DeploymentUnit context) {
}
public static final Set<String> BOOT_CLASSES = new HashSet<String>();
static {
Collections.addAll(BOOT_CLASSES, ResteasyBootstrapClasses.BOOTSTRAP_CLASSES);
}
/**
* If any servlet/filter classes are declared, then we probably don't want to scan.
*/
protected boolean hasBootClasses(JBossWebMetaData webdata) throws DeploymentUnitProcessingException {
if (webdata.getServlets() != null) {
for (ServletMetaData servlet : webdata.getServlets()) {
String servletClass = servlet.getServletClass();
if (BOOT_CLASSES.contains(servletClass))
return true;
}
}
if (webdata.getFilters() != null) {
for (FilterMetaData filter : webdata.getFilters()) {
if (BOOT_CLASSES.contains(filter.getFilterClass()))
return true;
}
}
return false;
}
protected void scanWebDeployment(final DeploymentUnit du, final JBossWebMetaData webdata, final ClassLoader classLoader, final ResteasyDeploymentData resteasyDeploymentData) throws DeploymentUnitProcessingException {
// If there is a resteasy boot class in web.xml, then the default should be to not scan
// make sure this call happens before checkDeclaredApplicationClassAsServlet!!!
boolean hasBoot = hasBootClasses(webdata);
resteasyDeploymentData.setBootClasses(hasBoot);
Class<?> declaredApplicationClass = checkDeclaredApplicationClassAsServlet(webdata, classLoader);
// Assume that checkDeclaredApplicationClassAsServlet created the dispatcher
if (declaredApplicationClass != null) {
resteasyDeploymentData.setDispatcherCreated(true);
// Instigate creation of resteasy configuration switches for
// found provider and resource classes
resteasyDeploymentData.setScanProviders(true);
resteasyDeploymentData.setScanResources(true);
}
// set scanning on only if there are no boot classes
if (!hasBoot && !webdata.isMetadataComplete()) {
resteasyDeploymentData.setScanAll(true);
resteasyDeploymentData.setScanProviders(true);
resteasyDeploymentData.setScanResources(true);
}
// check resteasy configuration flags
List<ParamValueMetaData> contextParams = webdata.getContextParams();
if (contextParams != null) {
for (ParamValueMetaData param : contextParams) {
if (param.getParamName().equals(RESTEASY_SCAN)) {
resteasyDeploymentData.setScanAll(valueOf(RESTEASY_SCAN, param.getParamValue()));
} else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_SCAN_PROVIDERS)) {
resteasyDeploymentData.setScanProviders(valueOf(RESTEASY_SCAN_PROVIDERS, param.getParamValue()));
} else if (param.getParamName().equals(RESTEASY_SCAN_RESOURCES)) {
resteasyDeploymentData.setScanResources(valueOf(RESTEASY_SCAN_RESOURCES, param.getParamValue()));
} else if (param.getParamName().equals(ResteasyContextParameters.RESTEASY_UNWRAPPED_EXCEPTIONS)) {
resteasyDeploymentData.setUnwrappedExceptionsParameterSet(true);
}
}
}
}
protected void scan(final DeploymentUnit du, final ClassLoader classLoader, final ResteasyDeploymentData resteasyDeploymentData)
throws DeploymentUnitProcessingException, ModuleLoadException {
final CompositeIndex index = du.getAttachment(Attachments.COMPOSITE_ANNOTATION_INDEX);
if (!resteasyDeploymentData.shouldScan()) {
return;
}
if (!resteasyDeploymentData.isDispatcherCreated()) {
final Set<ClassInfo> applicationClasses = index.getAllKnownSubclasses(APPLICATION);
try {
for (ClassInfo c : applicationClasses) {
if (Modifier.isAbstract(c.flags())) continue;
@SuppressWarnings("unchecked")
Class<? extends Application> scanned = (Class<? extends Application>) classLoader.loadClass(c.name().toString());
resteasyDeploymentData.getScannedApplicationClasses().add(scanned);
}
} catch (ClassNotFoundException e) {
throw JaxrsLogger.JAXRS_LOGGER.cannotLoadApplicationClass(e);
}
}
List<AnnotationInstance> resources = null;
List<AnnotationInstance> providers = null;
if (resteasyDeploymentData.isScanResources()) {
resources = index.getAnnotations(JaxrsAnnotations.PATH.getDotName());
}
if (resteasyDeploymentData.isScanProviders()) {
providers = index.getAnnotations(JaxrsAnnotations.PROVIDER.getDotName());
}
if ((resources == null || resources.isEmpty()) && (providers == null || providers.isEmpty()))
return;
final Set<ClassInfo> pathInterfaces = new HashSet<ClassInfo>();
if (resources != null) {
for (AnnotationInstance e : resources) {
final ClassInfo info;
if (e.target() instanceof ClassInfo) {
info = (ClassInfo) e.target();
} else if (e.target() instanceof MethodInfo) {
//ignore
continue;
} else {
JAXRS_LOGGER.classOrMethodAnnotationNotFound("@Path", e.target());
continue;
}
if(info.annotations().containsKey(DECORATOR)) {
//we do not add decorators as resources
//we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
continue;
}
if (!Modifier.isInterface(info.flags())) {
resteasyDeploymentData.getScannedResourceClasses().add(info.name().toString());
} else {
pathInterfaces.add(info);
}
}
}
if (providers != null) {
for (AnnotationInstance e : providers) {
if (e.target() instanceof ClassInfo) {
ClassInfo info = (ClassInfo) e.target();
if(info.annotations().containsKey(DECORATOR)) {
//we do not add decorators as providers
//we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
continue;
}
if (!Modifier.isInterface(info.flags())) {
resteasyDeploymentData.getScannedProviderClasses().add(info.name().toString());
}
} else {
JAXRS_LOGGER.classAnnotationNotFound("@Provider", e.target());
}
}
}
// look for all implementations of interfaces annotated @Path
for (final ClassInfo iface : pathInterfaces) {
final Set<ClassInfo> implementors = index.getAllKnownImplementors(iface.name());
for (final ClassInfo implementor : implementors) {
if(implementor.annotations().containsKey(DECORATOR)) {
//we do not add decorators as resources
//we can't pick up on programatically added decorators, but that is such an edge case it should not really matter
continue;
}
resteasyDeploymentData.getScannedResourceClasses().add(implementor.name().toString());
}
}
}
protected Class<?> checkDeclaredApplicationClassAsServlet(JBossWebMetaData webData,
ClassLoader classLoader) throws DeploymentUnitProcessingException {
if (webData.getServlets() == null)
return null;
for (ServletMetaData servlet : webData.getServlets()) {
String servletClass = servlet.getServletClass();
if (servletClass == null)
continue;
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(servletClass);
} catch (ClassNotFoundException e) {
throw new DeploymentUnitProcessingException(e);
}
if (Application.class.isAssignableFrom(clazz)) {
servlet.setServletClass(HttpServlet30Dispatcher.class.getName());
servlet.setAsyncSupported(true);
ParamValueMetaData param = new ParamValueMetaData();
param.setParamName("javax.ws.rs.Application");
param.setParamValue(servletClass);
List<ParamValueMetaData> params = servlet.getInitParam();
if (params == null) {
params = new ArrayList<ParamValueMetaData>();
servlet.setInitParam(params);
}
params.add(param);
return clazz;
}
}
return null;
}
private boolean valueOf(String paramName, String value) throws DeploymentUnitProcessingException {
if (value == null) {
throw JaxrsLogger.JAXRS_LOGGER.invalidParamValue(paramName, value);
}
if (value.toLowerCase(Locale.ENGLISH).equals("true")) {
return true;
} else if (value.toLowerCase(Locale.ENGLISH).equals("false")) {
return false;
} else {
throw JaxrsLogger.JAXRS_LOGGER.invalidParamValue(paramName, value);
}
}
}