Package org.codehaus.enunciate.modules.jersey

Source Code of org.codehaus.enunciate.modules.jersey.JerseyDeploymentModule

/*
* 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.jersey;

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.contract.jaxrs.ResourceMethod;
import org.codehaus.enunciate.contract.jaxrs.RootResource;
import org.codehaus.enunciate.contract.validation.ValidationException;
import org.codehaus.enunciate.contract.validation.Validator;
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.jersey.config.JerseyRuleSet;
import org.codehaus.enunciate.template.freemarker.ClassForNameMethod;

import javax.ws.rs.core.MediaType;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
* <h1>Jersey Module</h1>
*
* <p>The Jersey module generates and compiles the support files and classes necessary to support a REST application according to
* <a href="https://jsr311.dev.java.net/">JSR-311</a>, using <a href="https://jersey.dev.java.net/">Jersey</a>.</p>
*
* <ul>
* <li><a href="#app">Jersey Application</a></li>
* <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="app">Jersey Application</a></h1>
*
* <p>We direct you do the documentation for <a href="https://jsr311.dev.java.net/">JAX-RS</a> and <a href="https://jersey.dev.java.net/">Jersey</a> to
* learn how to build a REST application using these technologies. However, it is important to note a few idiosyncrasies of the Enunciate-supported
* Jersey application.</p>
*
* <h3>REST subcontext</h3>
*
* <p>Because the Jersey application is presumably deployed along with other Enunciate-supported applications (JAX-WS for SOAP, API documentation, etc.),
* it will, by default, be mounted at a specific subcontext as defined in the Enunciate configuration (attribute "defaultRestSubcontext" of the
* "enunciate/services/rest" element). This means that a JAX-RS resource applied at path "mypath" will actually be mounted at "rest/mypath", assuming
* that "rest" is the subcontext (which it is by default).</p>
*
* <p>While is it recommended that the subcontext be preserved, you can disable it in the <a href="#config">configuration</a> for this module. Note, however,
* that this increases the chance of the paths of your REST resources conflicting with the paths of your documentation, SOAP endpoints, etc.  Enunciate
* provides an additional check to see if a REST resource is too greedy because it has a <a href="https://jsr311.dev.java.net/nonav/javadoc/javax/ws/rs/Path.html">path
* parameter</a> in the first path segment.  This can also be disabled in configuration, but doing so will effectively disable the Enunciate-generated
* documentation and other web service endpoints.</p>
*
* <h3>Content Negotiation</h3>
*
* <p>Enuncite provides a special content negotiation (conneg) to Jersey such that 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". You can disable this path-based content
* negotiation feature by setting <tt>usePathBasedConneg="false"</tt>.</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>
*
* <h1><a name="steps">Steps</a></h1>
*
* <h3>generate</h3>
*
* <p>The generate step of the Jersey module generates the configuration files for a servlet-based Jersey application.</p>
*
* <h1><a name="config">Configuration</a></h1>
*
* <p>The Jersey module supports the following 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: "true".</a></li>
* <li>The "useWildcardServletMapping" attribute is used to tell Enunciate to use a wildcard to map to the jersey servlet. By default, Enunciate
* attempts to map each endpoint to a specific servlet mapping. Default: "false".</a></li>
* <li>The "disableWildcardServletError" attribute is used to enable/disable the Enunciate "wildcard" resource check. Default: "false".</a></li>
* <li>The "resourceProviderFactory" attribute is used to specify the fully-qualified classname of an instance of
* com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory that jersey will use. The default is the spring-based factory or the
* jersey default instance if spring isn't enabled.</a></li>
* <li>The "defaultNamespace" attribute is used to specify the default XML namespace. This namespace will have no prefix during XML serialization.</li>
* <li>The "loadOnStartup" attribute is used to specify the order in which the servlet is loaded on startup by the web application. By default, no order is specified.</li>
* </ul>
*
* <p>The Jersey module also supports an arbitrary number of "init-param" child elements that can be used to specify the init parameters (e.g.
* container request filters, etc.) of the Jersey servlet. The "init-param" element supports a "name" attribute and a "value" attribute.</p>
*
* <h1><a name="artifacts">Artifacts</a></h1>
*
* <p>The Jersey deployment module exports no artifacts.</p>
*
* @author Ryan Heaton
* @docFileName module_jersey.html
*/
public class JerseyDeploymentModule extends FreemarkerDeploymentModule implements EnunciateClasspathListener, SpecProviderModule {

