Tuesday, June 8, 2010

Lightweight JDO Persistence Filter using Guice

Here is a very lightweight and simple PersistenceFilter for your Guice powered webapps:
@Singleton
public class PersistenceFilter implements Filter {

 private final Provider<PersistenceManager> pmp;

 @Inject
 public PersistenceFilter(Provider<PersistenceManager> pmp) {
  this.pmp = pmp;
 }

 @Override
 public void destroy() {
 }

 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  PersistenceManager pm = pmp.get();
  try {
   chain.doFilter(request, response);
  } finally {
   pm.close();
  }
 }

 @Override
 public void init(FilterConfig arg0) throws ServletException {
 }
}
This is no replacement for warp-persist and the soon to come guice persistence support, but look how simple it is, I love it! :-)

So how does this work? the "magic" is done by using Guice scoping, basically PersistenceManager is tied to the Request scope and all we have to do is close it at the end of the request. I'm going to show this in the context of an AppEngine application (cuz thats what I'm working on right now), so here is our web.xml:
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 version="2.5">

 <filter>
  <filter-name>guiceFilter</filter-name>
  <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>guiceFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 <listener>
  <listener-class>com.codeark.persistencefilterexample.Bootstrap</listener-class>
 </listener>
</web-app>
Its an ordinary, run of the mill Guice powered webapp web.xml config file (see this post if you're using wicket, and why...)

The Bootstrap listener looks like this:
public final class Bootstrap extends GuiceServletContextListener {

 @Override
 protected Injector getInjector() {
  return buildInjector();
 }

 public Injector buildInjector() {  
  Injector infrastructure = Guice.createInjector(    
    new AppEngineModule(), 
    new PersistenceModule(), 
    new WebModule());

  return infrastructure;
 }
}
AppEngineModule handles the bindings of GAE specific services:
public class AppEngineModule extends AbstractModule {
 
 private static final PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("transactions-optional");
  
 public AppEngineModule() {  
 }

 @Override
 protected void configure() {
 }
 
 @Provides
 PersistenceManagerFactory providesPersistenceManagerFactory() {
  return pmf;
 }

 @Provides
 UserService providesUserService() {
  return UserServiceFactory.getUserService();
 }
 
 @Inject
 @Provides
 @SessionScoped
 User providesCurrentUser(UserService userService) {
  return userService.getCurrentUser();
 }
}
Please note the providesPersistenceManagerFactory() which localizes the use of static state to this module only. Additionally, diverging from the main topic of this post, take a look at providesCurrentUser(UserService userService) which simply allows you to Inject the current Google Account User (if one is logged in this session) anywhere in your code, its really handy.

Now comes the PersistenceModule:
public class PersistenceModule extends AbstractModule {
 
 public PersistenceModule() {  
 }

 @Override
 protected void configure() {
  bind(PersistenceManager.class).toProvider(PersistenceManagerProvider.class).in(RequestScoped.class);
  bind(DataService.class).to(DataServiceImpl.class); 
 } 
}
The most important line here is the first one where we bind PersistenceManager to the request scope. The provider follows:
public class PersistenceManagerProvider implements Provider<PersistenceManager> {

 private final PersistenceManagerFactory pmf;

 @Inject
 public PersistenceManagerProvider(PersistenceManagerFactory pmf) {
  this.pmf = pmf;  
 }

 @Override
 public PersistenceManager get() {
  return pmf.getPersistenceManager();
 }
}
I could have used a @Provides method in PersistenceModule instead, but that would force me to inject PersistenceManagerFactory into PersistenceModule and I like to avoid module injection where possible.

Last but not least, WebModule:
public class WebModule extends ServletModule {
 
 @Override
 protected void configureServlets() {
  filter("/*").through(PersistenceFilter.class);  
  serve("/test").with(TestServlet.class);  
 }   
}
It is important that PersistenceFilter will precede any other filters or servlets that need persistence (see Dispatch order in Guice documentation) otherwise you might end up with a closed persistence manager.

Finally, our test servlet which uses persistence via DataService:
@Singleton
public class TestServlet extends HttpServlet {

 private final DataService dataService;
 private final Provider<User> currentUser;

 @Inject
 public TestServlet(DataService dataService, Provider<User> currentUser) {
  this.dataService = dataService;
  this.currentUser = currentUser;
 }

 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

  resp.setContentType("text/plain");

  User current = currentUser.get();

  if (current == null) {
   resp.getWriter().println("You are not signed in to your google account, but Hello, world! anyways :-)");
  } else {
   resp.getWriter().println(String.format("Hello, %s", current.getNickname()));
   //records user and ip in datastore
   dataService.persist(new MyEntity(current, req.getRemoteAddr()));
  }
 }
}
The DataService implementation looks like this:
public final class DataServiceImpl implements DataService {
 
 private Provider<PersistenceManager> pmp;
 
 @Inject
 public DataServiceImpl(Provider<PersistenceManager> pmp) { 
  this.pmp = pmp; 
 }
 
 @Override
 public <T> T persist(T instance) {
  return pmp.get().makePersistent(instance);
 }

 @Override
 public void delete(Object instance) {
  pmp.get().deletePersistent(instance);
 } 
}
To summarize:
  1. PersistenceManager is bound to a Request Scope, each request has its own PersistenceManager instance.
  2. The PersistenceFilter is configured at the beginning of the request dispatch chain, its main role is to close the PersistenceManager at the end of each request.
  3. Data Aware classes like DataServiceImpl use PersistenceManager by means of a Guice Provider (direct injection of PersistenceManager will impose a scope restriction on when we can instantiate classes that use it, so I think its best to avoid it)
As explained at the beginning of the post, this implementation is good for prototyping and small projects, for large / complex projects I'd go with a more solid, tested and much more capable framework (like warp-persist and guice-persist when it comes out)

The source is available in the form of an eclipse project here:


You will need Google AppEngine Eclipse plugin to make it work.

5 comments:

  1. Great article!
    I had a problem with session scoped objects, which Guice (or Appengine) didn't wanted to store in a session. It happened only with objects, which were injected in a class together with UserService. And injection of session scoped provider for User instead of UserService really helped me.

    But another problem occured. After user has logged out using UserService logout URL, injected User object still contains user info. It happens because User is now a session scoped object and guice doesn't inject a new User object, which suppose to be null. Session doesn't expire after user has logged out. Is there any workaround for that?

    ReplyDelete
  2. Sessions expire after a timeout. If you wish to expire a session deliberately, you should call HttpSession.invalidate() in your logout code.

    Also there are some restrictions on sessions in AppEngine, for additional details, please read this section in AppEngine docs

    ReplyDelete
  3. Is that using an open PersistenceManager per session or does it create a new one for each individual request?

    Opening up a new one for each request means that there is no caching of objects in the L1 cache between requests - which can reduce performance if each session has lots of objects.

    I have a home grown filter which does keep the PM open for the life of a session and the performance is excellent. I'm just looking around for third party 'off the shelf' alternatives.

    ReplyDelete
  4. Would using OpenPersistenceManagerInView be as simple as changing from Request to Session scope?

    ReplyDelete
  5. In this particular scenario the guice provider is simply a wrapper for the PersistenceManagerFactory, there is no telling whats going on inside there, its possible one is created for each request or not. The filter code doesn't do that, anyways.

    As for an "off the shelf" product, try the new Guice 3.0 persist module. Guice 3.0 is still in an "unreleased" state thought.

    If you work on app engine, I suggest you take a look at other alternatives to JDO, like objectify.

    ReplyDelete