Category Archives: Wicket

Wicket 7 – Component Queuing

Bisher ist Wicket-7 von den neuen Features recht überschaubar. Der Migration-Guide bietet eine ganz gute Übersicht, was sich tatsächlich geändert hat.

Component Queuing

Das wahrscheinlich interessanteste Feature, auf das viele Entwickler gewartet haben ist Component Queuing. Component Queuing versucht das leidige Problem zu lösen, dass wir in Wicket die Komponentenhierarchien doppelt zu pflegen haben.

<body>
 <div wicket:id="container">
     <div wicket:id="label"></div>
 </div>
</body>

Genau diesselbe Hierarchie findet sich auch im Code.

WebMarkupContainer container = new WebMarkupContainer("container");
Label label = new Label("label", new Model("Show me"));

container.add(label);
add(container);

Prinzipiell reicht es aus, die Hierarchie an einer Stelle zu definieren (beispielsweise im Markup). Der Code kann die Hierarchie der Komponenten dann anhand der Wicket-IDs automatsich herleiten. In der Theorie ganz einfach – genau das ist die Idee von Component Queuing. Ich muss ehrlich zugeben, mich hat die doppelte Hierarchie-Pflege nie wirklich gestört. Wicket zwingt uns Entwickler hier einfach sauber zu arbeiten.

Ändern wir die Component-Hierarchie ab und fügen alle Komponenten auf der gleichen Ebenen hinzu.

WebMarkupContainer container = new WebMarkupContainer("container");
Label label = new Label("label", new Model("Show me"));
//falsche Hierachie, label müsste im Container liegen
add(label);
add(container);

Es ergibt sich folgendes Bild, das alle Wicket-Entwickler mit Sicherheit schon dutzende Male gesehen haben.

Last cause: Unable to find component with id 'label' in [WebMarkupContainer [Component id = container]]
	Expected: 'container:label'.
	Found with similar names: 'label'

Die Hierarchie auf der Java-Seite stimmt nicht mit der Hierarchie im DOM überein.

Verwenden wir aber statt der so lieb gewonnenen add(..) die neue queue(…) Methode, sieht es schon wieder anders aus.

public HomePage(final PageParameters parameters)
  {
    super(parameters);
    WebMarkupContainer container = new WebMarkupContainer("container");
    Label label = new Label("label", new Model("Show me"));

    queue(label);
    queue(container);
  }

Obwohl die Hierarchie nicht übereinstimmt schafft es Wicket, aus der Hierarchie im DOM die Komponenten richtig zu schachteln.

Was aber genau passiert beim Component-Queuing?

Im uns allen bekannten MarkupContainer hält eine Liste an Komponenten, die der Hierarchie mit queue(…) hinzugefügt wurden. Sobald der RequestCycle beginnt, versucht jeder MarkupContainer, seine Queue-Komponenten aus der Queue zu entnehmen und korrekt in die Hierarchie zu setzen.

Dies geschieht in zwei Schritten

@Override
protected void onInitialize()
{
	super.onInitialize();
	dequeueAutoComponents();
}

Zunächst wird versucht, alle Auto-Components richtig zu platzieren. Hiermit fällt viel Komplexität aus dem Code heraus, denn was dequeuAutoComponents() nur macht ist zu prüfen, ob eine AutoComponent im Container verwendet wird (z.B. <wicket:enclosure/>) und queued diese dann automatisch. Das bedeutet, nach der onInitialize sind bereits alle AutoComponents korrekt in der Component-Queue hinterlegt. Schick..

ComponentTag.IAutoComponentFactory autoComponentFactory = tag.getAutoComponentFactory();
if (autoComponentFactory != null)
{
     queue(autoComponentFactory.newComponent(this, tag));
}      

Im nächsten Schritt wird das Dequeuing durchlaufen.

public void dequeue() {
        if(this instanceof IQueueRegion) {
            DequeueContext queueRegion = this.newDequeueContext();
            this.dequeuePreamble(queueRegion);
        } else {
            MarkupContainer queueRegion1 = (MarkupContainer)this.findParent(IQueueRegion.class);
            if(!queueRegion1.getRequestFlag((short)128)) {
                queueRegion1.dequeue();
            }
        }
}

Zunächst wird unterschieden, ob die aktuelle Komponente das Interface IQueueRegion implementiert. Eine IQueueRegion ist grundsätzlich soweit ich das verstehe alles, was ein eigenes Markup mitbringt, also Fragments, Panels und Pages.

Die Unterscheidung ist aber nur marginal, denn intern wird nichts anderes gemacht, als den State der Komponente auf QUEUED GERADE zu setzen und anschließend mit dem Dequeuing-Prozess zu beginnen.

Das Dequeuing selbst ist sehr einfach und basiert auf der Struktur des DOM.

Wicket iteriert über alle Nodes im DOM.

Für alle Nodes wird folgender Algorithmus durchlaufen.

  1. Komponente mit Node-ID bereits geadded? Wenn ja, für diesen Node nichts weiter zu tun.
  2. Iteriere über alle Container inkl. der aktuellen IQueueRegion und durchsuche deren Queues nach einer Komponente mit der Wicket-ID des Nodes. (Start immer bei einer IQueueRegion, also Page oder Panel). Je höher in der HIerarchie, desto früher werden die Komponenten geadded.
  3. Wird die Komponente irgendwo in der IQueueRegion gefunden, rufe add(..) für diese Komponente auf dem aktuellen Container auf.

Boundaries

Component-Queuing arbeitet in engen Grenzen die vom Interface IQueueRegion vorgegeben werden.

public interface IQueueRegion
{
	/**
	 * Creates a new {@link DequeueContext} that will be used to dequeue children of this region.
	 * 
	 * Usually containers will create a context with their associated markup by getting it via
	 * {@link MarkupContainer#getAssociatedMarkup()}, but components that do not render markup in a
	 * standard way (such as repeaters and borders) may choose to override this method to implement
	 * custom behavior for the dequeueing process.
	 */
	public DequeueContext newDequeueContext();

	/**
	 * Starts component dequeueing on this {@link IQueueRegion}. This is the entry point into the
	 * dequeuing process, it creates the {@link DequeueContext} and delegates the operation to the
	 * {@link org.apache.wicket.MarkupContainer#dequeue(DequeueContext)} method which performs the
	 * actual dequeueing. The context's markup is retrieved using the {@link MarkupContainer#getAssociatedMarkup()}.
	 */
	public void dequeue();
}

Die IQueueRegion erlaubt das Erzeugen eines neuen DequeueContext. Der DequeueContext hält eine Liste an allen Containern sowie eine Liste
an Tags, die aktuell bearbeitet werden.

Erzeugen wir uns ein einfaches Panel mit folgendem Markup:

<wicket:panel>
    <div wicket:id="panelLabel"/>;
</wicket:panel>

Zusätzlich fügen wir das Panel der Page hinzu.

<div wicket:id="container">
    <div wicket:id="label"></div>
</div>
<strong><div wicket:id="panel"></div>
</strong>

Wie erwartet erhalten wir:

org.apache.wicket.markup.MarkupException: Unable to find component with id 'panelLabel' in [MyPanel [Component id = panel]]

Interessanterweise erhalten wir außerdem:

org.apache.wicket.WicketRuntimeException: Detach called on component with id '0' while it had a non-empty queue: ComponentQueue{queueSize=1, queue=[[Component id = panelLabel], null, null, null, null, null, null, null]}

Unser gequeutes Label ist immer noch da, da es nicht aufgelöst werden konnte.

Queues verlassen ihre Boundaries also nicht.

Fazit:

Tolles Feature. Mir persönlich gefällt vor allem das saubere Arbeiten mit AutoComponents durch diese Änderung sehr gut. Ich bin mir noch nicht sicher, ob ich grundsätzlich dazu raten würde Queueing der bisherigen Arbeitsweise vorzuziehen, das muss die Praxis zeigen.
Component-Queuing ist derzeit nur im 7.0-SNAPSHOT verfügbar.

Links


Announcement auf der Wicket Mailing List


Component Queuing Artikel (Igor Vaynberg)

Derzeit noch offene Issues mit Component Queuing

Source-Code auf GitHub

Wicket 7 – Migration Guide

Wicket und CDI 1.1 – auf den richtigen Kontext kommt es an

Es gab viel Diskussion um Wicket und CDI. Insbesondere der Sprung von CDI 1.0 auf CDI 1.1 hat viele Probleme gemacht. Die CDI Integration für Wicket wurde komplett überarbeitet und verbessert. CDI gefällt mir aufgrund der Einfachheit besonders gut und ich arbeite sehr gerne damit.

Schon die Integration mit CDI 1 und Weld war sehr schön gemacht und ist hier beschrieben.

Ein erster Archetype

Wir generieren uns ein neues Projekt und arbeiten auf der Wicket 7 SNAPSHOT Version. Das ist zum jetzigen Zeitpunkt Work in Progress, uns stehen aber alle neuen Features zur Verfügung. In der aktuell veröffentlichten Version 6.12.0 ist der neue CDI Support leider noch nicht verfügbar.

mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=7.0.0-SNAPSHOT -DgroupId=de.effectivetrainings -DartifactId=wicket-6-cdi -DarchetypeRepository=https://repository.apache.org/content/repositories/snapshots/ -DinteractiveMode=false

Der Stand auf dem wir hier arbeiten ist wirklich brandneu. Es ist also notwendig, den Wicket Source Code auszuchecken und zumindest das CDI Modul einmal zu bauen.

git clone http://git-wip-us.apache.org/repos/asf/wicket.git

mvn clean install -Dmaven.test.skip -fae -rf :wicket-cdi-1.1

Anschliessend deklarieren wir die Dependency in unserem Projekt.

&lt;dependency&gt;
     &lt;groupId&gt;org.apache.wicket&lt;/groupId&gt;
     &lt;artifactId&gt;wicket-cdi-1.1-weld&lt;/artifactId&gt;
     &lt;version&gt;0.2-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;

Zusätzlich holen wir uns die aktuelle WELD Version. WELD ist die Referenzimplementierung für die CDI 1.1 Spezifikation.

&lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld&lt;/groupId&gt;
            &lt;artifactId&gt;weld-core&lt;/artifactId&gt;
            &lt;version&gt;2.1.0.Final&lt;/version&gt;
&lt;/dependency&gt;

 &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;weld-servlet-core&lt;/artifactId&gt;
            &lt;version&gt;2.1.0.Final&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
            &lt;groupId&gt;javax.el&lt;/groupId&gt;
            &lt;artifactId&gt;javax.el-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
            &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;