  private boolean jacksonAvailable = false;
  private boolean useSubcontext = true;
  private boolean usePathBasedConneg = true;
  private boolean disableWildcardServletError = false;
  private boolean useWildcardServletMapping = false;
  private String resourceProviderFactory = null;
  private String applicationClass = null;
  private String defaultNamespace = null;
  private String loadOnStartup = null;
  private final Map<String, String> servletInitParams = new HashMap<String, String>();

  /**
   * @return "jersey"
   */
  @Override
  public String getName() {
    return "jersey";
  }

  /**
   * The root resources template URL.
   *
   * @return The root resources template URL.
   */
  public URL getRootResourceListTemplateURL() {
    return JerseyDeploymentModule.class.getResource("jaxrs-root-resources.list.fmt");
  }

  /**
   * The providers template URL.
   *
   * @return The providers template URL.
   */
  public URL getProvidersListTemplateURL() {
    return JerseyDeploymentModule.class.getResource("jaxrs-providers.list.fmt");
  }

  /**
   * The jaxb types template URL.
   *
   * @return The jaxb types template URL.
   */
  public URL getJaxbTypesTemplateURL() {
    return JerseyDeploymentModule.class.getResource("jaxrs-jaxb-types.list.fmt");
  }

  /**
   * @return A new {@link JerseyValidator}.
   */
  @Override
  public Validator getValidator() {
    return new JerseyValidator(isUseSubcontext() || !isDisableWildcardServletError());
  }

  @Override
  public void init(Enunciate enunciate) throws EnunciateException {
    super.init(enunciate);

    if (!isDisabled()) {
      enunciate.getConfig().addCustomResourceParameterAnnotation("com.sun.jersey.multipart.FormDataParam"); //support for multipart parameters
      enunciate.getConfig().addSystemResourceParameterAnnotation("com.sun.jersey.api.core.InjectParam"); //support for inject param.
    }
  }

