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

Archief ‘Tools/Frameworks’ categorie




Temporale data – deel 1: de concepten

Door: gnoij, 19 November 2008

In een serie van 4 artikelen wil ik een framework voor gebruik van temporale data presenteren. In dit eerste deel wordt uitgelegd wat temporale data is en wat de consequenties zijn. In de volgende delen wordt het framework in een aantal stappen gepresenteerd.

Een korte introductie in temporale data

Temporale data is data waarbij tijdsaspecten een rol spelen.

Ter illustratie even een voorbeeld van een simpel HRM systeem:

Kees werkte vanaf 1-1-2003 op de afdeling Inkoop. Vanaf 1-1-2006 werkt hij op de afdeling Verkoop. Deze overgang werd op 1-2-2006 geregistreerd in het systeem.

Schematisch ziet dat er als volgt uit:

HRM voorbeeld

Uit bovenstaand voorbeeld blijkt dat de tijdsaspecten die een rol kunnen spelen zijn: een ingangsdatum, een einddatum en een registratiedatum. Welke tijdaspecten voor de te ontwikkelen applicatie een rol spelen hangt af van de vragen die de applicatie moet kunnen beantwoorden.

De mogelijke vragen met betrekking tot de tijdsaspecten in bovenstaand voorbeeld zijn:

  1. Waar werkt Kees nu?
  2. Sinds wanneer werkt Kees op de afdeling Verkoop?
  3. Gedurende welke periode werkte Kees op de afdeling Inkoop?
  4. Op welke afdeling werkte Kees op 15-1-2006 als we keken in het systeem op 15-1-2006?

Om elke vraag te kunnen beantwoorden heb je andere tijdsaspecten nodig.

Vraag 1 kun je beantwoorden zonder extra tijdsgegevens op te slaan.Vraag 2 kun je beantwoorden door een ingangsdatum te gebruiken.Voor vraag 3 heb je een ingangsdatum en een einddatum nodig en voor vraag 4 heb je naast de ingangs- en einddatum ook registratiedata nodig.

Om de vragen 2, 3 en 4 ook voor het verleden te kunnen beantwoorden moet er ook historie van de data worden bijgehouden. Dat betekent dat elke wijziging leidt tot een nieuw record in de database.

De historievormen met geldigheid (een ingangsdatum en een einddatum) noemen we temporale data en met geldigheid en registratiedata bitemporale data.

temporale data

Bij temporale data hebben we te maken met de tijdsaspecten ingangsdatum en einddatum. Willen we ook historie gaan bijhouden, moeten de wijzigingen op een temporal object leiden tot een nieuw record in de database. Dat betekent dat een object in principe immutable is. Wijzigingen op een object leiden altijd tot een nieuw object.

In principe onderscheiden we drie situaties: inschrijving, wijziging en beëindiging van de data.

Bij inschrijving wordt er een record aan een tabel toegevoegd in de database toegevoegd. Voor ons voorbeeld ziet dat er als volgt uit:

temporal insert

De FK kolom wijst naar de tabel Werknemer met Primary Key 1.

Voor een wijziging wordt er een nieuw record in de tabel geschreven, en wordt het vorige record afgesloten met een einddatum.

Tenslotte voor een beëindiging wordt het actuele record afgesloten met een einddatum. Als in het voorbeeld Kees uit dienst gaat op 1-1-2008 krijgen we de volgende records te zien.

We kunnen nu de vragen 1 tot en met 3 beantwoorden, maar niet vraag 4.

Antwoorden:

  1. Nergens, want Kees is per 1-1-2008 uit dienst.
  2. Sinds 1-1-2006 werkte Kees op de afdeling Verkoop
  3. Tussen 1-1-2003 en 1-1-2006 werkte Kees op de afdeling Inkoop
  4. Op 15-1-2006 wisten wij dat Kees op de afdeling Verkoop werkte. (Terwijl het systeem dit pas op 1-2-2006 heeft geregistreerd, dus is het antwoord fout. Dit moet Inkoop zijn)

bitemporale data

Zoals uit bovenstaande records blijkt kunnen we niet achterhalen wanneer iets is geregistreerd. Of wanneer de organisatie wist wanneer een wijziging is doorgevoerd. In sommige (vooral juridische) situaties is dit wel nodig. Hiervoor moeten we de geldigheid uitbreiden met registratiedata voor opvoer en afvoer. Dit noemen we bitemporale data

We kunnen dezelfde drie situaties als bij temporale data onderscheiden: inschrijving, wijziging en beëindiging.

