Skip to content


E-Mail mit Wicket verschicken

Wäre es nicht praktisch, wenn man eine E-Mail verschicken könnte, in dem man die Komponenten, die man für die Darstellung auf der Webseite verwendet, auch für das Erstellen der E-Mail heranziehen könnte. Das Unterfangen ist leider nicht trivial, weil man nicht ohne Weiteres um die "Magie", die Wicket an vielen Stellen benutzt, herum kommt. Nun bietet Wicket die Möglichkeit, Komponenten und Seiten, Formulare und vieles mehr in Unit-Test zu testen. Was liegt also näher, als diese Funktionalität für unsere Zwecke zu missbrauchen.

Die E-Mail

Als erstes erstellen wir eine Seite, die den Inhalt der E-Mail erzeugen soll:

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;

public class EmailContentPage extends WebPage
{
  public EmailContentPage(IModel<List<? extends String>> list)
  {
    add(new ListView<String>("list",list)
    {
      @Override
      protected void populateItem(ListItem<String> item)
      {
        item.add(new Label("name",item.getModel()));
      }
    });
  }
}

Der Code soll zeigen, dass Komponenten ganz normal Funktionieren. Deshalb bekommt die Seite von außen eine Liste mit Werten übergeben, die dann durch eine ListView dargestellt wird.

<h1>TestEmail</h1>
<ul>
  <li wicket:id="list"><span wicket:id="name"></span></li>
</ul>

Im Markup habe ich absichtlich den "Rahmen" der Seite (html > body) weggelassen. Die Klasse und das Markup dienen in diesem Beispiel nur als Platzhalter.

Darstellen und Abschicken

In Wicket sind sehr viele Objekte an den aktuellen Thread gebunden. Das ist notwendig, damit man nicht eine ganze Liste von Objekten als Übergabeparameter durch Methodenaufrufe schleifen muss. Das ist allerdings genau dann ein Problem, wenn wir innerhalb einer Wicketanwendung eine andere Seite darstellen möchten. Außerdem ist es ein Problem, wenn es wie in einem im Hintergrund laufenden Prozess überhaupt keine WicketApplication gibt. Daher erstellen wir uns ein paar Hilfsklassen, die sich um dieses Problem kümmern.

package de.wicketpraxis.web.blog.pages.questions.email;

public interface WicketCallback<I,O>
{
  public O getResult(I input);
}

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.logging.Logger;

public class WicketThreadAdapter<I,O> extends Thread
{
  private static final Logger _logger = Logger.getLogger(WicketThreadAdapter.class.getName());
  
  Object _lock=new Object();
  
  WicketCallback<I, O> _callback;
  I _input;
  O _output;
  boolean _done=false;
  
  protected WicketThreadAdapter(WicketCallback<I, O> callback, I input)
  {
    _callback=callback;
    _input=input;
  }
  
  @Override
  public void run()
  {
    synchronized (_lock)
    {
      _output=_callback.getResult(_input);
      _done=true;
      _lock.notify();
    }
  }
  
  protected O getResult() throws InterruptedException
  {
    synchronized (_lock)
    {
      while (!_done)
      {
        _lock.wait();
      }
      return _output;
    }
  }
  
  public static <I,O> O getResult(WicketCallback<I, O> callback, I input) throws InterruptedException
  {
    WicketThreadAdapter<I,O> threadAdapter = new WicketThreadAdapter<I,O>(callback,input);
    threadAdapter.start();
    return threadAdapter.getResult();
  }
}

Die WicketThreadAdapter-Klasse wird über die statische Methode getResult(...) angesprochen und macht folgendes: Es wird ein neuer Thread erzeugt. Im laufenden Thread wird der Callback aufgerufen und der Thread beendet. Das Ergebnis, das im Thread ermittelt wurde, wird als Ergebnis herausgereicht. Kurz: Das Ergebnis wird in einem anderen Thread als dem aktuellen ermittelt und führt so nicht zu Kollisionen mit der laufenden Anwendung.

Der Trick

Das alles führt noch nicht zum Ziel. Wir müssen uns jetzt noch darum kümmern, dass wir die Seite dargestellt bekommen. Dazu benutzten wir die Klasse BaseWicketTester, die sich um alles weitere kümmert. Dort übergeben wir die Seite, die wir darstellen wollen. Dann können wir auf den Inhalt der Seite zugreifen und als Ergebnis zurück liefern.

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.Arrays;
import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.tester.BaseWicketTester;

public class EmailFromComponentPage extends WebPage
{
  public EmailFromComponentPage()
  {
    WicketCallback<List<String>, String> callback = new WicketCallback<List<String>, String>()
    {
      public String getResult(List<String> input)
      {
        final IModel<List<? extends String>> listModel = Model.ofList(input);
        
        BaseWicketTester tester=new BaseWicketTester();
        tester.startPage(new EmailContentPage(listModel));
        return tester.getServletResponse().getDocument();
      }
    };
    
    String result;
    try
    {
      result = WicketThreadAdapter.getResult(callback, Arrays.asList("Klaus","Susi","Bert"));
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
      result=e.getLocalizedMessage();
    }
    
    add(new Label("email",result).setEscapeModelStrings(false));
  }
}