Und wir definieren wie das letzte Mal den Weld-Servlet-Listener.

 &lt;listener&gt;
        &lt;listener-class&gt;org.jboss.weld.environment.servlet.Listener&lt;/listener-class&gt;
 &lt;/listener&gt;

Zuletzt legen wir uns unter src/main/resources/META-INF eine leere Datei beans.xml an. Eigentlich ist dieser Schritt seit CDI 1.1 nicht mehr notwendig, sondern es reicht, eine Bean mit einer Scope-Annotation (@RequestScoped, @SessionScoped, @ApplicationScoped) im Classpath zu haben. Leider scheint dies mit Jetty noch nicht zu funktionieren, so dass die beans.xml weiterhin notwendig ist.

Beim Startup des Jetty sehen wir folgende Meldung und wissen dadurch, dass WELD aktiv ist.

INFO  - Version                    - WELD-000900: 2.1.0 (Final)

Die ersten Schritte

Um CDI für Wicket zu aktivieren braucht es nach wie vor nur eine Zeile in der init()-Methode der WicketApplication.

CdiConfiguration.get().configure(this);

Wir definieren uns eine Klasse EventProvider, der uns Events generiert (Event-basierte Systeme sind derzeit sehr gefragt).

@ApplicationScoped
public class EventProvider {

    @Produces
    @RequestScoped
    public Event newEvent(){
        return new Event("Simple Default Event");
    }
}

Und zusätzlich eine einfache Event-Bean.

@Vetoed
public class Event implements Serializable {

    private String value;

    private long random = new Random().nextInt();

    public Event(String value) {
        this.value = value;
    }

    public Event() {
    }

    public String getValue() {
        return value + random;
    }
}

Die Klasse ist deswegen als @Vetoed markiert, damit sie nicht vom CDI Container geparsed und als Bean erkannt wird. Das übernimmt der EventProvider für uns. Ohne das @Vetoed würden wir folgende Exception sehen.

WELD-001409: Ambiguous dependencies for type Event with qualifiers @Default

Nur um zu testen ob das Ganze funktioniet injezieren wir eine Event-Instanz in unsere WicketPage.

public class HomePage extends WebPage {

   @Inject
   private Event event;

   private static final long serialVersionUID = 1L;

   public HomePage(final PageParameters parameters) {
        super(parameters);
        add(new Label("eventName", new PropertyModel&lt;Event&gt;(event, "value")));
    }
}

Sobald wir starten sehen wir, dass CDI aktiv ist und ein Event bereitgestellt wurde.

CDI Injected Event Class

CDI Injected Event Class

Jetzt wirds fachlich

Nehmen wir das Beispiel aus dem letzten Artikel zum Thema CDI nochmal auf.

Wir haben einen ProductProvider und eine Product-Klasse.

@ApplicationScoped
public class ProductProvider {

    public ProductProvider(){}

    @Produces
    public List&lt;Product&gt; productList(){

        Product samba = new Product("Adidas Samba",49.95);
        Product air = new Product("Nike Air",79.95);
        Product balerina = new Product("Balerinas",9.95);

        return Arrays.asList(samba, air, balerina);

    }
}

Und ein LoadableDetachableModel zum Laden der Produkte in der WicketPage.

public class ProductsModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    private ProductProvider productProvider;

    public ProductsModel() {
        CdiConfiguration.get().getNonContextualManager().inject(this);
    }

    @Override
    protected List&lt;Product&gt; load() {
        return productProvider.productList();
    }
}

Das Prinzip ist das Gleiche. Model-Klassen stehen weder unter Wicket- noch unter CdiContainer-Kontrolle sondern sind einfache Pojos. Um den ProductProvider also injezieren zu können müssen wir die CDI-Injection manuell anstossen. Das funktioniert mit folgender Zeile.

CdiConfiguration.get().getNonContextualManager().inject(this);

Doch halt.

Was macht eine CDI Bean zu einer CDI Bean? CDI Beans sind normale Pojos mit einem Default-Konstruktor oder einem Konstruktor der mit @Inject annotiert ist. Unser LoadableDetachableModel ist zur CDI-Bean qualifiziert.

Ändern wir das Model folgendermaßen ab.

public class ProductsModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    private ProductProvider productProvider;

    @Override
    protected List&lt;Product&gt; load() {
        return productProvider.productList();
    }
}

Und injezieren uns das Model in der WicketPage.

public class HomePage extends WebPage {

    @Inject
    private Event event;

    @Inject
    private ProductsModel productsModel;

    private static final long serialVersionUID = 1L;

    public HomePage(final PageParameters parameters) {
	super(parameters);
        add(new ListView&lt;Product&gt;("products", productsModel) {
            @Override
            protected void populateItem(ListItem&lt;Product&gt; item) {
                item.add(new Label("productName", new PropertyModel(item.getModel(), "name")));
                item.add(new Label("productPrize", new PropertyModel(item.getModel(), "prize")));
            }
        });
    }
}

Wir könnten das Model sogar als @ApplicationScoped definieren und würden in der gesamten Anwendung mit einer einzigen Instanz des Models arbeiten. Valide da das Model selbst keinen State besitzt und nur an den ProductProvider delegiert.

Auf diese Art und Weise funktioniert CDI-Injection sogar in Model-Klassen.

WicketTester und CDI

Wie gut oder schlecht funktioniert jetzt die Zusammenarbeit mit dem WicketTester und CDI?

Wie es scheint, ohne gut zureden nicht besonders gut, denn starten wir den Test aus dem Archetype sehen wir folgendes.

java.lang.IllegalStateException: Singleton is not set. Is your Thread.currentThread().getContextClassLoader() set correctly?
	at org.jboss.weld.bootstrap.api.helpers.IsolatedStaticSingletonProvider$IsolatedStaticSingleton.get

Was müssen wir also tun, um den WicketTester mit CDI bekannt zu machen?

Wir brauchen CDI-Unit von JGlue.  Diese Bibliothek wird übrigens auch von Wicket und dem Wicket-CDI Modul selbst verwendet.

Wir deklarieren folgende Abhängigkeit in unserer pom.

&lt;dependency&gt;
	&lt;groupId&gt;org.jglue.cdi-unit&lt;/groupId&gt;
	&lt;artifactId&gt;cdi-unit&lt;/artifactId&gt;
	&lt;version&gt;2.2.1&lt;/version&gt;
	&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;

Und ändern unseren Test folgendermaßen ab.

@RunWith(CdiRunner.class)
@AdditionalClasses(value = {
    CdiConfiguration.class,
    ConversationPropagator.class,
    ConversationExpiryChecker.class,
    DetachEventEmitter.class},
    late = "org.apache.wicket.cdi.NonContextualManager")
public class TestHomePage
{
	private WicketTester tester;

	@Before
	public void setUp()
	{
		tester = new WicketTester(new WicketApplication());
	}

	@Test
	public void homepageRendersSuccessfully()
	{
		// start and render the test page
		tester.startPage(HomePage.class);

	}
}

Zunächst sorgen wir dafür, dass der Test als CDI Test ausgeführt wird.

@RunWith(CdiRunner.class)

Leider findet der CdiRunner nicht alle Klassen. Warum genau verstehe ich nicht. Wir müssen also manuell dafür sorgen, dass die fehlenden Klassen geladen werden.

@AdditionalClasses(value = {
    CdiConfiguration.class,
    WeldCdiContainer.class,
    ProductsModel.class,
    AbstractCdiContainer.class,
    ConversationPropagator.class,
    ConversationExpiryChecker.class,
    DetachEventEmitter.class},
    late = "org.apache.wicket.cdi.NonContextualManager")

Die Klasse NonContextualManager ist in Wicket als Packageprivate deklariert. Deswegen verwenden wir das late-Binding und müssen die Klasse über den vollqualifizierten Namen ansprechen.

Beim Start bekommen wir jetzt die nächste Fehlermeldung.

org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.ConversationScoped

Die Initialisierung scheint unvollständig.

Wir haben am Anfang des Artikels den ServletListener org.jboss.weld.environment.servlet.Listener in der web.xml konfiguriert. Dies fehlt uns jetzt natürlich.

Das lässt sich aber simulieren (analog der Implementierung in Wicket-CDI).

@Inject
private ContextController contextController;

/*
before test
*/
@Before
public void setUp()
{
     tester = new WicketTester(new WicketApplication());
     prepareRequest(tester.getRequest());
}

/*
prepare contexts
*/
private void prepareRequest(HttpServletRequest request)
{
     contextController.openRequest(request);
}

Wir sorgen für jeden Request dafür, dass der CDI-Kontext aktiviert wird.

Insgesamt ist der Overhead ziemlich groß und für jede neue Klasse muss die @AdditionalClasses – Liste erweitert werden. Ohne etwas wie Arquillian zu verwenden ist mir aktuell nicht klar, wie das vereinfacht werden könnte.

Links

Source Code (GitHub)

Dev List Diskussion

Weitere Dev List Diskussion

Hint to use Arquillian

Wicket 6 – Von VoodooKatzen und Dynamischen Bildern

Heute machen wir einen kleinen Ausflug in die Welt der Bilder.

Was geht alles mit Wicket und vor allem wie?

Zunächst erstellen wir uns wie immer einen Archetype als Spielwiese.

mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.8.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-images -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false

Zunächst räumen wir auf und löschen alles aus HomePage.html sowie HomePage.java was nicht gebraucht wird.

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;head&gt;
    &lt;/head&gt;
	&lt;body&gt;
	&lt;/body&gt;
&lt;/html&gt;

Und HomePage.java

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

	public HomePage(final PageParameters parameters) {
		super(parameters);
    }
}

Als Bildquelle verwenden wir placekitten.com – wer Katzen sucht wird hier garantiert fündig.

Statische Images

Eine erste einfache Möglichkeit Bilder zu laden ist über den Klassenpfad, beispielsweise direkt aus einem Package.

Wir laden uns also zunächst ein Kätzchen herunter und platzieren es in src/main/java/de/effectivetrainings.

Zur Anzeige des Kätzchens verwenden wir folgendes Markup.

 &lt;div class="static"&gt;
      &lt;img wicket:id="staticKitten"/&gt;
&lt;/div&gt;

Die einfachste Möglichkeit ein Bild zu referenzieren ist direkt über den Dateinamen.

private Component staticKitten() {
        return new Image("staticKitten","kitten.jpg");
 }

