Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LoggingFilter is ignored in Jersey and embedded Jetty

I'm trying to configure LoggingFilter for Jersey in an embedded Jetty setup. The glue code that is used is as follows:

ServletContainer servletContainer = new ServletContainer(application);
ServletHolder servletHolder = new ServletHolder(servletContainer);
servletHolder.setInitParameter("com.sun.jersey.config.feature.Debug", "true");
servletHolder.setInitParameter("com.sun.jersey.config.feature.Trace", "true");
servletHolder.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", 
  "com.sun.jersey.api.container.filter.LoggingFilter");
servletHolder.setInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters", 
  "com.sun.jersey.api.container.filter.LoggingFilter");

But the logging filter is actually ignored and I see no relevant logs in the console. How can I do this? Tested both on Jersey 1.x and 2.x.

A relevant answer describes how to achieve this using web.xml.

like image 403
nobeh Avatar asked Oct 05 '22 10:10

nobeh


1 Answers

I think this is a very subtle nuance of the documented behavior of ServletContainer if it isn't an outright bug. The ServletContainer docs on the subject of init params read as:

All initialization parameters are added as properties of the created ResourceConfig.

The answer is hidden in there. Specifically, if the ResourceConfig instance isn't created by the ServletContainer, then the servlet init parameters aren't added as properties and thus won't influence your app's configuration. When you provide your own Application instance, as you did with the new ServletContainer(application), initialization follows roughly this course:

Your code invokes the following ServletContainer constructor with your Application instance:

public ServletContainer(Application app) {
    this.app = app;
}

The container initializes your ServletContainer as part of a typical Servlet lifecycle:

protected void init(WebConfig webConfig) throws ServletException {
    webComponent = (app == null)
            ? new InternalWebComponent()
            : new InternalWebComponent(app);
    webComponent.init(webConfig);
}

There goes your Application instance into the InternalWebComponent constructor. An InternalWebComponent is just a slight customization of WebComponent, so:

InternalWebComponent(Application app) {
    super(app);
}

calls:

public WebComponent(Application app) {
    if (app == null)
        throw new IllegalArgumentException();

    if (app instanceof ResourceConfig) {
        resourceConfig = (ResourceConfig) app;
    } else {
        resourceConfig = new ApplicationAdapter(app);
    }
}

This is where, since you provided an Application instance directly, a ResourceConfig is built for you in one of the branches of that second if. Immediately after construction, WebComponent.init() is called on the new component (refer back to the ServletContainer.init() call above, where we came from). Inside this init() call is where the "created ResourceConfig" referred to by the docs would be created, but in your case, one already exists, as shown by the trail we followed to get here. I.e., the resourceConfig isn't null, so the important line below doesn't execute:

public void init(WebConfig webConfig) throws ServletException {
    ...
    if (resourceConfig == null)
        resourceConfig = createResourceConfig(config);
    ...
}

That createResourceConfig() method (still in WebComponent) reads as:

private ResourceConfig createResourceConfig(WebConfig webConfig)
        throws ServletException {
    final Map<String, Object> props = getInitParams(webConfig);
    final ResourceConfig rc = createResourceConfig(webConfig, props);
    rc.setPropertiesAndFeatures(props);
    return rc;
}

You can see in that call that setPropertiesAndFeatures() is used to copy the servlet's init params into the ResourceConfig instance. Unfortunately, this is the only place where that call is made, and in your case, execution never makes it here, basically because of your use of one of the non-default ServletContainer constructors.

I expect that the original authors wrote ServletContainer with just one, no-arg constructor and the other two were added later on for ease of use with Servlet 3.0 containers without realizing that this behavior was being introduced. Otherwise, I'd expect to see some mention of it in the docs.

So, long story short: either use the default ServletContainer constructor or find a way to take care of this part yourself:

Map<String, Object> props = getInitParams(webConfig);
rc.setPropertiesAndFeatures(props);

The first way is probably the simplest. For instance, you could specify your Application class as an init parameter, too, as long as there's nothing requiring you to instantiate it ahead of time, such as:

servletHolder.setInitParameter("javax.ws.rs.Application", "org.foo.MyApplication");

That way, the "normal" initialization path will be taken, meaning the WebComponent will create the ResourceConfig for you and apply the init params correctly.

like image 130
Ryan Stewart Avatar answered Oct 10 '22 02:10

Ryan Stewart