Tapestry

Aus Claudio's Wiki
Wechseln zu: Navigation, Suche

Start

Die Informationen dieser Seite basieren auf Tapestry 4.1.

Installation

Voraussetzungen

  • Java Development Kit (JDK)
Ohne JDK lässt sich nichts entwickeln, also bei Sun runterladen.
  • Integrated Development Environment (IDE)
Das ist die Entwicklungsumgebung. Die bekannteste ist vermutlich Eclipse.
Bei Eclipse ist empfehlenswert, die WebToolsPlattform (WTP) mitzuinstallieren.
  • Servlet container
In der Regel reicht Tomcat. Ich bevorzuge allerdings JBoss.
  • Optional: Maven
Wenn Maven genutzt wird, einfach die dependencies ins Pom.xml schreiben:

...
<dependencies>
  ...
  <dependency>
    <groupId>org.apache.tapestry</groupId>
    <artifactId>tapestry-framework</artifactId>
    <version>4.1.6</version>
  </dependency>
  <dependency>
    <groupId>org.apache.tapestry</groupId>
    <artifactId>tapestry-annotations</artifactId>
    <version>4.1.6</version>
  </dependency>
  ...
</dependencies>
...

Bei Maven nicht vergessen, den Compiler auf version 1.5 oder 1.6 zu setzen:

...
<build>
  ...
  <plugins>
    ...
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>1.5</source>
        <target>1.5</target>
      </configuration>
    </plugin>
    ...
  </plugins>
  ...
</build>
...

Ohne Maven muss man Tapestry manuell runterladen und zu den Libraries von Eclipse hinzufügen. Hier eine Anleitung dazu, falls notwendig. Ist auch gleich ein kleines Tutorial dabei.

Struktur

Eine Web-Applikation in Java hat immer einen Ordner WEB-INF. Generell (bei Tapestry) gehören alle HTML-, .page-, .jwc- und .application-Files in diesem Ordner. Damit Tapestry überhaupt geladen wird, muss es einem Servlet zugewiesen werden. Das tut man im web.xml (auch im WEB-INF-Ordner):

...
<servlet>
  <servlet-name>App</servlet-name>
  <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>App</servlet-name>
  <url-pattern>/app</url-pattern>
</servlet-mapping>
...

Der Wert im Element <load-on-startup>1</load-on-startup> definiert, in welcher Reihenfolge die Servlets gestartet werden. Ein Wert >= 0 startet diesen Servlet automatisch, sobald der Servletcontainer läuft. Ein Wert < 0 oder leer startet diesen Servlet irgendwann, spätestens beim ersten Aufruf. Dieser Wert ist optional.




Dateien

Tapestry startet mit Home. Das heist, es braucht eine Home.html, Home.page und Home.java.

Datei Zweck Notizen
Home.html Html File für die visualisierung der Daten x
Home.page Beschreibung, welche Java-Datei für diese Html-Datei zuständig ist Wird in bindings konfiguriert (mehrere bindings sind moeglich)
Home.java Java-Datei mit Daten und Logik Meistens abgeleitet von BasePage (extend BasePage)


Inhalte

Version 1 (declared) Version 2 (implicit)
Home.html
 <span jwcid="now">…</span>
 
Home.html
 <span jwcid="@Insert" value="ognl:currentDate">…</span>
 
Home.page
 
 <component id="now" type="Insert">
    <binding name="value" value="currentDate"/>
 </component>
 
Home.page
 <!-- no longer needed
 <component id="now" type="Insert">
    <binding name="value" value="currentDate"/>
 </component>
 -->
Home.java
 public getCurrentDate(){
     return "jetzt";
 }
 
Home.java
 public getCurrentDate(){
     return "jetzt";
 }
 


Home.page alternatives values
<binding name="value" value="ognl:currentDate"/>
==> gleich wie: <binding name="value" value="currentDate"/>
<binding name="value" value="literal:my birthday"/>
==> gleich wie <binding name="value" value="’my birthday’"/>
==> ’my birthday’ wird als String ausgegeben (ohne ' ' natuerlich)


