Stolpersteine unter Java 9, Teil 1

Erstes Posting einer Reihe, die hoffentlich nicht allzu lang wird.

Java 9 ist gestern tatsächlich offiziell erschienen. Im Prinzip sind Jameica und Hibiscus bereits auf dieser Java-Version lauffähig. Leider hat Oracle aber ein paar Packages von der SE-Version in die EE-Version verschoben (keine Ahnung, ob das bei den ersten Betas auch schon so war). Das betrifft u.a. "javax.xml.bind" aber auch "javax.annotation" (dort ist z.Bsp. die Annotation "@Resource" enthalten). Die Packages sind zwar im JDK9 selbst enthalten, werden aber nicht mehr per Default zum Classpath hinzugefügt. Stattdessen liegen sie in Form von Modulen vor, die beim Start explizit angegeben werden müssen.

Wenn man Klassen aus den oben genannten Packages verwendet und jetzt beim Start seines Programmes unter Java 9 z.Bsp. die Fehlermeldung "java.lang.NoClassDefFoundError: javax/annotation/Resource" erhält, dann kann man den Programm-Aufruf in etwa so ergänzen:
java --add-modules=java.se.ee ....
Leider hat das einen unschönen Nebeneffekt. Der Parameter "--add-modules" existiert erst seit Java 9. Java 8 und ältere Versionen ignorieren ihn jedoch nicht einfach sondern brechen mit folgender Fehlermeldung ab:
Unrecognized option: --add-modules=java.se.ee
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Findet der Aufruf z.Bsp. aus einem Shell-/Batch-Script heraus statt, dann müsste man eigentlich ein Script für Java 9 und eines für Java 8 erstellen. Sehr unpraktisch.

Glücklicherweise gibt es einen Workaround:

Mit Java 9 wurde eine neue Umgebungsvariable "JDK_JAVA_OPTIONS" eingeführt, in der man JVM-Parameter platzieren kann. Diese Umgebungsvariable wird von älteren Java-Versionen ignoriert. Damit kann man sein Shell-Script so ergänzen, dass vor dem eigentlichen Programmstart die Umgebungsvariable gesetzt wird:
export JDK_JAVA_OPTIONS='--add-modules=java.se.ee'
Der eigentliche Programmaufruf im Anschluss kann dann unverändert bleiben. Das Script funktioniert damit sowohl unter Java 9 als auch mit älteren Java-Versionen.

Siehe auch http://www.oracle.com/technetwork/java/javase/9-new-features-3745613.html#JDK-8170832

Eclipse: Variable Parameter in Debug-Konfiguration

Eigentlich gäbe es viel öfter interessante Dinge, über die ich hier im Blog schreiben könnte - meist sind es nützliche Tipps oder verzwickte Probleme, deren Lösung mich einige Zeit gekostet hat. Aber irgendwie denke ich dann oft einfach nicht dran, das hier zu posten. Aber heute hat es mal geklappt ;)

Und zwar: Wenn man in Eclipse eine Launch-/Debug-Konfiguration oft mit unterschiedlichen Programm- oder VM-Parametern startet, dann ist es ziemlich nervig, die vor dem Start immer erst bearbeiten zu müssen, um den Parameter auf den gewünschten Wert zu setzen. Dabei lässt sich das sehr komfortabel mit Prompts lösen. Eclipse fragt dann vorm Start nach den gewünschten Werten.
"Eclipse: Variable Parameter in Debug-Konfiguration" vollständig lesen

Java: URL#equals() ist gefährlich

java.net.URL implementiert equals(Object). Also was liegt näher, als URLs damit zu vergleichen? Gefährlich.
  public static void main(String[] args) throws Exception
  {
    URL u1 = new URL("http://www.willuhn.de");
    URL u2 = new URL("http://www.jameica.org");
    
    System.out.println(u1.equals(u2));
  }
Ausgabe:
true
Die beiden URLs verweisen zwar per DNS auf die selbe IP. Per VirtualHost-Konfiguration werden jedoch unterschiedliche Webseiten angezeigt. Weder ist die URL identisch, noch der Inhalt der Webseite. Dennoch liefert der Vergleich "true". Wegen gleichem Protokoll, Port und Pfad sowie gleicher IP. Im Javadoc findet sich hierzu auch:
Two URL objects are equal if they have the same protocol, reference equivalent hosts, have the same port number on the host, and the same file and fragment of the file.
Two hosts are considered equivalent if both host names can be resolved into the same IP addresses [...]
Note: The defined behavior for equals is known to be inconsistent with virtual hosting in HTTP.
Im letzten Satz findet sich auch ein entsprechender Hinweis, den man schnell überlesen kann.

