blog.smart-java.nl
Ordina J-Technologies – Java Blog

Archief ‘Java API’ categorie




[Closures] Introductie

Door: Jan-Kees van Andel, 29 August 2008

Closures, één van de meest bediscussieerde onderwerpen van de afgelopen paar jaren in de Java wereld. Deels een hype, deels bieden closures meerwaarde voor Java development. Ik ga niet meedoen met alle discussies waarom ze (niet) gewenst zijn, maar het is een feit dat het mogelijkheden biedt qua API design. In dit stuk hoop ik a) de mystieken van closures iets duidelijker te maken en b) wat inzicht te geven in de huidige closures oorlog bij Java.

Closures

Wat is een closure? WikiPedia (http://en.wikipedia.org/wiki/Closure_(computer_science)) heeft de volgende nietszeggende definitie: “In computer science, a closure is a function that is evaluated in an environment containing one or more bound variables. When called, the function can access these variables.”. Hoewel deze definitie klopt, is hij niet echt duidelijk. Waar komt het op neer?

Bij een closure wordt een functie uitgevoerd in een andere context dan waarin deze wordt gecreëerd. Deze zogenaamde “function context” wordt bij het creëeren van de functie bij de referentie naar de functie opgeslagen. Dit betekent dat als iemand deze referentie te pakken krijgt en de functie aanroept, de variabelen uit de context waarin de functie gemaakt is, beschikbaar zijn.

Voordat ik concreter wordt, wil ik nog even aangevan dat closures niet nieuw zijn. In vrijwel elke mainstream taal zit een soortgelijke constructie. Ook in Java, namelijk met anonymous inner classes, maar andere talen, zoals Smalltalk, JavaScript en Ruby bieden veel meer flexibiliteit.

Nog steeds nietszeggend? Dan maar een codevoorbeeld (JavaScript):

<html>
  <head>
    <script type="text/javascript">
      function initPage() {
        var counter = 0;
        window.onclick = function() {
          alert("Click #" + ++counter);
        };
      }
      window.onload = initPage;
    </script>
  </head>
  <body></body>
</html>

Wat gebeurt hier? Om te beginnen zijn functies in JavaScript first class objecten, in tegenstelling tot in Java, waarin methoden altijd onderdeel van een object zijn. Dit betekent onder andere dat je naar een functie kunt refereren, of deze referentie in een variabele kunt stoppen om deze daarna rond te gooien. Dit kun je zien aan de regel “window.onclick = …” waarin een object van het type function aan de onclick property van het window object toegekend wordt.

Wat (hopelijk) ook opvalt is dat er een function in een andere function gedeclareerd wordt. In dit voorbeeld spreken we van een anonymous inner function. Dit is een belangrijk detail, aangezien dit voor de JavaScript interpreter betekent dat er een closure gecreeerd moet worden. Wat betekent dit?

Kort gezegd betekent een closure dat bij de referentie naar de anonymous inner function, alle variabelen uit de enclosing scope bewaard blijven. Vandaar de naam closure. Deze variabelen vormen de function context welke vanaf nu een deel van de functie geworden. Elke keer als je deze functie aanroept, zijn de variabelen in de function context beschikbaar.

Wat is het nut dan van deze closure? Nou, de variabele “counter” heeft een function scope en verdwijnt op het moment dat de functie eindigt. Bij een event handler zoals in bovenstaand voorbeeld betekent dit dus dat de variabele op het moment van de klik niet bruikbaar is, aangezien het click-event niet in dezelfde stack plaatsvindt. Dit betekent dus dat je zonder closures een globale variabele nodig hebt om een simpel tellertje te maken. Met closures heb je deze beperking niet, want de variabele counter blijft bewaard en de ++ operator werkt de variabele in de function context bij, zodat het ook een echte teller is.

Conclusie

Closures bieden dus de mogelijkheid om stack confined variabelen te bewaren over meerdere stacks heen en zijn daardoor dus zeer geschikt voor event handlers. Dat is ook de reden dat ze in JavaScript veel gebruikt worden. JavaScript wordt voornamelijk gebruikt voor het koppelen van event handlers aan buttons etc. Daarnaast biedt het een mooie compacte syntax.

Volgende keer kijken we naar de Java closures initiatieven.




Request in LoginModule

Door: Jan-Kees van Andel, 28 July 2008

Je zult het misschien weleens gewild hebben, vanuit je JAAS LoginModule toegang hebben tot je HttpServletRequest. Helaas biedt JAAS hier geen ondersteuning voor.
Je JAAS LoginModule wordt namelijk door JAAS aangeroepen en je krijgt alleen een Subject, een CallbackHandler implementatie en nog een zootje parameters mee. Maar dus geen servlet objecten.

JACC

Voor deze beperking heeft Sun een oplossing (bestaal al sinds 2003), namelijk JACC. JACC staat voor Java Authorization Contract for Containers en is een specificatie die eigenlijk voor Container leveranciers bedoeld is. Echter, je kunt het als applicatie ontwikkelaar ook gewoon gebruiken.

JACC heeft namelijk de klasse PolicyContext en in deze klasse zit een methode getContext(String).

getContext accepteert de volgende Strings:

  • javax.security.auth.Subject.container
    Geeft het huidige Subject terug.
  • javax.xml.soap.SOAPMessage
    Geeft de SOAPMessage terug bij een EJB Web Service Endpoint (alleen EJB).
  • javax.servlet.http.HttpServletRequest
    Geeft het request terug.
  • javax.ejb.EnterpriseBean
    Idem voor de EJB.
  • javax.ejb.arguments
    Geeft een array terug met de parameters waarmee de EJB aangeroepen wordt. (alleen EJB)

In de spec staat niets over Java EE 5 of EJB 3.0, maar aangezien aan de onderliggende architectuur niets veranderd is, zal het ook op een Java EE 5 container wel werken.

Het HttpServletRequest

Hieronder staat een LoginModule die de HttpSession nodig heeft.

import javax.security.jacc.PolicyContext;
import javax.security.jacc.PolicyContextException;

// Other imports skipped

public class CustomLoginModule implements LoginModule {

    private Subject subject;
    private CallbackHandler callbackHandler;
    private Object object;

    public void initialize(Subject subject,
                           CallbackHandler callbackHandler,
                           Map sharedState,
                           Map options) {

        this.subject = subject;
        this.callbackHandler = callbackHandler;

        // Get an object from the HttpSession.

        try {
            HttpServletRequest req = (HttpServletRequest) PolicyContext.getContext("javax.servlet.http.HttpServletRequest");
            object = req.getSession().getAttribute("someAttribute");
        } catch (PolicyContextException e) {
            LOGGER.error("Unable to retrieve object from user session:", e);
            object = null;
        }
    }

    public boolean login() throws LoginException {
        // Do some login stuff and use the object from the session
    }

    public boolean commit() throws LoginException {}

    public boolean abort() throws LoginException {}

    public boolean logout() throws LoginException {}
}

Zoals je ziet, kun je via PolicyContext.getContext gemakkelijk aan het request komen. En als je een request hebt, is de sessie niet ver meer.

Met deze klasse wordt het dus stukken gemakkelijker om context specifieke zaken te gebruiken bij het inloggen.




JSF 2.0 EDR: Publish/Subscribe Event System

Door: Jan-Kees van Andel, 13 July 2008

In de vorige post heb ik het over het nieuwe resource management systeem van JSF 2.0 gehad. Dit keer wil ik met een voorbeeldje laten zien wat je kunt met het nieuwe Publish/Subscribe Event System.

Publish/Subscribe

Publish/Subscribe is een algemeen toegepast mechanisme om event driven applicaties te maken. Het werkt zo: Een publisher is de zender van een bericht. Deze verzendt een bericht naar de runtime die Publish/Subscribe verzorgt, zoals middleware of een framework. Op basis van bijvoorbeeld configuratie is bekend welke ontvangers geïnteresseerd zijn in dit bericht en het bericht wordt naar al deze ontvangers verstuurd.

Eigenschappen van Publish/Subscribe zijn:

  • Ontkoppeling van zender en ontvanger; het enige dat ze verbindt, is het bericht,
  • Een zender kan een bericht naar meerdere ontvangers sturen,
  • Een ontvanger kan van meerdere zenders een bericht ontvangen.

Typische Publish/Subscribe voorbeelden zijn een radiostation en RSS feeds. Publish/subscribe in GUI’s wordt doorgaans geimplementeerd middels het Observer pattern.

Laten we eerst kijken hoe JSF 2.0 dit model wil implementeren.

JSF 1.x events

De huidige versie van JSF bevat al een event mechanisme waarmee objecten/componenten events kunnen versturen en ontvangen. Een voorbeeld hiervan is het UICommand component, waarvan <h:commandLink /> en <h:commandButton /> gebruikmaken. Dit component kan ActionEvents versturen, wat ervoor zorgt dat bijvoorbeeld een action method in een managed bean uitgevoerd wordt.

Dit hele systeem draait om de methode queueEvent, gedefinieerd in UIComponent. Met deze methode worden events verstuurd die door de lifecycle op het juiste moment naar de juiste listener verstuurd worden.

Daarnaast heb je PhaseEvents, die aan het begin en aan het einde van elke fase in de lifecycle verstuurd worden en door PhaseListeners afgevangen worden.

Ten slotte definieert de JSF 1.x spec ValueChangeEvents, die afgevuurd worden als de nieuwe waarde (submittedValue) van een UIInput anders is dan de waarde tijdens het renderen van de view.

Naast de standaard events is het mogelijk om eigen events te definiëren, bijvoorbeeld als je een AJAX component library ontwikkelt.

JSF 2.0

In JSF 2.0 wordt een nieuw soort event geïntroduceerd, namelijk het SystemEvent. Dit event wordt gebruikt om op specifieke momenten in te prikken in de JSF applicatie. Op het moment zijn er 4 soorten SystemEvents gedefinieerd, namelijk:

  • AfterAddToParentEvent
    Geeft aan dat het component (de source parameter) is toegevoegd aan de component tree.
  • BeforeRenderEvent
    Geeft aan dat het component (de source parameter) straks gerenderd wordt. Dit is het moment om last minute aanpassingen te doen.
  • ViewMapCreatedEvent
    De view scope is aangemaakt. Zie View scope.
  • ViewMapDestroyedEvent
    De view scope is verwijderd. Zie View scope.

De spec is echter nog niet definitief, dus er kunnen nog events bijkomen. Daarnaast kun je zelf events definiëren.

Deze events maken het mogelijk om tweaks te doen aan de lifecycle van JSF, zonder terug te hoeven vallen op low-level zelfbouwoplossingen, zoals eigen PhaseListeners. Wie zelf weleens een AJAX component geschreven heeft met een PhaseListener (om het AJAX request af te vangen of voor resource requests), weet hoe onhandig het werkt en hoe foutgevoelig het is.

Bovendien wordt integratie van component libraries hiermee stukken makkelijker, aangezien ze onder water allemaal van dezelfde mechanismen gebruik maken. Nu nog hopen dat het voldoende flexibiliteit biedt voor libraries zoals Trinidad en Ajax4jsf, zodat ze hun eigen (Filter/RenderKit/ContextListener) oplossingen te migreren naar de officiële manier.

Eerlijk gezegd valt het systeem me tot nu toe nog wat tegen, voornamelijk het registreren van event listeners is niet echt handig. De voorbeeldapplicatie die met de JSF 2.0 EDR meegeleverd wordt, bevat een hack in de meegeleverde Facelets versie (de methode apply in ValidateHandler is non-final gemaakt) die het mogelijk maakt om de listener te registreren. Zonder een dergelijke hack kun je niet fatsoenlijk listeners registreren. Ik hoop dat ze een soort configuratie systeem toevoegen, want nu moet je weer terugvallen op PhaseListeners. :(

View scope

In JSF 2.0 is een nieuwe scope gedefinieerd, naast de bestaande scopes (request, session, application), namelijk de View scope. De spec zegt nog niets over de view scope, maar conceptueel is de view scope al wel bekend.

https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=290

De view scope zit tussen request en sessie in qua lifetime. De view scope begint op het moment dat een UIViewRoot instantie aangemaakt wordt en eindigt als een nieuwe UIViewRoot instantie actief wordt gemaakt via FacesContext.setViewRoot.

Concreet betekent dit dat zolang je op dezelfde pagina blijft (bijvoorbeeld een invoerscherm, waarin je – voordat je op “Save” drukt – nog wat extra acties doet), de data in de view scope in leven blijft. Op het moment dat je naar de volgende pagina (bijvoorbeeld van detail terug naar master) navigeert, wordt de view scope gereset.

Hoe werkt het?

Om dit alles mogelijk te maken zijn er een paar aanpassingen gedaan aan de API.

SystemEvent (nieuw)
Dit is de base klasse waarvan alle SystemEvents moeten overerven.

SystemEventListener (nieuw)
Dit is een nieuwe interface, die aangeeft dat de implementerende klasse als listener kan dienen voor SystemEvents. Er zijn twee methoden gedefinieerd, namelijk isListenerForSource en processEvent. processEvent spreekt voor zich, die wordt aangeroepen met als parameter de SystemEvent instantie. isListenerForSource wordt gebruikt om de calls van processEvent te beperken. Bijvoorbeeld tot events die gegooid zijn door instanties van een bepaald type. Je kunt een SystemEventListener dus configureren om alleen events van bepaalde componenten te verwerken.

ComponentSystemEventListener (nieuw)
Deze interface doet hetzelfde als SystemEventListener, maar bevat geen methode isListenerForSource, aangezien de koppeling met een bepaald type component al voor een impliciete variant van deze methode zorgt.

UIComponent (toevoegingen)
Hieraan zijn nieuwe methoden toegevoegd, namelijk subscribeToEvent, unsubscribeFromEvent en getListenersForEventClass.

public void subscribeToEvent(FacesContext context,
                             Class<? extends SystemEvent> eventClass,
                             ComponentSystemEventListener componentListener)

Met subscribeToEvent kun je componentListener aan deze component instantie koppelen die alle events afvangt die van het type eventClass zijn.

Met unsubscribeToEvent doe je precies het tegenovergestelde, namelijk de listener weer verwijderen.

getListenersForEventClass returned een lijst met alle listeners die events van het opgegeven type afvangen.

Application (toevoegingen)
Hieraan zijn ook een aantal methoden toegevoegd, namelijk subscribeToEvent, unsubscribeFromEvent en publishEvent. De eerste twee zijn volgens mij wel duidelijk, maar nu zijn de event handlers niet gekoppeld aan een specifieke component instantie. Met publishEvent kun je een event opgooien die dan door de JSF runtime verwerkt wordt.

@ListenerFor (nieuw)
Ten slotte is een annotation gemaakt, genaamd ListenerFor. Klassen die hiermee geannoteerd zijn, worden als listener geregistreerd. Dit gebeurt op dezelfde manier als hoe de subscribeToEvent methoden op Application en UIComponent werken. Dit is de verantwoordelijkheid van JSF.

Als je de annotation gebruikt heb je minder last van het feit dat er geen fatsoenlijk configuratiemechanisme is.

Voorbeeld

Iedereen (die met JSF gewerkt heeft) heeft het weleens gehad. Er treedt een validatiefout op, maar je hebt het niet door, omdat er geen <h:messages /> tag op de pagina ligt. Er vervelend. In dit voorbeeld laat ik een oplossing hiervoor zien met behulp van het beschreven event mechanisme.

We gaan door met de code uit het vorige voorbeeld. Ik beschrijf hier alleen de onderdelen die betrekking hebben op dit onderwerp.

Het idee is dat we op het moment dat we de view gaan renderen, kijken of er al een <h:messages /> tag op de pagina ligt. Als dit niet het geval is, voegen we deze programmatisch toe.

Een extra detail, dit mag alleen zichtbaar zijn tijdens development, daarom moeten we de volgende code toevoegen aan web.xml:

web.xml

<context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
</context-param>

Om te beginnen, zorgen we dat er geen <h:messages /> tag in de pagina ligt. Zo forceren we dat ons mechanisme afgaat. Zie de volgende pagina.

homepage.xhtml

<html ... >
<h:head>
  <title>JSF2 test</title>
</h:head>
<h:body>
  <p>Hello, you are logged in. </p>
  <h:form>
    <h:commandButton action="#{loginBean.addMessage}" value="Click" />
    <hr />
    <h:commandLink action="loginPage">Back to login page</h:commandLink>
  </h:form>
</h:body>
</html>

Dan maken we de event listener klasse, die ervoor zorgt dat een <h:messages /> tag aan de pagina wordt toegevoegd. In dit voorbeeld heb ik er zelfs een custom component van gemaakt, zodat je tijdens development goed kunt zien dat dit component automatisch toegevoegd is. Dat doen we in de volgende klasse.

AddDefaultMessagesListener.java

public class AddDefaultMessagesListener implements SystemEventListener {

    public boolean isListenerForSource(Object source) {
        return source instanceof UIViewRoot;
    }

    public void processEvent(SystemEvent evt) throws AbortProcessingException {
        if (!(evt instanceof BeforeRenderEvent)) {
            String klazz = (evt != null) ? evt.getClass().getName() : "null";
            throw new AssertionError("Event '"+evt+"' is not of type BeforeRenderEvent, but of type '"+klazz+"'");
        }

        FacesContext fc = FacesContext.getCurrentInstance();
        if (fc.getApplication().getProjectStage().equals(ProjectStage.Development)) {
            if (!hasMessagesComponent(fc, fc.getViewRoot())) {
                String viewId = fc.getViewRoot().getViewId();
                System.out.println("No <h:messages /> tag found in view '"+viewId+"'. ");
                addMessagesComponentToPage(fc, fc.getViewRoot());
            }
        }
    }

    private void addMessagesComponentToPage(FacesContext fc, UIViewRoot viewRoot) {
        String viewId = fc.getViewRoot().getViewId();
        UIForm form = findFirstForm(fc, viewRoot);
        if (form != null) {
            System.out.println("Adding default <h:messages /> tag to view '"+viewId+"' in the first form on the page. ");
            form.getChildren().add(0, new DummyUIMessages());
        } else {
            System.out.println("No form found while trying to add default <h:messages /> tag to view '"+viewId+"'. No <h:messages /> added. ");
        }
    }

    private boolean hasMessagesComponent(FacesContext fc, UIComponent comp) {
        if (comp instanceof UIMessages) {
            return true;
        }
        for (UIComponent child : comp.getChildren()) {
            if (hasMessagesComponent(fc, child)) {
                return true;
            }
        }
        return false;
    }

    private UIForm findFirstForm(FacesContext fc, UIComponent comp) {
        if (comp instanceof UIForm) {
            return (UIForm) comp;
        }
        for (UIComponent child : comp.getChildren()) {
            UIForm form = findFirstForm(fc, child);
            if (form != null) {
                return form;
            }
        }
        return null;
    }
}

Deze code zal vast niet optimaal zijn, al is het maar dat er naar de std out gelogged wordt. Ook zullen de zoekfuncties niet bijzonder efficiënt zijn, al weet ik zo snel geen snellere manier dan een vorm van caching en dat is weer erg fragiel aangezien de component tree nogal dynamisch is.

Deze listener wordt nog niet aangeroepen, want hij is nog niet geregistreerd. Dat doen we nu.

ConfigPhaseListener.java

public class ConfigPhaseListener implements PhaseListener {

    private static final Object LOCK = new Object();

    private static boolean initialized = false;

    //@BadCode(type="reallyBadCodeDoNotUseInProduction")
    public ConfigPhaseListener() {
        synchronized (LOCK) {
            if (!initialized) {
                AddDefaultMessagesListener listener = new AddDefaultMessagesListener();
                FacesContext fc = FacesContext.getCurrentInstance();
                fc.getApplication().subscribeToEvent(BeforeRenderEvent.class, listener);

                initialized = true;
            }
        }
    }

    public void afterPhase(PhaseEvent event) {}

    public void beforePhase(PhaseEvent event) {}

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }
}

Deze code is een beetje eng. Ten eerste wordt er in de constructor gesynchroniseerd. Dit is sowieso alleen maar mogelijk omdat het op een static variabele gebeurt, want in de constructor synchroniseren op this is gevaarlijk, aangezien this naar een algemeen beschikbaar object wijst. Ook staat er een static boolean initialized die in de constructor op true gezet wordt. De reden dat die er staat, is dat in de JSF spec niets gezegd wordt over de lifecycle van PhaseListeners.
Deze twee constructies (syncen en initalized boolean) zorgen er samen voor dat er niet meerdere instanties van de listener geregistreerd worden. Meerdere listeners zorgen namelijk voor dubbele output in de HTML pagina en dat willen we niet.

Note: Dit is absoluut niet production ready! Ik heb bijvoorbeeld geen rekening gehouden met geclusterde deployments.

We hebben nu de SystemEventListener en een PhaseListener om deze te registreren, ter volledigheid zal ik de faces-config nog even laten zien.

faces-config.xml

<lifecycle>
    <phase-listener>nl.ordina.jsf2.bean.ConfigPhaseListener</phase-listener>
</lifecycle>

Ten slotte mijn eigen UIMessages implementatie, die duidelijk laat zien dat je een fout gemaakt hebt.

public class DummyUIMessages extends UIMessages {

    public void encodeEnd(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        assert(writer != null);

        writer.startElement("div", this);
        writer.writeAttribute("style", "border: 1px solid #FF0000", null);
        writer.startElement("h1", this);
        writer.writeAttribute("style", "color: #FF0000", null);
        writer.write("Automatically inserted messages tag, for development purposes only. ");
        writer.write("");
        writer.write("Add a <h:messages /> tag to remove this crap. ");
        writer.endElement("h1");

        super.encodeEnd(context);

        writer.endElement("div");
    }
}

Je kunt waarschijnlijk wel zien wat deze code doet. Er wordt een dikke vette grote rode tekst getoond met een waarschuwing. Daaronder staan de messages.

En om het te bewijzen, hier een screenshot.

DummyUIMessages screenshot

Conclusie

Het nieuwe event mechanisme staat nog in zijn kinderschoenen, maar er staat wel een vrij goede basis waarmee je op zijn minst aan de gang kunt. Ik heb momenteel maar een paar dingen aan te merken, namelijk:

  • Ik wil nog meer soorten events, bijvoorbeeld events voor Exception handling, of events die veroorzaakt worden door specifieke lifecycle events, zoals validatiefouten waardoor de lifecycle kortsluit.
  • Ook zijn de event listeners nog niet echt goed te configureren. De API is er, maar waar roep je die methoden aan? Een ServletContextListener is geen optie, want die valt buiten de JSF FacesContext. Ik heb voor bovenstaand voorbeeld een PhaseListener gebruikt, maar dat is ook niet ideaal, aangezien een PhaseListener bedoeld is om faseovergangen te herkennen en niet gemaakt is voor initialisatie van je framework. JSF biedt hier echter geen hooks voor. Bovendien geeft het overhead. Deze overhead valt op zich wel mee, aangezien de logica in de constructor staat, maar dat is ook een hack, want de spec zegt niets over de lifecycle van PhaseListeners. Dat is ook de reden van deze overdreven defensieve constructie. Het is simpelweg een hack, maar hij werkt wel en deze extra checks maken hem ook portable over JSF implementaties.
  • Bovendien zou een meer declaratieve manier van configureren prettig zijn. Er zit een annotation bij, maar ik geef de voorkeur aan een XML config file, zoals een paar extra tags in faces-config.xml. Misschien kunnen ze bij JSF ook overstappen op namespaces zoals bij Spring.

Download sources

Klik hier voor de voorbeeldcode. Er zit ook een simpel autocomplete component bij dat gebruikmaakt van het nieuwe event mechanisme om AJAX requests af te handelen.

Autocomplete component screenshot

Note: ook dit autocomplete component is niet production-ready! Ik heb geen rekening gehouden met performance, security, robuuste code, JSF implementatie portability, Java versie, cross browser issues, client side memory usage, etc. Ook zul je de Prototype.js dependency waarschijnlijk niet willen.

Bronnen

Weblog van Ryan Lubke, waar nieuwe features van JSF 2.0 beschreven worden.
http://blogs.sun.com/rlubke/entry/jsf_2_0_new_feature1

De download pagina van de JSF 2.0 specificatie.
http://jcp.org/aboutJava/communityprocess/edr/jsr314/index.html




JSF 2.0 Early Draft

Door: Jan-Kees van Andel, 30 June 2008

Eindelijk, de eerste Mojarra (JSF 2.0) release kan gedownload worden van de JSF site. Eens kijken of ze de beloftes waar kunnen maken.

Ze zeggen dat ze op dit moment de volgende features hebben geïmplementeerd:

  • Resource management
  • Nieuw (publish/subscribe) event mechanisme
  • Project stages
  • Ajax API

Ik zie nog niets over Facelets, page flow, nieuwe scopes, integratie met WebBeans of Servlet 3.0, maar goed, daar staat momenteel ook nog niets over in de spec. Ik vind het belachelijk, maar goed, daar ga ik het dit keer niet over hebben.

Laten we eens gaan experimenteren met wat we WEL hebben, te beginnen met resource management.

Resource Management

Laten we beginnen met een simpele applicatie, namelijk een standaard inlogscherm. Je voert een username+password in en je wordt ingelogd. Daarna kom je op een homepage.

Libraries
Eerst eens de binaries downloaden. Die staan hier.
https://javaserverfaces.dev.java.net/servlets/ProjectDocumentList?folderID=9437&expandFolder=9437&folderID=0

In de lib directory staan 2 JAR’s. Die moet je naar de WEB-INF/lib van je web project kopiëren.

Config
web.xml

<web-app>
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>

  <context-param>
    <param-name>facelets.LIBRARIES</param-name>
    <param-value>/WEB-INF/facelet.taglib.xml</param-value>
  </context-param>

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>

</web-app>

Op het eerste gezicht weinig met een gewone web.xml in je huidige JSF project. In dit geval is er ook geen verschil, maar JSF 2 biedt extra configuratieopties die we later nog zien.

faces-config.xml

<faces-config>
  <application>
    <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
  </application>

  <navigation-rule>
    <navigation-case>
      <from-outcome>loginPage</from-outcome>
      <to-view-id>/login.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>

  <navigation-rule>
    <from-view-id>/login.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>success</from-outcome>
      <to-view-id>/homepage.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>

  <managed-bean>
    <managed-bean-name>loginBean</managed-bean-name>
    <managed-bean-class>nl.ordina.jsf2.bean.LoginBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>

  <validator>
    <validator-id>jsStringValidator</validator-id>
    <validator-class>nl.ordina.jsf2.validator.JsStringValidator</validator-class>
  </validator>

</faces-config>

Ook hier geen verschil met de huidige JSF versie. Het valt wel op dat er nog niets aan Facelets integratie gedaan is. Zie bijvoorbeeld de package naam van de ViewHandler, dat mag natuurlijk nooit in een Java API.

facelet.taglib.xml

<facelet-taglib>
  <namespace>http://www.ordina.nl/facelet/taglib</namespace>
  <tag>
    <tag-name>jsStringValidator</tag-name>
    <validator>
      <validator-id>jsStringValidator</validator-id>
      <handler-class>nl.ordina.jsf2.validator.JsStringValidatorHandler</handler-class>
    </validator>
  </tag>
</facelet-taglib>

Voor de mensen die Facelets kennen ziet deze file er ook niet vreemd uit. Gewoon een standaard Facelets taglib descriptor waarin we een Validator configureren voor gebruik binnen Facelets pagina’s.

Managed Beans
LoginBean.java

public class LoginBean implements Serializable {
    private String username, password;

    public String login() {
        if ("jan-kees".equals(username)
          && "van andel".equals(password)) {
            return "success";
        } else {
            FacesContext fc = FacesContext.getCurrentInstance();
            System.out.println(fc.getCurrentPhaseId().toString());
            fc.addMessage(null, new FacesMessage("Username and/or password invalid"));
            return "failure";
        }
    }

    // Getters and setters
}

Hier zijn ook niet veel verschillen te zien tussen een ouderwetse en moderne Managed Bean. En dat is maar goed ook. Het Managed Beans model van JSF zit goed in elkaar.

Leuk detail, je kunt nu ook de lifecycle fase opvragen vanuit de FacesContext. Geen PhaseListener meer nodig.

Views
De views zien er wel iets anders uit dan we gewend zijn.

login.xhtml

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:taglib="http://www.ordina.nl/facelet/taglib">

    <h:head>
        <title>JSF2 test</title>
    </h:head>
    <h:body>
        <img src="#{resource['images/logo.gif']}" alt="Logo Ordina" />
        <h2>Login</h2>
        <h:messages errorClass="error" />
        <h:form>
            <h:panelGrid columns="2">
                <h:outputLabel for="username" value="Username" />
                <h:inputText id="username" label="Username" required="true" value="#{loginBean.username}">
                    <taglib:jsStringValidator min="5" max="100" />
                </h:inputText>

                <h:outputLabel for="password" value="Password" />
                <h:inputSecret id="password" label="Password" required="true" value="#{loginBean.password}">
                    <taglib:jsStringValidator min="1" max="100" />
                </h:inputSecret>

                <f:facet name="footer">
                    <h:commandButton action="#{loginBean.login}" value="Login" />
                </f:facet>
            </h:panelGrid>

        </h:form>

        <h:outputStylesheet name="style/style.css" />

    </h:body>

</html>

In grote lijnen ziet het er herkenbaar uit, maar er zijn wel wat verschillen met voorheen.

Nieuwe tags
Mojarra heeft deze containers nodig voor zijn resource management mechanisme. Dit systeem maakt het mogelijk om op een willekeurige plek in de pagina een script of css te importeren, zonder dat je de ranzige HTML krijgt zoals bij de meeste frameworks, waar de <script> of <link /> tag gewoon tussen de content gegooid wordt. Los van dat het ranzig is, houden screenreaders er meestal ook niet van.Componenten kunnen ook resources nodig hebben, denk bijvoorbeeld aan een AJAX component die een stukje JavaScript nodig heeft. Die JavaScript wil je niet inline in je pagina hebben, ook wil je geen <script> tag in je body en je wilt je pagina ontwikkelaars niet lastigvallen met het feit dat jouw component een script nodig heeft.Je ziet bijvoorbeeld de tag staan. Deze staat in de body, dus het gebruik ervan is erg gemakkelijk voor een pagina ontwikkelaar, zeker wanneer je met een templating engine (zoals Facelets) werkt en je ergens in een geïnclude pagina een script wilt importeren. Met deze tag wordt de <h:outputstylesheet />
<link /> tag netjes in de head gezet.Je hebt ook nog een <h:outputscript /> tag. Die doet hetzelfde als de <h:outputstylesheet /> tag, maar dan voor een externe script file. Het verschil tussen outputScript en outputStylesheet is dat outputStylesheet standaard in de head wordt gezet en dat outputScript gewoon op dezelfde plaats blijft staan.Er is een attribuut “target” waarin de waarden “head” en “body” gezet kunnen worden. Hiermee overschrijf je de default target settings van outputScript. outputStylesheet wordt altijd in de head gerenderd.

Annotations
Het mooiste zou zijn als een component zelf zijn resources zou aangeven, zodat je in je pagina maar één tag hoeft neer te leggen, zonder het gedoe dat je scripts ofzo vergeet.In mijn testapplicatie heb ik een Validator geschreven die met JavaScript ook op de client een check doet. Deze JavaScript hoort bij het component en is dus daar ook geconfigureerd.

@ResourceDependency(name = "js/stringValidator.js")
public class JsStringValidator implements Validator, ComponentSystemEventListener {
    private int min = 5, max = 100;

    public void validate(FacesContext fc, UIComponent comp, Object value) throws ValidatorException {
        if ((fc == null) || (comp == null)) {
            throw new NullPointerException("comp or facescontext");
        }
        if (!(value instanceof String)) {
            throw new IllegalArgumentException("Argument value is no String, but a " + value);
        }
        String converted = (String) value;
        if (converted.length() < min
          || converted.length() > max) {
            String message = MessageFormat.format(
                                    "{0} is not between {1} and {2}",
                                    converted, min, max);
            FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
            throw new ValidatorException(facesMessage);
        }
    }

    public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
        UIComponent c = (UIComponent) event.getSource();
        FacesContext fc = FacesContext.getCurrentInstance();
        String js = MessageFormat.format(
                                                "validate(\"{0}\", {1}, {2})",
                                                c.getClientId(fc), min, max);
        c.getAttributes().put("onfocus", js);
        c.getAttributes().put("onblur", js);
        c.getAttributes().put("onkeyup", js);
    }

    // Getters and setters
}

