@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:- PersistenceManager is bound to a Request Scope, each request has its own PersistenceManager instance.
- 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.
- 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.
Great article!
ReplyDeleteI 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?
Sessions expire after a timeout. If you wish to expire a session deliberately, you should call HttpSession.invalidate() in your logout code.
ReplyDeleteAlso there are some restrictions on sessions in AppEngine, for additional details, please read this section in AppEngine docs
Is that using an open PersistenceManager per session or does it create a new one for each individual request?
ReplyDeleteOpening 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.
Would using OpenPersistenceManagerInView be as simple as changing from Request to Session scope?
ReplyDeleteIn 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.
ReplyDeleteAs 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.