So kommt ein Datenbank-Wert in eine nach Werten sortierte Selectbox

Als Beispiel dient die im yaaff-Projekt eingesetzte Tabelle der Ländernamen yaaff.list_countryname:

CREATE TABLE yaaff.list_countryname
(

id character varying(255) NOT NULL,
countrygerman character varying(255) NOT NULL,
countryenglish character varying(255) NOT NULL,
eu boolean,
CONSTRAINT list_countryname_pkey PRIMARY KEY (id)

)
WITH (OIDS=FALSE);
ALTER TABLE yaaff.list_countryname OWNER TO yaaff_admin;

Da wir als Persistenzframework EJB 3 einsetzen, wurde das SQL der Tabelle durch folgende Entity generiert, als sie auf dem Applicationserver deployed wurde:

@Entity
@Table(name="list_countryname")
public class CountryName implements Serializable {

@Id
private String id;
@Column(nullable = false)
private String countryGerman;
@Column(nullable = false)
private String countryEnglish;
private Boolean eu;


// Getters and setters ...

}

Die Entity repräsentiert die Tabelle im Java-Objektmodell und ist die unterste Zugriffsebene auf die DB.

Die nächsthöhere Ebene stellen sogenannte DAOs (Data-Access-Objects) dar, die die Entities verwalten. Um Entities zu speichern, upzudaten, zu finden und zu löschen stellt die Klasse EntityManager einfache Methoden bereit. So erfolgt das Speichern einer CountryName-Entity countryName einfach durch den Aufruf   em.persist(countryName); wobei em ein privates Attribut der Klasse EntityManager im DAO ist, versehen mit der Annotation @PersistenceContext.
Durch diese Annotation wird der benötigte EntityManager mittels Dependency Injection vom Container bereitgestellt und muss nicht selbst erzeugt werden. Dieses Prinzip heißt auch Inversion of Control oder Hollywood_Prinzip („Don’t call us, we call you“).

Nun hat man also im DAO elementare CRUD-Operationen für die Entity. Diese DAO-Methoden werden in der nächsthöheren Schicht, den Managers oder Services genutzt, um Dienste für das Frontend anzubieten. Oft werden noch sog. Fassaden dazwischengeschaltet, die die Dienste mehrerer Services nutzen. Services und Fassaden sind, genauso wie DAOs, meist stateless Session Beans.

Wie sieht nun eine Service-Methode aus, um dem Frontend (in unserem Fall Tapestry 5) die benötigte Selectbox zu liefern?

Tapestryseiten setzen z. B. folgendes Markup ein, um eine Selectbox darzustellen:

<select t:type="select" t:id="countryOfBirth" t:model="countries" value="person.countryOfBirth" size="1" blankOption="always" />

Die Selectbox hat in sich Schlüssel und Werte, die dem Model countries entstammen: Der Wert wird angezeigt, der Schlüssel bei der Auswahl gespeichert. countries der Seite wird in der zugehörigen Seitenklasse gefüllt durch  countries = cm.getCountries(persistentLocale.get().toString());
Dabei ist cm ein oben angesprochener Manager, der die Methode  public Map<String, String> getCountries(String locale) folgendermaßen implementiert: Er ruft am DAO die Methode, die alle CountryName Entities c liefert und iteriert sie durch. Je nach locale kommt in die Ergebnis-TreeMap map

map.put(c.getId(), c.getCountryGerman());

oder

map.put(c.getId(), c.getCountryEnglish());

Nun könnte man map zurückgeben. Als TreeMap ist sie sogar sortiert, aber leider nach den Schlüsseln, nicht nach den (von Tapstry angezeigten) Werten. Um das gewünschte Ergebnis zu bekommen, wurde eine eigene Klasse ValueSortedMap eingesetzt, bei der die Sortierung anhand der gespeicherten Werte erfolgt:

public class ValueSortedMap<K, V> implements Map<K, V>, Serializable { ... }

Die ValueSortedMap bildet alle Map-Operationen auf eine interne TreeMap<K, V> ab, die bei der Erzeugung einen eigenen Comparator erhält. Bei diesem ist die compare-Methode so geschrieben, dass sie statt zweier Schlüssel die zwei zugehörigen Werte vergleicht. Als special feature wird ausserdem abgecheckt, ob einer der Werte „sonstiges“ ist. Denn dieser Eintrag soll am Ende der Select-Werte erscheinen.