Eine Jar (OJDBC) nachträglich in die Ziel-Jar integrieren

Für das visualDependencies-Projekt stand ich eben vor dem Problem, dass zur Laufzeit ein Oracle-JDBC-Treiber benötigt wird. Leider kann man ihn jedoch nicht als Abhängigkeit konfigurieren, da zum einen das Projekt unter der GPL läuft, und zum anderen es überhaupt keinen offiziellen Weg dafür gibt. Für den eigenen Buildprozess kann man selbstverständlich ein lokales Repository nutzen, das Artefakt lokal deployen bzw. den Buildserver entsprechend konfigurieren.

Da jedoch das Projekt im Form des Sourcecodes „für alle“ erreichbar sein soll, fallen diese Optionen zumindest für diese Situationen weg. Es muss also eine Möglichkeit geben, den OJDBC-Treiber nachträglich in das fertige Applikations-Jar zu integrieren. Und ja, das ist eigentlich sehr trivial.

Im folgenden Ant-Script werden sowohl die Applikations-Jar (visualDependencies.one-jar.jar) als auch der JDBC-Treiber (ojdbc14.jar) in der Konfiguration vorbelegt. Selbstverständlich kann man alle Properties überschreiben bzw. das Script entsprechend anpassen.

Die wichtigen Teile sind: Das Ant-Jar-Command muss den Zusatz „update“ erhalten, denn die Ziel-Jar soll nicht ersetzt werden. Außerdem muss ein Fileset mit einer Verzeichnisstruktur angegeben werden, da die Treiber-Jar innerhalb der Ziel-Jar im Verzeichnis lib liegen muss (Struktur einer One-Jar). Das war’s.

Hinweis: Das Ant-File ist für den Gebrauch in einer Maven-Umgebung konfiguriert (basedir=../../../ entspricht dem Root-Verzeichnis bei der Annahme, dass Script unter src/main/scripts/ liegt).

<?xml version="1.0" encoding="UTF-8"?>
<project name="visualDependencies" default="help" basedir="../../../">
	<!-- The path of the actual artifact (project name), without the file suffix. -->
	<property name="project.name" value="visualDependencies.one-jar" />
	<!-- The path of the ojdbc driver, without the file suffix. -->
	<property name="ojdbc.name" value="ojdbc14" />
	<!-- DO NOT CHANGE THIS LINES UNLESS YOU KNOW WHAT YOU DO! -->
	<property name="project.jar" value="${project.name}.jar" />
	<property name="ojdbc.jar" value="${ojdbc.name}.jar" />
	<!-- Set actual paths of artifacts. -->
	<property name="application.path" location="${basedir}/target/${project.jar}" />
	<property name="ojdbc.parent.path" location="${basedir}/oracle" />
	<property name="ojdbc.path" location="${ojdbc.parent.path}/lib/${ojdbc.jar}" />
	<!-- Shows help (default target) -->
	<target name="help">
		<echo message="See http://www.knallisworld.de/blog/2010/11/23/eine-jar-ojdbc-nachtraglich-in-die-ziel-jar-integrieren/" />
		<echo message="Usage: attachOJDBC" />
	</target>
	<!-- Checks if the artifacts are available. Throws exceptions if they not exist. -->
	<target name="checkDependencies">
		<available file="${application.path}" property="application.exists" />
		<available file="${ojdbc.path}" property="ojdbc.exists" />
		<fail unless="application.exists" message="The application file (${application.path}) does not exist." />
		<fail unless="ojdbc.exists" message="The ojdbc file (${ojdbc.path}) does not exist." />
	</target>
	<!-- Integrates all files of ojdbc.parent.path into the target jar. -->
	<target name="attachOJDBC" depends="checkDependencies">
		<jar update="true" destfile="${application.path}">
			<fileset dir="${ojdbc.parent.path}" />
		</jar>
	</target>
</project>

Apples Java-Support

Nur, falls das irgendjemand durch die ganzen Blogs, Techs & Co missverstanden hat:

The runtime provided by Apple (effectively everything in the .jdk bundle) is deprecated, but will continue to be supported throughout the service lifetimes of 10.5 and 10.6. The JavaVM.framework and it’s sub-frameworks are still supported API.

(Mike Swingler, Java Engineering, Apple Inc.) Quelle

