5

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.

Community
  • 1
  • 1
nobeh
  • 9,784
  • 10
  • 49
  • 66
  • Your code looks to be equivalent to an XML servlet configuration except for one detail - the servlet name and class. Maybe you just didn't paste that portion here. Have you tried that XML configuration solution and confirmed it actually works in your case? – Cebence Apr 13 '13 at 23:47
  • Did you configure JDK logging appropriately? – skirsch Apr 14 '13 at 13:17
  • @Cebence `ServletHolder` serves as `web.xml`. The application runs and it works. – nobeh Apr 14 '13 at 17:51
  • @skirsch I see logs either from Jetty or Jersey and the application itself. – nobeh Apr 14 '13 at 17:51
  • 1
    @nobeh Sorry, I thought the `ServletContainer` was Jetty code. Just seen a full example https://github.com/yamsellem/Backbone-Jersey/blob/master/src/main/webapp/WEB-INF/web.xml – Cebence Apr 14 '13 at 18:10

2 Answers2

4

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.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • This `ServletContainer > Servlet` behavior is basically inverson of control (IoC) just like Spring's Dependency Injection - if you want your beans autowired you must not create them yourself with `new`. Same applies to servlets. – Cebence Apr 16 '13 at 18:23
  • @Cebence: I don't think I agree with that comparison. Take for example Spring's [DispatcherServlet](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html), which recently got a [new constructor](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html#DispatcherServlet(org.springframework.web.context.WebApplicationContext)) that lets you provide an existing Spring context for use in a Servlet 3.0 environment. That works as you'd expect. This doesn't. – Ryan Stewart Apr 17 '13 at 00:29
  • Yes, but I didn't say `3.0`. One would think after 4-5 years the implementation would be mature. – Cebence Apr 17 '13 at 05:01
  • @RyanStewart Thanks for the time for this answer. Maybe, if you're interested, this works with Jersey 1.x but since in Jersey 2.x namespaces have changed, I have not yet found the configuration for the filters. – nobeh Apr 17 '13 at 05:10
  • @nobeh: Sorry, but I've looked a little, and I can't find them either :\ – Ryan Stewart Apr 18 '13 at 03:07
0

I think you might be doing it wrong.

// Creating an instance of Jersey servlet, right?
ServletContainer servletContainer = new ServletContainer(application);

// Putting it in a container by reference.
ServletHolder servletHolder = new ServletHolder(servletContainer);

This page doesn't create the servlet like you do:

ServletHolder sh = new ServletHolder(WicketServlet.class);

This way Jetty creates an instance, and calls servlet's init(). In your case you might need to call init() but I am not sure where will you get the ServletConfiguration instance.

UPDATE: You should try it this way:

  ServletHolder servletHolder = new ServletHolder(ServletContainer.class);
  servletHolder.setInitParameter("javax.ws.rs.Application", "MyRESTApplication");
  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");

  ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
  contextHandler.addServlet(servletHolder, "/services/*");
  server.setHandler(contextHandler);
Cebence
  • 2,406
  • 2
  • 19
  • 20