Hinzu kommt noch, dass java.net.URLStreamHandler (übernimmt den eigentlichen Vergleich in der "equals"-Methode) für das DNS-Resolve InetAddress.getByName(host) verwendet. Welches so implementiert ist:
return InetAddress.getAllByName(host)[0];
Da wird also pauschal die erste gefundene IP-Adresse zu dem Host geliefert. Hat man nun aber eine Lastverteilung per DNS Round Robin für die Domain eingerichtet, dann ist der Rückgabewert von "equals" noch nicht mal deterministisch. Je nachdem, welche IP gerade zufällig zurückkommt, liefert der Vergleich manchmal true und manchmal false.

Beim Vergleich typischer HTTP-URLs von Webseiten ist es daher meiner Meinung nach sicherer, einfach einen String-Vergleich zu machen. Das ist zwar auch nicht 100%-ig sicher, dafür aber aber pragmatischer, schneller (weil kein DNS-Lookup nötig ist) und nicht so tückisch.

Eclipse: Class-Dateien in "Open Resource" ausblenden

Heute mal something completely different. Wer in Eclipse regelmäßig den Shortcut <CTRL><SHIFT><R> für "Open Resource" verwendet, der war sicher auch schon genervt, dass dort im Such-Ergebnis gern mal Dateien auftauchen, die da nichts zu suchen haben. Allen voran class-Files. Oder noch fieser: Properties- oder Config-Dateien aus den Build-Ordnern. Mir ist es schon regelmäßig passiert, dass ich Änderungen an so einer Datei vorgenommen habe, mich dann wunderte, warum die nicht wirksam wurde, sicherheitshalber noch ein "Project»Clean..." machte. Um dann festzustellen, dass ich versehentlich die Properties-Datei aus dem Build-Ordner geändert habe - die beim Rebuild natürlich überbügelt wird.

Das lässt sich abstellen. Man kann unerwünschte Ressourcen in dem Such-Dialog ausblenden: Erst im Dialog selbst oben die Option "Show Derived Resources" deaktivieren (ist meist eh schon deaktiviert).


Anschließend aber noch die Properties der auszuschließenden Ordner öffnen (per Rechtsklick) und dort das Attribut "Derived" aktivieren, um Eclipse bekannt zu machen, dass der Ordner generierte oder kopierte Dateien enthält.

Gefunden - natürlich - bei Stackoverflow.

PS: In der Ergebnis-Liste der File-Search via <CTRL><H> werden diese Dateien dann übrigens auch nicht mehr angezeigt - wirklich sehr sinnvoll.

Java-FAQ

Weil es immer wieder Missverständnisse um Java 7 in Bezug auf Hibiscus gibt sowie ein schon fast panikhaftes Verhalten wegen den in letzter Zeit veröffentlichten Sicherheitslücken in Java, habe ich im Wiki mal eine FAQ speziell zu Java-Fragen angelegt. Ich hoffe, das hilft.

Berlios macht Ende des Jahres zu

Sehr, sehr schade. Berlios - die Opensource-Hosting-Plattform, auf der ich den Quellcode von Jameica und Hibiscus sowie die Wikis habe - wird am 31.12. dicht machen. Den Quellcode kann ich zwar auch bei Sourceforge oder auf meinem eigenen Server hosten. Problematisch sind aber die beiden Wikis von Jameica und Hibiscus. Im ganzen Web verstreut finden sich Links zu den Tutorials und FAQs der Wikis, die dann allesamt ins Leere zeigen werden. Wenn die bei Berlios die Server runterfahren, hab ich noch nichtmal die Möglichkeit, Redirects einzurichten. Daher werde ich sowohl Quellcode als auch die Wikis künftig voraussichtlich auf meinem eigenen Server hosten. Denn wenn ich das jetzt alles zu Sourceforge schiebe, machen die in 'nem Jahr bestimmt auch zu ;)

Kein Java 7 für Jameica, Hibiscus & Co.

Wenn ihr Hibiscus verwendet, aktualisiert bitte noch nicht auf das neue Java 7. Unter Umständen wird dann beim Aufbau der Verbindung zur Bank die Fehlermeldung "Certificates does not conform to algorithm constraints" auftreten. Thomas hat das Problem in seinem Blog mal analysiert und ist zu dem Schluss gekommen, dass es durch SSL-Zertifikate ausgelöst wird, die veraltete Algorithmen verwenden. Leider sind solche aber noch bei vielen Banken im Einsatz. Da mir derzeit kein anderer Workaround bekannt ist, bleibt im Moment nur (falls eure Bank hiervon betroffen ist): Installiert nicht Java 7 sondern nutzt erstmal weiter Java 6.

Eclipse History-Fenster plötzlich leer