Und siehe da, hier ist das Kätzchen!

Wicket - Static Images

Wicket – Static Images

Wicket generiert folgende URL für dieses Bild.

&lt;img wicket:id="staticKitten" src="./wicket/resource/de.effectivetrainings.HomePage/kitten-ver-1372099283000.jpg"/&gt;

Über eine derartige URL (Prefix /wicket/resource) werden Ressourcen in Wicket generell angesprochen und referenzieren einen IResourceListener. (Das ist ein perfekter Zeitpunkt, sich die Klasse einmal etwas genauer anzuschauen, hochinteressant!).

Was auch auffällt, stimmt das Javadoc gibt es viele verschiedene Möglichkeiten, ein Bild zu referenzieren. Beispielsweise können wir auch im Markup einfach folgendes machen.

 &lt;div class="static"&gt;
            &lt;img wicket:id="staticKitten" src="kitten.jpg"/&gt;
 &lt;/div&gt;

Und im Java-Code das (also nichts).

 private Component staticKitten() {
        return new Image("staticKitten", new Model&lt;String&gt;());
    }

Die zugegebenermaßen etwas kompliziertere Logik hierzu findet sich in LocalizedImageResource.

Statische Ressourcen-Images

Manchmal ist es nicht erwünscht, Bilder statisch und relativ aus einem Package zu laden.Es kann auch sinnvoll sein, Images global beispielsweise in src/main/webapp abzulegen.

Platzieren wir also ein zweites Kätzchen.

Wir definieren hierfür folgenden Wicket-Tag.

&lt;img wicket:id="staticResourceKitten"/&gt;

Und im Code erstmal folgendes:

 private Component staticResourceKitten() {
        return new ContextImage("staticResourceKitten", "/images/kitten2.jpg");
    }

ContextImage erlaubt es uns, Bilder direkt über den Kontextpfad zu laden. Sehr schick und praktisch.

Es findet keinerlei Ressourcenmanagement statt, Wicket macht intern ein URL-Rewrite und sorgt dafür, dass das src-Attribut des Images korrekt gesetzt ist. Vorraussetzung hierfür ist, dass das Bild tatsächlich über den Kontextpfad abrufbar ist (nicht in einem Package! – siehe ResourceGuard)

Externe Images

Nicht immer liegen Bilder direkt bei uns (externe Ressourcen, CMS-System etc..).

Laden wir also ein Image über eine URL.

Wir definieren ein weiteres Tag.

&lt;img wicket:id="staticExternalImage"/&gt;

Und laden ein Kätzchen über eine URL.

Leider bietet Wicket hierfür nichts einfaches, was direkt funktioniert, ist aber schnell gemacht. Wir definieren ein ExternalImage und zwar so.

/**
 * @author &lt;a href=mailto:martin@effectivetrainings.de"&gt;Martin Dilger&lt;/a&gt;
 * @since: 24.06.13
 */
public class ExternalImage extends WebMarkupContainer {
    public ExternalImage(String id, IModel&lt;String&gt; model) {
        super(id, model);
        add(AttributeAppender.replace("src", model));
    }

    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        checkComponentTag(tag,"img");
    }
}

und anschließend bauen wir das dritte Kätzchen so ein.

private Component staticExternalKitten() {
        return new ExternalImage("staticExternalKitten", Model.of("http://www.placekitten.com/300/300"));
    }

Nicht immer hat man es aber mit statischen Ressourcen zu tun, bauen wir ein wenig Dynamik ein.

Dynamische Images

Bauen wir uns ein Voodoo-Kätzchen.

Zunächst wieder ein Tag.

&lt;div class="dynamic"&gt;
            &lt;img wicket:id="dynamicKitten"/&gt;
&lt;/div&gt;

Und anschließend ein wenig Magic-Code.

private Component dynamicKitten() {
        return new Image("dynamicKitten", new DynamicImageResource() {
            @Override
            protected byte[] getImageData(Attributes attributes) {
                try {
                    BufferedImage dynamicKitten = ImageIO.read(new URL("http://www.placekitten.com/300/400"));
                    dynamicKitten.setRGB(255,255,255);
                    dynamicKitten.getGraphics().fillOval(100,120,30,30);
                    dynamicKitten.getGraphics().fillOval(130,110,30,30);

                    ByteArrayOutputStream bout = new ByteArrayOutputStream();
                    ImageIO.write(dynamicKitten, "png", bout);
                    return bout.toByteArray();
                } catch (IOException e) {
                   //ignore
                }
                throw new RuntimeException("Narf..");
            }
        });
    }

Image erlaubt es uns, statt eines Models auch eine ImageResource anzugeben. Über die Resource haben wir die Möglichkeit, Images dynamisch zu erzeugen.
Wir laden uns ein beliebiges Kätzchen und platzieren die magischen Voodoo-Augen!! Das Bild wird also zur Laufzeit erzeugt.

Voodoo-Cat

Voodoo-Cat

Resource-Sharing

Ziehen wir die Voodoo-Katze doch mal zum Spaß in eine eigene Klasse.

public class VoodooKittenResource extends DynamicImageResource
{
    @Override
    protected byte[] getImageData(Attributes attributes)
    {
        int width = attributes.getParameters().get("width").toInt();
        int height = attributes.getParameters().get("height").toInt();
        try
        {
            BufferedImage dynamicKitten = ImageIO
                    .read(new URL(String.format("http://www.placekitten.com/%s/%s",width,height)));
            dynamicKitten.getGraphics().fillOval(100, 120, 30, 30);
            dynamicKitten.getGraphics().fillOval(130, 110, 30, 30);

            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ImageIO.write(dynamicKitten, "png", bout);
            return bout.toByteArray();
        }
        catch (IOException e)
        {
            // ignore
        }
        throw new RuntimeException("Narf..");
    }
}

Wir legen besonderes Augenmerk auf diese beiden Zeilen.

int width = attributes.getParameters().get("width").toInt();
int height = attributes.getParameters().get("height").toInt();

Über die Attributes haben wir u.a. Zugriff auf die RequestParameter!

Und Ressourcen lassen sich mounten!!

Machen wir die dynamische Voodoo-Katze doch mal per URL verfügbar. Hierfür mounten wir lediglich die VoodooKittenResource in der WicketApplication.

@Override
	public void init()
	{
		super.init();

        mountResource("/voodoo/${width}/${height}",new ResourceReference("voodooKitten") {
            @Override
            public IResource getResource() {
                return new VoodooKittenResource();
            }
        });
	}

Unser Voodoo-Kätzchen ist also künftig beispielsweise so aufrufbar.

http://localhost:8080/voodoo/366/288

http://localhost:8080/voodoo/366/288

http://localhost:8080/voodoo/270/299

http://localhost:8080/voodoo/270/299

Über /voodoo/${width}/${height} können wir dynamisch über die URL die Weite und Höhe angeben.

Das Schöne ist, hierdurch lässt sich unsere dynamische Katze jetzt super einfach verwenden.

 private Component superDynamicKitten(){
        return new ContextImage("superDynamicKitten","/voodoo/270/299");
    }

Fazit

Das Ergebnis kann sich durchaus sehen lassen.

Kitten-Gang

Auch Kätzchen sind nicht unfehlbar

Was passiert, wenn beim Laden eines Bildes ein Fehler auftritt?

Im Package de.effectivetrainings liegt eine Datei brokenKitten.jpg. Diese Bilddatei ist kaputt und wirft eine Exception, beim Versuch sie zu laden.

Schön wäre es, wenn wir hier einen schönen Fallback anzeigen könnten.

Hierfür bauen wir uns eine FallbackVoodooKittenResource.

/**
 * @author &lt;a href=mailto:martin@effectivetrainings.de"&gt;Martin Dilger&lt;/a&gt;
 * @since: 25.06.13
 */
public class FallbackVoodooKittenResource extends DynamicImageResource
{

    private Class&lt;?&gt; scope;
    private String fallbackFileName;
    private String fileSuffix;

    public FallbackVoodooKittenResource(Class&lt;?&gt; scope, String fallbackImageFilename, String suffix)
    {
        this.scope = scope;
        this.fallbackFileName = fallbackImageFilename;
        this.fileSuffix = suffix;
    }

    @Override
    protected byte[] getImageData(Attributes attributes)
    {
        try
        {

            return loadImage(HomePage.class,"brokenKitten.jpg","jpg");
        }
        catch (Exception e)
        {
            return loadImage(scope,fallbackFileName,fileSuffix);
        }

    }

    private byte[] loadImage(Class&lt;?&gt; scope, String filename, String suffix) {
        try
        {
            final BufferedImage loadedImage = ImageIO.read(
                scope.getResourceAsStream(filename));
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ImageIO.write(loadedImage, suffix, bout);
            return bout.toByteArray();
        }
        catch (IOException ioe)
        {
            return new byte[0];
        }
    }
}

Die Idee ist ganz einfach, die Fallback-Resource erwartet den Scope (also indirekt das Package) und den Dateinamen des Bildes, das als Fallback angezeigt werden soll.

Schlägt das Laden des eigentlichen Bildes fehl wird das Fallback-Bild angezeigt. (Schlägt das auch fehl kann man einfach nichts mehr machen).

In der Klasse HomePage.java fügen wir ein letztes vermeintliches Kätzchen ein.

private Component fallbackDynamicKitten(){
        return new Image("fallbackDynamicKitten", new FallbackVoodooKittenResource(HomePage.class,"fallbackKitten.jpg","jpg"));
    }

Beim Laden wird wie erwartet die Exception geworfen und wir sehen folgendes.

Wo ist der Fallback?

Bildquelle Hund : http://en.m.wikipedia.org/wiki/File:Military_dog_barking.JPG

 

Mit ein wenig Phantasie erkennt man, welches Bild der Fallback ist.

Gibt es noch weitere interessante Use-Cases? Hab ich etwas spannendes vergessen? Freue mich auf Kommentare.

Links

Source-Code auf GitHub

PlaceKitten

Going Mobile mit Apache Wicket – Teil 3 – JavaScript im Footer

Im letzten Teil haben bereits einen großen Schritt für die Performance-Optimierung unserer Responsive-Wicket Anwendung gemacht, indem wir Requests minimiert und Ressourcen gebundlet haben.

Ein weiterer Tipp aus den Yahoo Web Performance Best Practices ist es, Skripte ans Ende der Seite zu legen. Bedingt durch das Verhalten der meisten Browser werden nicht mehr als zwei Ressourcen gleichzeitig geladen. Haben wir jetzt ein Skript im <head/>-Tag, das sehr lange lädt, kann es passieren, dass die Seite blockiert und keine neuen Ressourcen geladen werden. Die Beschreibung bei Yahoo ist hierfür sehr gut.

