Skip to content


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, .