Damit das Ergebnis richtig angezeigt wird, teilen wir dem Label "email" mit, dass es den Text nicht umschreiben soll.

<html>
  <head>
    <title>Email Form Component</title>
  </head>
  <body>
    Email <span wicket:id="email"></span>
  </body>
</html>

Wenn man die Seite aufruft, erhält man dann folgende Darstellung: Ergebnis: Html-E-Mail aus Wicket

Wenn man die E-Mail durch Hintergrundprozesse versenden möchte, ist ein eigener Thread unnötig, weil keine WicketApplication-Klasse an den Thread gebunden ist. Dann sollte der Einsatz der BaseWicketTester-Klasse reichen. Es ist allerdings darauf zu achten, dass alles, was in der eigenen Application-Klasse definiert wurde, an dieser Stelle nicht ohne weiteres zur Verfügung steht (z.B. wird der OpenSessionInViewFilter für Spring nicht aufgerufen, die SpringBean-Annotationen sind ebenfalls funktionslos).

Fazit

Auch wenn sicher noch die eine oder andere Anpassung notwendig ist, damit man Komponenten sowohl für die Anwendung als auch für den E-Mail-Versand benutzten kann, lohnt sich der Aufwand, weil man a) auf die vielfältigen Möglichkeiten, die Wicket als "Template-Engine" bietet, zurückgreifen kann, b) man sich im besten Fall doppelten Code ersparen kann und c) man keine zweite Lösung einbinden muss. Und wieder hat mich Wicket ein wenig überrascht. Weil es dann doch so einfach ging:)

Tags:

Veröffentlicht in Technologie, Wicket, .

Pre-Optimization - Erkennen und Vermeiden

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason - including blind stupidity. W.A. Wulf - Quotes

Der Mensch ist faul und das ist gut so. Denn so denkt er ständig darüber nach, wie er Ziele mit dem geringsten Aufwand erreichen kann. Dabei geht es um den sparsamen Einsatz von Resourcen und ist so in vielen Bereichen der Natur anzutreffen. Wo Resourcen im Überfluss vorhanden sind, hält meist Verschwendung Einzug. Ein plötzlicher Lottogewinn hat zu selten zu langfristigem Glück und Wohlstand geführt. Genauso wenig ist eine künstliche Verknappung von Resource automatisch der Weg zu Glück und Erfolg.

Auch wenn man im Bereich der Softwareentwicklung meist nicht genug Resourcen hat, entstehen deshalb nicht automatisch optimale Lösungen. Das Problem ist komplizierter, aber gerade aus diesem Grund auch so spannend. Die folgende Darstellung veranschaulicht, mit welchem Themenkomplex wir es zu tun haben.

pre-optimization - übersicht

Ziele sind in den meisten fällen unscharf, der Weg zum Ziel ist komplex und mit vielen Stolpersteinen versehen. Auf dem Weg zum Ziel sind verschiedene Probleme zu lösen. Manche sind bereits bekannt, manche Problemstellungen werden erwartet. Je weiter ein Problem in der Zukunft liegt, desto höher ist das Risiko, dass man nicht nur in die falsche Richtung rennt. Denn die Energie und den Aufwand, den man in diese Problemlösung gesteckt hat, fehlt vielleicht am Ende oder sogar schon unterwegs.

Ein Wanderer hat von einer großen Wüste gehört, die er durchqueren muss. Also bepackt er sich mit Wasser und macht sich auf den Weg. Er kommt an einen Berg und stellt fest, dass er mit dem Wasser den Berg nicht hochkommen wird. Er hat also die ganze Zeit das Wasser umsonst mitgeschleppt. Wenn er an dem Wasser festhält, wird er den Berg nicht schaffen, das Ziel nicht erreichen. Hätte er gewusst, dass hinter dem Berg und vor der Wüste ein Fluss ist, hätte er das Wasser nicht eingepackt. Aber dummerweise kann man manchmal noch nicht mal den Berg sehen, hinter dem die Lösung liegt.

Daher sollte man sich immer fragen, ob man gerade ein Problem löst, dass zwischen dem Jetzt und dem nächsten Schritt liegt, den man gehen muss. Und man sollte sich fragen, ob man das Problem mit dem geringst möglichen Aufwand löst. Manchmal kann man sogar einen kleinen Umweg machen und das Problem umgehen. Alles andere löst Probleme, die man noch nicht hat.

Wenn man sich sehr viele Gedanken um die Zukunft macht, schreitet meist nur die Zeit voran.

Tags:

Veröffentlicht in Allgemein, .

Wicket nicht nur für Einsteiger

Ich durfte an einem Projekt teilhaben, in dem für eine neu zu erstellende Anwendung Wicket eingesetzt wurde. Am Anfang wurde das Projekt entsprechend der Anforderungen aufgesetzt (Maven, Hibernate, Spring, Wicket) und der Rahmen der Anwendung geschaffen (Konfiguration der Datenbank, Unittests, etc.). Nach einer doch recht kurzen Einführung in Wicket wurde die Anwendung ohne fremde Hilfe (wenn man mal Google mal ausklammert) entwickelt und mit immer mehr Funktionen angereichert. Gestern habe ich mir den aktuellen Stand angesehen und musste feststellen, dass entgegen meinen Erwartungen eine recht vernünftige Anwendung entstanden ist.