Aktuell sieht der Page-Header folgendermaßen aus.

&lt;head&gt;
    &lt;!-- Skalierung auf devices, volle Skalierung auf die Weite --&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;script type="text/javascript" src="./wicket/resource/de.effectivetrainings.WicketApplication/app-ver-1371750352000.js"&gt;&lt;/script&gt;
    &lt;link rel="stylesheet" type="text/css" href="./wicket/resource/de.effectivetrainings.WicketApplication/app-ver-1368356946000.css" /&gt;
&lt;/head&gt;

 

JavaScript muss in den Footer

Versuchen wir dem Tipp zu folgen und rendern das Javascript im Footer.  Der Effekt lässt sich leider an einer solchen Anwendung nicht so einfach nachstellen, wie im letzten Artikel.

Zunächst definieren wir in der  init-Methode der Klasse WicketApplication einen neuen HeaderResponseDecorator (IHeaderResponseDecorator ist übrigens ein wirklich wunderbares Feature – ich habe hiermit beispielsweise für ein Projekt HTML5-Cache-Manifest-Support für Wicket implementiert).

Mit einem HeaderResponseDecorator haben wir die völlige Freiheit, alles was im <head/>-Tag landen würde nochmals zu bearbeiten.

setHeaderResponseDecorator(new IHeaderResponseDecorator() {
            @Override
            public IHeaderResponse decorate(IHeaderResponse response) {
                return new JavaScriptFilteredIntoFooterHeaderResponse(response, WicketApplication.FOOTER_BUCKET);
            }
        });

Wicket rendert alle JavaScript-Ressourcen ab jetzt in einen Bucket, was intern nichts anderes ist als eine HashMap mit dem BucketNamen als Key. Den Namen des Buckets definieren wir als Konstante in der WicketApplication als “footerBucket“, denn der andere Bucket (quasi der Opposite, wenn man die Bezeichnung aus dem Wicket-Code übernimmt) nennt sich “headerBucket“.

Zusätzlich müssen wir dafür sorgen, dass die Bucket-Ressourcen korrekt in der Page gerendert werden. Hierfür verwenden wir den HeaderContainer.

public HomePage(final PageParameters parameters) {
	super(parameters);
        add(new HeaderResponseContainer("footerJS",WicketApplication.FOOTER_BUCKET));
    }

Zuletzt brauchen wir im Markup noch einen Tag, in den später der neue Javascript-Bucket gerendert wird.

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;head&gt;
        &lt;!-- Skalierung auf devices, volle Skalierung auf die Weite --&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;/head&gt;
	&lt;body&gt;
    &lt;div class="container"&gt;

        &lt;div class="masthead"&gt;
            &lt;ul class="nav nav-pills pull-right"&gt;
                &lt;li class="active"&gt;&lt;a href="#"&gt;Home&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href="#"&gt;About&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href="#"&gt;Contact&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
            &lt;h3 class="muted"&gt;Going Mobile&lt;/h3&gt;
        &lt;/div&gt;

        &lt;hr&gt;

        &lt;div class="jumbotron"&gt;
            &lt;h1&gt;Jetzt wirds mobil!&lt;/h1&gt;
            &lt;p class="lead"&gt;Responsive Design mit Bootstrap ist so einfach!&lt;/p&gt;
            &lt;a class="btn btn-large btn-success" href="#"&gt;Klick me!&lt;/a&gt;
        &lt;/div&gt;

        &lt;/div&gt;
        &lt;div wicket:id="footerJS"&gt;&lt;/div&gt;
	&lt;/body&gt;
&lt;/html&gt;

Das war es schon. Wir starten die Anwendung erneut und prüfen das Markup.

Wir sehen am Ende der Seite folgendes.

&lt;script type="text/javascript" src="./wicket/resource/de.effectivetrainings.WicketApplication/app-ver-1371750352000.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="./wicket/resource/de.effectivetrainings.WicketApplication/app-ver-1368356946000.css" /&gt;
&lt;/body&gt;

Wir sehen, das JavaScript wird direkt am Ende der Seite gerendert.
Seltsamerweise aber auch unser CSS. Eine der Best-Practices von Yahoo sagt, CSS soll im Header gerendert werden, da dadurch der Browser früher anfangen kann, die Seite zu rendern.

Das Problem ist, wir haben es hier mit einem Exoten zu tun. Wir haben eine CSS-Ressource-Referenz, die eine Abhängigkeit auf Javascript-Ressourcen deklariert. Dies scheint nicht korrekt aufgelöst zu werden, was ein echter Bug ist (Stand Wicket 6.8.0).

Prinzipiell funktioniert der Mechanismus aber sehr gut, und ich habe ihn schon mehrfach in verschiedenen Wicket-Projekten erfolgreich eingesetzt.

Im nächsten Artikel lassen geht es weiter und zwar mit Clientseitiger Validierung.

Links

Yahoo WebPerformance Best Practices
Source-Code auf Github

Und zu guter Letzt, noch viel genauer können wir über das Thema im Effective Wicket Workshop sprechen. Rufen Sie mich doch einfach an.

Going Mobile mit Apache Wicket Teil 2 – Webressourcen und Performance

Im letzten Artikel haben wir uns einen ersten einfachen Prototypen mit Wicket und Bootstrap erstellt.

Das war für den Anfang schon gar nicht schlecht. Ein kritischer Aspekt bei der Erstellung von mobilen Webanwendungen ist Performance.

Wie aber lässt sich die Performance von Webanwendungen sehr einfach messen, ohne viel Geld für Tools auszugeben. Für einen ersten Überblick ist das Tool der Wahl YSlow. YSlow bringt eine sehr schöne Integration in alle gängigen Browser (Es gibt leider wohl derzeit ein Kompatibilitätsproblem mit Firefox 21).
YSlow nimmt sich die Yahoo Web Performance Best Practices zu Herzen, und analysiert auf Basis von 23 der 34 definierten Regeln, wie gut oder auch schlecht eine Webseite abschneidet.Eine erste Analyse

Ein erster Testlauf für unsere Anwendung bringt folgendes hervor:
YSlow Performance Analyse - Wicket

Eine Auswertung der Einzelnen Kriterien ist in folgendem Screen zu sehen.
yslow_kriterien

Auf den ersten Blick scheint unsere Anwendung schon ganz gut abzuschneiden. Das lässt sich ändern, denn die guten Noten basieren derzeit nur darauf, dass die Anwendung bisher nichts macht.
Werfen wir einen Blick auf die Sourcen unserer Anwendung.

Im Header findet sich folgendes:

&lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-ver-1367063998000.js"&gt;&lt;/scrip&gt;
&lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-event-jquery-ver-1367063998000.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-ver-1368356946000.css" /&gt;
&lt;link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-responsive-ver-1368356946000.css" /&gt;
&lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/js/bootstrap-ver-1368356946000.js"&gt;&lt;/script&gt;

Wir laden bisher 3 JavaScript- und 2 CSS-Dateien von Wicket und Bootstrap. Nicht besonders viel und kaum eine Anwendung wird mit diesem Minimalset auskommen (wenn doch, Respekt!). Wir erschweren das Ganze, indem wir einige zusätzliche Ressourcen laden.

Wir definieren im Package de.effectivetrainings 6 zusätzliche JavaScript-Dateien, die nur ausgeben, dass sie geladen wurden (Hierdurch kann auch schön überprüft werden, in welcher Reihenfolge die Dateien abgearbeitet werden, was später noch eine Rolle spielen wird).

console.log("file xx loaded")

Zusätzlich definieren wir in der renderHead-Methode der Klasse Homepage folgende Ressourcen, damit Wicket die Javascript-Dateien in die Homepage einbindet.

        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file1.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file2.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file3.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file4.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file5.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file6.js")));

Direkt anschließend starten wir einen neuen YSlow-Test und betrachten die Früchte unserer Arbeit.

YSlow - viele Requests

Wir wurden auf ein B-Grade herabgestuft, da wir eines der wichtigsten Kriterien für performante Webanwendungen verletzen.

Make fewer HTTP-Requests

Welche Möglichkeiten haben wir jetzt, dieses Problem zu lösen?

Wicket ResourceBundles – Einfache Reduktion von HTTP-Requests

Mit ResourceBundles (neu in Wicket-6) lassen sich massiv HTTP-Requests einsparen, in dem Wicket serverseitig Ressourcen zusammenfasst und mit nur einem Request ausliefert. Dies sollte immer der erste Schritt sein, um Performance von Webanwendungen zu optimieren. Gerade im Mobile-Bereich tut jeder zusätzliche Request weh.

Natürlich wäre dies auch händisch möglich, meist hat es aber gute Gründe, dass JavaScript-Dateien separiert sind (Komponentenorientierung, Wartbarkeit, Third-Partly Libraries). Es ist immer gut, die Arbeit von Anderen machen zu lassen (so denn hierbei niemand darunter leidet). Und Wicket nimmt uns sehr gerne Arbeit ab.

Aktuell laden wir 9 JavaScript- und 2 CSS-Dateien. Wieso nicht alle JavaScript- und alle CSS-Ressourcen zusammenfassen, und alles über 2 statt 11 Requests laden?

Als Ergebnis wäre ein Request für eine app.js und ein Request für eine app.css doch völlig ausreichend.

ResourceBundles werden in der Application definiert und zwar global für die ganze Anwendung.

Versuchen wir uns zunächst an den CSS-Dateien, da hier nur zwei Ressourcen zusammengefasst werden müssen.

In der init-Methode der Klasse WicketApplication definieren wir folgendes ResourceBundle.

getResourceBundles().addCssBundle(WicketApplication.class,"app.css", Bootstrap.BOOTSTRAP_CSS, Bootstrap.BOOTSTRAP_RESPONSIVE_CSS);

Wir teilen Wicket mit, es gibt ein neues ResourceBundle, was unter der virtuellen Datei “app.css” für den Browser verfügbar sein soll. Außerdem definieren wir, welche Ressourcen in diese Datei kombiniert werden sollen (In diesem Fall die beiden Bootstrap-CSS).

Direkt nach dieser Änderung mache wir den nächsten YSlow-Test.

YSlow - mit Wicket CSS Resource Bundles