Let op de annotation bovenaan de klasse, namelijk @ResourceDependency(name = “js/stringValidator.js”). Hiermee kun je aangeven dat jou component bepaalde resources nodig heeft om zijn werk te doen. Dit component heeft dus een JavaScriptje nodig om op de client wat te doen.

Let ook op de methode processEvent, waarin de aanroep naar de volgende JavaScript function wordt gemaakt. Dit is de enige plaats waar een Validator in het rendering mechanisme kan inprikken. Hoe en waarom de processEvent methode aangeroepen wordt, komt een volgende keer, bij event handling.

function validate(id, min, max) {
    var inputFld = document.getElementById(id);
    if (inputFld.value.length < min
     || inputFld.value.length > max) {
        inputFld.style.borderColor = "#FF0000";
    } else {
        inputFld.style.borderColor = "#000000";
    }
}

Dit script doet weinig spannends, namelijk als een veld onjuist is ingevuld, het randje rood kleuren, maar je kunt natuurlijk ook geavanceerde RIA dingen op deze plaats programmeren, zoals effecten of AJAX calls.

De volgende keer gaat het over een andere nieuwe feature, namelijk het nieuwe event mechanisme. Daarin leg ik meteen uit hoe bovenstaande Validator opgenomen wordt in het renderen van de pagina, want een Validator wordt normaal gesproken niet gerenderd. Die bestaat alleen server side.

