Spring

Multiple Selects mit Spring MVC

Während unserer coding night, zu der mit Sicherheit noch ein separater Blogeintrag folgt, setzen wir voll auf Spring Technologien. Dabei hat sich eine vermutlich simple Anforderung als ziemlich halsbrecherisch herausgestellt.

Wie kann man mit Spring MVC eine Select box darstellen, bei der man multiple Elemente auswählen kann, welche dann in eine Collection der Bean hinzugefügt werden?

Wir implementiere eine einfache Zeiterfassung. In dem Domänenmodell gibt es Projekte, denen Mitarbeiter zugeordnet sind. Zudem können Projekte Aufgaben haben, denen auch Mitarbeiter zugeordnet sein können. In der View zum erstellen der Aufgaben benötigen wir also eine Selectbox aus allen verfügbaren Mitarbeitern.

Modell

Hier das wichtigste der Aufgaben und Mitarbeiter:

Task

@Entity
public class Task implements Serializable {
	@Id
	@GeneratedValue
	private long id;
 
	@ManyToMany
	private Set<Staff> staffs = new HashSet<Staff>(0);
 
	//...
}

Staff

@NamedQuery(name = "staff.activeStaff", query = "select s from Staff s where s.disabled = false")
@Entity
@DiscriminatorValue("STAFF")
public class Staff extends Person {
 
	//...
}

Person

@NamedQuery(name = "person.findByUsername", query = "from Person p where p.login.username = :username")
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Person implements Serializable {
 
	@Id
	@GeneratedValue
	private long id;
 
	//...
}

View

In der view, in der die Tasks erstellt werden sollen, brauchen wir also eine select box, welche alle verfügbaren Mitarbeiter anzeigt. Die ausgewählten Mitarbeiter sollen dann der Aufgabe hinzugefügt werden. Die View ist mit der Task hinterlegt, und fügt alle aktiven Mitarbeiter zu einem Attribut hinzu:

@Controller
@SessionAttributes("project")
public class ProjectController {
	@RequestMapping(method = RequestMethod.GET, value = "/project/createTask.action")
	public void createTaskView(@ModelAttribute Task task, Model model) {
		List<Staff> activeStaff = timetrackingService.getActiveStaff();
		model.addAttribute("activeStaff", activeStaff);
	}
 