Die Runtime ist „Java“, während das Framework quasi nur eine Schnittstelle ist. Und diese Schnittstelle ist *nicht* deprecated. Ganz im Gegenteil, sie wurde im letzten Update sogar aufgewertet (zugegeben, die Möglichkeit mehrere unterschiedlicher JVMs ist nur auf OSX ein Novum).

Java auf dem Mac

Mit dem jüngsten Java-Update hat Apple angekündigt, dass ihre eigene Java-Version als „deprecated“ markiert wurde und das nach dem üblichen Support-Lifecycle eine Java-Entwicklung eingstellt wird.

Oder, mit anderen Worten:

  • OS X 10.6 „Snow Leopard“ ist die letzte OS X Version, die ein vor-installiertes Java (JVM+JDK) besitzt und innerhalb der üblichen Apple-Updates aktualisiert wird;
  • Apple hat mit dem jüngsten Update einige Änderungen in der internen Java-Framework-Architektur gemacht, damit parallele JVMs (und auch Nicht-Apple-JVMs) besser erkannt werden können und (einfacher) aktiviert werden können (ein nahe liegende Grund ist bspw. das Testen einer Developer-JVM, was bisher entweder gar nicht ging oder nur mit großem Aufwand);
  • Apple macht damit in Zukunft genau das gleiche wie Microsoft.

Viel FUD kommt aber in einigen (Java-)Entwickler- oder Apple/Mac-Communities (oder auch der offiziellen Java-Mailingliste von Apple) auf, da die Ankündigung wahlweise als Java-Verbannung, „Jobs hates Java“ oder sonstiger streng objektiven Aussagen interpretiert wird.

Zunächst einmal bedeutet das Wort „deprecated“ nur, das beim Release der Schnittstelle/des Frameworks die „deprecated“ Komponente eben zu einem späteren Zeitpunkt hinfällig wird. Damit ist also nicht etwa eine „veraltete [Java] Technik“ gemeint. Also, Apple sagt nur, das ihre eigene entwickelte JVM eingestellt wird. (Genau das steht auch in den Release Notes.)

Zusammengefasst kann man als Java-Mac-Entwickler derzeit nur sagen:

  • Schade, dass man sich auf lange Sicht in Zukunft nicht mehr auf der sicheren Seite bewegt, das eine JVM (und sogar JDK!) auf dem OSX-System vorhanden ist. Auch dort wird man also in einigen Monaten Hinweise auf das Vorhandensein von Java oder automatische Installationsangebote wiederfinden.
  • Schade, dass es kein Vendor-Java gibt. (Es gibt Vorteile).
  • Schön, denn die Releasezyklen sind bestenfalls so gut wie von Sun (Oracle), nie besser. Und aus der Vergangenheit wissen wir, dass OS X teilweise Wochen bis Monate (und im Falle von Java 6 sogar Jahre) auf neue Versionen warten musste. Das wäre bei Sun nicht passiert.
  • Schön, denn die interne Grundlage mit dem jüngsten Update sorgt dafür, dass OS X‘ Java-Framework-Komponente erst wirklich Non-Apple-JVM-tauglich ist.

Wirklich schlimm wäre es nur, wenn sich entweder keiner für eine (offizielle) Weiterentwicklung zur Verfügung stellt oder die einzige Weiterentwicklung nur mit X11 funktioniert. Beides würde dann tatsächlich das Aus bedeuten. Diese beiden Alternativen halte ich jedoch für unwahrscheinlich.

Zum Thema auch folgender Beitrag. Man beachte die beiden Kommentare von Ottinger und Gosling.

Und auch der hier, mit dem guten Hinweis auf die Apple Extension, die natürlich „in Gefahr“ sein könnte. Für diejenigen, die die überhaupt benötigen.

[Howto] Maven: Wie man eine ausführbare Jar in eine Java-Webanwendung (War) via Webstart integriert.

Die Situation

Es bestehen zwei lauffähige, fertige Projekte in Maven, welche beide auch vollwertige Artefakte bilden können.

Zum einen die Webanwendung — nennen wir sie hier mal webportal — mit einem WAR-Artefakt, etwa für einen Tomcat. Ob das Artefakt als Snapshot oder Release gemacht wird, ob es nur generiert oder auch in ein Repository deployt wird, ist hierbei nicht von weiterer Bedeutung.