Abstrakte Methoden

anstelle von: abststrakt:

 public String getTheWord() {
   return theWord;
 } 
public void setTheWord(String theWord) { this.theWord = theWord; }

 public abstract String getTheWord();
 public abstract void setTheWord(String theWord);

Nicht vergessen, dass wenn man sich für die abstrakte Methoden entscheidet, Tapestry auch das Initialisieren und somit auch das Löschen der Werte übernimmt, bevor ein anderer Benutzer drauf zugreift. Siehe auch Properties.

Komponenten

Eigene Komponenten

Eigene Komponenten können (sind) Webseiten, die in Pages eingefügt werden können. Sie lassen sich so überall in der Applikation wiederverwenden.

Um Komponenten zu erstellen, braucht es ein HTML- und ein jwc-File (im Gegensatz zur Page, die ein page-File braucht). Komponenten können dann in Pages (oder Komponenten) so eingefügt werden:

<div jwcid="@MeineKomp" />

Ein jwc-File enthaelt im Minimum diesen Code:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE component-specification PUBLIC 
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<component-specification />

Falls eine Java-Klasse zugewiesen ist, wird <component-specification /> wie folgt erweitert:

Beispiel:
<component-specification class="ch.miggiano.app.comp.SerienList" />
</component-specification>

Die Klasse 'muss' von BaseComponent abgeleitet sein (extends BaseComponent).

Es ist empfehlenswert, die Ablage der Komponenten in einem eigenen Ordner zu organisieren. Damit das geht, muss man in die app.application diese Zeile eintragen:

Beispiel:
<meta key="org.apache.tapestry.component-class-packages"
      value="ch.imis.ishop.web.comp"/>

Das heisst, alle Komponenten sind im Ordner comp abgelegt

Parameter

Wenn man der eigenen Komponente Parameter übergeben will, kann man das z.B. mit der annotation @Parameter machen.

Beispiel:
Wenn man z.B. eine Komponente Suchergebnis erstellt, und die braucht die Parameter (Searchtext, FromPage, ToPage), kann man das so machen:

Suchergebnis.java

@Parameter(name = "searchtext")
public abstract String getSearchValue();

@Parameter(name = "fromPage")
public abstract int getFromPage();

@Parameter(name = "toPage")
public abstract int getToPage();

Aufruf von Suchergebnis.html aus einer anderen html-Seite

<div jwcid="@Suchergebnis" searchtext="'Audi RS6'" fromPage="4" toPage="5" />

oder

<div jwcid="@Suchergebnis" searchtext="ognl:selectedItem" fromPage="ognl:thisPage + 1" toPage="ognl:thisPage + 2"/>

Die Angabe vom Parameter 'name' ist optional. Wenn man es nicht angibt, heisst der Parameter gleich wie die sofort folgende Methode.
Doku: Tapestry4 annotations parameter

Tapestry Komponenten

Komponentenliste
Komponentenname (type) Beschreibung Beispiel
Insert Text einfuegen
 <span jwcid="@Insert" value="ognl:currentDate">…</span>
 
TextField Textfeld (in einem Formular)

...
<input type="text" jwcid="firstName"/>
<input type="text" jwcid="lastName"/>
...
<component id="firstName" type="TextField">
   <binding name="value" value="firstName"/>
</component>
<component id="lastName" type="TextField">
   <binding name="value" value="lastName"/>
</component>
public abstract String getFirstName();
public abstract String getLastName();

Form HTML-Form

<form action="" jwcid="secretWordForm">

<component id="secretWordForm" type="Form">
  <binding name="listener" value="listener:onWordSubmit"/>
</component>

public String onWordSubmit() {
  return "Secret";
}

PageLink Link zu einer bestimmten Tapestry-Page

<a href="" jwcid="@PageLink" page="CelebritiesList">
   List Celebrities
</a>

For Iterator über eine Collection oder Array (For each) Bei diesem Beispiel funktionieren die Links nicht! Diese müssen mit @DirectLink implementiert werden.