Warum habe ich das so nicht erwartet?

In dem Projekt war bisher wenig bis kein Wissen über Java und die benutzten Frameworks vorhanden. In der kurzen Einführung konnten weder die Sprache, noch die verwendeten Technologien ausführlich genug erklärt werden. Das bedeutet normalerweise, dass die Lösungen, die bei solchen Unterfangen entstehen, verschiedenste Defizite aufweisen. Neben der Tatsache, dass die Anwendung funktioniert (sicher gibt es noch irgendwo ein paar Fehler, aber wo gibt es die nicht), ist die Anwendung auch noch in einer ausreichend guten Verfassung, so dass die Struktur verstanden werden konnte und die Anpassungen, die wir durchgeführt haben, nicht dazu führten, dass etwas nicht mehr funktionierte.

Gründe

Ein nicht unwesentlichen Anteil daran, dass das Projekt besser als erwartet funktioniert hat, trägt sicherlich Wicket als Framework. Wenn man die Hürde genommen hat, die das Thema Modelle darstellt, ist der Rest recht offensichtlich. Eigene Komponenten werden erstellt, weil es a) sehr einfach ist und b) (viel entscheidender) man als Entwickler automatisch davon profitiert. Die Übersichtlichkeit der Anwendung entsteht auf diese Weise automatisch.

Fazit

Das eine Anwendung funktioniert, ist mindestens ein Maßstab zur Bewertung. Diese Anforderung ist gerade bei Webanwendungen nicht einfach zu erreichen. In Anbetracht der eher schlechten Voraussetzungen muss man das Ergebnis als gelungen bezeichnen. Da ich gebeten wurde, meine Einschätzung zu diesem Projekt abzugeben, freut es nicht nur mich, sondern sicher auch das Team, dass ich positiv überrascht bin.

Mit welcher anderen Technologie wäre dieses Projekt ähnlich gut verlaufen?

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .

Praxisbuch Wicket - Feedback

Mein erstes Buch ist ein Erfolg. Zumindest für mich. Nicht im finanziellen Sinn, sondern in Bezug auf Dinge, die ich dabei gelernt habe. Das es z.B. sehr schwer ist, bei einem Framework wie Wicket eine Struktur zu finden, die leicht zu verstehen ist. Was ist diese Model-Klasse? Was ist eine Komponente? Erklärt man Komponenten, bevor man die Model-Klasse erklärt hat? Kann man das überhaupt? .. Viele Fragen mit mehr als einer Antwort.

Da es mein erstes eigenes (ok, ich war an einem Wordpress-Buch beteiligt) Buch war, betrat ich in vielen Disziplinen Neuland. Da ist es gut, wenn man jemanden hat, der zurück spielt, was man anders machen müsste, was unverständlich ist. Nach dem erscheinen des Buches gab es sehr viele Rückmeldungen, die durchweg positiv waren. Ich war ehrlich gesagt ein wenig überrascht, weil ich wohl manchmal auch ein wenig zu selbstkritisch bin. Soweit ich sehen konnte, fand das Buch nicht nur Anklang bei Entwicklern, die erst neu in die Webentwicklung mit Java eingestiegen sind. Auch Entwickler, die bereits professionell mit Wicket arbeiten, haben das Buch gelobt. Das das der Seele schmeichelt ist unstrittig. Danke für all die positiven Worte. Natürlich gab es auch Fehler in den Texten, unverständliche Stellen, alles was bei einem Buch so "schief" gehen kann. Diese konstruktiven Hinweise sind sehr hilfreich, weil sie mir zeigen, wo ich vielleicht schon betriebsblind war.

Es liegt in der Natur der Dinge, dass man nicht jeden glücklich machen kann, da Menschen zum Glück nicht gleich sind. Das jemand besser mit einem anderen Buch zurechtkommt, ist zu erwarten. Dass die Art und Weise, wie ich Dinge erkläre, nicht bei jedem ankommt, damit habe ich gerechnet. Daher war ich überrascht, dass bei Amazon doch sehr unterschiedliche Kundenbewertungen (Praxisbuch Wicket bei Amazon) zustande gekommen sind.

Daher habe ich eine Bitte, einen Wunsch. Es wäre sehr schön, wenn ich mehr Rückmeldung bekommen würde (entweder als Rezension bei Amazon, als Kommentar in diesem Blog oder per Email (würde ich gern im Blog veröffentlichen (natürlich anonym, wenn gewünscht))). Dabei ist mir wichtig, zu betonen, dass es hier nicht darum geht, mir zu schmeicheln. Ich versuche dann, die Dinge, die ich bereits gut gemacht habe, beizubehalten und Dinge, die man besser machen kann, zu verbessern.

Danke:)

Tags:

Veröffentlicht in Allgemein, Wicket, .

PropertyModel - einfach ist nicht immer gut

