Reputation: 16383
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!
Upvotes: 5
Views: 4648
Reputation: 16554
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...
}
}
Upvotes: 1
Reputation: 16383
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>
Upvotes: 3
Reputation: 27104
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).
Upvotes: 2
Reputation: 18787
Maybe simple singelton initialization. Something like that:
RecordManagerMaster.initRecordManager(...);
Getter:
RecordManager recman = RecordManagerMaster.getRecordManager();
Upvotes: 0