Wir haben aus den beiden Bootstrap-CSS eine app.css-Datei gemacht. Aber moment, wir laden in der Klasse HomePage immer noch beide Ressourcen, wieso wird nur das neue app.css-ResourceBundle geladen?

Wicket ist sehr smart. Wann immer eine JavaScript- oder CSS-Ressource angefragt wird prüft Wicket zunächst, ob diese Ressource teil eines definierten ResourceBundles ist. Ist dies der Fall, wir das ResourceBundle gerendert und nicht die angefragte Ressource selbst. Wicket stellt außerdem sicher, dass Ressourcen nicht mehrfach gerendert werden. Wir fragen also sowohl die Bootstrap.css als auch die Bootstrap-Responsive.css im Code an, Wicket rendert aber nur einmalig das app.css-ResourceBundle.

Damit haben wir einen Request eingespart, übrig sind 9 JavaScript-Requests, die es zu optimieren gilt (wir haben nach wie vor ein B-Grade in YSlow!).

Wir definieren ein neues JavaScript-ResourceBundle in der init-Methode der WicketApplication.

getResourceBundles().addJavaScriptBundle(WicketApplication.class,"app.js",
            WicketEventJQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class,"file1.js"),
            new JavaScriptResourceReference(HomePage.class,"file2.js"),
            new JavaScriptResourceReference(HomePage.class,"file3.js"),
            new JavaScriptResourceReference(HomePage.class,"file4.js"),
            new JavaScriptResourceReference(HomePage.class,"file5.js"),
            new JavaScriptResourceReference(HomePage.class,"file6.js")
        );

Wir kombinieren sowohl die Wicket-JavaScript-, die Bootstrap-Ressourcen
als auch die zuvor definierten fileX.js-Ressourcen in ein app.js-ResourceBundle.

Ein kleiner Hinweis: So haben wir doppelte Datenhaltung, wir referenzieren den Dateinamen file1.js an zwei Stellen.
Einmal in der WicketApplication bei der Definition des ResourceBundles, einmal in der Homepage
beim eigentlichen Anfordern der Ressource. Idealerweise definiert man für jede Ressourcen eine eigene ResourceReferenz-Klasse, die
von überall referenziert werden kann.

Zeit für den nächsten YSlow-Testrun.

yslow-komprimierte-js-resourcen

Die Seite fragt derzeit noch zwei Ressourcen an. Wieso aber zwei? Ich persönlich würde davon ausgehen, dass die Dependencies, die in RessourceReferenzen definiert sind auch für das Auflösen in ResourceBundles verwendet werden. Da lässt sich aber vortrefflich diskutieren.

Wir referenzieren die WicketEventJQueryResourceReference. Dies ist die Kompatibilitäts-Bibliothek für die Integration von JQuery in Wicket. Diese ResourceReference definiert eine Abhängigkeit auf JQuery selbst. Wird also die WicketEventJQueryResourceReference angefragt, sorgt Wicket dafür, dass alle Abhängigkeiten vorher geladen sind.

Da wir auch den letzten Request einsparen möchten, definieren wir zusätzlich die JQueryResourceReference als Abhängigkeit im RessourceBundle.

getResourceBundles().addJavaScriptBundle(WicketApplication.class, "app.js",
            WicketEventJQueryResourceReference.get(),
            JQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class, "file1.js"),
            new JavaScriptResourceReference(HomePage.class, "file2.js"),
            new JavaScriptResourceReference(HomePage.class, "file3.js"),
            new JavaScriptResourceReference(HomePage.class, "file4.js"),
            new JavaScriptResourceReference(HomePage.class, "file5.js"),
            new JavaScriptResourceReference(HomePage.class, "file6.js")
        );

Ich habe die Abhängigkeite absichtlich in der falschen Reihenfolge definiert, denn starten wir die Anwendung jetzt erscheint folgendes:

Uncaught ReferenceError: jQuery is not defined

Achtung: Bei der Verwendung von RessourceBundles werden die Abhängigkeiten nicht richtig gehandhabt und wir als Entwickler sind in der Pflicht, die Ressourcen in der richtigen Reihenfolge einzubinden.
Im obigen Beispiel definieren wir die WicketEventJQueryResourceReference vor der JQueryResourceReference. Wicket gibt uns keinen Hinweis, dass hier ein Konflikt vorliegt.

Die korrekte Einbindung sieht so aus:

getResourceBundles().addJavaScriptBundle(WicketApplication.class, "app.js",
            JQueryResourceReference.get(),
            WicketEventJQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class, "file1.js"),
            new JavaScriptResourceReference(HomePage.class, "file2.js"),
            new JavaScriptResourceReference(HomePage.class, "file3.js"),
            new JavaScriptResourceReference(HomePage.class, "file4.js"),
            new JavaScriptResourceReference(HomePage.class, "file5.js"),
            new JavaScriptResourceReference(HomePage.class, "file6.js")
        );

Höchste Zeit für einen letzten YSlow Test.

yslow grade A

Und am allerwichtigsten

success

Fazit

Geht es um Performance-Optimierung sind ResourceBundles die erste Verteidigungslinie.

Man muss allerdings ein wenig aufpassen, was man tut, denn Wicket “kopiert” alle für ein ResourceBundle notwendigen Dateien stur in das virtuelle File. Nehmen wir testweise die Semikolons aus unseren fileXX.js-Dateien heraus, geht gar nichts mehr mit dem Fehler:

Uncaught SyntaxError: Unexpected identifier

Warum? Weil beim Erstellen des ResourceBundles dann folgendes ensteht:

console.log("file2")console.log("file3")

Eigentlich sollte Wicket für jede inkludierte Datei zumindest einen Zeilenumbruch am Ende einfügen. Ich werde hierzu gleich mal einen Bug inkl. Patch einstellen. Achtet also zumindest für die aktuelle und vielleicht auch die kommende Version noch darauf, das JavaScript mit Semikolons und am Ende einem Zeilenumbruch abzuschliessen (aber das tut Ihr ja sowieso schon, oder?)

Was interessiert Euch für den nächsten Teil? Ich habe noch im Angebot

  • Page Performance Imrovement über die alternative Platzierung von JavaScript-Resourcen
  • Einbindung von CSS- und JavaScript Kompressoren
  • Mögliche Integration mit Wro4J
  • Andere Vorschläge?

Links

YSlow Addon Firefox
YSlow Addon Chrome
Yahoo WebPerformance Best Practices
Source-Code auf Github
Der eingestellte Wicket BUG

Und zu guter Letzt, noch viel genauer können wir über das Thema im Effective Wicket Workshop sprechen. Rufen Sie mich doch einfach an.

Going Mobile mit Apache Wicket – Teil 1 – Setup mit Bootstrap

Hallo,

in der folgenden kleinen Artikelserie geht es um einige kleine Tipps und Tricks, um Webanwendungen mit Apache Wicket zu entwickeln, und zwar Responsive. Hierbei gibt es einige Herausforderungen zu meistern.

Was bedeutet Responsive Web / Responsive Design

Um Anwendungen sowohl auf Desktop- als auch auf mobilen Endgeräten nutzbar zu machen gibt es einiges zu beachen.

  • Richtige Skalierung der Anwendung
  • Performance (gerade wichtig für Mobile Endgeräte die im Mobilnetz unterwegs sind)
  • Anzahl der Requests sollte minimiert werden
  • Ladezeiten verkürzen
  • Caching
  • Browser-Inkompatibilitäten (besonders nett bei älteren Android-Devices)
  • Device-Detection (handelt es sich beim aktuellen Kunden um ein Mobile-, oder Tabletdevice oder haben wir einen Desktoprechner)

Fragen die man unbedingt von vorne weg klären sollte

  • Was sind unsere Target-Devices (was nicht gilt ist – ALLE – das ist nicht zu bezahlen), sondern eine valide Antworte wäre IPhone, IPad und Android ab 2.1)
  • Wird eine Variante für alle Devices entwickelt (Responsive!) oder gibt es eine Mobile- und eine Desktopvariante?

Tools der Wahl

Was man um jeden Preis vermeiden sollte ist, das Rad neu zu erfinden, es gibt sehr viele brauchbare Lösungen für viele Probleme, und eins verspreche ich, wer versucht diese selbst zu lösen wird fast garantiert brutal und schnell scheitern.

  • JQuery als Javascript-Basis-Bibliothek (glücklicherweise bereites Bestandteil von Wicket 6)
  • Bootstrap (Bitte, Bitte, Bitte schreiben Sie kein eigenes CSS-Toolkit, sondern verwenden Sie was bereits da ist)
  • Require.js kann verwendet werden, um Abhängigkeiten richtig aufzulösen, ein Teil der Require.js-Funktionalität wird schon durch Wicket und sein Ressourcenmanagement bereitgestellt

Responsive Design auf einen  Blick

Meine Webseite www.effectivetrainings.de ist zwar nicht mit Wicket gemacht sondern Picket (Wicket-PHP Port), aber mit Bootstrap und man sieht sehr schön, was Responsive Design ist und wie es sich anfühlt, wenn man das Browserfenster einfach mal skaliert.

Desktop Medium-Screen Small-Screen
desktop medium small

Die Seite sieht auf allen Browser / Device-Größen zumindest akzeptabel aus.

Fangen wir also an

Zunächst erzeugen wir uns wie immer einen Wicket-Quickstart.

[code lang=”bash”]mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.7.0 -DgroupId=de.effectivetrainings -DartifactId=going-mobile -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false[/code]

Wir entscheiden uns natürlich für die Möglichkeit, eine Anwendung für alle Screen-Größen zu entwickeln, um doppelte Arbeit zu sparen und so günstig wie möglich entwickeln zu können.

Als CSS Framework verwenden wir the One-And-Only Bootstrap – für CSS-Laien (wie mich, auch nach Jahren noch…) die erste und beste Wahl.  Wicket bringt hier schon ein kleines Schmankerl mit, denn Bootstrap ist schon an Bord. Um es zu aktivieren deklarieren wir folgende Abhängigkeit in unserer Pom.

 &lt;dependency&gt;
            &lt;groupId&gt;org.apache.wicket&lt;/groupId&gt;
            &lt;artifactId&gt;wicket-bootstrap&lt;/artifactId&gt;
            &lt;version&gt;0.8&lt;/version&gt;
&lt;/dependency&gt;

Die Bootstrap-Integration ist keine Zauberei und wäre auch in ein paar Minuten selber geschrieben, aber wie ich ja früher im Artikel bereits erwähnt habe – es gilt um jeden Preis zu vermeiden das Rad neu zu erfinden.