Ich bin kürzlich über ein Stück Code gestolpert, der mich zum nachdenken brachte. Um zu illustrieren, welcher Umstand dafür verantwortlich war, benötige ich diesemal am schon Anfang ein paar Zeilen Code. Die folgende Hilfsklasse dient nur dazu, bei dem einzig möglichen Methodenaufruf eine Liste von Daten zu erzeugen:

package de.wicketpraxis.web.blog.pages.migration.model.property;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

public class Generator implements Serializable
{
  public List<String> findAll()
  {
    return Arrays.asList("fun","with","wicket");
  }
}

Der Generator dient hier als Platzhalter für eine wie auch immer gelagerte Datenschicht.

package de.wicketpraxis.web.blog.pages.migration.model.property;

import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;

import de.wicketpraxis.wicket.model.Models;
import de.wicketpraxis.wicket.model.transformation.Function1;

public class UseOrNotUsePropertyModelPage extends WebPage
{
  public UseOrNotUsePropertyModelPage()
  {
    final Generator gen=new Generator();
    
    IModel<List<String>> model = new LoadableDetachableModel<List<String>>()
    {
      protected List<String> load()
      {
        return gen.findAll();
      };
    };
        
    model=new PropertyModel<List<String>>(gen,"findAll");
    
    add(new ListView<String>("list",model)
    {
      @Override
      protected void populateItem(ListItem<String> item)
      {
        item.add(new Label("value",item.getModel()));
      }
    });
  }
}

Da es nicht meine Art ist, unvollständigen Code abzubilden, kommt auch hier erst das Markup und dann die Erklärung:

<html>
  <head>
    <title>PropertyModel Usage Page</title>
  </head>
  <body>
    <h1>Liste</h1>
    <ul>
      <li wicket:id="list"><span wicket:id="value"></span></li>
    </ul>
  </body>
</html>

Was ist an diesem Beispiel bemerkenswert?

Am Anfang erzeuge ich ein Model über eine LoadableDetachableModel-Implementierung, die die Daten aus dem Methodenaufruf zurück gibt. Das ist nichts neues und wurde bereits oft behandelt. Diese Model-Instanz wird in der folgenden Zeile mit einer PropertyModel-Instanz überschrieben. Es ist unstrittig, dass dieser Ansatz funktioniert. Die Methode wird per Reflection aufgerufen und die Werte werden per getObject() zurückgeliefert. Doch wo liegt das Problem?

Refactoring

Die Zeile Code, die man erhält, wenn man das PropertyModel benutzt und die Methode aufzurufen ist angenehm kurz. Aber diese Kürze gibt es nicht umsonst, sondern man muss dafür zahlen. In Bezug auf das Thema Refactoring sogar zwei mal. Das erste Problem ist offensichtlich: Wenn sich der Methodenname ändert (oder Parameter hinzukommen) wird der Aufruf per Reflection nicht mehr funktionierten (oder sogar eine falsche Methode aufrufen), und man erhält zur Laufzeit eine Fehlermeldung.

Der zweite Punkt ist weniger offensichtlich, aber vielleicht noch schwer wiegender, weil zur Laufzeit der Fehler an einer anderen Stelle zu Tage tritt: Der Rückgabewert der Methode wird angepasst. Der Fehler würde in unserem Fall dann vermutlich nur in der Darstellung der ListView auftreten, so dass statt "fun with wicket" möglicherweise "SomeData@65b778 SomeData@32ac12" angezeigt wird. Es kommt also noch nicht mal zu einem Laufzeitfehler.

Performance

In einem anderen Bereich (der in unserem Beispiel nebensächlich ist) funktioniert die PropertyModel-Variante nicht wie erwartet. Wenn die Methode getObject() aufgerufen wird, wird immer die entsprechende Methode per Reflection aufgerufen. Das bedeutet, die Ergebnisse werden nicht wie beim LoadableDetachedModel zwischengespeichert. Würde ich das Model an zwei ListView-Komponenten binden, würde die Liste zweimal erzeugt werden. Je nach Aufwand der Erzeugung ist das ein eher unerwünschtes Ergebnis. Außerdem kann es natürlich dazu kommen, dass sich die jeweiligen Rückgabewerte unterscheiden, so dass der Nutzer im Zweifel inkonsistente Daten angezeigt bekommt.

Das PropertyModel ist nützlich

Man sollte nach dem Lesen der Zeilen die Verwendung des PropertyModel nicht verbieten, denn Aufwand und Nutzen sind für die Entstehung dieser Model-Klasse verantwortlich. Um die Eingaben aus einem Formularfeld in einem Attribut einer JavaBean zu speichern sollte man das PropertyModel einsetzen, weil der Mehraufwand für eine typsichere (und damit durch normale Refactoring-Operationen greifbare) Variante enorm hoch ist. Ich würde mir wünschen, das Java Sprachmittel bereitstellt, die es ermöglicht, Methoden nicht nur per Reflection zu ermitteln, so dass man gerade für diesen Fall kompakten und trotzdem typsicheren Code schreiben kann. Bis dahin sollte man diese Model-Klasse überlegt verwenden, dann kann eigentlich gar nicht soviel schief gehen.

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .

Wicket Theme - CSS-Layout für Wicket-Anwendungen

