4

We have multiple Java web apps packaged as WARs all packaged in an EAR. Our RESTful services are built using JAX-RS and in version specific WARs.

We'd like to add static content for each of these version specific WARs, but use the root context (of the WAR) for both the static content and the RESTful service API calls, such that all of the following URLs will work:

{hostname}/v1/swagger.yaml  <-- Static Content describing the v1 API
{hostname}/v1/orders/{uid}  <-- JAX-RS RESTful API (v1)

{hostname}/v2/swagger.yaml  <-- Static Content describing the v2 API
{hostname}/v2/orders/{uid}  <-- JAX-RS RESTful API (v2)

The following deployment structure is what we currently have, which works for the JAX-RS services, but not the static content. In other words these URLs work:

{hostname}/v1/orders/{uid}  <-- JAX-RS RESTful API (v1)
{hostname}/v2/orders/{uid}  <-- JAX-RS RESTful API (v2)

But the problem is that these URLS are inaccessible:

{hostname}/v1/swagger.yaml  <-- Static Content describing the v1 API
{hostname}/v2/swagger.yaml  <-- Static Content describing the v2 API

Question: How do we get these static URLs to work?

Here's the exploded EAR:

example.ear

    META-INF
        application.xml

    v1.war
        swagger.yaml
        WEB-INF
            web.xml
            classes
                ApplicationV1.class
                OrdersResourceV1.class

    v2.war
        swagger.yaml
        WEB-INF
            web.xml
            classes
                ApplicationV2.class
                OrdersResourceV2.class

Here's the EARs application.xml:

<?xml version="1.0"?>
<application xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="6">
  <application-name>...</application-name>
  <module>
    <web>
      <web-uri>v1.war</web-uri>
      <context-root>/v1</context-root>
    </web>
  </module>
  <module>
    <web>
      <web-uri>v2.war</web-uri>
      <context-root>/v2</context-root>
    </web>
  </module>
  <module>
    <web>
      <web-uri>another.war</web-uri>
      <context-root>/</context-root>
    </web>
  </module>
</application>

Here's the v1 & v2 web.xml:

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
</web-app>

Here's the ApplicationV1.java (JAX-RS) implementation, notice the URL mapping:

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;


@ApplicationPath("/")
public class ApplicationV1 extends Application {

  private Set<Object> singletons = new HashSet<Object>();

  public ApplicationV1() {
    singletons.add(new OrdersResourceV1());
  }

  @Override
  public Set<Object> getSingletons() {
    return singletons;
  }

}

Here's the OrdersResourceV1.java (JAX-RS) implementation, notice the URL mapping:

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.*;

@RequestScoped
@Path("/orders")
public class OrdersResourceV1 {

  @GET
  @Path("/{uid}")
  @Produces("application/json;charset=UTF-8")
  public Response getOrder(@PathParam("uid") String orderUid) {
    <ommited for brevity>
  }

}

I do know that if we changed ApplicationV*.java's mapping to @ApplicationPath("/services"), that the static content will be assessable, but the URLs will then be as follows, but it's our goal to not include any extraneous path elements like this.

{hostname}/v1/swagger.yaml  <-- Static Content describing the v1 API
{hostname}/v1/services/orders/{uid} <-- JAX-RS RESTful API (v1)

{hostname}/v2/swagger.yaml  <-- Static Content describing the v2 API
{hostname}/v2/services/orders/{uid} <-- JAX-RS RESTful API (v2)

So my question is, how do we get the JAX-RS and static content under the root context for the WAR (/v1 and /v2)? Is there a different way to map the URLs? Or is there a configuration option that allows for a static fallback if JAX-RS doesn't have a handler for the URL?

I'm hoping that this can be solved a JavaEE generic fashion, but if not, we're using Wildfly 8.1 on Java8.

peterl
  • 1,881
  • 4
  • 21
  • 34
  • Problem is that the servlet container has a default servlet that handles the static content. When you define a servlet with the same mapping i.e. `/*`, this will shadow the static content servlet. When the Resteasy servlet invoked, if it can't find the requested static content, it will not fallback to the default servlet. I know Jersey has a configuration property to set, where it handle the static content itself, but I don't know about Resteasy. I [checked, but you can check again](http://docs.jboss.org/resteasy/docs/3.0.9.Final/userguide/html/Installation_Configuration.html#d4e125) – Paul Samsotha May 07 '15 at 22:58
  • You can get away with a filter that understands when a request is for static content and responds itself; you *will* have to reproduce some (or all?) of the functionality of the static resource servlet. If you *only want to serve the swagger.yaml*, then you may create a JAX-RS service that responds to this URL by loading e.g the file from the classpath and sets the right headers. – Nikos Paraskevopoulos May 08 '15 at 06:13

3 Answers3

2

So I found a solution (tested) that works. As stated in the Resteasy Documentation - RESTEasy as a servlet Filter:

The downside of running Resteasy as a Servlet is that you cannot have static resources like .html and .jpeg files in the same path as your JAX-RS services. Resteasy allows you to run as a Filter instead. If a JAX-RS resource is not found under the URL requested, Resteasy will delegate back to the base servlet container to resolve URLs.

web.xml example

<web-app>
    <filter>
        <filter-name>Resteasy</filter-name>
        <filter-class>
            org.jboss.resteasy.plugins.server.servlet.FilterDispatcher
        </filter-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.restfully.shop.services.ShoppingApplication</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>Resteasy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Even tho Resteasy's own documentation says servlet filters are **deprecated**, I think this is the only option when we need to serve both static resources and services. Or is there some config we could do to still use pure JAX-RS inside your app? – fasfsfgs May 24 '15 at 03:51
  • 1
    @fasfsfgs Yeah it seems kind of odd to me that the documentation would mention both the deprecation of the filter usage and it being the solution to a problem, let alone in the same paragraph. If it's any consolation, even with Jersey, to enable this feature, we need to register as a filter. Seems more like a limitation of servlets as a whole, rather than JAX-RS. As far as any other solution, this is the only one I was able to find, unless you're willing to change the servlet mapping to something else beside `/*`, which is not a possible solution for the OP – Paul Samsotha May 24 '15 at 04:11
  • Unlike other deprecated features (resteasy.scan property), resteasy still allows you to use this functionality.. so it's not really completely deprecated for servlet 3.0 containers.. – Alkanshel Jun 06 '16 at 22:31
0

I found another solution (thanks to https://stackoverflow.com/a/3582215/589525 ). This works for RestEasy on JBoss 6. Simply add the default servlet to the mapping

<!--existing mapping: -->
<servlet-mapping>
  <servlet-name>Resteasy</servlet-name>
  <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*</url-pattern>
</servlet-mapping>
Jaap D
  • 501
  • 5
  • 18
0

If there is a limited (small) count of static resources, each could be added as a static <servlet>+<servlet-mapping> entry in the web.xml. This shall work independently of the container used - as is a standard feature. E.g. like so:

<servlet>
    <servlet-name>swagger-yaml</servlet-name>
    <jsp-file>/swagger.yaml</jsp-file>
</servlet>
<servlet-mapping>
    <servlet-name>swagger-yaml</servlet-name>
    <url-pattern>/swagger.yaml</url-pattern>
</servlet-mapping>

Again - this makes most sense only for very limited count of static resources (e.g. 1-5).

Mc Bton
  • 111
  • 1
  • 4