Zunächst aber räumen wir unsere generierte Klasse Homepage ein wenig auf und Löschen alles aus HomePage.html was nicht gebraucht wird, bis das Markup so aussieht.

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;head&gt;
    &lt;/head&gt;
	&lt;body&gt;

	&lt;/body&gt;
&lt;/html&gt;

Sehr klar, oder? Zu beachten ist auf jedenfall die DOCTYPE-Deklaration ganz oben im File. Mit diesem DOCTYPE geben wir den Browsern einen Hinweis, dass wir evtl. mit HTML5 in Berührung kommen.

&lt;!DOCTYPE html&gt;

Um die Bootstrap-Integration jetzt zu verwenden machen wir in HomePage.java einfach folgendes.

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

	public HomePage(final PageParameters parameters) {
		super(parameters);
    }

    @Override
    public void renderHead(IHeaderResponse response) {
        super.renderHead(response);
        response.render(JavaScriptHeaderItem.forReference(Bootstrap.responsive()));
    }
}

Wichtig ist in der renderHead()-Methode folgende Zeile.

container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(Bootstrap.responsive()));

Die Seite die uns daraus gerendet wird beinhaltet derzeit zwar noch keinen Inhalt, aber schaut man sich den <head/>-Tag an, sieht man folgendes

&lt;head&gt;
    &lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-ver-1367063998000.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-event-jquery-ver-1367063998000.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-ver-1368356946000.css" /&gt;
&lt;link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-responsive-ver-1368356946000.css" /&gt;
&lt;script type="text/javascript" src="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/js/bootstrap-ver-1368356946000.js"&gt;&lt;/script&gt;
&lt;/head&gt;

Alle notwendigen Bootstrap Ressourcen sind vorhanden und werden gerendert.

Zuletzt definieren wir uns noch ein minimalistisches Basis-Layout, das wir in den folgenden Artikeln weiter ausbauen werden.

Zunächst stellen wir sicher, dass die initiale Skalierung der Seite auf die Breite des Devices angepasst wird. Hierfür wird der Meta-Tag viewPort verwendet. Eine schöne Beschreibung hierzu findet sich in den Links.

&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;

Zusätzlich bedienen wir uns zunächst einfach mal an einem Standard-Template von Bootstrap und definieren folgendes Basis-Markup in der HomePage.html

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;head&gt;
        &lt;!-- Skalierung auf devices, volle Skalierung auf die Weite --&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;/head&gt;
	&lt;body&gt;
    &lt;div class="container"&gt;

        &lt;div class="masthead"&gt;
            &lt;ul class="nav nav-pills pull-right"&gt;
                &lt;li class="active"&gt;&lt;a href="#"&gt;Home&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href="#"&gt;About&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href="#"&gt;Contact&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
            &lt;h3 class="muted"&gt;Going Mobile&lt;/h3&gt;
        &lt;/div&gt;

        &lt;hr&gt;

        &lt;div class="jumbotron"&gt;
            &lt;h1&gt;Jetzt wirds mobil!&lt;/h1&gt;
            &lt;p class="lead"&gt;Responsive Design mit Bootstrap ist so einfach!&lt;/p&gt;
            &lt;a class="btn btn-large btn-success" href="#"&gt;Klick me!&lt;/a&gt;
        &lt;/div&gt;

        &lt;/div&gt;
	&lt;/body&gt;
&lt;/html&gt;

Das sieht gerendert dann so aus (einmal Desktop und einmal als Mobile-Version mit verkleinertem Browser-Fenster).

Desktop Small-Screen
desktop-version small-screen-version

Bis jetzt wars einfach..

Bis jetzt war alles relativ einfach, oder? Im nächsten Artikel werden wir unsere Anwendung mit einigen kleinen Spielereien aufpeppen und werden ziemlich schnell an unsere Grenzen kommen.

Links

Beschreibung des ViewPort MetaTags

Sourcen auf GitHub

 

 

Wicket 6 und CDI – es kann so schön sein

Wicket 6.7.0 ist released, und das ist wie immer ein guter Zeitpunkt um sich einige Features  anzuschauen.

Heute geht es um Wicket in Verbindung mit CDI. Ein äusserst spannendes und praktisches Thema das bisher in diesem Blog sträflich vernachlässigt wurde. Am besten, wir fangen direkt an.

Ein neues Projekt – Wicket Archetype

Zunächst erzeugen wir uns ein neues Projekt mit Hilfe des Wicket-Maven-Archetypes.

[code lang=”bash” inline=”yes”]
mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.7.0 -DgroupId=de.effectivetrainings -DartifactId=wicket-cdi -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false
[/code]

Dieses Projekt lässt sich sehr einfach starten.

Um mit CDI arbeiten zu können deklarieren wir zusätzlich eine Depdendency in der pom auf das Wicket-CDI Modul, das mittlerweile nativ mit Wicket mitkommt.

[code lang=”xml” inline=”yes”]
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-cdi</artifactId>
<version>${wicket.version}</version>
</dependency>[/code]

Zusätzlich brauchen wir unbedingt eine Implementierung der CDI-Spezifikation. Natürlich verwenden wir hierfür Weld – die Referenzimplementierung.

Hierfür deklarieren wir zusätzlich folgende Abhängigkeiten in unserer Projekt-Pom.

[code lang=”xml”]

   &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld&lt;/groupId&gt;
            &lt;artifactId&gt;weld-core&lt;/artifactId&gt;
            &lt;version&gt;1.1.11.Final&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- needed for conversation scope propagation --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.seam.conversation&lt;/groupId&gt;
            &lt;artifactId&gt;seam-conversation-weld&lt;/artifactId&gt;
            &lt;version&gt;3.0.0.CR2&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.jboss.weld.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;weld-servlet-core&lt;/artifactId&gt;
            &lt;version&gt;1.1.11.Final&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.el&lt;/groupId&gt;
            &lt;artifactId&gt;javax.el-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet.jsp-api&lt;/artifactId&gt;
            &lt;version&gt;2.2.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;slf4j-ext&lt;/artifactId&gt;
            &lt;version&gt;1.6.2&lt;/version&gt;
        &lt;/dependency&gt;

[/code]

Zuletzt müssen wir noch das Jboss-Repository (oder müsste das mittlerweile Wildfly-Repository:) heissen?) integrieren, da die Weld-Ressourcen aus einem mir nicht erfindlichen Grund nicht im öffentlichen Maven-Repository hinterlegt sind.

[code lang=”xml”]
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
[/code]

Damit Weld / CDI mit unseren Container (Jetty) startet deklarieren wir noch einen ServletContextListener in der web.xml

[code lang=”xml”]
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
[/code]

<em>Hinweis - das initiale Projekt-Setup mit allen Abhängigkeiten findet man im Commit 62479a3888ca93dc6d9095bafb4858df014ad8d1</em>

Immer auf den Scope fokussieren – CDI Scopes in Wicket

Beim Start der Anwendung sieht man sofort, ob CDI mit dem Container startet, wenn man folgende Meldung in der Konsole sieht.

INFO  - Version                    - WELD-000900 1.1.11 (Final)

Jetzt sind wir bereit, uns so richtig schön mit Scopes, Conversations und Dependency Injection zu beschäftigen.

Wicket mit CDI verheiraten

Das Projekt ist soweit vorbereitet und Wicket möchte gerne eine gepflegte Konversation mit CDI führen. Hierfür müssen wir noch einige kleine Schritte vornehmen. Zunächst brauchen wir den BeanManager, der uns vom zuvor konfigurierten ServletContextListener bereitgestellt wird.

Hierzu definieren wir eine neue Methode initCDI() in unserer Application-Klasse und rufen diese Methode aus der bekannten init()-Methode auf.

[code lang=”java”]

   @Override
   public void init()
   {
        super.init();
        initCDI();
   }

    private void initCDI(){
        BeanManager manager = (BeanManager)getServletContext().getAttribute(
            Listener.BEAN_MANAGER_ATTRIBUTE_NAME);
        new CdiConfiguration(manager).configure(this);
    }

[/code]

Den BeanManager können wir uns direkt aus dem ServletContext laden und übergeben diesen in eine neue Instanz der CdiConfiguration (hier sind wir bereits in der Wicket-Welt). Mit Hilfe der Methode configure(..) binden wir die CdiConfiguration an unsere Application und wir sind bereit für CDI und Wicket.

Mehr ist übrigens nicht zu tun. Die CdiConfiguration kümmert sich bereits um die Registrierung der entsprechenden RequestCycle- und ComponentInstantiationListener. Sehr schön gemacht.

<em>Hinweis - Die Konfiguration für CDI findet man im Commit 6eb7a81549a9138f586fc49b13e434457e77f033</em>

Bitte injezieren Sie..

Um CDI testen zu können bauen wir uns zunächst mal eine kleine Mini-Domäne. Wir bauen einen Miniatur-Online-Shop und brauchen hierfür einige Produkte.

[code lang=”java”]

public class Product implements Serializable{

    private String name;
    private Double prize;

    public Product(String name, Double prize) {
        this.name = name;
        this.prize = prize;
    }

    public String getName() {
        return name;
    }

    public Double getPrize() {
        return prize;
    }
}

[/code]

Und einen Provider für unsere Schuhe.

[code lang=”java”]

@ApplicationScoped
public class ProductProvider {

    public List&lt;Product&gt; productList(){

        Product samba = new Product("Adidas Samba",49.95);
        Product air = new Product("Nike Air",79.95);
        Product balerina = new Product("Balerinas",9.95);

        return Arrays.asList(samba, air, balerina);

    }
}

[/code]

Der Provider liegt im ApplicationScope, da er von allen Benutzern unserer Webseite verwendet wird.

Injezieren können wir in alles, was von Wicket gemanaged wird und die ComponentInstantiationListeneres notifiziert – also Session, Komponenten, die Application und Behaviors.

Wir löschen zunächst alles aus der Homepage und dem zugehörigen Markup, injezieren uns den Provider und zeigen alle Produkte in einem ListView an.

[code lang=”java”]

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

    @Inject
    ProductProvider provider;

    public HomePage(final PageParameters parameters) {
		super(parameters);
                add(new ListView&lt;Product&gt;("productsList", provider.productsList()) {
                     @Override
                     protected void populateItem(ListItem&lt;Product&gt; item) {
                          item.add(new Label("name", new PropertyModel&lt;String&gt;(item.getModel(),"name")));
                          item.add(new Label("prize", new PropertyModel&lt;Double&gt;(item.getModel(),"prize")));
                     }
                });
    }
}