Das Auge isst mit.

Unter diesem Stichpunkt könnt man wohl einen wesentlichen Aspekt der Anwendungsentwicklung zusammenfassen. Denn gerade in der Web-Entwicklung geht es oft um das Aussehen einer Anwendung. Im Unterschied zu Desktop-Anwendungen muss man sich als Webentwickler oft bereits am Anfang damit beschäftigen, die Anwendung durch einbinden von CSS-Definitionen nach etwas aussehen zu lassen. Unzulänglichkeiten von CSS und die schlechte Browser-Unterstützung machen diesen Teil der Entwicklung schwerer als nötig.

Das das Aussehen einen nicht unwesentlichen Anteil am Erfolg eines Frameworks hat, kann man gut an Grails/Rails sehen. Denn von Anfang an kommt eine Grails-Anwendung gefällig daher. Dabei geht es nicht darum, dass der Stil jedem gefällt, sondern das eine Grails-Anwendung eben nicht wie eine Web1.0 Seite aussieht.

Und genau da hat Wicket noch Potential. Deshalb habe ich mich entschieden, ein entsprechendes WicketStuff-Projekt zu starten, dass so einfach wie möglich eingebunden und benutzt werden kann, so dass man recht schnell und einfach eine ganz nett aussehende Anwendung entwickeln kann, die auch das Auge anspricht. Das wird sicher noch eine wenig Zeit benötigen, doch vorab schon einmal ein Screenshot, der zeigt, dass man ohne zusätzliche CSS-Klassen im eigenen Markup bereits ansehnliche Ergebnisse erhalten kann:

Wicket Theme Vorschau

Alles weitere demnächst hier im Blog.. wer helfen mag, ist herzlich eingeladen.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Wicket Model Transformation

Eine Liste von Ergebnissen aus einer Tabelle in der Datenbank zu laden und über ein Model zur Verfügung zu stellen, ist eine recht einfache Angelegenheit. Wicket sorgt dafür, dass die Liste kein zweites mal geladen wird, nur weil das Model durch eine zweite Komponente dargestellt wird. Wenn man z.B. so triviale Dinge wie die Summe aller angezeigten Einträge ermitteln möchte, empfiehlt es sich, auf die bereits geladenen Daten aus dem Model zurückzugreifen. Für diesen Zweck kann man auf eine spezialisierte Model-Klasse zurückgreifen, die sich darum kümmert, das die detach()-Methode auch für alle referenzierten Modelle aufgerufen wird. Der Ansatz ist recht einfach, aber es geht vielleicht noch ein wenig eleganter.

Die Transformation

Als erstes definieren wir Klassen, die für die Transformation sorgen. Da in unserem Beispiel nicht nur ein Model, sondern auch zwei oder drei Model-Referenzen benutzt werden und damit bis zu drei Parametern übergeben werden können, müssen wir auch drei unterschiedliche Schnittstellen definieren:

package de.wicketpraxis.wicket.model.transformation;

import java.io.Serializable;

public interface Function1<T,M1> extends Serializable
{
  public T apply(M1 value);
}

package de.wicketpraxis.wicket.model.transformation;

import java.io.Serializable;

public interface Function2<T,M1,M2> extends Serializable
{
  public T apply(M1 value,M2 value2);
}

package de.wicketpraxis.wicket.model.transformation;

import java.io.Serializable;

public interface Function3<T,M1,M2,M3> extends Serializable
{
  public T apply(M1 value,M2 value2, M3 value3);
}

Da die verschiedenen Modelle auch verschiedene Daten bereitstellen können, kann jeder Parameter einen eigenen Typ annehmen. Jetzt benötigen wir ein Model, dass die Daten der referenzierten Modelle lädt und der Funktion übergibt und sich das Ergebnis bis zum Aufruf von detach() merkt.

package de.wicketpraxis.wicket.model.transformation;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;

public abstract class Transformator<T> extends LoadableDetachableModel<T>
{
  IModel<?>[] _subModels;
  
  protected Transformator(IModel<?> ... subModels)
  {
    _subModels=subModels;
  }
  
  @Override
  protected void onDetach()
  {
    for (IModel<?> m : _subModels)
    {
      m.detach();
    }
  }
  
  public static class Model1<T,M1> extends Transformator<T>
  {
    IModel<M1> _m1;
    Function1<T, M1> _function;
    
    public Model1(IModel<M1> m1, Function1<T, M1> function)
    {
      super(m1);
      
      _m1=m1;
      _function=function;
    }
    
    @Override
    protected T load()
    {
      return _function.apply(_m1.getObject());
    }
  }
  
  public static class Model2<T,M1,M2> extends Transformator<T>
  {
    IModel<M1> _m1;
    IModel<M2> _m2;
    Function2<T, M1, M2> _function;
    
    public Model2(IModel<M1> m1, IModel<M2> m2, Function2<T, M1, M2> function)
    {
      super(m1,m2);
      
      _m1=m1;
      _m2=m2;
      _function=function;
    }
    
    @Override
    protected T load()
    {
      return _function.apply(_m1.getObject(),_m2.getObject());
    }
  }

