EJB in Wicket: Wrapping in ein LoadableDetachableModel

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.