Java tutorial
/* * 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.*; /** * <h1>JBoss Module</h1> * * <p>The JBoss module assembles a JBoss-based server-side application for hosting the WS endpoints.</p> * * <ul> * <li><a href="#steps">steps</a></li> * <li><a href="#config">configuration</a></li> * <li><a href="#artifacts">artifacts</a></li> * </ul> * * <h1><a name="steps">Steps</a></h1> * * <h3>generate</h3> * * <p>The "generate" step generates the configuration files.</p> * * <h1><a name="config">Configuration</a></h1> * * <h3>Content Negotiation</h3> * * <p>Enuncite provides content type negotiation (conneg) to Jersey that conforms to the <a href="module_rest.html#contentTypes">content type negotiation of * the Enunciate REST module</a>. 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".</p> * * <p>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.</p> * * <p>The JBoss module supports the following configuration attributes:</p> * * <ul> * <li>The "useSubcontext" attribute is used to enable/disable mounting the JAX-RS resources at the rest subcontext. Default: "true".</li> * <li>The "usePathBasedConneg" attribute is used to enable/disable path-based conneg (see above). Default: "false".</a></li> * <li>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</li> * <li>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</li> * </ul> * * <p>The JBoss module also supports a list of <tt>option</tt> 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. * <a href="http://docs.jboss.org/resteasy/docs/2.0.0.GA/userguide/html/Installation_Configuration.html#d0e72">See the RESTEasy docs for details</a>.</p> * * <h1><a name="artifacts">Artifacts</a></h1> * * <p>The JBoss deployment module exports no artifacts.</p> * * @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<String, String> options = new TreeMap<String, String>(); /** * @return "jboss" */ @Override public String getName() { return "jboss"; } // Inherited. public void onClassesFound(Set<String> 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<String, String> 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<String, Set<String>> subcontextsByContentType = new HashMap<String, Set<String>>(); 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<String>(Arrays.asList(subcontext))); resourceMethod.putMetaData("defaultSubcontext", subcontext); if (isUsePathBasedConneg()) { for (String producesMime : resourceMethod.getProducesMime()) { MediaType producesType = MediaType.valueOf(producesMime); for (Map.Entry<String, String> 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<String> subcontextList = subcontextsByContentType.get(contentTypeValue); if (subcontextList == null) { subcontextList = new TreeSet<String>(); 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<String, String> jbossContextParameters = new HashMap<String, String>(); webappFragment.setContextParameters(jbossContextParameters); ArrayList<WebAppComponent> servlets = new ArrayList<WebAppComponent>(); 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<String>(Arrays.asList(path))); servlets.add(wsComponent); } } } if (this.enableJaxrs) { WebAppComponent jaxrsServletComponent = new WebAppComponent(); jaxrsServletComponent.setName("resteasy-jaxrs"); jaxrsServletComponent.setClassname(EnunciateJBossHttpServletDispatcher.class.getName()); TreeSet<String> jaxrsUrlMappings = new TreeSet<String>(); 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<String> subcontextList : ((Map<String, Set<String>>) 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<String> 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<String, String> contentTypesToIds = getModelInternal().getContentTypesToIds(); if (contentTypesToIds != null && contentTypesToIds.size() > 0) { StringBuilder builder = new StringBuilder(); Iterator<Map.Entry<String, String>> contentTypeIt = contentTypesToIds.entrySet().iterator(); while (contentTypeIt.hasNext()) { Map.Entry<String, String> 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; } }