  public static class Model3<T,M1,M2,M3> extends Transformator<T>
  {
    IModel<M1> _m1;
    IModel<M2> _m2;
    IModel<M3> _m3;
    Function3<T, M1, M2, M3> _function;
    
    public Model3(IModel<M1> m1, IModel<M2> m2, IModel<M3> m3, Function3<T, M1, M2, M3> function)
    {
      super(m1,m2,m3);
      
      _m1=m1;
      _m2=m2;
      _m3=m3;
      _function=function;
    }
    
    @Override
    protected T load()
    {
      return _function.apply(_m1.getObject(),_m2.getObject(),_m3.getObject());
    }
  }
}

Das war jetzt ein größerer Block, der aber eigentlich nur aus 3 fast gleichen Klassendefinitionen bestand, die leider nicht viel besser zusammenzuführen sind. Wichtig ist dabei die Übergabe aller Model-Referenzen an die Basisklasse, die sich in der onDetach()-Methode (die von der detach()-Methode nur aufgerufen wird, wenn für das Model noch nicht detach() aufgerufen wurde) um das Aufrufen der detach()-Methode dieser Modelle kümmert.

Noch ist dieses ganze Konstrukt nicht viel besser handhabbar als die bisher favorisierte Lösung. Wir benötigen daher noch ein paar Hilfsklassen.

package de.wicketpraxis.wicket.model.transformation;

import org.apache.wicket.model.IModel;

public abstract class ModelSet
{
  public static class Set1<M1>
  {
    IModel<M1> _m1;
    
    public Set1(IModel<M1> m1)
    {
      _m1=m1;
    }
    
    public <T> IModel<T> apply(Function1<T, M1> function)
    {
      return new Transformator.Model1<T, M1>(_m1,function);
    }
  }
  
  public static class Set2<M1,M2>
  {
    IModel<M1> _m1;
    IModel<M2> _m2;
    
    public Set2(IModel<M1> m1,IModel<M2> m2)
    {
      _m1=m1;
      _m2=m2;
    }
    
    public <T> IModel<T> apply(Function2<T, M1, M2> function)
    {
      return new Transformator.Model2<T, M1, M2>(_m1,_m2,function);
    }
  }

  public static class Set3<M1,M2,M3>
  {
    IModel<M1> _m1;
    IModel<M2> _m2;
    IModel<M3> _m3;
    
    public Set3(IModel<M1> m1,IModel<M2> m2,IModel<M3> m3)
    {
      _m1=m1;
      _m2=m2;
      _m3=m3;
    }
    
    public <T> IModel<T> apply(Function3<T, M1, M2, M3> function)
    {
      return new Transformator.Model3<T, M1, M2, M3>(_m1,_m2,_m3, function);
    }
  }
}

Wir man erkennen kann, bestehen diese Klassen auch wieder aus drei Kopien, die mehr oder weniger die selbe Funktion, nur andere Parameter haben. Bis jetzt muss das Bild noch verschwommen sein, mit dem letzten Baustein sollte es sich klären.

package de.wicketpraxis.wicket.model;

import org.apache.wicket.model.IModel;

import de.wicketpraxis.wicket.model.transformation.ModelSet;

public abstract class Models
{
  public static <T,M1> ModelSet.Set1<M1> on(IModel<M1> model)
  {
    return new ModelSet.Set1<M1>(model);
  }
  public static <T,M1,M2> ModelSet.Set2<M1,M2> on(IModel<M1> model,IModel<M2> model2)
  {
    return new ModelSet.Set2<M1,M2>(model,model2);
  }
  public static <T,M1,M2,M3> ModelSet.Set3<M1,M2,M3> on(IModel<M1> model,IModel<M2> model2,IModel<M3> model3)
  {
    return new ModelSet.Set3<M1,M2,M3>(model,model2,model3);
  }
}

Fertig. Nun, vielleicht noch nicht ganz. Wir haben eine ganze Reihe von Klassen, eine ganze Menge Code, um die selben Anforderungen zu Erfüllen, wie mit dem im ersten Moment sehr viel einfacher gehaltenen Model-Ansatz aus dem anderen Artikel? Sehen wir uns daher das ganze in der Anwendung an.

Beispiel

In dem folgenden Beispiel benötigen wir ein Model, das eine Zufallszahl bereitstellt:

package de.wicketpraxis.web.blog.pages.migration.model.transformation;

import org.apache.wicket.model.LoadableDetachableModel;

public class RandomNumberModel extends LoadableDetachableModel<Double>
{
  @Override
  protected Double load()
  {
    return Math.random();
  }
}

Diese Modell benutzen wir auf einer Seite als Datenquelle:

package de.wicketpraxis.web.blog.pages.migration.model.transformation;

import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.time.Duration;

import de.wicketpraxis.wicket.model.Models;
import de.wicketpraxis.wicket.model.transformation.Function1;
import de.wicketpraxis.wicket.model.transformation.Function2;