Bij inschrijving wordt er een record aan de database toegevoegd. Dit is te zien in onderstaande tabel.

Hierbij zijn de kolommen Opvoer en Afvoer toegevoegd aan de tabel.

Bij wijziging wordt het eerste record afgesloten, wordt er een kopie gemaakt met einddatum en wordt de wijziging toegevoegd.

Per wijziging wordt dus (naast de wijziging) een extra record in de database toegevoegd met de oorspronkelijke informatie plus een afvoerdatum.

Bij beëindiging wordt een kopie gemaakt van het actuele record, de actuele versie krijgt een afvoerdatum en de kopie krijgt een einddatum.

Ook voor beëindiging wordt een extra record toegevoegd met de oorspronkelijke informatie plus een afvoerdatum.

Met het gebruik van zowel geldigheid als registratiedata is ook vraag 4 juiste te beantwoorden. Door te zoeken naar het record dat opgevoerd is voor 15-1-2006 en niet voor die datum is afgevoerd zien we dat het juiste antwoord Inkoop is.

Mogelijke oplossingen

Mogelijke oplossingsrichtingen voor de bouw van applicaties met (bi)temporale data zijn:

Het framework dat ik in de volgende artikelen wil presenteren is gebaseerd op de maatwerkoplossing, waarbij gebruik wordt gemaakt van de Temporal patterns van Fowler.

Dit framework bestaat uit patterns voor temporale data en bitemporale data. In het volgende artikel zal ik patterns voor temporale data presenteren. Het derde deel zal patterns voor bitemporale data behandelen en in het (voorlopig) laatste deel zullen we deze patterns uitbreiden voor collections.




Flat file parsing met Spring Batch

Door: Richard Kettelerij, 7 November 2008

Legacy systemen gebruiken vaak flat files voor gegevens uitwisseling. Bij integratie met dergelijk systemen moeten deze files worden geparst. In zo’n geval kan je natuurlijk zelf met de Java Scanner of StingTokenizer aan de slag gaan maar het is waarschijnlijk verstandiger om een bestaand framework te gebruiken. Spring Batch biedt hiervoor een elegante oplossing.

Mocht je overigens meer willen weten over Spring Batch en batch processing in het algemeen? Schrijf je dan in voor Special Meeting op 18 november a.s.

Bestandsdefinitie
Als voorbeeld gebruiken we een fixed-width file afkomstig uit een legacy systeem van een fictieve online videotheek. Zoals gebruikelijk bij deze vorm van gegevensuitwisseling bestaat het bestand uit verschillende soorten records: Een header record met metadata als een bedrijfsnaam en batchnummer, een footer record met een controlegetal en uiteraard een aantal records met de daadwerkelijke informatie; in dit geval film titels.

H OrdinaVideoStore.nl 12
M Shrek II
M Lord of War
M Godfather, The
M Kungfu Panda
F 0000000000000000006

Tokenizing
Om bovenstaand bestand te kunnen parsen dient allereerst onderscheid te worden gemaakt tussen de verschillende soorten records. Aangezien dit uit het eerste karakter van elk record kan worden afgeleid gebruiken we de PrefixMatchingCompositeLineTokenizer.

<bean id="movieFileLayout"
class="org.springframework.batch.item.file.transform.PrefixMatchingCompositeLineTokenizer">
	<property name="tokenizers">
		<map>
			<entry key="H" value-ref="movieHeaderRecordLayout" />
			<entry key="M" value-ref="movieRecordLayout" />
			<entry key="F" value-ref="movieFooterRecordLayout" />
		</map>
	</property>
</bean>
 
<bean id="movieHeaderRecordLayout"
	class="org.springframework.batch.item.file.transform.FixedLengthTokenizer">
	<property name="names" value="recordtype, videostore, batchid" />
	<property name="columns" value="1,5-25,28-30" />
</bean>

Deze tokenizer geeft records op basis van het type door aan een nieuwe LineTokenizer. Zoals je in bovenstande configuratie kunt zien mapped deze tokenizer elke fixed-width kolom naar een bijbehorende kolomnaam. Hier zie je duidelijk de kracht van de configuratie mogelijkheden in Spring. Je geeft gewoon in de application context de ranges van gerelateerde kolommen op en Spring Batch doet de rest. Om deze magic mogelijk te maken heeft Spring wel wat hulp nodig in de vorm van een CustomEditorConfigurer. Deze vertaald de range definities (5-3, etc) naar betekenisvolle Range objecten.