<table width="300" cellpadding="5" border="1">
   <tr>
      <th>Last Name</th>
      <th>First Name</th>
   </tr>
   <tr jwcid=”eachCelebrity”>
      <td>
         <a href="">
            <span jwcid="lastName">Smith</span>
         </a>
      </td>
      <td>
         <span jwcid="firstName">John</span>
      </td>
   </tr>
   <tr jwcid=”$remove$”>
      <td>
         <a href="">Smithson</a>
      </td>
      <td>Jane</td>
   </tr>
   <tr jwcid=”$remove$”>
      <td>
         <a href="">Swedenborg</a>
      </td>
      <td>Emmanuel</td>
   </tr>
</table>
<page-specification
       class="com.devshed.tapestry.celebrities.CelebritiesList">
   <property name="currentCelebrity"/>

   <component id="eachCelebrity" type="For">
      <binding name="source" value="celebrities"/>
      <binding name="value" value="currentCelebrity"/>
   </component>

   <component id="firstName" type="Insert">
      <binding name="value" value="currentCelebrity.firstName"/>
   </component>
   <component id="lastName" type="Insert">
      <binding name="value" value="currentCelebrity.lastName"/>
   </component>
</page-specification>

DirectLink Im Gegensatz zu @PageLink hat @DirectLink folgende (Haupt-)Vorteile:
  • Ist selbst ein Link
  • Kann einem Listener assoziiert werden
  • Kann dem Listener Parameter übergeben

Basiert auf die Beispiele von @For

<a href="" jwcid="detailsLink">
  <span jwcid="lastName">Smith</span>
</a>
<component id="detailsLink" type="DirectLink">
   <binding name="listener" value="listener:onShowDetails"/>
   <binding name="parameters" value="currentCelebrity.id"/>
</component>
<component id="birthday" type="Insert">
   <binding name="value" value="celebrity.dateOfBirth"/>
   <binding name="format" value="dateFormat"/>
</component>
public abstract class CelebritiesList extends BasePage {

   private DataSource dataSource = new DataSource();

   @InjectPage("Details")
   public abstract Details getDetailsPage();

   public List getCelebrities() {
      return dataSource.getCelebrities();
   }

   public IPage onShowDetails(int id) {

      Celebrity celebrity = dataSource.getCelebrityById(id);

      Details nextPage = getDetailsPage();
      nextPage.setCelebrity(celebrity);

      return nextPage;
   }
}
public abstract class Details extends BasePage {

   public abstract Celebrity getCelebrity();
   public abstract void setCelebrity(Celebrity c);

   public Format getDateFormat() {
      return new SimpleDateFormat("MMM d, yyyy");
   } 
}

Radio und RadioGroup xx
@InitialValue("literal:F")
public abstract String getGender();
<component id="gender" type="RadioGroup">
   <binding name="selected" value="gender"/>
</component>
<form jwcid="@Form">
   ...
   <div jwcid="gender">
      <input type="radio" name="gender" value="M" 
             jwcid="@Radio"/> Male   
      <input type="radio" name="gender" value="F"
             jwcid="@Radio"/> Female
   </div>
   ...
</form>

PropertySelection Creates form elements that allow a property of an object to be set from a drop-down list.

<select jwcid="occupation">
<component id="occupation" type="PropertySelection">
    <binding name="model" value="occupationsModel"/>
    <binding name="value" value="occupation"/>
</component>
public abstract String getOccupation();

public IPropertySelectionModel getOccupationsModel() {
    String[] occupations = {"Actor/Actress", "Wine-maker", "Programmer"};
    return new StringPropertySelectionModel(occupations);
}

x xx xxx
x xx xxx



Spezielle jwcid

Quelle

$remove$

Die Komponente wird von Tapestry nicht gerendert. So kann eine statische Seite gebaut werden mit Beispieldaten und diese später von Tapestry ignorieren lassen.

Beispiel:

<table>
  <tr>
    <th>First Name</th>
    <th>Last Name</h>
  </tr>
  <tr jwcid="loop">
    <td><span jwcid="insertFirstName">John</span></td>
    <td><span jwcid="insertLastName">Doe</span></td>
  </tr>
  <tr jwcid="$remove$">
    <td>Frank</td>
    <td>Smith</td>
  </tr>
  <tr jwcid="$remove$">
    <td>Jane</td>
    <td>Jones</td>
  </tr>
</table>

Vorsicht! Es sind keine Komponenten in einem $remove$-Block erlaubt. Dies verursacht eine Exception von Tapestry.
Tapestry '$remove$ jwcid'

$content$

TODO

Tapestry '$content$ jwcid'



Properties

Die typischen Getter und Setter inkl. deren private Variablen nennen sich Properties.

Properties Beispiele
Wo Beispiel Notizen
In der Java-Klasse public abstract String getTheWord(); Abstrakte Methode benutzen.
Man muss sich keine Sorgen machen um die Zurücksetzung der Daten vor dem Initialisieren.

public abstract setTheWord(String s); macht Tapestry selbstständig. Allerdings muss man sie selber schreiben, wenn man im Code drauf zugreifen will.

In der Java-Klasse
private String theWord;

public String getTheWord() {
   return theWord;
}

public void setTheWord(String theWord) {
   this.theWord = theWord;
}

Ausprogrammieren. Mehr Möglichkeiten, aber man darf nicht vergessen, die Variablen zurückzusetzen beim Initialisieren.
In der Java-Klasse
public abstract String getTheWordStore();
public abstract void setTheWordStore(String word);

public String getTheWord() {
   // Eigener Code...
   return getTheWordStore();
}

public void setTheWord(String theWord) {
   // Eigener Code...
   setTheWordStore(String word);
}

Kombination der ersten Beiden. Ist vermutlich der beste Ansatz.
Page-File <property name="theWord"/> Ist gleichwertig wie die Abstrakte Methode.

Wird meistens dann eingesetzt, wenn ein temporäres Objekt gebraucht wird, z.B. bei einem @For

x xx xxx


Falls man ein Startwert definieren will, geht man so vor:

Abstrakt-Methode 
Vor der Abstrakt-Methode wird eine Annotation gesetzt:
 @InitialValue("literal:enter a word")
 public abstract String getTheWord();

Da InitialValue eine OGNL-Expression ist, ist auch folgendes möglich:

 @InitialValue("initialValue")
 public abstract String getTheWord();
 
 public String getInitialValue() {
    return "enter a word";
 }
Page-File 
Property-Tag wird um initial-value erweitert:
<property name="theWord" initial-value="literal:enter a word"/>
Manuell in der Java-Klasse 
Indem man getTheWord() überschreibt:
public String getTheWord() {
   if (theWord == null) theWord = “enter a word";
   return theWord;
}
Manuell in der Java-Klasse 
Oder indem man finishLoad() überschreibt (aus BasePage):
protected void finishLoad() {
   theWord = “enter a word";
}


Injection (mit Annotations)

Dieser Code-Fragment sorgt in dieser Klasse dafür, dass über getDetailsPage() ein Verweis auf die Page Details vorhanden ist:

@InjectPage("Details")
public abstract Details getDetailsPage();




Spezielle Interfaces

PageBeginRenderListener

Bei Komponenten (oder Pages), die eine einmalige Aktion ausführen müssen, bevor die Seite geladen wird, kann man das Interface
PageBeginRenderListener
implementieren.
Dies bedingt die Methode
public void pageBeginRender(PageEvent event)

Listener

Ist die Methode, die ausgeführt wird z.B. bei einem Form submit.

Simple Listener

Beispiel:

Home.html
<form action="" jwcid="secretWordForm">
… some content …
</form>

Home.page
<component id="secretWordForm" type="Form">
   <binding name="listener" value="listener:onWordSubmit"/>
</component>

Wir wollen also die Methode onWordSubmit() aufrufen:

public String onWordSubmit() {
   return "Secret";
}

Das zurückgeben eines Strings bedeutet, dass Tapestry die Seite mit dem zurückgegebenen Namen anzeigen soll.
In diesem Fall 'Secret.html'.

Listener mit Referenz auf andere Pages