Zum anderen die normale Clientanwendung — nennen wir sie doch einfach userclient — mit einem JAR-Artefakt. Wichtig ist natürlich, dass hier eine startfähige Main-Klasse vorhanden ist. Dies sollte jedoch der Regelfall sein, daher nur der formale Hinweis.

Zusammengefasst, und der Auftrag an dieses Howto ist also: Wie konfiguriert und erweitert man den Buildzyklus in welchem Projekt an welcher Stelle am geschicktesten, um auf einfachem Wege die JAR-Datei userclient in die Webanwendung webportal als Java Webstart zu integrieren. Dabei ist es hilfreich, wenn man via pom.xml (und natürlich der Macht der Properties) die Versionen spezifizieren kann. Der gesamte Prozess von auswählen, signieren und ausliefern soll dabei automatisch und ohne weiteres Eingreifen des Entwicklers geschehen können. Idealerweise sollte das ganze auch optional sein — dazu gibt es dann mehr unter „Optimierungen und Verbesserungen“.

„[Howto] Maven: Wie man eine ausführbare Jar in eine Java-Webanwendung (War) via Webstart integriert.“ weiterlesen

Howto: Ein firmeneigenes Java-Maven-Repository aufsetzen

Für diesen Artikel setze ich jetzt einfach mal voraus, dass die Begriffe und Technologien hinter Java, Maven, Repository, Eclipse und Tomcat bekannt und geläufig sind. Und nein, jeweiliger Profi muss man zum Verständnis nicht sein.

Was wollen wir?

An ein firmeneigenes Repository — oder auch: corporate repository — gelten besondere Anforderungen. Diese können bei einem „einfach privat-eigenen“ verändert werden, aber man kommt meistens auf folgende Punkte:

  1. Ein Repository soll den Entwicklern zum Deployen der eigenen Artifakte und Module zur Verfügung gestellt werden. Wahlweise übernimmt dies auch ein automatischer Build-Agent wie Hudson, TeamCity und haste-nicht-gesehen.
  2. Ein Repositorymanager soll als proxy fungieren. In einer Firma spart dies nicht nur einfache Bandbreite. Da ein solcher Manager in der Regel im lokalen Netz steht, sind die Interaktionszeiten um ein Vielfaches besser.
  3. Ein so genanntes third party pepository für die Abhängigkeiten, die unbedingt notwendig sind und wovon es keine Maven-Abhängigkeiten gibt.

Für Punkt zwei spricht auch eine wesentlich einfachere Konfiguration der Clients (Entwicklerprofile), da nur noch ein Repository eingetragen werden muss. Alle „bekannten“ Repositories werden zentral gebündelt, damit schwindet natürlich gleichzeitig die „Freiheit“ des einzelnen Entwicklers, andere „unbekannte“ Repositories zu verwenden. Dies ist jedoch vernachlässigbar, weil: Diese „Freiheit“ schränkt im Endeffekt den Buildprozess und auch die Wiederverwendbarkeit ein. Das Hinzufügen von weiteren Repositories in die POM ist aus Gründen der Versionisierung und Nachhaltigkeit auch keine optimale Lösung. Dennoch, alles nur eine Sache der Konfigurations der Clients.

Was brauchen wir?

Es gibt eine Reihe von Repository-Managern, die allesamt viel können. Die Wahl auf Nexus fällt hier aus folgenden Gründen:

  • der Footprint ist mit 30 Megabyte wesentlich kleiner als bspw. Artifactory
  • die interne Verzeichnisstruktur entspricht mehr oder weniger 1:1 der realen Organisationsstruktur eines Maven-Repositorys (im Vergleich: Artifactory speichert ein eigenes Datenbankstruktur ähnliches Layout)
  • Nexus und das Eclipse-Plugin m2eclipse sind vom gleichen Hersteller Sonatype und ergänzen sich; nach dem Umstellen bemerkt der Entwickler keinen Unterschied in der Suche, Auto-Discovery, o.ä.

Eine umfangreiche Online-Dokumentation ist auf der Nexus-Seite zu finden.

Installation