	//...

Die entsprechende JSP sieht folgendermaßen aus:

<td><label for="staffs">Chose Staff: </label></td>
<td><form:select path="staffs" multiple="true" items="${activeStaff}" itemLabel="fullName" itemValue="id"/></td>
<td><form:errors path="staffs" /></td>

Das Problem, sobald man die Form submitted, bekommt man eine ServletRequestBindingException, denn Spring weiß nicht wie man aus einem String (bzw. String[] wenn mehrere Personen markiert waren) ein Set erstellt.

org.springframework.web.bind.ServletRequestBindingException: Errors binding onto object 'task'; nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'task' on field 'staffs': rejected value [3]; codes [typeMismatch.task.staffs,typeMismatch.staffs,typeMismatch.java.util.Set,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [task.staffs,staffs]; arguments []; default message [staffs]]; default message [Failed to convert property value of type [java.lang.String] to required type [java.util.Set] for property 'staffs'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [de.codecentric.timetracking.model.Staff] for property 'staffs[0]': no matching editors or conversion strategy found]

Zudem ist der generierte HTML-code lückenhaft, er enthält keine id für die einzelnen Mitarbeiter!

<select id="staffs" multiple="multiple" name="staffs">
<option value="">firstname lastname</option>
<option value="">aaaa aaaa</option>
<option value="">firstname lastName</option>
<option value="">firstname aaaa</option>
<option value="">firstname lastname</option>
</select>
<input type="hidden" value="1" name="_staffs"/>

Id als String

Um die ID des Mitarbeiters in das value-Attribut der option zu bekommen, kann man einen eigenen Getter auf der Person implementieren, und diesen dann in der JSP statt der ID verwenden:

public abstract class Person implements Serializable {
	//...
	public String getIdAsString() {
		return new Long(id).toString();
	}
}
<form:select path="staffs" itemValue="idAsString" multiple="true" items="${activeStaff}" itemLabel="fullName"/>

InitBinder

Die Lösung für das Binding-Problem ist, dass wir für das Attribut ’staffs’ einen eigenen PropertyEditor registrieren müssen. Spring bringt eine Reihe eigener PropertyEditoren mit, in diesem Falle können wir den CustomCollectionEditor wiederverwenden. Um von der String-ID wieder auf den Mitarbeiter mappen zu können, müssen wir uns auch noch eine Map initialisieren, die dieses Mapping vorhält.

public class ProjectController {
 
private Map<String, Staff> staffCache;
 
@RequestMapping(method = RequestMethod.GET, value = "/project/createTask.action")
public void createTaskView(@ModelAttribute Task task, Model model) {
	List<Staff> activeStaff = timetrackingService.getActiveStaff();
	staffCache = new HashMap<String, Staff>();
	for (Staff staff : activeStaff) {
		staffCache.put(staff.getIdAsString(), staff);
	}
	model.addAttribute("activeStaff", activeStaff);
}
 
@InitBinder
protected void initBinder(WebDataBinder binder) throws Exception {
	binder.registerCustomEditor(Set.class, "staffs", new CustomCollectionEditor(Set.class) {
		protected Object convertElement(Object element) {
			if (element instanceof Staff) {
				System.out.println("Converting from Staff to Staff: " + element);
				return element;
			}
			if (element instanceof String) {
				Staff staff = staffCache.get(element);
				System.out.println("Looking up staff for id " + element + ": " + staff);
				return staff;
			}
			System.out.println("Don't know what to do with: " + element);
			return null;
		}
	});
}

Der Code enthält noch etwas Debug-output nach System.out, der natürlich noch entfernt werden muss. Er zeigt aber sehr schön, dass der Code sehr (zu?) häufig aufgerufen wird. Außerdem wird erwartetn, dass die Property in beide Richtungen konvertiert werden kann!

Allein wenn die select box angezeit werden soll, steht folgendes im Log:

Looking up staff for id 1: Staff(firstname lastname)
Looking up staff for id 1: Staff(firstname lastname)
Converting from Staff to Staff: Staff(firstname lastname)
Looking up staff for id 2: Staff(aaaa aaaa)
Looking up staff for id 2: Staff(aaaa aaaa)
Converting from Staff to Staff: Staff(aaaa aaaa)
Looking up staff for id 3: Staff(firstname lastName)
Looking up staff for id 3: Staff(firstname lastName)
Converting from Staff to Staff: Staff(firstname lastName)
Looking up staff for id 4: Staff(firstname aaaa)
Looking up staff for id 4: Staff(firstname aaaa)
Converting from Staff to Staff: Staff(firstname aaaa)
Looking up staff for id 5: Staff(firstname lastname)
Looking up staff for id 5: Staff(firstname lastname)
Converting from Staff to Staff: Staff(firstname lastname)

Wenn man einen Mitarbeiter aus der Select-Box auswählt und die Form submitted, findet sich dann aber wie erwartet nur ein Eintrag im Log:

Looking up staff for id 3: Staff(firstname lastName)

Fazit

Angesichts der Masse an Webframeworks, die es gibt, finde ich es erschreckend Kompliziert so etwas einfaches wie eine multiple select Box mit Spring MVC zu implementieren. Da ich mir Spring MVC aber auch erst seit gestern genauer angesehen habe, verstehe ich es vielleicht noch nicht richtig, von daher bin ich sehr für Vorschläge zu haben, wie man das beschriebene Szenario mit Spring MVC-Mitteln eleganter und einfacher implementieren kann.

Andreas Ebbert-Karroum

 

Über ein Erlebnis der besonderen Art: JDK6, JDK5 und Spring 2.0.0

In einem unserer Projekte führen wir derzeit eine Migration von JDK5 Update 7 zu JDK6 Update 12 durch. In einer unserer Anwendungen verwenden wir JCaptcha zur Absicherung von Formularen. Den größtmöglichen Konfigurations-Komfort für das Captcha erreichen wir durch die Verwendung von Spring-Beans, die beim Initialisieren des Captcha-Servlets zur Anwendung kommen.

Bei der Umstellung auf Java 6 (Update 12) kam es auf einer lokalen Entwicklungsmaschine plötzlich zu einer IllegalArgumentException bei der Initialisierung des Captcha-Servlets. JBoss lokal weiterhin mit Java 5 laufen zu lassen behob zwar das Problem, war jedoch nicht zielführend, da die Zielsysteme schrittweise alle auf Java 6 umgestellt werden sollen.

Anbei ein Auszug aus dem Stacktrace der Exception:

java.lang.IllegalArgumentException: Color parameter outside of expected range: Red Green Blue
	at java.awt.Color.testColorValueRange(Color.java:298)
	at java.awt.Color.(Color.java:382)
	at java.awt.Color.(Color.java:357)
	at java.awt.Color.(Color.java:448)

Eine genauere Untersuchung des Phänomens im Debugger ergab letztlich folgendes Bild:

Es wurde also offensichtlich der Color-Konstruktor verwendet, der 3 float-Parameter akzeptiert. Ein Auszug aus der Spring-Bean-Konfiguration:

<bean id="captchaBackgroundColor" class="java.awt.Color">
	<constructor-arg index="0"><value>255</value></constructor-arg>
	<constructor-arg index="1"><value>255</value></constructor-arg>
	<constructor-arg index="2"><value>255</value></constructor-arg>
</bean>

Der float-Konstruktor enthält als erstes die Zeile:

this( (int) (r*255+0.5), (int) (g*255+0.5), (int) (b*255+0.5));

Damit wird der Konstruktor für 3 int-Parameter aufgerufen und bekommt dreimal 65025 als Argument übergeben. Das Resultat: die oben beschriebene IllegalArgumentException.

Die exakte Ursache für das Problem liegt in einer Kombination aus mehreren Umständen. Weniger technisch interessierte Leser können die folgende Aufzählung guten Gewissens überspringen:

  • Es wird Spring 2.0.0 verwendet. Der zu verwendende Konstruktor wird mittels ConstructorResolver.autowireConstructor(…) ermittelt. In Zeile 100, in ebendieser Methode, wird per Reflection ein Array der potenziellen Konstruktoren aufgebaut. Anschließend wird dieses Array sortiert. Das darunter wirkende Arrays.sort(…) liefert mit JDK6 auf Windows-Systemen ein anderes Ergebnis als mit JDK5.
  • In dem sortierten Array steht also der Konstruktor Color(float, float, float) weiter vorn als der Konstruktor Color(int, int, int). Mit JDK5 steht der int-Konstruktor weiter vorn.
  • Es folgt eine Schleife, die aus dem sortierten Konstruktor-Array den Konstruktor auswählt, der zur Instanzierung des gewünschten Objekts verwendet werden soll. Diese Schleife ermittelt anhand der Anzahl Argumente (trivial) und einer sogenannten TypeDifferenceWeight (etwas komplizierter) den zu verwendenden Konstruktor.
  • TypeDifferenceWeight bedeutet, dass eine Differenz in der Klassenhierarchie zwischen Typen und Argumenten errechnet wird. Wir wollen int-Argumente verwenden, um unser Color-Objekt zu instanzieren. Die Methode zur Berechnung der TypeDifferenceWeight geht für jeden Parameter-Typ die Klassenhierarchie des dazugehörigen Arguments so lange nach oben, bis keine höhere Superklasse auffindbar ist. Solange die gefundene Superklasse ein Typ ist, dem das dazugehörige Argument zugewiesen werden kann, wird der TypeDifferenceWeight-Wert erhöht und die Suche fortgesetzt. (AutowireUtils.getTypeDifferenceWeight(…))
  • Dies bedeutet logischerweise, dass die TDW von float und int 0 ist, da beide primitive Datentypen sind.
  • Ist die ermittelte TDW kleiner als die bisher kleinste gefundene TDW, wird der Konstruktor aus dem aktuellen Schleifendurchlauf als zu verwendender Konstruktor gesetzt.
  • Da im Array der float-Konstruktor mit JDK6 weiter vorn steht, und da bei den nachfolgenden Konstruktoren die kleinste gefundene TDW nicht mehr kleiner werden kann (0 kann nicht kleiner als 0 sein), wird im Endeffekt der float-Konstruktor verwendet.
  • Der float-Konstruktor übergibt die Argumente multipliziert mit 255 und um 0,5 erhöht als int-Werte an den int-Konstruktor.
  • Der int-Konstruktor wird also als Color(65025, 65025, 65025) aufgerufen. Folge: die IllegalArgumentException, weil RGB-Werte nur zwischen 0 und 255 liegen können.

Zurück zum greifbaren Phänomen: auf dem Testserver, der bereits auf Java 6 umgestellt ist, läuft das Captcha nach wie vor fehlerfrei. Offensichtlich ist also das Problem darin begründet, dass die Konstruktoren von einer JVM auf einem Linux-System anders sortiert werden als auf einem Windows-System. Zusätzlich sollte der Type Weight Difference Mechanismus in Spring hoffentlich in neueren Versionen intelligenter sein, falls Autowire noch darauf basiert.

Abhilfe schafft eine explizite Typangabe in der Bean-Konfiguration:

<bean id="captchaBackgroundColor" class="java.awt.Color">
	<constructor-arg index="0" type="int"><value>255</value></constructor-arg>
	<constructor-arg index="1" type="int"><value>255</value></constructor-arg>
	<constructor-arg index="2" type="int"><value>255</value></constructor-arg>
</bean>

Damit ist sichergestellt, dass der gewünschte Konstruktor, nämlich java.awt.Color#Color(int r, int g, int b), verwendet wird.

Meine weitestmöglich in die Tiefe gehende Sourcecode-Analyse von Spring 2.0.0 und JDK6 Update 12 ergab keine exakte Erkenntnis, warum Arrays.sort(…) mit dem vom Spring Framework übergebenen Comparator auf Windows-Systemen ein anderes Ergebnis als auf Linux-Systemen liefert. Wer dazu etwas sagen kann, ist herzlich eingeladen dies zu tun.

Fazit: der Teufel steckt im Detail. Auch eine vermeintlich “kleine” Änderung wie ein Update der Java-Version kann dazu führen, dass schwer zu findende Fehler entstehen. Intensives und präzises Testen ist bei einer solchen Änderung unerlässlich!

Vielen lieben Dank an Mike Wiesner, Senior Consultant bei SpringSource, der mir bei einer Frage zu Spring 2.0.0 “in Echtzeit” weitergeholfen hat!

Robert Spielmann

 

OSGi Anwendungen vs. Ansammlung von Bundles

Wenn ich von einer OSGi-Anwendung spreche, dann meine ich in der Regel eine Gruppe von Bundles, die gemeinsam eine Anwendung ergeben. Eine solche Aufteilung bietet sich förmlich an, wenn man OSGi verwendet. Die Kommunikation zwischen den einzelnen Teilen der Anwendung findet dann über die OSGi-Registry statt. Einer der großen Vorteile, den man dadurch gewinnt, ist die Möglichkeit Teile der eigenen Anwendung zur Laufzeit austauschen zu können, ohne dass der Rest der Anwendung davon betroffen wird.

Leider bietet OSGi keine Möglichkeit Bundles, die zu zwei unterschiedlichen Anwendungen gehören voneinander zu unterscheiden. Das Fehlen einer solchen Funktion lässt sich vielleicht historisch erklären. OSGi war ursprünglich für den Einsatz auf Kleingeräten gedacht, wo die Anzahl der Bundles klein ausfällt. Heute wird OSGi in Projekten wie zum Beispiel der Eclipse IDE eingesetzt, wo die Anzahl an Bundles dreistellig oder mehr werden kann. Wenn ich meine aktuelle Eclipse-Installation starte (./eclipse -console) und mir mit dem Befehl “ss” die Anzahl der installierten Bundles anzeigen lasse, komme ich auf über 600 Bundles. Und dabei habe ich kaum zusätzliche Plugins installiert. Die eine oder andere Installation der Eclipse IDE wird durchaus auf 1000+ kommen. Würde ich jetzt ein bestimmtes Plugin über die Konsole deinstallieren wollen, hätte ich alle Hände voll zu tun, die Bundles, die zu diesem Plugin gehören zu finden und zu deinstallieren. Bei Eclipse nimmt man eher den Update-Manager und spart sich die Mühe. In einem anderen Projekt, wo ich einen OSGi-Container (Equinox in diesem Fall) einsetze, habe ich leider keinen Update-Manager – nur die Konsole.

Zurzeit befinden sich neben meiner Anwendung (bestehend aus 6 Bundles) ein Servlet-Container, das Spring Framework und eine Reihe von Bibliotheken im OSGi-Container. Wenn ich an meiner Anwendung arbeite, interessieren mich die restlichen Bundles eigentlich nicht. Manche davon werden nach der Installation gar nicht mehr verändert. Trotzdem kommt man nicht umhin, die Liste mit allen installierten Bundles durchzugehen, wenn man Bundles aus der eigenen Anwendung sucht. Es wäre deutlich eleganter, wenn man mit einem Befehl alle Bundles anzeigen lassen könnte, die zu einer bestimmten Anwendung gehören und direkt deren Status ändern könnte. Wie die Blog-Einträge [1][2] zeigen, bin ich nicht der einzige, der eine solche Funktion vermisst. Also habe ich mich nach einer bestehenden Lösung umgeschaut.

Dabei bin ich auf das Platform Archive von SpringSource gestoßen. Mit dem SpringSource dm Server (früher SpringSource Application Platform) hat SpringSource das Platform Archive (PAR) eingeführt. Das PAR ist eine JAR-Datei, die mehrere Bundles beinhalten kann. Auf diese Weise kann man alle Bundles, die zu einer Anwendung gehören gleichzeitig installieren, deinstallieren, etc. Das PAR bietet somit genau die gewünschten Funktionen mit einer Funktionsweise für OSGI, die man sonst von WAR und EAR Archiven gewohnt ist. Leider lassen sich PAR Dateien zurzeit nur in einem SpringSource dm Server installieren. Als Nachteil kann man zudem anführen, dass man das PAR Archiv neu bauen muss, wenn ein Bundle ausgetauscht werden soll, was das Austauschen einzelner OSGI Bundle komplexer macht.

Um mehr Flexibilität zu haben, wollte ich das Equinox Framework um die gewünschte Funktion selbst erweitern. Was ich erreichen wollte, ist die Möglichkeit Gruppen von Bundles zu definieren, ohne dass ich dabei die OSGi Spezifikation verletze. Schließlich sollen die Bundles auch in jedem anderen OSGi-Container installiert werden können. Außerdem sollen die Bundles weiterhin getrennt voneinander vorliegen.

Nach einem Blick in den Sourcecode von Equinox habe ich festgestellt, dass ich an der OSGi Implementierung nicht einmal etwas ändern musste. Es reicht aus das CommandProvider Interface aus dem org.eclipse.osgi.framework.console Package zu implementieren und ein Bundle zu bauen, was eine Instanz meiner Implementierung in die OSGi-Registry einträgt. Auf diese Weise kann man die Equinox Konsole um eigene Befehle erweitern.

Die entstandene Klasse sieht wie folg aus:

public class GroupCommandProvider implements CommandProvider {
   private BundleContext bctx;
 