[/code]

und das zugehörige Markup.

[code lang=”html”]

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
	&lt;body&gt;
        &lt;div wicket:id="productsList"&gt;
            &lt;span wicket:id="name"&gt;&lt;/span&gt;
            &lt;span wicket:id="prize"&gt;&lt;/span&gt;
        &lt;/div&gt;
	&lt;/body&gt;
&lt;/html&gt;

[/code]

Die beans.xml wird immer vergessen…

Beim Zugriff auf die Seite bekommen wir als allererstes eine Unsatisfied-Dependency Exception. CDI ist zwar konfiguriert, aber inaktiv, denn das scannen nach Beans und Abhängigkeiten passiert erst, wenn wir die so gehasste beans.xml im Verzeichnis src/main/resources/META-INF/beans.xml anlegen mit folgendem Inhalt.

[code lang=”xml”]

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"&gt;
&lt;/beans&gt;

[/code]

Endlich ist es soweit

wicket cdi und weld in aktion

Unser ProductProvider wird injeziert und funktioniert. Ideal ist das aber nicht, was wir hier machen. Denn eigentlich sollte die Liste von verfügbaren Produkten nicht direkt beim ProductProvider angefragt werden, sondern über ein entsprechendes LoadableDetachableModel. Machen wir das doch einfach mal.

Injezieren in Non-Managed Klassen

Hierfür bauen wir uns folgendes Model.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    ProductProvider provider;

    @Override
    protected List&lt;Product&gt; load() {
        return provider.productList();
    }
}

[/code]

Funktioniert das? NEIN! Denn Models sind nicht unter Wicket-Kontrolle – hier haben wir per Definition keine Möglichkeit, uns Abhängigkeiten über CDI injezieren zu lassen… Oder?

Das Prinzip ist immer dasselbe, ob wir jetzt mit Spring, Guice oder CDI arbeiten. Für die Kombination mit Spring würde man das allseits bekannte Injector.get().inject(this) verwenden.

Das CDI-Pendant hierfür heisst  CdiContainer.get().getNonContextualManager().inject(this);

Die korrekte Model-Implementierung sieht also so aus.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends LoadableDetachableModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    ProductProvider provider;

    public ProductsLoadableDetachableModel() {
        CdiContainer.get().getNonContextualManager().inject(this);
    }

    @Override
    protected List&lt;Product&gt; load() {
        return provider.productList();
    }
}

[/code]

Fahren wir die Anwendung hoch, funktioniert das Ganze natürlich.

Einige CDI-Spielereien

Jetzt können wir uns noch einige Spezialitäten von CDI anschauen, wir können beispielsweise statt uns den ProductProvider direkt in das Model zu injezieren mit @Produces arbeiten.

[code lang=”java”]

@ApplicationScoped
public class ProductProvider {

    public ProductProvider(){}

    @Produces
    public List&lt;Product&gt; productList(){

        Product samba = new Product("Adidas Samba",49.95);
        Product air = new Product("Nike Air",79.95);
        Product balerina = new Product("Balerinas",9.95);

        return Arrays.asList(samba, air, balerina);

    }
}

[/code]

Wir deklarieren im ProductProvider dass dieser eine Liste von Produkten bereitstellt. Im Model sieht das Ganze dann so aus.

[code lang=”java”]

public class ProductsLoadableDetachableModel extends AbstractReadOnlyModel&lt;List&lt;Product&gt;&gt; {

    @Inject
    List&lt;Product&gt; productList;

    public ProductsLoadableDetachableModel() {
        CdiContainer.get().getNonContextualManager().inject(this);
    }

    @Override
    public List&lt;Product&gt; getObject() {
        return productList;
    }
}

[/code]

Achtung, durch das injezieren der Liste lösen wir zwar die Abhängigkeit zum ProductProvider auf, machen die Liste aber statisch. Wir würden keine Updates auf unsere Produktliste für diese Model-Instanz bekommen.

Kauf dir nen Schuh..

Zuletzt bauen wir uns noch die Möglichkeit, einen Schuh zu kaufen.

Hierfür bauen wir die Page folgendermaßen um.

[code lang=”java”]

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

    @Inject
    private Event&lt;Product&gt; productEvent;

	public HomePage(final PageParameters parameters) {
		super(parameters);

        ListView&lt;Product&gt; listView = new ListView&lt;Product&gt;("productsList", new ProductsLoadableDetachableModel()) {
            @Override
            protected void populateItem(ListItem&lt;Product&gt; item) {
                Form&lt;Product&gt; form = new Form&lt;Product&gt;("form", item.getModel()){
                    @Override
                    protected void onSubmit() {
                        super.onSubmit();
                        productEvent.fire(getModelObject());
                    }
                };
                item.add(form);
                form.add(new Label("name", new PropertyModel&lt;String&gt;(item.getModel(), "name")));
                form.add(new Label("prize", new PropertyModel&lt;Double&gt;(item.getModel(), "prize")));
            }
        };
        listView.setReuseItems(true);
        add(listView);
    }
}

[/code]

Und das entsprechende Markup.

[code lang=”html”]

&lt;!DOCTYPE html&gt;
&lt;html xmlns:wicket="http://wicket.apache.org"&gt;
&lt;body&gt;
&lt;div wicket:id="productsList"&gt;
    &lt;form wicket:id="form"&gt;
        &lt;span wicket:id="name"&gt;&lt;/span&gt;
        &lt;span wicket:id="prize"&gt;&lt;/span&gt;
        &lt;input type="submit" value="bestellen"&gt;
    &lt;/form&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

[/code]

cdi-weld-order-by-event

Wir stellen also ein Formular bereit, über das unsere Schuhe bestellt werden können.

Interessant ist die neue Instanzvariable

[code lang=”java”]

 @Inject
 private Event&lt;Product&gt; productEvent;

[/code]

Wir verwenden den CDI-Eventmechanismus um Schuhe zu kaufen. Beim Klick auf den Submit-Button wird die onSubmit-Methode der Form-Klasse aufgerufen und hier feuern wir ein neues Event.

[code lang=”java”]

 super.onSubmit();
 productEvent.fire(getModelObject());

[/code]

Zuletzt bauen wir uns einen Miniatur-Product-Service, der auf dieses Event reagiert.

[code lang=”java”]

@ApplicationScoped
public class ProductService {

    public void order(@Observes Product product){
        System.out.println("Produkt " +   product.getName() + " bestellt");
    }
}

[/code]

Und bestellen wir beispielsweise den Nike-Schuh sehen wir in der Konsole wie erwartet.

Produkt Nike Air bestellt

Fazit

Die Integration von CDI und Wicket ist genauso einfach wie mit Spring oder Guice und sieht sehr vielversprechend aus. Ich hoffe, ich habe demnächst die Gelegenheit, dieses nette Duo mal in einem Projekt verwenden zu dürfen.

Links

Anleitung von Igor Vaynberg zu Wicket und CDI
Schöne Anleitung zu CDI zum Ausprobieren
API-Docs zu Wicket & CD
Sourcen auf Github

 

 

Wicket 6 und JSR-303 – Beanvalidation

Hallo zusammen,

Wicket 6.4.0 ist released und es versteckt sich eine wirkliche Perle hier. Mit WICKET-4883 hat Igor Vaynberg eine Implementierung für BeanValidation (JSR-303) eingefügt. Das bedeutet, Wicket unterstützt BeanValidation nun “Out-of-the-Box”.

Das lasse ich mir natürlich nicht nehmen.

Zunächst erzeugen wir uns wie immer ein Maven-Wicket-Archetype für die Version 6.3.0 (Die Snapshot-Repositories für den 6.4.0-SNAPSHOT Archetype sind leider nicht aktuell).

[code]

mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=6.4.0 -DgroupId=de.effectivetrainings -DartifactId=jsr303-beanvalidation -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false

[/code]

Zunächst registrieren wir die BeanValidationConfguration in der init-Methode der Application.

[code language=”java”]

/**
* @see org.apache.wicket.Application#init()
*/
@Override
public void init()
{
super.init();
new BeanValidationConfiguration().configure(this);
// add your configuration here
}

[/code]

In Wicket-Experimental ist ein neues Modul entstanden. Dieses Modul deklarieren wir ebenfalls als Dependency in der pom.

[code language=”xml”]

<groupId>org.apache.wicket<groupId>
<artifactId>wicket-bean-validation</artifactId>
<version>0.5</version>

[/code]

Zusätzlich brauchen wir eine Implementierung der Bean-Validation-API. Typischerweise Hibernate Validator.

[code language=”xml”]
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
</dependency>
[/code]

Jetzt fehlt noch ein schön annotiertes Model-Objekt. Wir nehmen hierfür einen….

[code language=”java”]