Source code
Sample application demonstrating resource management




Lees de Javadoc

Door: Jan-Kees van Andel, 27 June 2008

Om de een of andere reden zie je nog steeds vaak dat mensen hun SimpleDateFormat instanties in members verpakken.

DOE DIT NIET!

De Javadoc van SimpleDateFormat zegt heel duidelijk dat de klasse niet thread safe is en dus ook niet op zo’n manier gebruikt mag worden.
http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#synchronization

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

Dit is niet alleen een theoretisch probleem, maar kan tot hele vreemde bugs leiden, zoals:

  • OutOfBoundsExceptions op positie 13 (in een datum :S),
  • java.lang.NumberFormatException: For input string: “”,
  • java.lang.NumberFormatException: multiple points,
  • java.lang.NumberFormatException: empty String,
  • Of gewoon verkeerde output (invoer 11-11-1911, output 11-01-0001).

Dit waren gewoon een paar voorbeeldjes uit wat test runs. Er zullen vast nog wel vreemdere fouten kunnen optreden. Bovendien komen dergelijke bugs vaak alleen in productie (onder hoge load) aan het licht, waardoor het nog vervelender wordt.

Dus maak nooit meer private static final SimpleDateFormat SDF = new SimpleDateFormat(“dd-MM-yyyy”); constructies, tenzij je alle aanroepen naar dat object synchronized maakt.

