2

Is there a way to run the Jersey servlet container (2.x) descriptor-less as a javax.servlet.Filter in a Servlet 3.x container? I need to serve static resources alongside my services and therefore need to use jersey.config.servlet.filter.forwardOn404 or jersey.config.servlet.filter.staticContentRegex which only work when run as a filter according to Javadoc

The property is only applicable when Jersey servlet container is configured to run as a javax.servlet.Filter, otherwise this property will be ignored.

I'd like to get rid of the web.xml completely

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <display-name>My-Webservice</display-name>

    <filter>
        <filter-name>Jersey Filter</filter-name>
        <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.foo.webservices.MyApplication</param-value>
        </init-param>
    </filter>
</web-app>

and have everything in my custom Applicationclass

@ApplicationPath(value = "/")
public class MyApplication extends ResourceConfig
{    
    public MyApplication()
    {
        packages("com.foo.webservices.services");
        property(ServletProperties.FILTER_FORWARD_ON_404, true);
    }
}

The official documentation (https://jersey.java.net/documentation/latest/deployment.html#deployment.servlet.3) doesn't state anything about filters unfortunately.

jansohn
  • 2,246
  • 2
  • 28
  • 40

1 Answers1

5

It's possible, but not gonna be as easy as just setting some config property. It would help if you understand a little about how it actually works. With Servlet 3.x, introduced a ServletContainerInitializer that we can implement to load servlets dynamically (this is discussed further here). Jersey has an implementation that it uses. But it follows the JAX-RS which says that the application should be loaded as a servlet. So Jersey doesn't doesn't offer any way around this.

We could write our own ServletContainerInitializer or we can just tap into Jersey's. Jersey has a SerletContainerProvider we can implement. We would need to register the servlet filter ourselves. The implementation would look something like this

@Override
public void preInit(ServletContext context, Set<Class<?>> classes) throws ServletException {
    final Class<? extends Application> applicationCls = getApplicationClass(classes);
    if (applicationCls != null) {
        final ApplicationPath appPath = applicationCls.getAnnotation(ApplicationPath.class);
        if (appPath == null) {
            LOGGER.warning("Application class is not annotated with ApplicationPath");
            return;
        }
        final String mapping = createMappingPath(appPath);
        addFilter(context, applicationCls, classes, mapping);
        // to stop Jersey servlet initializer from trying to register another servlet
        classes.remove(applicationCls);
    }
}

private static void addFilter(ServletContext context, Class<? extends Application> cls,
                              Set<Class<?>> classes, String mapping) {
    final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(cls, classes);
    final ServletContainer filter = new ServletContainer(resourceConfig);
    final FilterRegistration.Dynamic registration = context.addFilter(cls.getName(), filter);
    registration.addMappingForUrlPatterns(null, true, mapping);
    registration.setAsyncSupported(true);
}

Once we have our implementation, we need to create a file

META-INF/services/org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider

Which should be at the root of the class path. The contents of that file should be the fully qualified name of our implementation.

You can see a complete example in this GitHub Repo

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Great answer, thank you. I just had to add another check in the getApplicationClass method as it falsely identified `org.glassfish.jersey.server.ResourceConfig$RuntimeConfig` as the application class. I just added `if (applicationCls != null && applicationCls.getAnnotation(ApplicationPath.class) != null)` around the `break` statement. – jansohn Oct 17 '16 at 06:31