Das CVS-History-Fenster unter "Compare with>History..." war plötzlich leer. Wo sind die alten Versionen hin? "cvs log" in der Shell funktioniert. Projekt geschlossen, neu geöffnet. Eclipse beendet, neu gestartet. Nichts half. Keinerlei Fehlermeldungen. Och nö, ich will den Eclipse-Workspace nicht wegwerfen. Also etwas googlen. Im Eclipse-Forum werde ich fündig. Das Löschen der Datei
.metadata/.plugins/org.eclipse.team.cvs.ui/dialog _settings.xml
im Workspace half. Und es war tatsächlich ein Bug in Eclipse - #293551 / #313480.

Schmerzen mit Navision

Wir sind gerade mit einer Navision-Anbindung beschäftigt (ich weiss, das Ding heisst jetzt wohl "Dynamics NAV" - ist mir aber egal). Unter anderem auch via SOAP. Und das ist alles so furchtbar - ich weiss gar nicht, wo ich anfangen soll. Nachdem ich funktionierende WSDL-URLs gekriegt hab (was eine ganze Weile gedauert hat), dachte ich, dass der Rest nur noch Fleissarbeit ist. Pustekuchen.
"Schmerzen mit Navision" vollständig lesen

Generische Service-Instanz typsicher erzeugen

Notiz für mich, damit ich beim nächsten Mal nicht wieder rumprobieren muss ("Service" ist eine beliebige Klasse, von dem der Service abgeleitet sein muss):
public <T extends Service> T create(Class<? extends Service> c)
  throws Exception
{
  T s = (T) c.newInstance();
  // init stuff
  return s;
}

SUN nervt mit JDK 5.0

Versucht mal, ein SUN JDK 5.0 herunterzuladen. Aussichtslos. Entweder man wird auf eine Zwangsregistrierungsseite weitergeleitet, wo man eine eMail-Adresse angeben muss. Angeblich kriegt man darüber dann den Download-Link (vorher muss man vermutlich schriftlich begründen, wofür man diese alte Version denn brauche). Bis heute ist keine Mail eingetroffen. Oder aber der Server ist - wie bei SUN nicht unüblich - mal wieder nicht zu erreichen. Ich hab Projekte, in denen Java5-Features genutzt werden und beim Kunden ist nun mal diese Version installiert. Also will ich damit auch testen können. Wenigstens hab ich auf https://jdk-distros.dev.java.net/developer.html direkte Download-Links gefunden. Obwohl die eigentlich für Distributoren sind.

Was soll dieser Unsinn?

Update: dct.sun.com liefert nach wie vor ein Timeout. Und in der Distributor-Version fehlt die tools.jar. Eine Glanzleistung, SUN. Ich lade mir die Java-Version jetzt via scp vom Kundenserver. Und komm mir vor wie ein Schwarzkopierer. Muss man sich veraltete Java-Versionen jetzt von Rapidshare holen?

Messwerte speichern ohne zu ertrinken

Wie archiviert man Messwerte (Temperaturen, Zugriffszahlen, System-Auslastung) ohne dabei im Laufe der Zeit die Datenbank zu fluten? Bei meinem aktuellen Bastelprojekt werden die Temperatur-Messungen von 13 Sensoren im 5-Minuten-Takt gespeichert. Das ergibt 3.744 Messwerte. Täglich. Bereits nach einem Jahr enthielte die Datenbank über 1,3 Mio. Datensätze. Die Daten sollen aber über mehrere Jahre gespeichert werden, um auch grosse Zeiträume miteinander vergleichen zu können. War der letzte Winter beispielsweise strenger als der aktuelle? Falls noch weitere Sensoren hinzukommen, könnte mein Stubenserver schnell an seine Leistungsgrenzen stossen.
"Messwerte speichern ohne zu ertrinken" vollständig lesen

Merkwürdigkeiten von JAXB 2.0

Gegeben sei ein Schema "schema.xsd":
<?xml version="1.0" encoding="ISO-8859-1" ?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="test">
    <xsd:complexType>
      <xsd:attribute name="value" type="xsd:integer" use="optional"/>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>
Wir generieren nun mit "xjc" (SUN Java 1.6) die passenden JAXB(2.0)-Beans:
xjc -d src -p generated schema.xsd
Die folgende Datei "test.xml" soll eingelesen werden:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<test value=""></test>
Das Attribut "value" besitzt einen Leerstring als Wert. Das ist eigentlich nicht korrekt - aber leider nicht änderbar, da das Backend die Daten so liefert. Die Deklaration 'use="optional"' im Schema bedeutet lediglich, dass das Attribut fehlen darf. Wenn es jedoch vorhanden ist, muss es einen gültigen Wert haben. Wir wollen das Attribut auch nicht als "xsd:string" deklarieren, weil wir sonst ein unschönes "Integer.parseInt(String)" machen müssten. Nützt also nichts. Das müssen wir so hinnehmen.

