org.codehaus.enunciate.modules.jboss.JBossDeploymentModule Maven / Gradle / Ivy
/*
* Copyright 2006-2008 Web Cohesion
*
* Licensed 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.codehaus.enunciate.modules.jboss;
import com.sun.mirror.declaration.TypeDeclaration;
import freemarker.template.TemplateException;
import org.apache.commons.digester.RuleSet;
import org.codehaus.enunciate.EnunciateException;
import org.codehaus.enunciate.apt.EnunciateClasspathListener;
import org.codehaus.enunciate.apt.EnunciateFreemarkerModel;
import org.codehaus.enunciate.config.EnunciateConfiguration;
import org.codehaus.enunciate.config.WsdlInfo;
import org.codehaus.enunciate.config.war.WebAppConfig;
import org.codehaus.enunciate.contract.jaxrs.ResourceMethod;
import org.codehaus.enunciate.contract.jaxrs.RootResource;
import org.codehaus.enunciate.contract.jaxws.EndpointInterface;
import org.codehaus.enunciate.contract.validation.ValidationException;
import org.codehaus.enunciate.contract.validation.Validator;
import org.codehaus.enunciate.jboss.EnunciateJBossHttpServletDispatcher;
import org.codehaus.enunciate.main.Enunciate;
import org.codehaus.enunciate.main.webapp.BaseWebAppFragment;
import org.codehaus.enunciate.main.webapp.WebAppComponent;
import org.codehaus.enunciate.modules.FreemarkerDeploymentModule;
import org.codehaus.enunciate.modules.SpecProviderModule;
import org.codehaus.enunciate.modules.jboss.config.JBossRuleSet;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.util.*;
/**
* JBoss Module
*
* The JBoss module assembles a JBoss-based server-side application for hosting the WS endpoints.
*
*
* - steps
* - configuration
* - artifacts
*
*
* Steps
*
* generate
*
* The "generate" step generates the configuration files.
*
* Configuration
*
* Content Negotiation
*
* Enuncite provides content type negotiation (conneg) to Jersey that conforms to the content type negotiation of
* the Enunciate REST module. This means that each resource is mounted from the REST subcontext (see above) but ALSO from a subcontext that conforms to the
* id of each content type that the resource supports. So, if the content type id of the "application/xml" content type is "xml" then the resource at path
* "mypath" will be mounted at both "/rest/mypath" and "/xml/mypath".
*
* The content types for each JAX-RS resource are declared by the @Produces annotation. The content type ids are customized with the
* "enunciate/services/rest/content-types" element in the Enunciate configuration. Enunciate supplies providers for the "application/xml" and "application/json"
* content types by default.
*
* The JBoss module supports the following configuration attributes:
*
*
* - The "useSubcontext" attribute is used to enable/disable mounting the JAX-RS resources at the rest subcontext. Default: "true".
* - The "usePathBasedConneg" attribute is used to enable/disable path-based conneg (see above). Default: "false".
* - The "enableJaxws" attribute (boolean) can be used to disable the JAX-WS support, leaving the JAX-WS support to another module if necessary. Default: true
* - The "enableJaxrs" attribute (boolean) can be used to disable the JAX-RS (RESTEasy) support, leaving the JAX-RS support to another module if necessary. Default: true
*
*
* The JBoss module also supports a list of option child elements that each support a 'name' and 'value' attribute. This can be used to configure the RESTEasy
* mechanism, and the properties will be passed along as context parameters.
* See the RESTEasy docs for details.
*
* Artifacts
*
* The JBoss deployment module exports no artifacts.
*
* @author Ryan Heaton
* @docFileName module_jboss.html
*/
public class JBossDeploymentModule extends FreemarkerDeploymentModule implements EnunciateClasspathListener, SpecProviderModule {
private boolean enableJaxrs = true;
private boolean enableJaxws = true;
private boolean useSubcontext = true;
private boolean jacksonAvailable = false;
private boolean usePathBasedConneg = false;
private final Map options = new TreeMap();
/**
* @return "jboss"
*/
@Override
public String getName() {
return "jboss";
}
// Inherited.
public void onClassesFound(Set classes) {
jacksonAvailable |= classes.contains("org.jboss.resteasy.plugins.providers.jackson.ResteasyJacksonProvider");
}
// Inherited.
@Override
public void initModel(EnunciateFreemarkerModel model) {
super.initModel(model);
if (!isDisabled()) {
if (enableJaxrs) {
Map contentTypes2Ids = model.getContentTypesToIds();
if (getEnunciate().isModuleEnabled("amf")) { //if the amf module is enabled, we'll add amf rest endpoints.
contentTypes2Ids.put("application/x-amf", "amf");
}
else {
debug("AMF module has been disabled, so it's assumed the REST endpoints won't be available in AMF format.");
}
if (jacksonAvailable) {
contentTypes2Ids.put("application/json", "json"); //if we can load jackson, we've got json.
}
else {
debug("Couldn't find Jackson on the classpath, so it's assumed the REST endpoints aren't available in JSON format.");
}
for (RootResource resource : model.getRootResources()) {
for (ResourceMethod resourceMethod : resource.getResourceMethods(true)) {
Map> subcontextsByContentType = new HashMap>();
String subcontext = this.useSubcontext ? getRestSubcontext() : "";
debug("Resource method %s of resource %s to be made accessible at subcontext \"%s\".",
resourceMethod.getSimpleName(), resourceMethod.getParent().getQualifiedName(), subcontext);
subcontextsByContentType.put(null, new TreeSet(Arrays.asList(subcontext)));
resourceMethod.putMetaData("defaultSubcontext", subcontext);
if (isUsePathBasedConneg()) {
for (String producesMime : resourceMethod.getProducesMime()) {
MediaType producesType = MediaType.valueOf(producesMime);
for (Map.Entry contentTypeToId : contentTypes2Ids.entrySet()) {
MediaType type = MediaType.valueOf(contentTypeToId.getKey());
if (producesType.isCompatible(type)) {
String id = '/' + contentTypeToId.getValue();
String fullpath = resourceMethod.getFullpath();
if (fullpath.startsWith(id) || fullpath.startsWith(contentTypeToId.getValue())) {
throw new ValidationException(resourceMethod.getPosition(), String.format("The path of this resource starts with \"%s\" and you've got path-based conneg enabled. So Enunciate can't tell whether a request for \"%s\" is a request for this resource or a request for the \"%s\" representation of resource \"%s\". You're going to have to either adjust the path of the resource or disable path-based conneg in the enunciate config (e.g. usePathBasedConneg=\"false\").", id, fullpath, id, fullpath.substring(fullpath.indexOf(contentTypeToId.getValue()) + contentTypeToId.getValue().length())));
}
debug("Resource method %s of resource %s to be made accessible at subcontext \"%s\" because it produces %s/%s.",
resourceMethod.getSimpleName(), resourceMethod.getParent().getQualifiedName(), id, producesType.getType(), producesType.getSubtype());
String contentTypeValue = String.format("%s/%s", type.getType(), type.getSubtype());
Set subcontextList = subcontextsByContentType.get(contentTypeValue);
if (subcontextList == null) {
subcontextList = new TreeSet();
subcontextsByContentType.put(contentTypeValue, subcontextList);
}
subcontextList.add(id);
}
}
}
}
resourceMethod.putMetaData("subcontexts", subcontextsByContentType);
}
}
}
if (enableJaxws) {
EnunciateConfiguration config = model.getEnunciateConfig();
for (WsdlInfo wsdlInfo : model.getNamespacesToWSDLs().values()) {
for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) {
String path = "/soap/" + ei.getServiceName();
if (config != null) {
path = config.getDefaultSoapSubcontext() + '/' + ei.getServiceName();
if (config.getSoapServices2Paths().containsKey(ei.getServiceName())) {
path = config.getSoapServices2Paths().get(ei.getServiceName());
}
}
ei.putMetaData("soapPath", path);
}
}
}
}
}
@Override
public void init(Enunciate enunciate) throws EnunciateException {
super.init(enunciate);
if (this.enableJaxws) {
enunciate.getConfig().setForceJAXWSSpecCompliance(true); //make sure the WSDL and client code are JAX-WS-compliant.
}
}
@Override
public void doFreemarkerGenerate() throws IOException, TemplateException {
WebAppConfig webAppConfig = enunciate.getConfig().getWebAppConfig();
if (webAppConfig == null) {
webAppConfig = new WebAppConfig();
enunciate.getConfig().setWebAppConfig(webAppConfig);
}
webAppConfig.addWebXmlAttribute("version", "3.0");
webAppConfig.addWebXmlAttribute("xmlns", "http://java.sun.com/xml/ns/javaee");
webAppConfig.addWebXmlAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
webAppConfig.addWebXmlAttribute("xsi:schemaLocation", "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd");
}
@Override
protected void doBuild() throws EnunciateException, IOException {
super.doBuild();
BaseWebAppFragment webappFragment = new BaseWebAppFragment(getName());
HashMap jbossContextParameters = new HashMap();
webappFragment.setContextParameters(jbossContextParameters);
ArrayList servlets = new ArrayList();
if (this.enableJaxws) {
for (WsdlInfo wsdlInfo : getModelInternal().getNamespacesToWSDLs().values()) {
for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) {
String path = (String) ei.getMetaData().get("soapPath");
WebAppComponent wsComponent = new WebAppComponent();
wsComponent.setName(ei.getServiceName());
wsComponent.setClassname(ei.getEndpointImplementations().iterator().next().getQualifiedName());
wsComponent.setUrlMappings(new TreeSet(Arrays.asList(path)));
servlets.add(wsComponent);
}
}
}
if (this.enableJaxrs) {
WebAppComponent jaxrsServletComponent = new WebAppComponent();
jaxrsServletComponent.setName("resteasy-jaxrs");
jaxrsServletComponent.setClassname(EnunciateJBossHttpServletDispatcher.class.getName());
TreeSet jaxrsUrlMappings = new TreeSet();
StringBuilder resources = new StringBuilder();
for (RootResource rootResource : getModel().getRootResources()) {
if (resources.length() > 0) {
resources.append(',');
}
resources.append(rootResource.getQualifiedName());
for (ResourceMethod resourceMethod : rootResource.getResourceMethods(true)) {
String resourceMethodPattern = resourceMethod.getServletPattern();
for (Set subcontextList : ((Map>) resourceMethod.getMetaData().get("subcontexts")).values()) {
for (String subcontext : subcontextList) {
String servletPattern;
if ("".equals(subcontext)) {
servletPattern = resourceMethodPattern;
}
else {
servletPattern = subcontext + resourceMethodPattern;
}
if (jaxrsUrlMappings.add(servletPattern)) {
debug("Resource method %s of resource %s to be made accessible by servlet pattern %s.",
resourceMethod.getSimpleName(), resourceMethod.getParent().getQualifiedName(), servletPattern);
}
}
}
}
}
if (jaxrsUrlMappings.contains("/*")) {
jaxrsUrlMappings.clear();
jaxrsUrlMappings.add("/*");
}
else {
Iterator iterator = jaxrsUrlMappings.iterator();
while (iterator.hasNext()) {
String mapping = iterator.next();
if (!mapping.endsWith("/*") && jaxrsUrlMappings.contains(mapping + "/*")) {
iterator.remove();
}
}
}
StringBuilder providers = new StringBuilder();
for (TypeDeclaration provider : getModel().getJAXRSProviders()) {
if (providers.length() > 0) {
providers.append(',');
}
providers.append(provider.getQualifiedName());
}
if (jacksonAvailable) {
if (providers.length() > 0) {
providers.append(',');
}
providers.append("org.codehaus.enunciate.jboss.ResteasyJacksonJaxbProvider");
}
if (getEnunciate().isModuleEnabled("amf")) {
if (providers.length() > 0) {
providers.append(',');
}
providers.append("org.codehaus.enunciate.modules.amf.JAXRSProvider");
}
jaxrsServletComponent.setUrlMappings(jaxrsUrlMappings);
jbossContextParameters.put(ResteasyContextParameters.RESTEASY_RESOURCES, resources.toString());
jbossContextParameters.put(ResteasyContextParameters.RESTEASY_PROVIDERS, providers.toString());
String mappingPrefix = this.useSubcontext ? getRestSubcontext() : "";
if (!"".equals(mappingPrefix)) {
jbossContextParameters.put("resteasy.servlet.mapping.prefix", mappingPrefix);
jaxrsServletComponent.addInitParam("resteasy.servlet.mapping.prefix", mappingPrefix);
}
if (isUsePathBasedConneg()) {
Map contentTypesToIds = getModelInternal().getContentTypesToIds();
if (contentTypesToIds != null && contentTypesToIds.size() > 0) {
StringBuilder builder = new StringBuilder();
Iterator> contentTypeIt = contentTypesToIds.entrySet().iterator();
while (contentTypeIt.hasNext()) {
Map.Entry contentTypeEntry = contentTypeIt.next();
builder.append(contentTypeEntry.getValue()).append(" : ").append(contentTypeEntry.getKey());
if (contentTypeIt.hasNext()) {
builder.append(", ");
}
}
jbossContextParameters.put(ResteasyContextParameters.RESTEASY_MEDIA_TYPE_MAPPINGS, builder.toString());
}
}
jbossContextParameters.put(ResteasyContextParameters.RESTEASY_SCAN_RESOURCES, Boolean.FALSE.toString());
servlets.add(jaxrsServletComponent);
}
webappFragment.setServlets(servlets);
if (!this.options.isEmpty()) {
webappFragment.setContextParameters(this.options);
}
getEnunciate().addWebAppFragment(webappFragment);
}
@Override
public Validator getValidator() {
return this.enableJaxws ? new JBossValidator() : super.getValidator();
}
// Inherited.
public boolean isJaxwsProvider() {
return this.enableJaxws;
}
// Inherited.
public boolean isJaxrsProvider() {
return this.enableJaxrs;
}
public void setEnableJaxrs(boolean enableJaxrs) {
this.enableJaxrs = enableJaxrs;
}
public void setEnableJaxws(boolean enableJaxws) {
this.enableJaxws = enableJaxws;
}
/**
* Whether to use path-based conneg.
*
* @return Whether to use path-based conneg.
*/
public boolean isUsePathBasedConneg() {
return usePathBasedConneg;
}
/**
* Whether to use path-based conneg.
*
* @param usePathBasedConneg Whether to use path-based conneg.
*/
public void setUsePathBasedConneg(boolean usePathBasedConneg) {
this.usePathBasedConneg = usePathBasedConneg;
}
/**
* Whether to use the REST subcontext.
*
* @param useSubcontext Whether to use the REST subcontext.
*/
public void setUseSubcontext(boolean useSubcontext) {
this.useSubcontext = useSubcontext;
}
protected String getRestSubcontext() {
String restSubcontext = getEnunciate().getConfig().getDefaultRestSubcontext();
//todo: override default rest subcontext?
return restSubcontext;
}
public void addOption(String name, String value) {
this.options.put(name, value);
}
@Override
public RuleSet getConfigurationRules() {
return new JBossRuleSet();
}
// Inherited.
@Override
public boolean isDisabled() {
if (super.isDisabled()) {
return true;
}
else if (getModelInternal() != null) {
if (getModelInternal().getRootResources().isEmpty()) {
debug("CXF module is disabled because there are no root resources to process.");
return true;
}
else if (getModelInternal().getEnunciateConfig() != null && getModelInternal().getEnunciateConfig().getWebAppConfig() != null && getModelInternal().getEnunciateConfig().getWebAppConfig().isDisabled()) {
debug("Module '%s' is disabled because the web application processing has been disabled.", getName());
return true;
}
}
return false;
}
}