Nexus wird unter anderem als WAR ausgeliefert, insofern die Installation in einen Tomcat ein leichtes ist. Beachtenswert ist dabei nur, dass Nexus ein Verzeichnis ~/sonatype-work erstellt. Da sich dort unter Umständen viele Nutzdaten ansammeln, kann ein Verschieben (symbolischer Link?) nicht verkehrt sein. Da sich mit der Laufe der Zeit einiges an Daten ansammeln kann, sollte der Platz nicht zu sparsam vermessen sein.

Umfang

Nachdem Tomcat bzw. Nexus gestartet ist, kann man sich mit dem Default-Daten admin/admin123 anmelden (analog mit den Daten der anderen beiden Benutzern!).

Nexus kommt bereits mit einer Reihen von vorkonfigurierten, eigenen hosted repositories einher.

  • releases sammelt alle Release-Artifakte der Firma
  • snapshots sammelt alle Snapshot-Artifakte der Firma
  • third-party sammelt alle Release-Artifakte externer Quellen, wofür es keine Maven-Repositories gibt (oder wo man jenes Repository nicht generell zur Verfügung stellen will), gutes Beispiel ist ein (aktueller) Oracle-JDBC-Treiber

Daneben gibt es so genannte proxy repositories, die praktisch gesehen nur aus einem Index bestehen. Wie ein Proxy hängen sie sich zwischen dem Client und dem tatsächlichen Repository und cachen alle Artifakte lokal. Selbst der Index ist mehrere Megabytes groß, das sollte man nicht vernachlässigen. Voreingetragene proxy repositories sind Apache Snapshots, Codehaus Snapshots, Central Maven Repository (Maven1/Maven2-Repository-Konverter sind in Nexus vorhanden a.k.a. virtual repositories).

Die grouped repositories sind auch rein virtuelle Gruppierungen von verschiedenen Repositories. Das Standard Repository „Public“ ist in der einfachsten Konfiguration eine Sammlung aller (aktivierten) Repositories auf dem Manager — also sowohl der externen Spiegel, der Third-Parties als auch den eigenen Artifakten.

Konfiguration

Die Aktivierung und Verwaltung von (neuen) Repositories ist abhängig der eigenen Bedürfnisse. Meistens sinnvoll ist es jedoch, bei den drei großen Spiegeln (proxy repositories) in dem Konfigurationstab das Indizieren (Download Remote Indexes) zu aktivieren. Je nach Belieben kann man in der Gruppe Administration auch das Aktualisierungsverhalten steuern.

Konfiguration: Deployment

Um ein Deployment zu gewährleisten, muss man nebst Kenntnis der Repository-URL (eben die URL) nur wissen, ob der anonyme Zugriff erlaubt sein soll, oder ob man einen Deployment-Benutzer einrichten und nutzen willst. Falls eine Richtlinie vorschreiben sollte, dass dies nur ein Build-Agent machen darf, ist ein (geheimes) Passwort oder Schlüssel unabdingbar.

Konfiguration: Client

Nehmen wir an, der Nexus-Manager ist auf dem Host 192.168.0.10:8080/nexus installiert. In der einfachen Installation und Konfiguration sammeln sich im public repository praktisch alle relevanten Artifakte (sowohl Releases als auch Snapshots).

<settings>
<mirrors>
<mirror>
<id>corporate</id>
<name>Corporate Repository</name>
<url>http://192.168.0.10:8080/nexus/content/groups/public</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>

Der Deployment-User benötigt ggf. Zugangsdaten für das entsprechende Repository.

Ein Client wie m2eclipse sollte danach einen kompletten Rebuild des Index machen.

Eclipse 3.6: Ersteindruck

Der dritte Release Candidate von Eclipse 3.6 alias Helios wurde vor wenigen Tagen bereitgestellt – man kann wohl davon ausgehen, das dem baldigen Release Juni/Juli nicht mehr viel im Weg steht.

Damit reiht sich nun ein weiterer Jupitermond in die Reihe der 3.x-Releases ein: besser, umfangreicher, aktueller.

Auf dem ersten Blick sieht Eclipse wie immer aus. Zwar wurde der Welcome-Screen, wie immer, in Punkto Design und Content geändert.. aber ansonsten ist alles wie gehabt.