Wir lesen die Test-Datei ein:
JAXBContext ctx = JAXBContext.newInstance(generated.ObjectFactory.class);
Unmarshaller m  = ctx.createUnmarshaller();
generated.Test t = (generated.Test) m.unmarshal(new File("test.xml"));
System.out.println(t.getValue());
Der Vorgang schlägt fehl mit:
java.lang.NumberFormatException: Zero length BigInteger
"Merkwürdigkeiten von JAXB 2.0" vollständig lesen

Erste (eigene) Daten von der Heizung

Mein Güte, war das ein Kampf. Die meisten Probleme hat unerwartet javax.comm (das ist die Java-API für den Zugriff auf serielle Schnittstellen) gemacht. Das Ding warf mir dauernd die tolle IOException "Not all params are supported by kernel" um die Ohren. Das Problem ist offensichtlich bekannt. Die dort im Forum geposteten Workarounds riechen allesamt nach gruseligen Race-Conditions in der SUN-Implementierung. Ohnehin scheint sich SUN nicht mehr um diese Bibliothek kümmern zu wollen. Nach einiger Recherche bin ich auf die Alternative RXTX gestossen. Und entgegen der Aussage auf der jamod-Webseite liess sich das modbus-Teil auch damit compilieren (Parameter "build.serial.gnu=true" in build.properties gesetzt und den ganzen Forrest-Kram aus build.xml entfernt). Der Zugriff auf die serielle Schnittstelle funktionierte - die Anlage lieferte Daten zurück. Einen anonymen Byte-Strom. Abgesehen vom aktuellen Datum konnte ich darin nichts erkennen. Mehr oder weniger zufällig hab ich die Daten dann mal mit DataInputStream#readFloat() beackert - in der Hoffnung, dabei Werte zu finden, die wie Temperaturen aussehen. Und tatsächlich tauchten ab Byte 56 eine Hand voll Zahlen zwischen 20 und 50 auf. Schnell ausgedruckt, in den Heizungsraum gerannt und mit den Werten vom Display verglichen. Volltreffer. Die 13 wichtigsten Temperatur-Werte der Anlage. Mehr wollte ich gar nicht ;)

Inzwischen hab ich mir eine erste Version mit Webfrontend gebastelt, die im 5-Minutentakt aktuelle Werte von der Anlage holt und anzeigt. Als nächstes kommt noch eine Seite zum Konfigurieren des seriellen Ports (Device, Baudrate, Parity, etc.). Und dann natürlich ein Archiv-Service, der die Messwerte in eine Datenbank schreibt sowie ein Chart-Renderer, um den zeitlichen Verlauf der Werte grafisch im Webfrontend anzuzeigen.

Ich werd's dann hier auf der Webseite veröffentlichen. Achso - ist natürlich ein Jameica-Plugin ;)

XML-Schema: Element-Gruppen in abgeleiteten Typen

Gar nicht so einfach, hierfür 'nen passenden Titel zu finden. Problemstellung: In einer XML-Schema-Datei sei ein abstrakter complexType definiert:
<xsd:complexType name="abstractSearch" abstract="true">
  <xsd:sequence>
    <xsd:element name="date" ... />
    <xsd:element name="operator"  .. />
  </xsd:sequence>
  ...
</xsd:complexType>

Weiterhin existiert ein konkreter Typ, der davon abgeleitet ist:
<xsd:complexType name="search">
  <xsd:complexContent>
    <xsd:extension base="abstractSearch">
      <xsd:all>
        <xsd:element name="combi" ... />
      </xsd:all>
      <xsd:attribute name="itc" ... />
    </xsd:extension>
  </xsd:complexContent>
</xsd:complexType>

Beim Erzeugen der zugehörigen JAXB-Klassen mittels xjc-Compiler wird diese unverständliche Fehlermeldung entstehen:
cos-all-limited.1.2: An all model group must appear in a particle with
{min occurs} = {max occurs} = 1, and that particle must be part 
of a pair which constitutes the {content type} of a complex type 
definition.

Aja. Nachdem ich stundenlang vergeblich an den "minOccurs"- und "maxOccurs"-Attributen rumgedreht hab, kam mir spontan die Erleuchtung. Im Basis-Typ "abstractSearch" ist eine "sequence" definiert, im abgeleiteten Typ "search" jedoch eine "all"-Gruppe. Der Compiler versucht nun offensichtlich, beide Element-Gruppen zu mergen. Pro Element darf nur eine existieren. Und da es sich um zwei verschiedene Arten handelt, kriegt er genau das nicht auf die Reihe.

Die einfache Lösung lautet daher: Sowohl in "abstractSearch" als auch in "search" die gleiche Art von Gruppe verwenden. Also entweder nur "sequence" oder nur "all". Aber nicht beides.