public IPage onWordSubmit(IRequestCycle cycle) {
   Secret nextPage = (Secret)cycle.getPage("Secret");
   nextPage.setTheWord(getTheWord());

   return nextPage;
}

IPage 
Ist ein Interface von Tapestry. Alle pages implementieren dies. Also wird mit diesem Listener eine page zurückgegeben (die, wo wir hinwollen).
IRequestCycle 
Bestandteil von majordomo. Jeder User hat ein majordomo. Er verwaltet unter Anderem die pages. In diesem Fall bekommen wir eine Referenz zu jeder beliebigen Tapestry-page.
cycle.getPage("Secret") 
Gibt eine IPage zurück. Da wir wissen, dass es die page Secret.html (bzw. Secret.java) ist, können wir sie mit (Secret) casten.



Listener mit Referenz auf andere Pages und Parameters

Einfache Variante ohne IRequestCycle.getListenerParameters()

Mit einem Parameter

public IPage onWordSubmit(Integer id)
{
   // some code here ...
}

<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:element.id" >
</a>

Mit mehrereren Parameter

public IPage onWordSubmit(Integer id, String name, Integer page)
{
   // some code here ...
}

<a jwcid="@DirectLink" listener="listener:onWordSubmit"
   parameters="ognl:{ element.id, element.name, currentPage }" >
</a>



Alternative mit IRequestCycle.getListenerParameters()

Mit einem Parameter

public IPage onWordSubmit(IRequestCycle cycle) {
   Object[] parameters = cycle.getListenerParameters();
   String theWord = (String)parameters[0];

   Secret nextPage = (Secret)cycle.getPage("Secret");
   nextPage.setTheWord(theWord);

   return nextPage;
}

<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:theWord">
   <span jwcid="@Insert" value="ognl:theWord" />
</a>

Mit mehrereren Parameter

public IPage onWordSubmit(IRequestCycle cycle) {
   Object[] parameters = cycle.getListenerParameters();
   String theWord = (String)parameters[0];
   String theSecondWord = (String)parameters[1];

   Secret nextPage = (Secret)cycle.getPage("Secret");
   nextPage.setTheWord(theWord);

   return nextPage;
}

<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:{ theWord, theSecondWord }">
   <span jwcid="@Insert" value="ognl:theWord" />
</a>

OGNL Expressions

Wenn eine Methode keine Klammern hat, geht Tapestry davon aus, dass es Properties sind, stellt also get und set vorne ran:

ognl:person.address.town 
getPerson().getAddress().getTown()
ognl:currentDate + someMessage 
getCurrentDate() + getSomeMessage()


Wenn man Klammern setzt, geht Tapestry von einer Methode aus. Get und set werden nicht gesetzt:

ognl:doThat() 
doThat()
ognl:getList(currChar) 
getList(Char currChar)


Kombinationen:

ognl:theWord == 'abrakadabra' 
getTheWord == 'abrakadabra' => getTheWord().equals("abrakadabra")


ognl:'backoffice' + important 
'backoffice' + getImportant() => "backoffice" + getImportant()

Conditions

<div jwcid="@If" condition="ognl:theWord == 'abrakadabra'">
   <!—Stuff to show if everything is OK --/>
</div>
<div jwcid="@Else">
   <!-- An alternative content goes here -->
</div>

Diesmal wurde ein div statt ein span benutzt, weil div auch andere Elemente beinhalten kann im Gegensatz zu span, der nur zur Formatierung von Text benutzt wird.
Das ognl hier ist wichtig, denn sonst wird es als Text interpretiert und dann ist alles, was nicht leer ist, true.
Wichtig für das @Else ist, dass es sofort nach dem @If kommt.


Annotations



HiveMind

Tapestry 4 braucht HiveMind für IoC (Inversion of Control). Da HiveMind schon implementiert ist, braucht es nur konfiguriert zu werden, und zwar in hivemodule.xml.


hivemodule.xml

<?xml version="1.0" encoding="UTF-8"?>

