So I figured out somewhat decent stateful ejb (3) integration into Tapestry. Here is what we did, in the interest to complete this thread and provide some level of documentation for future.
First, regarding the architecture, I think it's best to open this discussion too deeply. This kind of stuff can easily side track the discussion. For the purpose of this entire thread consider the fact that we have multiple front ends to the same business logic (EJBs). Tapestry (web), SOAP (web services) and Eclipse RPC (fat client). Our model dictates that HttpSession is used ONLY to retain state pertinent to the UI (page flow, element configuration etc). All business state is managed by the EJBs and consequently stored within SFSBs. We do not want to re-engineer business state mechanism for each UI. Having said that, back to Tapestry.. Since DI is not an option with SFSBs given only two available lifecycles Tapestry comes with out of the box (PerThreadServiceLifecycle and SingletonServiceLifecycle), without implementing a custom lifecycle (PerHttpSessionServiceLifecycle) Tap IOC will never know how to retain SFSB proxy within the HttpSession. However, a very much DI-like mechanism can be accomplished with @SessionState. In the nutshell, in any page or component we now do: @SessionState private MyStatefulEjbRemote statefulEjb; And Tapestry either creates stateful bean instance, or pulls it out of the session if one already exists. Of course, for this to work we need an ApplicationStateContribution and the actual ApplicationStateManager configuration. Thanks to generics, this can be a minimal burden: public class EjbModule { /** * Builds ApplicationStateContribution (ASC) with the underlying ApplicationStateCreator able to * produce a stateful EJB, based on the arguments provided. * * @param <T> The stateful EJB type * @param aEjbContext a valid jndi context used to look up the ejb * @param aEjbJndiName * @param aEjbInterface * @return */ private <T> ApplicationStateContribution buildEjbAsc(final Context aEjbContext, final String aEjbJndiName, final Class<?> aEjbInterface) { ApplicationStateCreator<T> creator = new ApplicationStateCreator<T>() { @SuppressWarnings("unchecked") public T create() { T service = null; try { Object ref = aEjbContext.lookup(aEjbJndiName); service = (T)PortableRemoteObject.narrow(ref, aEjbInterface); } catch(NamingException ne) { log.error("Unable to create " + aEjbInterface.getSimpleName(), ne); } return service; } }; return new ApplicationStateContribution("session", creator); } /** * Tell Tapestry which stateful EJBs are to be used with @SessionState. * * @param configuration * @param aEjbContext */ public void contributeApplicationStateManager( MappedConfiguration<Class<?>, ApplicationStateContribution> configuration, final @InjectService("EjbContext") Context aEjbContext) { configuration.add(StatefulMessageRemote.class, buildEjbAsc(aEjbContext, "StatefulMessageEjbRemote", StatefulMessageRemote.class)); configuration.add(StatefulNumberRemote.class, buildEjbAsc(aEjbContext, "StatefulNumberEjbRemote", StatefulNumberRemote.class)); configuration.add(CounterRemote.class, buildEjbAsc(aEjbContext, "CounterEjbRemote", CounterRemote.class)); } } As you can see, the generics-enabled buildEjbAsc reduces individual SFSB contributions down to one line of code. That's really all you need to get somewhat decent integration of EJB3 stateful session beans with your Tapestry applications. Now.... the ugly side.. Of course, there are some obvious issues, main one being session timeout (on both ends: servlet container vs. ejb container) and synchronization of such. We found that it's possible to write (on OpenEJB) a custom passivator that works in-tandem with HttpSession but that's too much work, and we are lazy around here :-) The scheme we use is to set SFSB timeout much larger (but finite) than HttpSession timeout. For example, if Jetty's session times out in two hours, our stateful bean container times out SFSBs after 12 hours of inactivity. (Don't worry about our other UI clients and what effects this may have on them). While not perfect, this should never break. For this to be exploited we'd have to have an active HttpSession longer than 12 hours while not invoking SFSB call during entire time - very unlikely. With that said, we still need to clean up the ejbs, particularly when HttpSession itself is timed out. HttpSessionListener does a nice trick here: public class StatefulEjbDestroyer implements HttpSessionListener { private static final Logger log = LoggerFactory.getLogger(StatefulEjbDestroyer.class); @Override public void sessionCreated(HttpSessionEvent aEvent) { HttpSession session = aEvent.getSession(); log.debug("--> session created [" + session.getId() + "]"); } @SuppressWarnings("unchecked") @Override public void sessionDestroyed(HttpSessionEvent aEvent) { HttpSession session = aEvent.getSession(); log.debug("--> destroying stateful EJBs from session [" + session.getId() + "]"); Enumeration<String> attributeNames = session.getAttributeNames(); while(attributeNames.hasMoreElements()) { String name = attributeNames.nextElement(); Object attribute = session.getAttribute(name); if(attribute instanceof DestroyableEjb) { DestroyableEjb ejb = (DestroyableEjb)attribute; log.debug("sending destroy signal for " + ejb.getEjbInfo()); ejb.destroy(); } } } } The DestroyableEjb is part of our scheme. Under our model any SFSB remote interface extends DestroyableEjb. This way we know which HttpSession object is the SFSB. We found that due to Tapestry's internal session management, same cleanup mechanism cannot be implemented with HttpSessionAttributeListener as Tapestry swaps session attributes in between requests retaining them elsewhere (its internal structures). I think this is due the 5.2 performance enhancement, but not sure. In any case, attempting to clean up the EJB in attributeRemoved will not do the trick, so keep that in mind. That's about it. I know there are better solutions to this problem, but having searched the mailing list I didn't find a single one so at least something is documented. The idea of creating PerHttpSessionServiceLifecycle so that stateful beans could potentially be injected with @InjectService is somewhat intriguing to me, but honestly, a bit over my head given my limited experience with Tapestry (5). Kind Regards, Adam On Fri, Nov 5, 2010 at 10:06 AM, Christian Riedel <cr.ml...@googlemail.com> wrote: > Hi Adam, > > perthread-services are recreated on each request... > >> PERTHREAD >> An alternate scope provided with Tapestry; a per-thread instance is created >> on demand, behind a shared proxy. > > You could create a simple Object to hold your proxy instead and store it in > the session. Something like that: > >> @SessionState private SFSBWrappingObject yourService; > > http://tapestry.apache.org/tapestry5.1/guide/appstate.html > http://tapestry.apache.org/tapestry5.1/apidocs/org/apache/tapestry5/annotations/SessionState.html > http://tapestry.apache.org/tapestry5.1/apidocs/org/apache/tapestry5/services/ApplicationStateManager.html > > If it's an option to have your Tapestry-App and the OpenEJB backend merged > you could try out Tapestry Jumpstart's approach of integrating OpenEJB with > Tapestry: > > http://jumpstart.doublenegative.com.au/ > > It's quite unfortunate architecture to have stateful services on a remote > machine... aren't there any alternatives speaking of architecture? > > Best, > Christian > > > Am 05.11.2010 um 12:56 schrieb Adam Zimowski: > >> Hi Christian - >> >> I did not know about @Scope annotation and it is nice. To answer your >> question yes, we need to retain a specific sfsb proxy instance per >> session, so yes, across multiple requests. Can I annotate service >> builder method with @Scope to tell Tap IOC to pull a specific instance >> for a session? Or is it purely a global singleton override to merely >> return a new instance for each @Inject ? >> >> To put it differently I would expect Tap IOC to be aware of user >> sessions, store sfsb for each session in some sort of a map and on >> each @Inject/@InjectService look up the map, if there pull the >> instance otherwise create new one. >> >> Adam >> >> On Fri, Nov 5, 2010 at 5:51 AM, Thiago H. de Paula Figueiredo >> <thiag...@gmail.com> wrote: >>> On Fri, 05 Nov 2010 08:30:52 -0200, adasal <adam.salt...@gmail.com> wrote: >>> >>>> Hi Christian, >>> >>> Hi, guys! >>> >>>> You know Tapestry very well. >>>> Do you have any points of comparison with JEE JSF, e.g. Ice Faces? >>>> It seems to me that JSF is very similar (by borrowed design) to Tapestry. >>>> But there must be technical points of significant difference? >>> >>> I know very little about JSF, but Tapestry 5 is very, very different from >>> JSF. The similarity is that their built on Java and are component >>> (event)-oriented frameworks. >>> >>> * Tapestry 5 doesn't use XML configuration besides web.xml, JSF does. >>> * JSF has a very complicated lifecycle, Tapestry doesn't. >>> * Tapestry is built and configured on an IoC framework, JSF doesn't, so you >>> can adapt it to your needs way simpler than JSF. >>> * Writing components in Tapestry is way easier. >>> * Tapestry has live class reloading, JSF doesn't. >>> * In Tapestry, pages are objects in a very OOP sense, while JSF, IMHO, >>> doesn't have a page concept. In Tapestry, there's a 1:1:1 relationship >>> between the page class, its template and its URL. >>> * Tapestry has its own template engine, not using JSP. JSF uses JSP (which >>> sucks) or Facelets (which its own documentation says that it was inspired >>> from Tapestry's template engine). >>> * Tapestry doesn't store page rendering information anywhere, JSF does. >>> * Tapestry 5 has native AJAX support. As far as I know, JSF doesn't have. >>> * JSF is an specification with two implementations which aren't 100% >>> compatible with each other. Tapestry is a framework. >>> >>> I could go on and on . . . >>> >>> -- >>> Thiago H. de Paula Figueiredo >>> Independent Java, Apache Tapestry 5 and Hibernate consultant, developer, and >>> instructor >>> Owner, Ars Machina Tecnologia da Informação Ltda. >>> http://www.arsmachina.com.br >>> >>> --------------------------------------------------------------------- >>> To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org >>> For additional commands, e-mail: users-h...@tapestry.apache.org >>> >>> >> >> --------------------------------------------------------------------- >> To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org >> For additional commands, e-mail: users-h...@tapestry.apache.org >> > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org > For additional commands, e-mail: users-h...@tapestry.apache.org > > --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org For additional commands, e-mail: users-h...@tapestry.apache.org