<bean id="customEditorConfigurer"
	class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="customEditors">
	  <map>
	    <entry key="org.springframework.batch.item.file.transform.Range[]">
	      <bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" />
	    </entry>
	  </map>
	</property>
</bean>

Mapping naar domein objecten
Vervolgens is het de bedoeling om de kolom/veld definities naar domein objecten te vertalen. Hiervoor heeft Spring Batch de FieldSetMapper interface geïntroduceerd. Hoewel we hiermee zelf de mapping van velden naar domein objecten kunnen verzorgen is het mogelijk om dit aan Spring Batch over te laten middels een FieldSetMapper gebaseerd op Spring’s BeanWrapper. Er moet echter ook onderscheid worden gemaakt tussen de verschillende record soorten. Hiervoor is – in tegenstelling tot de prefix-enabled tokenizer – geen standaard fieldset mapper aanwezig. Daarom schijven we zelf een eenvoudige mapper:

public class PrefixAwareMovieFieldSetMapper implements FieldSetMapper {
	private FieldSetMapper headerFieldSetMapper;
	private FieldSetMapper movieFieldSetMapper;
	private FieldSetMapper footerFieldSetMapper;
 
	public Object mapLine(FieldSet fieldSet) {
		final String recordType = fieldSet.readString("recordtype");
		if (recordType.equals("H")) {
			return headerFieldSetMapper.mapLine(fieldSet);
		}
		else if (recordType.equals("M")) {
			return movieFieldSetMapper.mapLine(fieldSet);
		}
		else if (recordType.equals("F")) {
			return footerFieldSetMapper.mapLine(fieldSet);
		}
		throw new IllegalStateException("onbekend record type");
	}
	// setters
}

Wanneer we deze FieldSetMapper configureren is de mapping van tokens naar domein objecten compleet.

<bean id="movieFileFieldSetMapper"
	class="nl.ordina.batch.PrefixAwareMovieFieldSetMapper">
	<property name="headerFieldSetMapper" ref="movieHeaderFieldSetMapper" />
	<property name="movieFieldSetMapper" ref="movieFieldSetMapper" />
	<property name="footerFieldSetMapper" ref="movieFooterFieldSetMapper" />
</bean>
 
<bean id="movieHeaderFieldSetMapper" 
   class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
	<property name="prototypeBeanName" value="headerRecord" />
</bean>
 
<bean id="headerRecord" class="nl.ordina.batch.model.MovieHeaderRecord"
scope="prototype" />

Parsen
Nu we hebben gedefinieerd welke karakters naar welke velden mappen, en welke velden naar welk domein object mapped wordt het tijd om bestanden te parsen. Hiervoor gebruiken we een FlatFileItemReader die we injecteren met de eerder gecreërde tokenizer en fieldset mapper. Geconfigureerd binnen een batch job ziet dit er als volgt uit. Wanneer deze job wordt uitgevoerd ontvangt de “DummyWriter” voor elke record in het bronbestand een overeenkomstig domein object.

<bean id="videotheekJob" parent="simpleJob">
  <property name="steps">
    <list>
      <bean id="printRecordStep" parent="simpleStep">
	<property name="itemReader">
	   <bean class="org.springframework.batch.item.file.FlatFileItemReader">
	     <property name="resource" ref="file:///videotheek/films.txt" />
	     <property name="lineTokenizer" ref="movieFileLayout" />
	     <property name="fieldSetMapper" ref="movieFileFieldSetMapper" />
	   </bean>
	</property>
	<property name="itemWriter" ref="dummyWriter" />
      </bean>
    </list>
  </property>
</bean>

Zoals je wellicht opvalt wijst de FlatFileItemReader naar een hardcoded filepath. Momenteel biedt Spring Batch nog geen mogelijkheid om dit voor meerdere bestanden configurabel te maken. Dit heb ik inmiddels gemeld via een JIRA ticket en een oplossing bijgevoegd in de vorm van een DynamicMultiResourceItemReader.

Tot slot, Spring Batch biedt m.i. behoorlijke ondersteuning om diverse soorten bestanden te parsen. Dit is met name handig in applicaties die reeds gebruik maken van het Spring Framework. Maar Spring Batch bevat méér en is zeker het overwegen waard buiten pure Spring applicaties.




Facelets code completion in Eclipse Ganymede

Door: Ron Thijssen, 23 October 2008

Onlangs hebben Jan-Kees en ik de J-Tech JSF cursus flink op de schop genomen. We hebben de keuze gemaakt om JSP er definitief uit te bonjouren, en daarvoor in de plaats de Facelets te gebruiken. Facelets biedt enorm veel mogelijkheden, en is de facto standaard voor een JSF project. Het maakt het leven van een JSF ontwikkelaar op een flink aantal gebieden een stuk aangenamer.