public class EffectiveTrainer implements Serializable {

@NotNull
private String name;

@Pattern(regexp = "^[_A-Za-z0-9-]+(.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(.[A-Za-z0-9-]+)*((.[A-Za-z]{2,}){1}$)")
@NotNull
private String email;

@Pattern(regexp = "[0-9]+")
private String phone;

@Past
@NotNull
private Date birthDay;

public EffectiveTrainer(String name, String email, String phone, Date birthDay) {
this.name = name;
this.email = email;
this.phone = phone;
this.birthDay = birthDay;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public String getPhone() {
return phone;
}

public Date getBirthDay() {
return birthDay;
}
}

[/code]

Im HomePage-Markup ersetzen wir alles durch folgendes Formular.

[code language=”html”]
<body>
<div wicket:id="feedback"/>
<form wicket:id="form">
<input type="text" wicket:id="name"/> <br/>
<input type="text" wicket:id="email"/> <br/>
<input type="text" wicket:id="birthDay"/> <br/>
<input type="text" wicket:id="phone"/> <br/>
<input type="text" wicket:id="zip"/> <br/>
<input type="text" wicket:id="globalZip"/><br/>
<input type="submit"/>
</form>
</body>
[/code]

und den passenden Java-Code hierzu:

[code language=”java”]

public HomePage(final PageParameters parameters) {
super(parameters);

IModel effectiveTrainerModel = Model.of(new EffectiveTrainer());
add(new FeedbackPanel("feedback"));
Form form = new Form("form", effectiveTrainerModel);

form.add(new TextField("name", new PropertyModel(effectiveTrainerModel, "name"))
.add(new PropertyValidator()));
form.add(new TextField("email", new PropertyModel(effectiveTrainerModel, "email"))
.add(new PropertyValidator()));
form.add(new TextField("phone", new PropertyModel(effectiveTrainerModel, "phone"))
.add(new PropertyValidator()));
form.add(new TextField("date", new PropertyModel(effectiveTrainerModel, "birthDay"))
.add(new PropertyValidator()));

add(form);

}

[/code]

Starten wir die Anwendung und schicken das Formular testweise ab ergibt sich folgendes Bild.

Bean Validation

Bean Validation in Action

Jede FormComponent bekommt einen neuen PropertyValidator spendiert. Der PropertyValidator liest die entsprechenden Properties und deren Constraints aus.

Sehr schön, das Prinzip funktioniert. Aber die Fehlermeldungen sind nicht ideal. Wie lässt sich hier etwas machen?

Feedback-Messages

Das erste was einem wahrscheinlich einfällt ist die Fehlermeldungen direkt an den Constraints zu definieren.

[code language=”java”]

@NotNull(message = "Bitte geben Sie Ihren Namen ein.")
private String name;

@Pattern(message = "Die E-Mailadresse ist nicht gültig",
regexp = "^[_A-Za-z0-9-]+(.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(.[A-Za-z0-9-]+)*((.[A-Za-z]{2,}){1}$)")
@NotNull(message = "Bitte geben Sie Ihre E-Mailadresse ein")
private String email;

@Pattern(message = "Ihre Telefonnummer sollte aus Ziffern bestehen",regexp = "[0-9]+")
private String phone;

@Past(message = "Sie können nicht in der Zukunft Geburtstag haben")
@NotNull(message = "Bitte geben Sie Ihr Geburtsdatum ein.")
private Date birthDay;

[/code]

Und das funktioniert auch.

bean-validation - angepasste meldungen

bean-validation – angepasste meldungen

Ideal ist das aber nicht, denn meiner Ansicht nach ist die Wicket-Komponente (also die UI) verantwortlich sein zu definieren, wann in welchem Kontext welche Fehlermeldung angezeigt wird. Das Domain-Objekt “EffectiveTrainer” ist so nicht Kontextübergreifend wiederverwendbar. Das muss besser gehen, oder?

Dynamische FeedbackMessages

Natürlich geht das besser. Wir definieren die Attribute am EffectiveTrainer um.

[code language=”java”]

@NotNull
private String name;

@Pattern(message = "{email.invalid}",
regexp = "^[_A-Za-z0-9-]+(.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(.[A-Za-z0-9-]+)*((.[A-Za-z]{2,}){1}$)")
@NotNull
private String email;

@Pattern(message = "{phone.invalid}",regexp = "[0-9]+")
private String phone;

@Past(message = "{birthday.invalid}")
@NotNull
private Date birthDay;

[/code]

Die JSR-303 Spezifikation definiert, dass Fehlermeldungen mit Platzhaltern befüllt sein dürfen. Platzhalter sind definiert als { value }. Wir verwenden die Platzhalter einfach als Keys in die Property-Datei der jeweiligen Komponente.

Hierfür definieren wir jetzt nur noch diese Property-Datei (HomePage.properties).

[code]

email.Required=Bitte geben Sie Ihre E-Mailadresse ein.
email.invalid = Ihre E-Mailadresse ist ungueltig
name.Required=Bitte geben Sie Ihren Namen ein
phone.invalid=Ihre Telefonnummer sollte aus Ziffern bestehen
birthday.invalid=Ihr Geburtsdatum ist ungültig
birthday.Required=Bitte geben Sie Ihr Geburtsdatum ein

[/code]

Wieso aber email.Required? Wir haben überhaupt keine message für @NotNull definiert? Igor Vaynberg hat uns hier einen Gefallen getan:). Ein Formkomponente, deren zugeordnetes Attribut mit @NotNull annotiert ist wird standardmässig auf Required gesetzt. Das bedeutet, aber hier greifen die Wicket-Standards – also #attributName.Required.

Ergänzen wir das Beispiel noch um eine Postleitzahl. Ich biete Wicket-Trainings deutschlandweit an, aber ein Effective-Trainer kann nur aus München kommen…

Definieren wir also zusätzlich folgende Property.

[code language=”java”]
//hibernate validator specific
@Range(message = "{zip.muenchen}",min = 80805, max=80805)
private Integer zip;

[/code]

Folgendes Textfeld.

[code language=”java”]

form.add(new TextField("zip", new PropertyModel(effectiveTrainerModel, "zip"))
.add(new PropertyValidator()));

[/code]

und folgende Text-Property.

[code]

zip.muenchen=Ein Effective-Trainer kann nur aus Muenchen-Schwabing kommen

[/code]

Probleme

Warum hat es wohl so lange gedauert, bis die erste Implementierung direkt im Wicket-Code zur Verfügung steht?

Wicket arbeitet extrem viel mit PropertyModels. PropertyModels lösen Expressions (diese Punkt-separierten Strings) zur Laufzeit auf.

Das bedeutet, zur Laufzeit müssen wir wissen, welche Typen wir haben und wie Constraints auf diesen konfiguriert sind. Das ist etwas komplizierter. Ich habe auch vor ca. 2 Jahren schon eine JSR-303-BeanValidation Implementierung für Wicket gemacht. Lange nicht so schick wie diese hier, aber hat funktioniert.

Versuchen wir doch mal eine Vereinfachung. Lösen wir die ganzen PropertyModels im Formular auf und ersetzen diese durch ein CompoundPropertyModel. Standard so weit.

[code language=”java”]

IModel effectiveTrainerModel = Model.of(new EffectiveTrainer());
add(new FeedbackPanel("feedback"));
Form form = new Form("form", new CompoundPropertyModel(effectiveTrainerModel));

form.add(new TextField("name")
.add(new PropertyValidator()));
form.add(new TextField("email")
.add(new PropertyValidator()));
form.add(new TextField("phone")
.add(new PropertyValidator()));
form.add(new TextField("date")
.add(new PropertyValidator()));
form.add(new TextField("zip")
.add(new PropertyValidator()));

[/code]

Sieht tatsächlich besser aus. Funktionierts? Leider nein.

[code language=”java”]
java.lang.IllegalStateException: Could not resolve Property from component: [TextField [Component id = name]]. Either specify the Property in the constructor or use a model that works in combination with a IPropertyResolver to resolve the Property automatically

[/code]

Das Model der Komponente muss vom Typ IPropertyReflectionAwareModel sein, damit BeanValidation funktioniert. Im Fall CompoundPropertyModel haben wir gar keine speziellen Models.
Das Problem ist, wir kommen so leider nicht ganz einfach an das zu validierende Feld und noch schwieriger, die zugehörige Klasse ran.
Normlerweise durchläuft Wicket standardmässig für jede Expression die Getter-Chain:

[code language=”java”]
"customer.address.street" getCustomer().getAddress().getStreet()
[/code]

Die Typen der einzelnen Hierarchien sind hierbei egal. Nicht jedoch, wenn es um BeanValidation geht, denn ein JSR303-Validator will den Typ wissen.

Wenn wir mit dem CompoundPropertyModel arbeiten möchten, brauchen wir als einen kleinen Workaround.
Wir teilen Wicket bzw. dem Validator einfach selbst mit, um welchen Typ es sich handelt.

[code language=”java”]
form.add(new TextField("name")
.add(new PropertyValidator(new Property(EffectiveTrainer.class,"name"))));
form.add(new TextField("email")
.add(new PropertyValidator(new Property(EffectiveTrainer.class,"email"))));
form.add(new TextField("phone")
.add(new PropertyValidator(new Property(EffectiveTrainer.class,"phone"))));
form.add(new TextField("birthDay")
.add(new PropertyValidator(new Property(EffectiveTrainer.class,"birthDay"))));
form.add(new TextField("zip")
.add(new PropertyValidator(new Property(EffectiveTrainer.class,"zip"))));
[/code]

Validation Groups

Über Gruppen kann noch ein wenig genauer definiert werden, wann eine Property wie validiert wird. Ich bin kein großer Fan hiervon, denn wenn man so etwas wie Gruppen braucht, stellt sich die Frage, ob das Domain-Objekt richtig geschnitten ist. Nichtsdestotrotz, wofür könnte man das brauchen?

Eine Group ist technisch nichts anderes als ein Interface. Definieren wir also ein Interface für unseren EffectiveTrainer.

[code language=”java”]

public interface EffectiveTrainer {
String getName();

String getEmail();

String getPhone();
}

[/code]

Und benennen die Implementierung so um, dass es Sinn macht in MuenchensEffectiveTrainer.

Die Constraints definieren wir jetzt so um:

[code language=”java”]

@Range.List(value = {
@Range(message = "{zip.muenchen}",min = 80805, max=80805, groups = EffectiveTrainer.class),
@Range(message = "{zip.global}",min = 0, max=Integer.MAX_VALUE)
})
private Integer zip;

[/code]

Wir haben einen Default-Constraint, der quasi immer dann greift, wenn keine Group definiert ist (alle positiven Zahlen ausser 0 sind erlaubt!).

Im Fall von Münchens Trainer brauchts schon eine Postleitzahl in Schwabing.

Der Einfachheit halber definieren wir einfach nochmal ein zweites Textfeld, das auf ebenfalls auf die Zip-Property im Trainer-Objekt geht.

[code language=”java”]
form.add(new TextField("zip")
.add(new PropertyValidator(new Property(MuenchensEffectiveTrainer.class, "zip"), EffectiveTrainer.class)));

IModel globalZipModel = compoundModel.bind("zip");
form.add(new TextField("globalZip", globalZipModel)
.add(new PropertyValidator(new Property(MuenchensEffectiveTrainer.class, "zip"))));

[/code]

Im ersten Fall definieren wir im PropertyValidator, dass dieser nur die Group “EffectiveTrainer.class” validieren soll. Im zweiten Fall geben wir gar nichts an, das bedeutet, die Default Group greift.

Gruppen können unterschiedlich validiert werden

Gruppen können unterschiedlich validiert werden

Wie gesagt, Gruppen sollten die Ausnahme sein. Man kann eine Property auf unterschiedliche Weisen validieren, aber der Code ist schwerer zu verstehen.

Über dieses und viele weitere Themen spreche ich auch in meinem Wicket-Workshop
Kontaktieren Sie mich zum Thema Wicket, ich freue mich, wenn ich Sie unterstützen kann.

Effective Trainings Wicket Workshop

Links

JSR-303 Spezifikation

Safe-Model (von Carl Eric Menzel)
(geht auch in die richtige Richtung)

Mein Wicket Workshop