Zwecks des kleinen Reviews habe ich die 64-Bit-Version (Cocoa) für Mac OS X heruntergeladen; da ich aber auch noch eine Reihe von Plugins benötige, diese dann später nachinstalliert. Prinzipiell hat sich der Updatemanager zumindestens optisch nicht zum Vorgänger geändert. Immerhin: Mit schätzungsweise 50 Einzelplugins ist der Installationsvorgang erstmalig in meiner persönlichen Eclipse-Geschichte problemlos von statten gegangen – sonst hat es immer irgendwo ein bisschen geknallt. Manchmal mit, meist ohne Auswirkungen. Unter anderem war auch ein SVN Connector Plugin dabei.

Es sieht so aus, als wäre in Eclipse kein VCS-Plugin (mehr) enthalten, vielmehr wird dies durch den Connector (ggf. bekannt aus 3.5) bekannt. Nach dem obligatorischen Neustart wird man sofort vom Connector-Wizard begrüßt und kann zwischen verschiedenen HavaHL und SVNKit-Versionen wählen. Ausgewählt, installiert, neugestartet. Fertig. Import, From SVN.. und voilá. Projekt ist drin. Interessant: Neuerdings wird man informiert, dass man gespeicherte Passwörter (SVN-Dialog) mittels Passphrasen absichern kann.

Die kleinen Details

"Eigenschaften" in Galileo
"Eigenschaften" in Helios

Ich habe mich im Vorfeld noch nicht mit den Änderungen und Neuerungen von Helios auseinandergesetzt, daher stochere ich etwas herum. Augenfällige Änderung: Die Eigenschaftsfenster einer Dateiressource wurden um eine detaillierte Rechte-Verwaltung erweitert.

Resources / Syncing

Aha… Resources können jetzt endlich gefiltert in einem Projekt eingefügt werden. Soll heißen: Man kann nicht nur weitere Resourcen dazu linken sondern auch explizit welche ausschließen. Yes!

Der Produktiveinsatz in den nächsten Wochen wird zeigen, was es sonst noch gibt.

Java Reflection API – mit Cache, ey!

Einführung in Reflection

Die Java Reflection API ist Bestandteil des JDK und ermöglicht den Zugriff auf Methoden, Felder und Annotationen von Klassen und Objekte, also den Instanzen von Klassen. Mit Ausnahme von Annotationen geschieht der Zugriff immer auf einer Meta-Ebene.

Mit einem einfachen Beispiel ist diese Meta-Ebene erklärt.

class Foo {
private String bar = "private value";
}

Stellen wir uns vor, wir wollen ganz exemplarisch von dem Objekt foo (foo = new Foo())den Inhalt von dem Feld bar erhalten. Auf dem üblichen Weg ist das nicht möglich, da das Feld ein privates ist.

Über den zugehörigen Klassentyp (via getClass()) stellt Java eine Reihe von Methoden aus dem Reflection-Paket zur Verfügung, u.a. auch Field getDeclaredField(String). Ein declared field ist ein Instanzattribut eines Objektes, während ein field (Field getField(String)) statische Felder finden.

Field field = foo.getClass().getDeclaredField('bar');

Die Variable field enthält aber – wie man dem Typ entnehmen kann – aber nicht den Inhalt von bar, sondern ein Field-Objekt. Um an den Inhalt zu kommen, verwendet man die Methode get(Object). Der Parameter bezeichnet den entsprechenden Kontext, also das Objekt foo von weiter oben.

Object value = field.get(foo);

Das bedeutet im Klartext: Auch für eine weitere Instanz der Klasse Foo (etwa foo2) kann dieses Field für dieses Feld verwendet werden. Es beinhaltet nur die Metainformationen, nicht die eigentlichen Inhalte.

Dabei ist jedoch zu beachten, dass der SecurityManager aktiv werden kann; in diesem Falle wird eine Exception bei get(Object) geschmissen, weil das Feld nicht sichtbar ist.

field.setAccessible(true);

schafft dabei Abhilfe.

Und Methoden?

Im Prinzip funktioniert das Schema bei Methoden genauso – aber bevor ich jetzt einen Radio Eriwan-Witz loslasse…

Im Gegensatz zu den vorherigen Feldern werden Methoden nicht nur über den Namen, sondern auch über die Parametertypen identifiziert (daher können Methoden sich vom Namen her in Java auch „überladen“).

class Bar {
private String name;
String getName() {
return name;
}
String getName(String defaultName) {
return (name == null) ? defaultName : name;
}
}

Die Metainformationen werden entsprechenderweise geladen:

Method method1 = bar.getClass().getDeclaredMethod("getName");
Method method2 = bar.getClass().getDeclaredMethod("getName", String.class);

Selbstverständlich ist bei Kenntnisnahme der Klasse auch folgende Zeile gleichwertend:

Method method1 = Bar.class.getDeclaredMethod("getName");

Methoden haben im Gegensatz zu Feldern keinen „Inhalt“, sondern sind eine Aktivität oder Operation — und zwar auf einem Objekt. Dementsprechend funktioniert das Ausführen mittels der Method Object invoke(Object, Object…args):

Object result1 = method1.invoke(bar); // getName()
Object result2 = method2.invoke(bar, "default name"); // getName("defaultName")

Und Annotationen?

Annotationen sind „Anmerkungen“ im Quellcode, und seit Java 5 fester Bestandteil des JDKs. Technisch gesehen sind Annotationen besondere Klassen mit eingeschränkten (Java-) Möglichkeiten, deren Instanz auf einem Meta-Attribut wie Klasse, Feld, Methode oder Methodenparameter „hängen“. Mit anderen Worten bedeutet dass, dass man mittels Reflection tatsächlich eine Instanz einer Annotation erhält — allerdings mittels einem Proxy (aus der Java API).

@Entity
class Account {
@Column(name="id_x")
private Long id;
}

Die Methode getAnnotation(Class) existiert in allen Typen: Class, Field und Method.

Account account = new Account();
Entity annotation1 = Account.class.getAnnotation(Entity.class).annotationType();
Column annotation2 = Account.class.getDeclaredField("id").getAnnotation(Column.class).annotationType();
Assert.assertEquals("id_x", annotation2.name());

Performance

Im Regelfall benötigt man keinen Zugriff auf Reflection und sollte es vermeiden. Nichtsdestotrotz gestalten sich Konfigurationen, welche auf Basis von Annotationen, meist als sehr einfach, simpel und vor allem Code-konzentriert. Das JPA-Mapping (etwa mit Hilfe von Hibernate) gestaltet sich via Annotationen wesentlich einfacher, schneller und schlanker als mit XML.

Jeder Zugriff über die Reflection API kostet Zeit. Für Konfigurationen und Pläne ist das meist nebensächlich; die JPA-Konfiguration wird beim Start der Applikation eingelesen, analysiert und gespeichert. Nebenbei profitiert das natürlich von der anfänglichen „Warmup-Phase“.

Besteht jedoch ein Access on Demand, so sollte man sich Gedanken um eine geeignete Cachestruktur machen. Wichtig ist dabei, die Metadaten von den eigentlichen Inhalten zu trennen.

Cachen & Lösungsansätze

Jeder der oben genannten Reflection-Getter gibt es auch jeweils eine getAll-Variante: Class.getDeclaredFields(),Class.getDeclaredMethods(), Class.getAnnotations(), Field.getAnnotations() und Method.getAnnotations().

Mit einem einfachen Algorithmus kann man die Laufzeit drastisch senken, wenn Objekte des gleichen Typs mittels Reflection untersucht werden.

public class CacheReflectionUtil {
// cache of declared fields of class types
private final Map<Class<?>, Field[]> classDeclaredFields = new HashMap<>();
// Return the declared fields of the given class type.
public Field[] getDeclaredFields(Class<?> type) {
Field[] fields = classDeclaredFields.get(type);
if (fields == null) {
fields = type.getDeclaredFields();
classDeclaredFields.put(type, fields);
ensureAccessibility(fields);
}
return fields;
}
// Ensure that the given fields are accessible.
public void ensureAccessibility(Field[] fields) {
for (Field field : fields) {
// setAccessible will not only set a property but invoke SecurityManager stuff
if (field.isAccessible()) {
field.setAccessible(true);
}
}
}
// Return the declared field of the given class type.
public Field getDeclaredField(Class<?> type, String name) {
for (Field field : getDeclaredFields(type)) {
if (field.getName().equals(name)) {
return field;
}
}
return null;
}
}

Um einen stetigen Speicherverbrauch zu verhindern, empfiehlt sich das Nutzen einer Least-Recently-Used-Struktur. Das Apache-Commons-Paket bietet dies etwa mit der LRUMap an.

Context Affair