<module id="com.devshed.tapestry.celebrities" version="1.0.0">
  <contribution configuration-id="tapestry.state.ApplicationObjects">
    <state-object name="dataSource" scope="session">
      <create-instance class="com.devshed.tapestry.celebrities.DataSource"/>
    </state-object>
  </contribution>
</module>


hivemodule.xml
Element Beschreibung Beispiel
<module>

The <module> element is the root of hivemodule.xml; we shall have it in every file like this. Note that the version attribute should look exactly as shown, i.e. it must contain three numbers separated by periods, or the application won't start.

xxx
<contribution>

The <contribution> element tells HiveMind that we are going to contribute something of our own to its already existing extensive list of services and objects. The tapestry.state.ApplicationObjects value for configuration ID explains what we are going to do with this object; it indicates an ASO.

xxx
<state-object>

The <state-object> element gives the ASO its name under which we shall be requesting it in the pages. Also we have specified that the scope for this ASO is session. Session is basically a piece of memory associated specifically with the given user; nobody else can access this memory. So in our case there will be a separate instance of DataSource for each user, which makes sense to me, as different collectors will have different collections.

Alternatively, we could use an application scope, and in that case all the users would share the same DataSource.

xxx
<create-instance>

Finally, the <create-instance> element specifies which class to instantiate to create the desired ASO (Application State Object -> A DataSource stored in the application's memory and easily accessible for all pages.

xxx
x xx xxx
x xx xxx


Usage

Um ein Element aus hivemodule zu benutzen, folgenden Beispielcode benutzen:

@InjectState("dataSource")
public abstract DataSource getDataSource();

Dieser Code kann überall in der Applikation benutzt werden. Mit getDataSource() greift man somit auf die Klasse com.devshed.tapestry.celebrities.DataSource zu.

Localization

Quelle: Tapestry 4 localization

Wenn man eine Page oder Component für eine Spezielle Locale zur verfügung stellen will, z.B. weil der Login in deutscher Sprache mehr Felder braucht als der auf französisch, kann man diese z.B. mit Login_de.html von Login_fr.html voneinander unterscheiden.
Auch Länderspezifisch: Login_de_DE.html bzw. Login_de_CH.html

Config

app.application

Speicherort der app.application: WEB-INF
Benennung: app muss dem Applikationsnamen (Servlet-Name) entsprechen (wie im web.xml definiert). Gross und Kleinschreibung sind wichtig! -> %servletname%.application


Beispiel:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE application PUBLIC 
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<application>

  <meta key="org.apache.tapestry.page-class-packages"
        value="ch.imis.ishop.web"/>
  <meta key="org.apache.tapestry.component-class-packages"
        value="ch.imis.ishop.web.comp"/>
  <meta key="org.apache.tapestry.messages-encoding"
        value="UTF-8"/>
  <meta key="org.apache.tapestry.accepted-locales"
        value="de_CH,fr_CH,it_CH,de_DE" />

</application>



Notizen

Abkuerzungen

ognl 
Object-Graph Navigation Language: it is an expression language for getting and setting properties of Java objects.
ognl Language Guide
ognl operators
jwcid 
Java Web Component ID
Komponente
listener 
Methode, die aufgerufen wird, wenn das Formular übermittelt wird.

Some notes

userdataForm@Form <== page oder komponente
   ^
   |- entspricht der id im der Website (<form id="userdataForm" ...)


Pages und Komponenten haben Java-Klassen mit gleichem Namen
.html = Pages
.html + .jwc = Komponenten

.jwc = Informationen zu entspr. Java-Klasse

Beispiel für ein Label (aus Properties-File)
<th class="label">
    <span jwcid="@insert" value="message:doc.number">
        irgendein Text
        (wird von Tapestry ersetzt, ausser man ruft das File ohne Tapestry auf)
    </span>
</th>

Vorlagen

*.page
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE page-specification PUBLIC
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
  "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd">
<page-specification class="ch.miggiano.app.Home">
</page-specification>


*.jwc
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE component-specification PUBLIC 
  "-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<component-specification class="ch.miggiano.app.comp.SerienList" />
</component-specification>