5

I am running RESTEasy in an embedded web server (Jetty).

My resources need access to a backing data store, configuration of which is passed in on the command line that is used to start our application.

Ideally, I'd inject the backing data store resource in the constructor of the resource:

@Path("/")    
public class MappingService{
    private final RecordManager recman;

    public MappingService(RecordManager recman) {
        this.recman = recman;
    }

    @PUT
    @Path("/mapping/{key}")
    @Produces({MediaType.APPLICATION_JSON, "application/*+json"})
    public Response createMapping(@PathParam("key") String key, @QueryParam("val") String val) {
                // do stuff with recman ...
           }
    }

Ideally, I'd configure the RecordManager object elsewhere in the application, then make it available to the MappingService constructor.

I see that I can use an Application object to return specific singleton instances. But it looks like RESTEasy constructs the Application object itself - so I'm not seeing any way to pass configuration objects into the Application singleton.

So: How do I pass an externally instantiated object into my resource handler?


I found this article ( Share variables between JAX-RS requests ) that describes how to use a context listener to add an object to the context and pull it inside the resource handler - but this is horrible - I have to take what should be a POJO and suddenly I've made it highly dependent on it's container. Please tell me there is a better way!

Community
  • 1
  • 1
Kevin Day
  • 16,067
  • 8
  • 44
  • 68

4 Answers4

3

ok - I've got it figured out - documenting here in case anyone else hits this.

The trick is in leveraging @Context injection to inject the resources I want, instead of obtaining references to the servlet context. This can be done if you have some control over the RestEASY bootstrap process (instead of bootstrapping using a context listener constructed from a class name specified in web.xml, we register our context listener programatically).

I'm using Jetty as my servlet container. Here is my main method:

public static void main(String[] args) throws Exception {
    final RecordManager recman = createRecordManager(args);

    Server server = new Server(DEFAULT_HTTP_PORT);
    try {

        WebAppContext webAppContext = new WebAppContext();
        webAppContext.setContextPath("/");
        webAppContext.setWar(WAR_LOCATION);

        webAppContext.addEventListener(new org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap(){
            @Override
            public void contextInitialized(ServletContextEvent event) {
                super.contextInitialized(event);
                deployment.getDispatcher().getDefaultContextObjects().put(RecordManager.class, recman);
            }
        });

        webAppContext.addServlet(org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.class, "/*");

        webAppContext.setServer(server);
        server.setHandler(webAppContext);

        server.start();

    } catch (Exception e) {
        logger.error("Error when starting", e);
        server.stop();
    } 
}

then my resource handler winds up looking like this:

@Path("/")
public class MappingResource {

    private final RecordManager recman;

    public MappingResource(@Context RecordManager recman) { //<-- sweet! resource injection
        this.recman = recman;
    }


    @PUT
    @Path("/mapping/{key}")
    @Produces({MediaType.APPLICATION_JSON, "application/*+json"})
    public Response createMapping(@PathParam("key") String key, @QueryParam("val") String val) {
        // do something with recman, key and val
    }
}

for thoroughness, here is the web.xml entry (this is just generic resteasy config stuff):

<context-param>
    <param-name>resteasy.resources</param-name>
    <param-value>my.package.MappingService</param-value>
</context-param>
Kevin Day
  • 16,067
  • 8
  • 44
  • 68
2

You can use Spring.

RESTEasy has an example using Spring where they inject a DAO object into the REST service in the application.xml. Not as a constructor-arg, but as a field.

I'd adapt application.xml as follows to make it work for your case:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="recordManager" class="full.package.name.here.RecordManager"/>

    <bean id="mappingService"
          class="full.package.name.here.MappingService">
        <constructor-arg index="0" value="recordManager"/>
    </bean>

</beans>

In the web.xml initialize this using

<listener>
   <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

<listener>
   <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>

You need to add the resteasy-spring jar, in which these classes reside.

Normally the RESTEasy Bootstrap listener does its own component scan of the classpath and instantiates classes annotated with @Path. This gets in the way of Spring instantiation.

The above setup works differently. Spring initialises everything, including your services. Then the RESTEasy initialisation kicks in, searches the Spring application context for beans that are annotated with @Path, and registers those as REST services.

Do not instantiate services with new operator. This would create new class outside of Spring context, and such class cannot access Spring manged beans, and therefore injections will not work (will remain null).

flup
  • 26,937
  • 7
  • 52
  • 74
  • Thanks, but not really what I'm looking for, unfortunately - field level injection via Spring is even more opaque than relying on @Context interaction... – Kevin Day Jan 15 '14 at 03:20
  • I think a constructor arg should work fine too. I've updated the answer to show what the application.xml should look like then. I find their documentation a bit unclear on the matter so you'd have to simply try it out. If it doesn't work, you can always add a setter for the field. – flup Jan 15 '14 at 08:28
  • Oh and I realised what the obscure part of the documentation means. The RESTEasy initialisation cannot fill in constructor arguments, since Spring has already instantiated the services. But Spring can do this just fine. – flup Jan 15 '14 at 10:25
1

You could also add your command line arguments as a servlet context attribute, and then access the servlet context via injection e.g. in your Application or resources. This avoids using any RESTEasy/Jetty-specific features (e.g. in case you ever wanted to use Jersey instead) and doesn't require a WAR/WebAppContext.

public static void main(String[] args) throws Exception  {
    Server server = new Server(0);
    ServletContextHandler handler = new ServletContextHandler(server, "/");
    handler.setAttribute("main.args", ImmutableList.copyOf(args));
    ServletHolder holder = new ServletHolder(new HttpServletDispatcher());
    holder.setInitParameter("javax.ws.rs.Application", MyApplication.getName());
    handler.addServlet(holder, "/*");
    server.start();
    server.join();
}

public class MyApplication extends Application {
    public PubSubApplication(@Context ServletContext servletContext) throws Exception {
        String[] args = servletContext.getAttribute("main.args");
        // do stuff with args...
    }
}
Trevor Robinson
  • 15,694
  • 5
  • 73
  • 72
0

Maybe simple singelton initialization. Something like that:

RecordManagerMaster.initRecordManager(...);

Getter:

RecordManager recman = RecordManagerMaster.getRecordManager();
Mark
  • 17,887
  • 13
  • 66
  • 93
  • Yeah - this is a way to do it, but not at all desirable. If I have to resort to Singleton, then that means that the jax-rs architecture has a fundamental flaw. Hopefully that won't be the case... – Kevin Day Jan 15 '14 at 12:10
  • 1
    I think that initialization with command line is fundamental flaw for servlet-based architecture )) – Mark Jan 15 '14 at 12:37
  • hah! Consider that these classes become *much* harder to unit test when they start becoming dependent on their container. – Kevin Day Jan 15 '14 at 12:48