public class TransformationPage extends WebPage
{
  public TransformationPage()
  {
    IModel<Double> randModel1=new RandomNumberModel();
    IModel<Double> randModel2=new RandomNumberModel();
    
    IModel<Double> diffResult = Models.on(randModel1, randModel2)
      .apply(new Function2<Double, Double, Double>()
    {
      public Double apply(Double value, Double value2)
      {
        return value-value2;
      }
    });
    
    IModel<Integer> scaleResult = Models.on(randModel1)
      .apply(new Function1<Integer, Double>()
    {
      public Integer apply(Double value)
      {
        return (int)(value*100);
      }
    });
    
    WebMarkupContainer ajaxUpdate=new WebMarkupContainer("ajaxUpdate");
    ajaxUpdate.setOutputMarkupId(true);
    ajaxUpdate.add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(1)));
    
    ajaxUpdate.add(new Label("rand1",randModel1));
    ajaxUpdate.add(new Label("rand2",randModel2));
    ajaxUpdate.add(new Label("diffResult",diffResult));
    ajaxUpdate.add(new Label("scaleResult",scaleResult));
    
    add(ajaxUpdate);
  }
}

In diesem Beispiel erstellen wir zwei unabhängige Modelle für jeweils zwei Zufallszahlen. Diese werden über zwei Funktionsinstanzen manipuliert und als Model bereitgestellt. Dabei ist zu beachten, dass nicht das Ergebnis als Model bereitgestellt wird, sondern dass ein Model erzeugt wird, bei dem beim ersten Aufruf von getObject() die load()-Methode des LoadableDetachedModel aufgerufen wird, dass dann dort die Daten aus den referenzierten Modellen ermittelt und durch die Funktion transformiert. Das Ergebnis wird bis zum Aufruf von detach() (durch die angebundene Komponente) gepuffert.

Damit die Zufallszahlen immer wieder erzeugt werden, lassen wir die Anzeige sich per Ajax selbsttätig aktualisieren.

<html>
  <head>
    <title>Transformation Page</title>
  </head>
  <body>
    <h1>Zufallszahlen</h1>
    <div wicket:id="ajaxUpdate">
      <table>
        <tr>
          <td>Nummer 1</td><td><span wicket:id="rand1"></span></td>
        </tr>
        <tr>
          <td>Nummer 2</td><td><span wicket:id="rand2"></span></td>
        </tr>
        <tr>
          <td>Number 1*100</td><td><span wicket:id="scaleResult"></span></td>
        </tr>
        <tr>
          <td>Number 1-Nummer 2</td><td><span wicket:id="diffResult"></span></td>
        </tr>
      </table>
    </div>
  </body>
</html>

Fazit

Wo liegt jetzt der Vorteil? Zum einen sind die Implementierungen einfacher, da laut Schnittstellendefinition nur eine Methode implementiert werden muss. Zum anderen muss man nicht die benutzten Model-Klassen kennen, die diese Transformationen durchführen. Die Lesbarkeit ist wesentlich besser, weil aus dem Code abgeleitet werden kann, was da in etwa passiert.

Ist das der Weisheit letzter Schluss? Vermutlich nicht. Anregungen sind daher willkommen.

Tags:

Veröffentlicht in Refactoring, Wicket, .

Wicket Tipps: Markup

Ich benutze für alle Projekte Maven. Der eine oder andere mag mit Maven auf dem Kriegsfuß stehen (was ich zwar nicht verstehe, aber akzeptiere). Bisher war ich (und an anderer Stelle war es auch nicht notwendig, darüber nachzudenken) für eine saubere Trennung von Code und Resourcen. Das bedeutet z.B. das der Code einer Wicket-Komponente z.B. im Pfad src/main/java/de/wicketpraxis/ zu finden ist und das Markup in src/main/resources/de/wicketpraxis.

Das Projekt wächst und gedeiht und es entstehen unzählige Komponenten (ein Zeichen dafür, dass man mit Wicket sehr sehr einfach eigene Komponenten erstellen kann). Im Laufe der Zeit hat es sich als unpraktisch herausgestellt, in der IDE immer zwischen diesen beiden Verzeichnissen zu wechseln. Auch beim Refactoring kann es sich als lästig erweisen, weil man an zwei Stellen die selbe Operation anwenden muss (zumindest in Eclipse).

Ich habe daher alle Projekte umgestellt und kann nun die Markup-Dateien in das selbe Verzeichnis/Package wie die Komponente legen. Die Angst, dass die Übersichtlichkeit leidet, hat sich als unbegründet erwiesen, die Produktivität ist spürbar besser. Folgende Anpassung muss man daher in einem Projekt vornehmen. In der pom.xml ist folgender Block einzufügen:

  <build>
...
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
...
  </build>

Wenn man nun die Anwendung über Jetty aus dem Projekt heraus starten möchte, empfiehlt sich ein Ansatz, den ich bereits im folgenden Beitrag erwähnt habe: Wicket Resourcen mit Jetty nachladen. Allerdings müssen wir dazu den angepassten ResourceStreamLocator entsprechend erweitern. Am besten wir schreiben eine neue Klasse:

package de.wicketpraxis.wicket.util.resource;