   public GroupCommandProvider(BundleContext bctx) {
      this.bctx = bctx;
   }
 
   public String getHelp() {
      return "\tstopgroup &lt;group&gt; - stops all bundles in the specified group.";
   }
 
   public void _stopgroup(CommandInterpreter ci) throws BundleException {
      String groupToStop = ci.nextArgument();
      if (groupToStop == null)
         return;
 
      Bundle[] bundles = bctx.getBundles();
      for (Bundle bundle : bundles) {
         String currentGroup = (String) bundle.getHeaders().get("Bundle-Group");
         if (currentGroup == null || !currentGroup.equals(groupToStop))
            continue;
 
         if (bundle.getState() == Bundle.ACTIVE) {
            bundle.stop();
         }
      }
   }
}

Die interessante Methode ist _stopgroup(). Wenn sie aufgerufen wird, werden alle Bundles gestoppt, die einen bestimmten Wert für „Bundle-Group“ in der Manifest-Datei stehen haben.

Eine Manifest-Datei könnte also wie folgt aussehen:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: de.codecentric.mybundle
Bundle-Group: myGroup
Bundle-Version: 0.1.0
Import-Package: org.osgi.framework

Alle Bundles aus der gleichen Gruppe können dann über die Equinox Konsole mit “stopgroup myGroup” auf einen Schlag gestoppt werden.

Damit der GroupCommandProvider aktiv wird, braucht man nur noch einen Activator. Dieser fällt sehr einfach aus:

public class Activator implements BundleActivator {
   private CommandProvider commandProvider;
 