Bei Spring ist die globale Einheit in der Konfiguration der so genannte ApplicationContext. Dieser Context ist etwa ein Container im Applicationserver (beispielsweise web.xml). Soweit so gut.

Tatsächlich ist der WebApplicationContext eine Spezialisierung des oben genannten ApplicationContext — und zuständig für Webanwendungen. Da ein Servlet die Steuerung im Applicationserver übernimmt, benötigt der Spring-Context ein DispatcherServlet. Damit ist die „Verbindung“ User->Server->Spring geschaffen. Soweit so gut.

Konfiguriert man das DispatcherServlet etwa – sinnigerweise – mit dem Namen“dispatcher“, dann sucht Spring standardgemäß nach einer dispatcher-servlet.xml. Diese XML-Konfigurationsdatei kann ähnlich der applicationContext.xml (u.ä.) ganz normale Bean-Konfigurationen enthalten. Interessant ist dabei, dass dabei ein zusätzlicher ServletContext erstellt wird. Das hat zwei Auswirkungen:

  1. Beans aus dem ServletContext sind nicht im ApplicationContext verfügbar
  2. Annotations-gestützte Konfigurationen müssen jeweilse in beiden Contexten konfiguriert, also aktiviert, werden

Seltsames Hibernate/Oracle-Verhalten bei @SequenceGenerator

Situation: Ich habe eine Menge von Entitäten (Oracle-Schema), die mittels der Klassen-Annotation @SequenceGenerator und dann entsprechend der Attribut-Annotation @GeneratedValue eine Sequenz zugeordnet werden.

Problem: Obwohl sich durch die Log zweifelsfrei zeigen lässt, dass Hibernate die Sequenz abfragt und auch den nächsten Wert enthält, ignoriert Hibernate dies beim eigentlichen Einfügen des neuen Tupels. Stattdessen wird wahlweise irgendeine Integerzahl wie 250, 500 oder auch gerne mal 1412 kollisionsfrei verwendet. Auch den eingebauten ID-Generator mit Zahlen jenseits von 99999 wird gerne verwendet, was dann zu netten „Hey, dass passt doch gar nicht in das Feld rein!“-Fehlern führt.

Erst folgende, zusätzliche Einstellung als Eigenschaft für @SequenceGenerator scheint Hibernate dazu zu bewegen, doch die Sequence zu verwenden: allocationSize 1. (impliziert so gesehen die Sequenz).

@SequenceGenerator(name = „LOCAL_GENERATOR“, sequenceName = „SEQ_NAME“, allocationSize = 1)

Dürfte ein Bug sein.

Featurities meets Fallstrick: Die Spring Security 3.0 Konfigurationsodyssey

Mit dem Majorrelease 3.0 wurde dem Modul Spring Security eine Menge von neuen Features angeignet. Spring Security ist die Modulkomposition, welches für das Java Framework Spring quasi die gesamte Authentifizierung, Autorisierung, Legitimierung jedwegiger Art ermöglicht.

Leider wurden mit dem Release 2.x auf 3.0 eine Reihe von API-Changes vollzogen. Zugegeben, die waren auch sicher alle sinnvoll, weil Komponenten wie die Authentifizierung weiter geteilt wurden und man somit wesentlich flexibler ist, neue Anforderungen zu ermöglichen (Baukastenprinzip). Aber die Dokumentation ist – gesamtheitlich betrachtet – irgendwie immer noch mies und oft nicht aktualisiert. Oder man findet im Internet einfach nur (alte) Beispiele.

Die http-Direktive

Im Namespace von Spring Security existiert das Tagelement http, mit welchem man kurze, knappe und verständliche Konfigurationen anlegen kann. Der Vorteil liegt klar auf der Hand: Man muss nicht alle Beans, Listener und Provider anlegen, denn das geschieht automatisch. Tja, wären da nicht ein paar Einschränkungen in der Funktionsvielfalt.

Konkretes Beispiel: Remember Me

Just wurde das Minor-Release 3.0.1 veröffentlicht, und nur wenige Tage später zu erfahren, dass Remember Me kaputt sei. Egal, fahren wir erstmal weiter mit 3.0.0.

