Das Problem
Im Artikel “Wicket, JPA und CMT” habe ich meine Nöte mit Wicket-Pages geschildert, da sich dort prinzipbedingt keine EJB als member halten lassen, ohne bei der Serialisierung einen Fehler zu liefern. Auch in einem Beitrag der mailing list habe das Thema vertieft und was gelernt.
Die Lösung
Ansatz
Letztendlich bin ich aber auch auf eine eigene, ganz gute Lösung gekommen, die ich hier vorstellen möchte. Die Idee ist, das EJB vor dem Request zu holen und nach dem Request, noch vor der Serializierung, zu entfernen, wie das in Wicket’s LoadableDetachableModel
sehr gut gemacht wird. Also der klassische Wicket-Ansatz große Datenblöcke nicht mit der Seite wegzuspeichern, sondern vor Gebrauch dynamisch wieder zu laden. Das Laden des EJB geschieht konsequenter Weise nicht mit dependency injection, sondern mit einem JNDI-Lookup.
Code
Die Implementierung des Model sieht so aus:
/** * Model for JPA facade beans. * @author Dieter Tremel */ public class EntityFacadeModel extends LoadableDetachableModel { private Class entityClass; public EntityFacadeModel(Class entityClass) { this.entityClass = entityClass; } @Override protected AbstractFacade load() { AbstractFacade result = null; try { InitialContext ctx = new InitialContext(); result = (AbstractFacade) ctx.lookup("java:module/" + entityClass.getSimpleName() + "Facade"); } catch (NamingException ex) { Logger.getLogger(EntityFacadeModel.class.getName()).log(Level.SEVERE, null, ex); } return result; } }
In der Seite wird das so eingebaut:
EntityDataProvider buchProvider = new EntityDataProvider<>(new EntityFacadeModel(Buch.class)); DefaultDataTable dTable = new DefaultDataTable<>("datatable", columns, buchProvider, 10);
Im ebenfalls generischen EntityDataProvider
muss dann die detach()
Methode überschrieben werden:
@Override public void detach() { facadeModel.detach(); super.detach(); }
Verfeinerung
Models, die eine EJB kapseln, kann man auch generisch implementieren, es muss dann nur noch die Bildung des Namens für den Lookup implementiert werden.
/** * Abstract model for wrapping an Enterprise Java Bean (EJB) in a {@link LoadableDetachableModel}. * The model can detach the EJB before serialization, which would fail, since EJB are not serializable. * On atach the EJB is loaded by an JNDI lookup. * The name of the lookup must be returned by {@link #getEjbName() }. * The input data for {@link #getEjbName() } are given in a constructor, which an implementation has to define. * * @param Type of wrapped EJB. * @author Dieter Tremel */ public abstract class AbstractEjbModel extends LoadableDetachableModel { /** * Get the name of the EJB type suitable for lookup. * @return EJB name. */ protected abstract String getEjbName(); @Override protected EJB load() { EJB result = null; try { InitialContext ctx = new InitialContext(); result = (EJB) ctx.lookup("java:module/" + getEjbName()); } catch (NamingException ex) { Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Error at JNDI lookup for " + getEjbName(), ex); } return result; } }
EntityFacadeModel
wird dann deutlich übersichtlicher:
/** * Model for JPA facade beans. * @param Type of JPA Entity. * * @author Dieter Tremel */ public class EntityFacadeModel extends AbstractEjbModel<AbstractFacade> { private Class entityClass; /** * Constructor creating an new model for a facade of an entity class. * @param entityClass Class of entity. */ public EntityFacadeModel(Class entityClass) { this.entityClass = entityClass; } @Override protected String getEjbName() { return entityClass.getSimpleName() + "Facade"; } }
Mit dieser Lösung kann man doch gut leben, insbesondere, wenn der JNDI-Lookup performant ist, was ich doch sehr hoffe.