import org.apache.wicket.util.file.File;
import org.apache.wicket.util.resource.FileResourceStream;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.locator.ResourceStreamLocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MavenDevResourceAndSourceStreamLocator extends ResourceStreamLocator
{
  private static final Logger _logger = LoggerFactory.getLogger(MavenDevResourceAndSourceStreamLocator.class);
  
  final static String MAVEN_RESOURCE_PATH="src/main/resources/";
  final static String MAVEN_JAVASOURCE_PATH="src/main/java/";
  
  @Override
  public IResourceStream locate(final Class<?> clazz, final String path)
  {
    IResourceStream located=getFileSysResourceStream(MAVEN_RESOURCE_PATH,path);
    if (located==null)
    {
      located=getFileSysResourceStream(MAVEN_JAVASOURCE_PATH,path);
    }
    if (located != null)
    {
      if (_logger.isInfoEnabled()) _logger.info("Locate: " + clazz + " in " + path+" -> "+located);
      return located;
    }
    located=super.locate(clazz, path);
    if (_logger.isInfoEnabled()) _logger.info("Locate: " + clazz + " in " + path+" -> "+located+"(Fallback)");    
    return located;
  }

  private static IResourceStream getFileSysResourceStream(String prefix, String path)
  {
    File f=new File(prefix+path);
    if ((f.exists()) && (f.isFile()))
    {
      return new FileResourceStream(f);
    }
    return null;
  }
}

Die Einbindung funktioniert wie beim letzten Mal:

if (DEVELOPMENT.equalsIgnoreCase(configurationType))
{
  getResourceSettings().setResourceStreamLocator(new MavenDevResourceAndSourceStreamLocator());
}

Jetzt sollte man (wenn alles andere entsprechend vorbereitet ist) mit mvn jetty:run -Dwicket.configuration=development Jetty aus dem Projekt heraus starten können. Sobald man Änderungen am Markup vorgenommen hat, werden diese neu geladen.

Tags:

Veröffentlicht in Allgemein, Maven, Refactoring, Wicket, .

Wicketbuch aus dem dpunkt Verlag

Wicket ist ein Webframework, dass bereits heute eine hohe Popularität genießt. Es werden zunehmen Projekte mit Wicket realisiert, das Angebot an freien Komponenten ist enorm groß (Wicket Stuff) und die Community erfreut such großer Beliebtheit. Trotzdem gibt es Widerstand aus den lagern der etablierten Frameworks, die daran zweifeln, dass Wicket auch für sogenannte "Enterprise"-Anwendungen geeignet ist (einen Beweis, dass JSF geeignet ist, hat IMHO auch noch niemand geliefert). Wie bei jedem neuen Framework sollte man sich auf der einen Seite damit beschäftigen, aber auf der anderen Seite auch abwägen, ob das Framework eine Zukunft hat. Gerade der letzte Punkt ist besonders schwer einzuschätzen und ist nur indirekt messbar, in dem man z.B. die veröffentliche Literatur zu diesem Thema als Anhaltspunkt für eine Bewertung nimmt. Und da sieht es eigentlich ganz gut aus, denn es gibt neben den einglischsprachigen auch bereits zwei deutsche Bücher zum Thema.

Man profitiert selbst als erfahrender Entwickler von Ideen und Sichtweisen, die andere Entwickler bei der Lösung ihrer Probleme gesammelt haben. Daher lese ich die verschiedenen Blogs, Erfahrungsberichte und Beiträge auf der Mailingliste. Aus diesem Grund habe ich das andere deutsche Wicketbuch aus dem dpunkt Verlag bereits fest eingeplant und musste nun leider feststellen, dass das Erscheinungsdatum auf den Januar 2010 verschoben wurde.

Es werden einfach noch zu viele Projekte mit JSF umgesetzt. Ein zweites deutsches Buch sollte eigentlich Signal genug sein, dass Wicket eine ernst zu nehmende Alternative ist. Ich drücke den Autoren die Daumen, dass sie das Projekt erfolgreich abschließen (ich weiß, wie hart das sein kann) und freue mich auf neue Erkenntnisse und Sichtweisen.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Wicket Komponentenübersicht

Ich habe in den letzten Tagen damit angefangen, die verschiedenen Componenten, die Wicket bereits in dem Basisbibliotheken bietet, zu visualisieren. Den ersten Versuch habe ich noch von Hand durchgeführt, um schnell festzustellen, dass man auf diese Weise nicht besonders schnell vorwärts kommt. Der zweite und damit aktuelle Ansatz ist da schon sehr viel versprechend. Es werden die Wicket-Quellen geparst und alle Klassendefinitionen eingelesen, die Abhängigkeiten untereinander ermittelt, verschiedene Filter angewendet um die Darstellung auf ein Thema einzugrenzen und das ganze dann mit dem dot-Programm aus dem Graphviz-Paket in ein Diagramm umgewandelt.

Die folgenden Grafiken sind das vorläufige Zwischenergebnis. Es ist nicht auszuschließen, dass es Fehler gibt. Für jeden gemeldeten Fehler bin ich daher sehr dankbar, aber auch Anregungen sind natürlich herzlich willkommen.

Wicket Kompontenten - Basis

Wicket Komponenten inklusive Extensions

Wicket Modelle

Wicket Behavior

Wicket Listener

Wicket RequestTarget

Tags:

Veröffentlicht in Technologie, Wicket, .