Laut Dokumentation ist es am einfachsten, wenn man die Direktive remember-me (Security Namespace) innerhalb der http-Direktive (Security Namespace) verwendet. Ohne irgendeine Angabe wird ein stinknormales, Cookie basiertes Tokenverfahren ohne (echten) privaten Schlüssel verwendet. Reicht für den ersten Einsatz erstmal auf, soll ja erstmal funktionieren.

Fehlermöglichkeit 1a: Man loggt sich ein, und es passiert nichts (kein „RememberMe“-Cookie).
Lösung: Wenn man einen eigenen Auth-Filter einsetzt, muss man diesem auch den RememberMe-Service „setten“. Außerdem muss der SecurityChainFilter (web.xml!) auch auf die login-Seite verweisen. Es dürfen auch keine Filter bei der Konfigurierung von intercepted Urls (speziell hier: login, logout) gemacht werden.

Fehlermöglichkeit 1b: Es passiert noch immer nichts?
Lösung: Vielleicht wurde vergessen, einen Parameternamen für den Request zu setzen. Der Standardname ist ein typischer Springname, der natürlich unschön ist. Und den kann man nicht über die RememberMe-Direktive setzen, also muss man eh einen eigenen Service definieren. Bäm. Referenzierung geht dann zwar noch, aber für mehr ist die RememberMe-Direktive dann nicht mehr zu gebrauchen.

Fehlermöglichkeit 2a: Man besucht die Seite ohne Login, aber mit Cookie – und die Loginseite kommt (Log sagt kein gültiger Auth).
Lösung: Man kann der Log trauen, wenn sie zwar beim Einloggen nun einen Token ablegt (kann man zum Beispiel sehr einfach mit diesem Firefox-Addon inkl. Editor(!) verifizieren), dieses aber beim erneuten Besuchen der Seite (bzw. ohne JSPSESSION-Cookie) nicht verwendet bzw. wird nicht erkannt. Schlussendlich half u.a. das Umbenennen der Userservices-Bean in „userService“. Außerdem sollte die Loginseite keinen Filter/Access haben (s.o.) Lieber 2x prüfen!

Fehlermöglichkeit 2b: Es erscheint eine Ausnahme, dass der Key falsch sei.
Lösung: Dazu muss man wissen: Sobald man eine individualisierte RememberMe-Konfiguration nutzt, wird auch der Key nicht mehr vernünftig auf alle Komponenten (Provider, Filter, Manager) gesetzt. Beim Anlegen wird also der eigene Key verwendet, beim Auslesen der Standardkey. Yes! (s.o.)

Fehlermöglichkeit 3: Man besucht die Seite ohne Login, aber mit Cookie – aber wie in 2 nur die Loginseite.
Lösung: Im Logger/Debugger kann man nun feststellen, dass zwar das Cookie gefunden wurde, das Token gefunden und validiert wurde aber dann keine Rechte existieren – aha? Wahrscheinlich fehlt im Provider noch ein zusätzliches Setting der Komponenten. Am besten von RememberMe Service/Filter/Provider jeweils alle möglichen Properties durchgehen. Jaja, wie gesagt.. 😉

Fehlermöglichkeit 4: Das Ausloggen (beispielsweise logout.html) hat nach Aktivierung von Rememberme plötzlich keine Auswirkungen mehr.
Lösung: Zwar wird die Seite gefunden, aber es wird kein „Logout“ gemacht. Auch hier sollte man prüfen, ob ein SecurityChainFilter (web.xml) auch für die logout-Seite greift.

Fehlermöglichkeit 5: Das Besuchen der Seite wirft einen Fehler (ggf. „mit weißer Seite“), dass keine neue Session erstellt werden kann.
Lösung: Richtig, nach einem Request ist ja dann auch zu spät. Das Attribut create-session in der Direktive http sollte daher auf „ifRequeried“ gestellt sein.

Fazit:

  • SecurityChainFilter immer prüfen
  • Intercepted Urls prüfen
  • RememberMe-Direktive innerhalb der http-Direktive ist quasi abgesehen von der services-ref unbrauchbar.

Anmerkung:

Natürlich kann man sich das Problem mit den SecurityChains vom Hals schaffen, indem man stupide ein /* filtert. Das hat jedoch zur Auswirkung, das Spring Security auch jeden verdammten Request anguckt; bei zusätzlichen (statischen) Inhalten wie Javascript, Stylesheets, Bildern, Flash u.ä. ist das ein Overhead, der unnötig ist.