Maar dit laatste is ook geen optie, om twee redenen.

  1. Creatie van SimpleDateFormat objecten is heel goedkoop, dus er is eigenlijk geen reden om ze te hergebruiken/poolen. Bovendien, als je ze op stack niveau gebruikt, kan de compiler en de Java runtime het veel efficiënter verwerken.
  2. Synchronized blokken zijn op zichzelf niet meer duur, zeker niet vanaf Java SE 6. Het grote nadeel van synchronized blijft echter nog steeds staan, namelijk dat meerdere threads het code block niet parallel uit kunnen voeren, waardoor je een bottleneck introduceert.

Dus zorg dat je je SimpleDateFormat klassen op stack niveau houdt.

Eventueel, als je ECHT veel instanties aan moet maken, kun je naar pool maken. Zorg wel dat die pool stack confined is, zoals een ThreadLocal. Voorbeeld:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.text.ParseException;
import java.text.SimpleDateFormat;
 
public class MultiThread {
	public static final FormatterPool	POOL	= new FormatterPool();
 
	public static void main(String[] args) {
		for (int i = 0; i < 50; i++) {
			new Thread(new Worker()).start();
		}
	}
 
}
 
class Worker implements Runnable {
 
	public void run() {
		try {
			SimpleDateFormat sdf = MultiThread.POOL.get();
			System.out.println(sdf.format(sdf.parse("11-11-1911")));
		} catch (ParseException e) {
			e.printStackTrace();
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
}
 
import java.text.SimpleDateFormat;
 
public class FormatterPool extends ThreadLocal<SimpleDateFormat> {
 
	private static final SimpleDateFormat	SDF	= new SimpleDateFormat("dd-MM-yyyy");
 
	@Override
	protected SimpleDateFormat initialValue() {
		return new SimpleDateFormat("dd-MM-yyyy");
	}
 
}

Let op bij het gebruik van ThreadLocal. De initialValue bevat in sommige Java versies nog bugs.