   public void start(BundleContext bctx) throws Exception {
      commandProvider = new GroupCommandProvider(bctx);
      bctx.registerService(CommandProvider.class.getName(), commandProvider, null);
   }
 
   public void stop(BundleContext arg0) throws Exception {
      commandProvider = null;
   }
}

Damit der GroupCommandProvider sinnvoll eingesetzt werden kann, braucht er ein paar weitere Methoden, um zum Beispiel Gruppen von Bundles starten, deinstallieren oder einfach anzeigen zu können. Bei der Methode für das Starten einer ganzen Gruppen müsste man sich noch überlegen, wie man Bundles kennzeichnet, welche gestartet werden sollen und welche nicht. Was mir da vorschwebt ist eine Erweiterung des „Bundle-Group“ Eintrags in der Manifest-Datei, zum Beispiel „Bundle-Group: myGroup;startable=true“. Aber ich denke, dass die _stopgroup() Methode die Idee deutlich macht.

Es bleibt abzuwarten, ob sich bei der OSGi Spezifikation in der nächsten Version etwas in diese Richtung tut. Für mich persönlich reicht die beschriebene Lösung aus.

Eugen Melnichuk

 

SpringSource Partnerschaft

In fast allen unseren Projekten setzen wir auf eine leichtgewichtige Architektur im Java Enterprise Umfeld und setzen dabei in vielen Projekten auf das Springframework und dessen Subprojekte wie Spring Batch oder Spring Webflow. Bereits in diesem Früjahr auf der JAX haben wir uns deshalb mit SpringSource über eine mögliche Partnerschaft unterhalten und diese jetzt auch offiziell bekannt gegeben.

Die Kombination aus unserem Java EE Knowhow in den Bereichen Performance, Architektur und Open Source und dem Produktportfolio von SpringSource erschien beiden Unternehmen als optimale Lösung für unsere Kunden. Gerade die Support Angebote für Produktion und Entwicklung, die hervorragenden Core Spring Trainings und die Enterprise Version von Spring, mit Monitoring Funktionen (auf Basis von Hyperic), Eclipse basierter IDE und erweitertem Oracle Support befördern Spring auf eine neue Ebene für geschäftskritische Enterprise Anwendungen.

In Zukunft wird sicherlich auch die SpringSource Application Platform für Kunden interessant werden, die die leichtgewichtige Architektur von Spring und Tomcat mit den Vorteilen von OSGi für die Anwendungsentwicklung kombiniert und so neue Möglichkeiten für Betrieb und Entwicklung bietet.

Diese neue Platform hat Eberhard Wolff bei uns während eines unserer “Freitags-Meetings” vorgestellt. Wir beschäftigen uns selber auch mit OSGi und den Einsatzmöglichkeiten in der Anwendungsentwicklung und als Implementierungslösung für Services innerhalb einer SOA und Eugen schreibt bei uns auch seine Diplomarbeit zu diesem Thema. Der Vortrag wurde deshalb besonders gut vom unserem Team aufgenommen und verschiedenste Herausforderungen und Lösungsansätze mit OSGi und der Spring Application Platform diskutiert.

Mirko Novakovic