Vorige week, tijdens de 1e dag van de JSF cursus, liepen we echter tegen het grootste struikelblok van Facelets aan, namelijk: Support in diverse IDE’s. Facelets vervangt de traditionele *.jsp files in op XML gebaseerde *.xhtml files. Deze worden standaard niet ondersteund door bijvoorbeeld Eclispe, dat wil zeggen; Geen tag completion of validatie van de gebruikte tags uit de namespaces. Hierdoor kan het voorkomen dat je nogal snel een typvoudt maakt die je pas tijdens het draaien van de applicatie tegenkomt. Ik had in een grijs verleden iets gelezen over het gebruiken van .jspx files ipv .xhtml. Tijd om eens uit te zoeken wat de opties zijn. (more…)




Spring MVC 2.5: BindingResult probleempje

Door: Jan-Kees van Andel, 9 October 2008

Zojuist liep ik tegen een vervelend probleempje aan. Ik was bezig met het bouwen van een weblog met Spring 2.5 en alles ging goed, tot het laatste schermpje. Namelijk een klein schermpje om je password te wijzigen.

Dit is de controller:

@Controller
@SessionAttributes("changePassword")
public class ProfileController extends AbstractAdminController {
 
    public static final class ChangePasswordBean {
        private String password1;
        private String password2;
        // Getters and setters
    };
 
    @Autowired private BlogService blogService;
 
    @RequestMapping(value="/admin/users/profile", method=RequestMethod.GET)
    public ModelAndView setupForm(ModelMap model, HttpSession session) {
        model.addAttribute("changePassword", new ChangePasswordBean());
        return new ModelAndView("admin/users/profile", model);
    }
 
    @RequestMapping(value="/admin/users/profile", method=RequestMethod.POST)
    public String processSubmit(
            @ModelAttribute ChangePasswordBean changePassword,
            BindingResult result, HttpSession session, SessionStatus status) {
        new PasswordValidator().validate(changePassword, result);
        if (result.hasErrors()) {
            return "admin/users/profile";
        } else {
            // Call to service layer
            return "redirect:/admin/home.html";
        }
    }
}

Dit is de JSP met het form:

  ...
  <form:form modelAttribute="changePassword">
    <form:errors />
    <table class="form">
      <tr>
        <td><form:label for="password1" path="password1">Password</form:label></td>
        <td><form:password id="password1" maxlength="20" path="password1" /></td>
        <td><form:errors path="password1" /></td>
      </tr>
      <tr>
        <td><form:label for="password2" path="password2">Retype password</form:label></td>
        <td><form:password id="password2" maxlength="20" path="password2" /></td>
        <td><form:errors path="password2" /></td>
      </tr>
      <tr>
        <td colspan="3">
          <input type="reset" value="Cancel" /> |
          <input type="submit" value="Save" />
        </td>
      </tr>
    </table>
  </form:form>
  ...

Deze opzet werkte bij alle schermen, maar om de een of andere reden niet voor dit “simpele” schermpje. Het form openen ging goed, submitten ging ook goed, zolang er geen validatiefouten waren, maar bij validatiefouten kreeg ik de volgende melding:

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'changePassword' available as request attribute
    at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141)
    at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:172)
    at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.resolveCssClass(AbstractHtmlElementTag.java:412)
    at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeOptionalAttributes(AbstractHtmlElementTag.java:388)
    ...

Toch creemd. Ik had de JSP en de controller gewoon gekopieerd van een wel werkend scherm, maar om de een of andere reden werkte het toch niet. Ligt het aan de inner class? Nee, vast niet. Hoewel, het is wel een final class. Misschien wil Spring wel een proxy aanmaken en mag de class niet final zijn ofzo. Hup, final eraf. Scheisse, nog steeds hetzelfde probleem. Wat kan het dan zijn?

Oplossing

Toen viel mijn oog op de processSubmit methode in de controller. Daar week de naam van de parameter af van de klassenaam. Class name: ChangePasswordBean, argument name: changePassword.

Hmm, dat zal wel het probleem zijn, want bij alle andere schermen waren deze gelijk. Toevallig wist ik dat in de bytecode van de klasse geen parameternamen staan en dus ook niet @runtime uit te lezen zijn. De mannen van Spring zijn waarschijnlijk tegen dezelfde beperking aan gelopen en hebben hier omheen gewerkt door standaard de type name als request attribute name te gebruiken, in plaats van de parameter naam (wat logischer is, maar dus niet kan in Java).