  // Inherited.
  @Override
  public void initModel(EnunciateFreemarkerModel model) {
    super.initModel(model);

    if (!isDisabled()) {
      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 = isUseSubcontext() ? 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);
        }
      }
    }
  }

  // Inherited.
  public void onClassesFound(Set<String> classes) {
    jacksonAvailable |= classes.contains("org.codehaus.jackson.jaxrs.JacksonJsonProvider");
  }

  public void doFreemarkerGenerate() throws EnunciateException, IOException, TemplateException {
    if (!isUpToDate()) {
      EnunciateFreemarkerModel model = getModel();
      model.put("forName", new ClassForNameMethod());
      processTemplate(getRootResourceListTemplateURL(), model);
      processTemplate(getProvidersListTemplateURL(), model);
      processTemplate(getJaxbTypesTemplateURL(), model);

      Map<String, String> conentTypesToIds = model.getContentTypesToIds();
      Properties mappings = new Properties();
      for (Map.Entry<String, String> contentTypeToId : conentTypesToIds.entrySet()) {
        mappings.put(contentTypeToId.getValue(), contentTypeToId.getKey());
      }
      File file = new File(getGenerateDir(), "media-type-mappings.properties");
      FileOutputStream out = new FileOutputStream(file);
      mappings.store(out, "JAX-RS media type mappings.");
      out.flush();
      out.close();

      Map<String, String> ns2prefixes = model.getNamespacesToPrefixes();
      mappings = new Properties();
      for (Map.Entry<String, String> ns2prefix : ns2prefixes.entrySet()) {
        mappings.put(ns2prefix.getKey() == null ? "" : ns2prefix.getKey(), ns2prefix.getValue());
      }
      if (this.defaultNamespace != null) {
        mappings.put("--DEFAULT_NAMESPACE_ALIAS--", this.defaultNamespace);
      }
      file = new File(getGenerateDir(), "ns2prefix.properties");
      out = new FileOutputStream(file);
      mappings.store(out, "Namespace to prefix mappings.");
      out.flush();
      out.close();
    }
    else {
      info("Skipping generation of JAX-RS support files because everything appears up-to-date.");
    }
  }

  @Override
  protected void doBuild() throws EnunciateException, IOException {
    super.doBuild();

    File webappDir = getBuildDir();
    webappDir.mkdirs();
    File webinf = new File(webappDir, "WEB-INF");
    File webinfClasses = new File(webinf, "classes");
    getEnunciate().copyFile(new File(getGenerateDir(), "jaxrs-providers.list"), new File(webinfClasses, "jaxrs-providers.list"));
    getEnunciate().copyFile(new File(getGenerateDir(), "jaxrs-root-resources.list"), new File(webinfClasses, "jaxrs-root-resources.list"));
    getEnunciate().copyFile(new File(getGenerateDir(), "jaxrs-jaxb-types.list"), new File(webinfClasses, "jaxrs-jaxb-types.list"));
    getEnunciate().copyFile(new File(getGenerateDir(), "media-type-mappings.properties"), new File(webinfClasses, "media-type-mappings.properties"));
    getEnunciate().copyFile(new File(getGenerateDir(), "ns2prefix.properties"), new File(webinfClasses, "ns2prefix.properties"));

    BaseWebAppFragment webappFragment = new BaseWebAppFragment(getName());
    webappFragment.setBaseDir(webappDir);
    WebAppComponent servletComponent = new WebAppComponent();
    servletComponent.setName("jersey");
    servletComponent.setClassname(EnunciateJerseyServletContainer.class.getName());
    TreeMap<String, String> initParams = new TreeMap<String, String>();
    initParams.putAll(getServletInitParams());
    if (!isUsePathBasedConneg()) {
      initParams.put(JerseyAdaptedHttpServletRequest.FEATURE_PATH_BASED_CONNEG, Boolean.FALSE.toString());
    }
    if (isUseSubcontext()) {
      initParams.put(JerseyAdaptedHttpServletRequest.PROPERTY_SERVLET_PATH, getRestSubcontext());
    }
    if (getResourceProviderFactory() != null) {
      initParams.put(JerseyAdaptedHttpServletRequest.PROPERTY_RESOURCE_PROVIDER_FACTORY, getResourceProviderFactory());
    }
    if (getApplicationClass() != null) {
      initParams.put("javax.ws.rs.Application", getApplicationClass());
    }
    if (getLoadOnStartup() != null) {
      servletComponent.setLoadOnStartup(getLoadOnStartup());
    }
    servletComponent.setInitParams(initParams);

    TreeSet<String> urlMappings = new TreeSet<String>();
    for (RootResource rootResource : getModel().getRootResources()) {
      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 = isUseWildcardServletMapping() ? "/*" : resourceMethodPattern;
            }
            else {
              servletPattern = isUseWildcardServletMapping() ? subcontext + "/*" : subcontext + resourceMethodPattern;
            }

            if (urlMappings.add(servletPattern)) {
              debug("Resource method %s of resource %s to be made accessible by servlet pattern %s.",
                    resourceMethod.getSimpleName(), resourceMethod.getParent().getQualifiedName(), servletPattern);
            }
          }
        }
      }
    }

    if (urlMappings.contains("/*")) {
      urlMappings.clear();
      urlMappings.add("/*");
    }
    else {
      Iterator<String> iterator = urlMappings.iterator();
      while (iterator.hasNext()) {
        String mapping = iterator.next();
        if (!mapping.endsWith("/*") && urlMappings.contains(mapping + "/*")) {
          iterator.remove();
        }
      }
    }

    servletComponent.setUrlMappings(urlMappings);
    webappFragment.setServlets(Arrays.asList(servletComponent));
    getEnunciate().addWebAppFragment(webappFragment);
  }

  protected String getRestSubcontext() {
    String restSubcontext = getEnunciate().getConfig().getDefaultRestSubcontext();
    //todo: override default rest subcontext?
    return restSubcontext;
  }

  @Override
  public RuleSet getConfigurationRules() {
    return new JerseyRuleSet();
  }

  /**
   * Whether the generated sources are up-to-date.
   *
   * @return Whether the generated sources are up-to-date.
   */
  protected boolean isUpToDate() {
    return enunciate.isUpToDateWithSources(getGenerateDir());
  }

  // Inherited.
  public boolean isJaxwsProvider() {
    return false;
  }

  // Inherited.
  public boolean isJaxrsProvider() {
    return true;
  }

  /**
   * Whether to use the REST subcontext.
   *
   * @return Whether to use the REST subcontext.
   */
  public boolean isUseSubcontext() {
    return useSubcontext;
  }

  /**
   * Whether to use the REST subcontext.
   *
   * @param useSubcontext Whether to use the REST subcontext.
   */
  public void setUseSubcontext(boolean useSubcontext) {
    this.useSubcontext = useSubcontext;
  }

  /**
   * 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;
  }

  /**
   * The fully-qualified classname of an instance of com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory that jersey will use.
   *
   * @return The fully-qualified classname of an instance of com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory that jersey will use.
   */
  public String getResourceProviderFactory() {
    return resourceProviderFactory;
  }

  /**
   * The fully-qualified classname of an instance of com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory that jersey will use.
   *
   * @param resourceProviderFactory The fully-qualified classname of an instance of com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory that jersey will use.
   */
  public void setResourceProviderFactory(String resourceProviderFactory) {
    this.resourceProviderFactory = resourceProviderFactory;
  }

  /**
   * The fully-qualified classname of an instance of the implementation of javax.ws.rs.core.Application that jersey will use.
   *
   * @return The fully-qualified classname of an instance of the implementation of javax.ws.rs.core.Application that jersey will use.
   */
  public String getApplicationClass() {
    return applicationClass;
  }

  /**
   * The fully-qualified classname of an instance of the implementation of javax.ws.rs.core.Application that jersey will use.'
   *
   * @param applicationClass The fully-qualified classname of an instance of the implementation of javax.ws.rs.core.Application that jersey will use.
   */
  public void setApplicationClass(String applicationClass) {
    this.applicationClass = applicationClass;
  }

  /**
   * The order in which the servlet is loaded on startup by the web application.
   *
   * @return The order in which the servlet is loaded on startup by the web application.
   */
  public String getLoadOnStartup() {
    return loadOnStartup;
  }

  /**
   * The order in which the servlet is loaded on startup by the web application.
   *
   * @param loadOnStartup The order in which the servlet is loaded on startup by the web application.
   */
  public void setLoadOnStartup(String loadOnStartup) {
    this.loadOnStartup = loadOnStartup;
  }

  /**
   * Whether to disable the greedy servlet pattern error.
   *
   * @return Whether to disable the greedy servlet pattern error.
   */
  public boolean isDisableWildcardServletError() {
    return disableWildcardServletError;
  }

  /**
   * Whether to disable the wildcard servlet pattern error.
   *
   * @param disableWildcardServletError Whether to disable the wildcard servlet pattern error.
   */
  public void setDisableWildcardServletError(boolean disableWildcardServletError) {
    this.disableWildcardServletError = disableWildcardServletError;
  }

  /**
   * Whether to use the wildcard servlet mapping.
   *
   * @return Whether to use the wildcard servlet mapping.
   */
  public boolean isUseWildcardServletMapping() {
    return useWildcardServletMapping;
  }

  /**
   * Whether to use the wildcard servlet mapping.
   *
   * @param useWildcardServletMapping Whether to use the wildcard servlet mapping.
   */
  public void setUseWildcardServletMapping(boolean useWildcardServletMapping) {
    this.useWildcardServletMapping = useWildcardServletMapping;
  }

  /**
   * The default namespace. This namespace will have no prefix associated with it during XML serialization.
   *
   * @return The default namespace.
   */
  public String getDefaultNamespace() {
    return defaultNamespace;
  }

  /**
   * The default namespace.
   *
   * @param defaultNamespace The default namespace.
   */
  public void setDefaultNamespace(String defaultNamespace) {
    this.defaultNamespace = defaultNamespace;
  }

  /**
   * Get the servlet init params.
   *
   * @return The servlet init params.
   */
  public Map<String, String> getServletInitParams() {
    return servletInitParams;
  }

  /**
   * Add a servlet init param.
   *
   * @param name The name of the init param.
   * @param value The value of the init param.
   */
  public void addServletInitParam(String name, String value) {
    this.servletInitParams.put(name, value);
  }

  // Inherited.
  @Override
  public boolean isDisabled() {
    if (super.isDisabled()) {
      return true;
    }
    else if (getModelInternal() != null && getModelInternal().getRootResources().isEmpty()) {
      debug("Jersey module is disabled because there are no root resources.");
      return true;
    }
    else if (getModelInternal() != null && 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;
  }
}
TOP

Related Classes of org.codehaus.enunciate.modules.jersey.JerseyDeploymentModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.