Dit was de oplossing:

    // Before fix (this didn't work)
    public String processSubmit(
			@ModelAttribute ChangePasswordBean changePassword,
			...) {
 
    // After fix (working solution)
    public String processSubmit(
			@ModelAttribute("changePassword") ChangePasswordBean changePassword,
			...) {

Dusss, gebruik in je JSP’s de klassenaam of geef expliciet de naam op in de @ModelAttribute annotation.




Rich web applicaties met Spring Web MVC

Door: Jethro Bakker, 5 September 2008

In de spring cursus van Ordina wordt het Spring Web MVC framework behandeld. Tijdens de cursus wordt ook gekeken naar de integratie met ajax technologie.  In 2007 is bijvoorbeeld het DWR (Direct Web Remoting) framework behandeld. Met de komst van Spring Webflow 2.0 is het DWR onderdeel komen te vervallen en behandelen we nu Spring Javascript. Het is een relatief onbekende library, die wat mij betreft meer aandacht mag krijgen.

Wat is Spring Javascript?

Je kunt Spring Javascript zien als een abstractie laag bovenop bestaande widget technologie en ajax remoting. Momenteel is er alleen nog ondersteuning voor de Dojo Toolkit maar op termijn zal dit verder uitgebreid worden. Het zal dan een generieke API zijn waarmee je andere javascript frameworks kunt gebruiken.  Spring Javascript biedt je verder een resource servlet, deze servlet handelt het CSS, JS verkeer af wat je nodig hebt voor rich widgets. Een Dojo javascript file laad je bijvoorbeeld als volgt in:

<script type=”text/javascript” src=”<c:url value=”/resources/dojo/dojo.js” />”> </script>

De servlet moet je natuurlijk registreren in de web.xml en mapt op url pattern /resources/*. De Spring Javascript JAR module bevat alle javascript en stylesheets resources die je nodig hebt voor het Dojo framework. Naast de genoemde zaken bevat Spring Javascript de volgende classes:

  • AjaxTilesView, is een Tiles view implementatie dat delen van een pagina kan renderen voor ajax requests.
  • AjaxUrlBasedViewResolver, een UrlBasedViewResolver met een specifieke implementatie voor redirect views.
  • SpringJavascriptAjaxHandler, een handler klasse die gebruikt wordt door de AjaxTilesView om ajax requests goed te kunnen afhandelen.

Voorbeeld

We starten met het registreren van twee beans, de tiles configurer en de view resolver.

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
  <property name="definitions">
    <list>
	<value>/WEB-INF/defs/general.xml</value>
    </list>
  </property>
</bean>

<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
  <property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
</bean>

De ajax support in Spring Javascript werkt nauw samen met Apache Tiles. Met Apache Tiles kun je de webpagina opdelen in logische stukken. Met behulp van Spring Javascript kun je een los deel vervangen. Bijvoorbeeld alleen de body. Een normale link moet je decoreren met een stuk javascript:

<a id="newEntry" href="entry/edit.html">Nieuwe entry</a>
<script type="text/javascript">
Spring.addDecoration(new Spring.AjaxEventDecoration({
  elementId: "newEntry",
  event: "onclick",
  popup: false,
  params: {fragments: "body"}
}));

Deze link zal nu ajax gebruiken en het onderdeel ‘body’ van de pagina vervangen door nieuwe content. Je hebt ook de mogelijkheid om de link in een pop-up te openen. Naast deze ondersteuning voor ajax remoting is het ook mogelijk om widgets te gebruiken. Dat kan als volgt:

<form:input id="subjectString" path="subject" />
<script type="text/javascript">
Spring.addDecoration(new Spring.ElementDecoration( {
  elementId :"subjectString",
  widgetType :"dijit.form.ValidationTextBox",
  widgetAttrs : {
    promptMessage :"Fill in a subject for this blog entry"
  }
}));
</script>

Zoals je kunt zien wordt de Spring Javascript library aangeroepen. Deze library zal intern de Dojo toolkit gebruiken. Spring Javascript is dus een abstractie laag bovenop de bestaande widget technologie. Op dit moment biedt dat nog niet zoveel voordeel maar op het moment dat er meerdere widget technlogieen ondersteund worden kun je op een uniforme manier de widget technologie gebruiken.

Ik ben benieuwd wat jullie er van vinden.

In een volgende post zal ik ingaan op Spring Faces, deze library maakt ook onderdeel uit van de Spring Webflow distributie.




Dojo Toolkit 1 – Uitbreidbare HTML

Door: jeroen weelink, 28 August 2008

Voor browsers bestaat een ongeschreven regel: alles dat de browser niet begrijpt, wordt genegeerd. Dit is ook de reden dat browsers XHTML lijken te begrijpen als het document met het (verkeerde) content-type text/html wordt verstuurd i.p.v. application/xhtml+xml:

<br />

wordt geinterpreteerd als een br-tag met een attribuut ‘/’ (zonder waarde). Browsers snappen dit attribuut niet en wordt genegeerd.

Deze ongeschreven regel biedt interessante mogelijkheden om HTML elementen uit te breiden met metadata:

<html>
    <head>
        <title>Dojo Toolkit 1: Validatie</title>

        <script type="text/javascript">
            function valideer()
            {
                var isGeldig = true;

                var naam = document.getElementById("naam");
                var required = naam.getAttribute("required");

                if (required && naam.value == "")
                {
                    alert("Naam is verplicht");

                    isGeldig = false;
                }

                var leeftijd = document.getElementById("leeftijd");
                var regexp = new RegExp(leeftijd.getAttribute("regexp"));

                if (! regexp.test(leeftijd.value))
                {
                    alert("Leeftijd moet tussen de 0 en de 99 liggen");

                    isGeldig = false;
                }

                return isGeldig;
            }
        </script>
    </head>

    <body>
        <form onsubmit="return valideer();">
            <label for="naam">Naam:</label>
            <input type="text" id="naam" required="true" />
            <br />

            <label for="leeftijd">Leeftijd:</label>
            <input type="text" id="leeftijd" regexp="^\d\d?$" />
            <br /><br />

            <input type="submit" value="Verstuur" />
        </form>
    </body>
</html>

Het tekstveld ‘naam’ heeft het attribuut ‘required’ gekregen. De browser negeert dit, maar via JavaScript is dit attribuut op te halen. De functie ‘valideer’ gebruikt dit attribuut om vast te stellen of dit element ingevuld moet worden. Voor het tekstveld ‘leeftijd’ wordt hetzelfde trucje uitgehaald, maar nu met een reguliere expressie.

Een JavaScript framework dat dit principe veel toepast is Dojo Toolkit. Keerzijde van deze aanpak is dat de geproduceerde HTML niet meer valid is. Maar valid HTML is alleen maar goed voor de ego van de ontwikkelaar, de rest van de wereld heeft er niets aan. De eindgebruiker in het bijzonder zal het niets interesseren.




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




Spring Security

Door: Jethro Bakker, 4 July 2008

Het is inmiddels al even geleden dat Acegi Security gerebrand is naar Spring Security. Acegi stond bekend om de erg lastige configuratie. Een gebruiker heeft zelfs gezegd dat: “every time I use Acegi a fairy dies..”

Je moest bijvoorbeeld handmatig een heel aantal Servlet Filters configureren en een werkende configuratie bestond uit minimaal een A4tje.

Dan Diephouse zegt hierover:

“The fact that I need to write *180* lines of XML to configure Acegi for a simple simple user authentication in front of a webapp means that something is eriously wrong. There should be default profiles which configure things out of the box and provide minimal switches so users can’t screw things up.”

Met de komst van Spring 2.0 is er de mogelijkheid om custom namespaces te gebruiken in je XML configuratie. Spring Security maakt hier, sinds versie 2.0, ook gebruik van. De configuratie kan hierdoor bestaan uit een paar regels ipv een A4tje. De referentie handleiding is een goed punt om te starten.

Om te beginnen moet je een DelegatingFilterProxy definiëren in je web.xml:

<filter>
 
  <filter-name>springSecurityFilterChain</filter-name>
 
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 
</filter><filter-mapping>
 
  <filter-name>springSecurityFilterChain</filter-name>
 
  <url-pattern>/*</url-pattern>
 
</filter-mapping>

In je Spring configuratie voeg je een extra namespace toe:

<beans xmlns="http://www.springframework.org/schema/security">
 
</beans>  xmlns:beans="http://www.springframework.org/schema/beans"
 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 
http://www.springframework.org/schema/security
 
http://www.springframework.org/schema/security/spring-security-2.0.xsd"
 
    ...

Met behulp van een paar regels heb je een basis configuratie (form based):

<http auto-config="true">
 
    <intercept-url pattern="/**" access="ROLE_USER"></intercept-url>
 
</http><authentication-provider>
 
    <user-service>
 
      <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN">   </user>
 
      <user name="bob" password="bobspassword" authorities="ROLE_USER"></user>
 
    </user-service>
 
</authentication-provider>

Op de achtergrond worden alle filters die in Acegi gebruikt werden gestart.

Om bovenstaande configuratie werkend te krijgen moest ik eerst upgraden naar de nieuwste versie van WAS 6. Er zit een enorme bug in zowel 6.0 als 6.1: http://www-1.ibm.com/support/docview.wss?rs=180&uid=swg1PK27620 Na het installeren van de laatste fixpack werkte alles.

Case
Op mijn huidige project is het de bedoeling dat onze applicatie geïntegreerd wordt in een Siebel omgeving. De gebruiker logt in op Siebel en kan vervolgens naar onze Java applicatie gaan. De gebruiker hoeft maar 1x in te loggen. Onze applicatie krijgt een Cookie binnen met o.a. de gebruikersnaam. Op basis van de gebruikersnaam roept onze applicatie een externe service aan om de rollen van de gebruiker op te halen. Hoe zetten we Spring Security in om dit te regelen? Het voorbeeld hierboven beschreven is in ieder geval niet toereikend.

De referentiehandleiding is weer een goed startpunt het heeft namelijk een hoofdstuk over pre authenticatie scenario’s. Dat zijn scenario’s waarin een ander systeem –dat aangeroepen wordt voor je eigen applicatie- verantwoordelijk is voor de authenticatie. Onze eigen applicatie is alleen verantwoordelijk voor de autorisatie. We moeten een eigen implementatie maken AbstractPreAuthenticatedProcessingFilter en van de UserDetailsService.

Het filter haalt o.a. de gebruikersnaam uit het request cookie.

protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
 
        Cookie[] cookies = request.getCookies();
 
        if (cookies != null) {
 
            for (int i = 0; i &lt; cookies.length; i++) {
 
                Cookie cookie = cookies[i];
 
                //TODO haal user principal uit cookie
 
            }
 
        }
 
        return "DUMMY_USER";
 
}

De service ziet er zo uit:

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
 
        //TODO check username against external system
 
        GrantedAuthority[] authorities = new GrantedAuthority[1];
 
        authorities[0] = new GrantedAuthorityImpl("ROLE_ALL");
 
        //authorities[0] = new GrantedAuthorityImpl("ROLE_NOACCESS");
 
        //authorities[0] = new GrantedAuthorityImpl("ROLE_RESTRICTED");
 
        return new User("DUMMY_USER", "DUMMY_PASSWORD", true, true, true, true, authorities);
 
}

En in de Spring configuratie doe je het volgende:

<security:http auto-config="true">
 
  <security:intercept-url pattern="/**" access="ROLE_ALL, ROLE_RESTRICTED">
 
</security:intercept-url><security:authentication-manager alias="authenticationManager"></security:authentication-manager>
 
<security:authentication-provider user-service-ref="userDetailsService"></security:authentication-provider>
 
<bean id="userDetailsService" class="MyUserDetailsService"></bean>
 
<bean id="cookieFilter" class="CookiePreAuthenticatedProcessingFilter">
 
  <security:custom-filter position="PRE_AUTH_FILTER"></security:custom-filter></bean>
<property name="authenticationManager" ref="authenticationManager"></property>
</security:http><bean id="preauthAuthProvider" class="org..preauth.PreAuthenticatedAuthenticationProvider">
 
  <security:custom-authentication-provider></security:custom-authentication-provider></bean>
<property name="preAuthenticatedUserDetailsService">
    <bean id="userDetailsServiceWrapper" class="..UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"></property>
    </bean>
 
  </property>

Het is mij erg meegevallen om werkende configuratie te maken en aan te passen aan de specifieke eisen binnen ons project. De configuratie van security is in ieder geval met de release van Spring Security 2.0 een stuk eenvoudiger geworden!




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




Activering van je Spring beans configurabel maken

Door: Michel.Schudel, 27 June 2008

Op een huidig project werken we aan een routeringscomponent waarin een aantal JMS listeners en endpoints hangen. Er komen berichten op de listeners binnen, die gerouteerd moeten worden naar een endpoint. Nu kan je voor ESB-achtige oplossingen beter terecht bij opensource producten als Mule of ServiceMix, maar daar wil ik het niet over hebben.

De listeners zijn geimplementeerd met Spring MessageListenerContainer types, en staan gewired in de Spring applicatie context:

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  ...properties..
</bean>

Het issue is nu dat de JMS listeners dienen in bepaalde omgevingen (zoals een aantal acceptatie en productie omgevingen) geactiveerd dienen te worden, en in andere omgevingen niet. Dit moet op zo’n manier kunnen dat het voor een beheer organisatie gemakkelijk is deze componenten aan en uit te zetten.

Je kan een Spring component natuurlijk uitcommentarieren in de applicationContext, maar je wilt een beheerclub eigenlijk niet opzadelen met zo’n taak, omdat deze taak vrij technisch is en behoorlijk foutgevoelig. Bovendien wil je niet dat beheer in je applicatie wiring wijzigingen gaat zitten maken. Import / configuratie van verschillende applicationContexts in verschillende omgevingen levert ook teveel overhead op.

Hoe maken we dit nu toch beheersbaar?

Een mogelijkheid zou zijn om het lazy-init attribuut van een Spring bean te wijzigen. Voor een bean als een MessageListenerContainer zou dit betekenen dat in het geval van lazy-init="true" de bean nooit wordt opgetuigd, aangezien er geen andere beans zijn die een referentie naar deze bean hebben.

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="true">
  ...properties..
</bean>

Echter levert dit nog steeds geen configurabel component voor beheer op, in het bovenstaande voorbeeld wordt de bean nooit gestart. Je zou geneigd zijn om te zeggen: los het met een externe property op:

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="${myListener.enabled}">
  ...properties..
</bean>

De PropertyPlaceholderConfigurer postprocessor kan dit echter niet aan, aangezien deze wel in kan grijpen na de constructie van het object maar niet ervoor. Er is ook geen mogelijkheid om via een eigen geschreven postprocessor het aanroepen van een init methode op een bean (wat in het geval van een MessageListenerContainer daadwerkelijk de JMS connnectie opzet) te onderdrukken.

Om toch het issue te kunnen oplossen heb ik een ConditionalInstantiationBean geschreven, die op basis van een beanName string property en een instantiate boolean een Spring bean uit de context ophaalt, waardoor deze geconstrueerd en geinitializeerd wordt:

package nl.project.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;

/**
 * A Spring bean-factory-aware bean that will onyl instantiate the bean with
 * name beanName if the property instantiate is set to true. Use this bean for
 * being able to control instantiation of beans depending on a true|false value
 * that is imported as a property from an external property file.
 *
 * It is not possible to do this with the "lazy-init" because you cannot switch
 * this value with an external property.
 *
 */
public class ConditionalInstantiationBean implements BeanFactoryAware,
        InitializingBean {

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    private BeanFactory beanFactory;

    private String beanName;

    private boolean instantiate;

    /**
     * Will instantiate the target bean defined by beanName if instantiate is
     * set to true.
     */
    public void afterPropertiesSet() throws Exception {
        if (instantiate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Instantiating bean with name '" + beanName + "'");
            }
            //keeping result is not necessary since we only want to instantiate
            // the bean.
            beanFactory.getBean(beanName);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping instantiation of bean with name '"
                        + beanName + "'");
            }

        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
     */
    public void setBeanFactory(BeanFactory bf) throws BeansException {
        this.beanFactory = bf;
    }

    /**
     * @param beanName
     *            The beanName to set.
     */
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    /**
     * @param instantiate
     *            The instantiate to set.
     */
    public void setInstantiate(boolean instantiate) {
        this.instantiate = instantiate;
    }
}

En hieronder de Spring configuratie:

  <bean id="conditionalInstantiationBean" class="nl.project.util.ConditionalInstantiationBean">
    <property name="beanName" value="myMessageListenerContainer"/>
    <property name="instantiate" value="${listener.enabled}"/>
</bean>

Hierbij de volgende toelichting:

  • De property beanName is een String en geen referentie naar de listener. Een referentie zou immers betekenen dat de listener bean automatisch wordt opgetuigd!
  • De property instantiate kan nu met een PropertyPlaceholderConfigurer geexternaliseerd worden, bijvoorbeeld in een property file. In de property file komt dan een listener.enabled property die door een beheerclub aan-en uitgezet kan worden!
  • Het lazy-init attribuut van de listener dient wel op true te worden gezet, om automatische activering van de bean te voorkomen:
  •   <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="true">
      ...properties..
    </bean>
    

Verbeteringen zouden nog zijn: testen of de target bean wel “lazy” geinitialiseerd wordt, aangezien anders de ConditionalInstantiationBean natuurlijk niets te doen heeft.
Ik kan me ook voorstellen dat iemand een mooie AOP oplossing voor dit issue heeft. Als dat zo is, hoor ik het graag!