Joda – “Bis 900 Jahre, wirst aussehen du nicht gut!”

15. Dezember 2009

Mit diesen Worten hat Yoda in Starwars nicht nur eine der traurigsten Szenen aufgelockert, sondern auch gleich – unbeabsichtigt – ein Grundproblem der Javawelt adressiert. Die API für die Zeitverwaltung.

Wann wenn nicht jetzt?

Beim Stöbern in Quellcode bin ich letztens wiedermal über eine lustige Zeile gestoßen:

...
System.currentTimeInMillis() //we can get the time in milliseconds after 1 Jan 1970 at 0:00:00am GMT
...

Auf den ersten Blick harmlos. Was soll da schon passieren? Ich will die Frage umformulieren: Was passiert danach? Mit dem Wert wird in der Regel angefangen zu arbeiten. D.h. Tage werden hinzugefügt, abgezogen um z.B. Startzeitpunkte zu ermitteln. Dabei wurde in dem Code, den ich gesehen habe in etwa folgendes gemacht:

final long now = System.currentTimeInMillis();
final long oneDay = 1000 * 60 * 60 * 24;
final long tomorrow = now + oneDay;
...

Sieht immer noch alles harmlos aus? Dann weiter …

Ein Tag hat 24 Stunden

An einer anderen Stelle wurde ein Vergleich zweier Daten durchgeführt. Hier die anonymisierte Fassung:

final long oneDay = 1000 * 60 * 60 * 24;

final Calendar calendar = Calendar.getInstance();
calendar.set(2009, 9, 24, 10, 0);
final long summerTime = calendar.getTimeInMillis();

final Calendar comparisonCalendar = Calendar.getInstance();
comparisonCalendar.set(2009, 9, 26, 10, 0);
final long winterTime = comparisonCalendar.getTimeInMillis();

final long difference = winterTime - summerTime;

if (difference == 2 * oneDay) {
    ...
}

Immer noch harmlos? Der ein oder andere wird jetzt sagen: klar. Leider steckt der Teufel im Detail. Was meistens vernachlässigt wird ist die europäische (Un)Sitte zwischen Sommer- und Winterzeit zu wechseln oder auch ganz allgemein alle 4 Jahre mal einen Tag im Februar mehr einzuplanen. Hier muss sich dieser Ansatz leider geschlagen geben.

Ich muss mich an dieser Stelle entschuldigen, denn ich habe den Code bereits so abgeändert, dass das Problem der Sommer- und Winterzeit offensichtlich wird. Der Originalcode sieht natürlich etwas anders aus. Aber nun sollte definitiv ersichtlich werden, warum das nicht funktionieren kann. Hier wird mit festen Zeitintervallen (oneDay) gearbeitet. Leider hat der Tag zum Zeitwechsel nicht 24, sondern entweder 23 oder 25 Stunden.

Geht man mit Java-Hausmitteln an dieses Problem landet man dann aber auch schnell in Code-Schnippseln wie dem Folgenden:

...
Calendar calendar = Calendar.getInstance();
Date now = calendar.getTime();
calendar.add(Calendar.HOUR_OF_DAY, 1);

...

oder die Alternative für den Vergleichscode:

...
Calendar calendar = Calendar.getInstance();
calendar.setTime(2009, 9, 24, 10, 0);
calendar.add(Calendar.HOUR_OF_DAY, 2);

Calendar comparisonCalendar = Calendar.getInstance();
comparisonCalendar.setTime(2009, 9, 26, 10, 0);
if (calendar.getTimeInMillis() == comparisonCalendar.getTimeInMillis()) {
...

Damit löst man zwar das Problem mit der Zeitumstellung, aber auf Dauer ist schön was anderes, denn ein weiterer Schwachpunkt der API, aus meiner Sicht, ist die schlechte Lesbarkeit. Denn wer sieht schon auf den ersten Blick, dass hier nicht der September, sondern der Oktober gemeint ist. Ja, man kann auch mit den Calendar-Konstanten arbeiten, aber auch das führt eher zu unnötig langen Codezeilen.

… einer, der der Macht das Gleichgewicht bringen wird …

Eine elegante Lösung hierfür bietet – nein nicht Skywalker, sondern – die Joda-Bibliothek. Diese erlaubt es das Millisecond-Massaker zu umgehen und die Schwächen der Standard-API zu minimieren. So sieht der Code von oben mit Joda etwa so aus:

...
DateTime dt = new DateTime();
DateTime oneDayLater = dt.plusDays(1);
DateTime oneDayEarlier = dt.minusDays(1);
...

Auch Vergleiche wirken wesentlich angenehmer:

final DateTime now = new DateTime();
final DateTime before = now.minusDays(100);
final Days days = Days.daysBetween(before, now);
if (days.getDays() == 2) {
...

“Schneller, leichter, verführerischer”

Auch wenn das nur ein erster Blick auf Joda war, zeigt sich hier schon die Eleganz und Mächtigkeit dieser Bibliothek. Wer sich nicht scheut ein wenig englisch zu lesen, sei für weitere Informationen auf das IBM-Tutorial Joda-Time und die Website http://joda-time.sourceforge.net selbst verwiesen.

Ansonsten, sehen wir uns in 900 Jahren um Mitternacht ;)

final LocalDate localDate = new LocalDate();
final DateTime then = localDate.toDateMidnight().toDateTime().plusYears(900);

Und plötzlich war alles dunkel …

14. November 2009

Ich weiss nicht, was passiert ist, aber nach dem Upgrade von Ubuntu auf 9.10 wurden diverse Entwickler-Tools unbenutzbar. Dabei handelt es sich insbesondere um Anwendungen, die nicht direkt aus dem Ubuntu-Repository stammen. Im Folgenden will ich auf Eclipse und Trac eingehen.

Eclipse 3.5

Nachdem auf GTK+ 2.18 aktualisiert wurde, funktionieren diverse Buttons in Eclipse nicht mehr. Schnell wird man dank Google auf der Suche nach einem Fix bzw. Workaround fündig (Migrating to client-side windows). Offensichtlich ist die Umgebungsvariable GDK_NATIVE_WINDOWS für das Problem verantwortlich. Diese muss auf den Wert true gesetzt werden, damit SWT-basierte Java-Oberflächen (Anmerkung: auch andere Java-Applikationen mit SWT haben das Problem) wieder in altem Glanz erstrahlen.

#!/bin/bash
env GDK_NATIVE_WINDOWS=true <your/eclipse/path>/eclipse

(Den Pfad <your/eclipse/path> bitte an das jeweilige Installationsverzeichnis von Eclipse anpassen.)

Leider ist dies nur ein bescheidener Workaround, denn sobald man Eclipse aus sich heraus neu startet (z.B. nach Upgrades oder Plugin-Installationen) existiert das alte Problem wieder. Genauso, wenn man den Workspace versucht zu wechseln, da sich Eclipse auch hier herunterfährt und neu startet.

Ein richtiger Fix ist für die Version 3.5.2 angedacht bzw. soll in der 3.6 schon integriert sein, welche für die SDK-Variante bereits existiert. Nur für JEE-Developer wird es wohl noch etwas dauern.

Von einem globalen Umsetzen der Variable GDK_NATIVE_WINDOWS wird an diversen Stellen explizit abgeraten. Hier sei aber anzumerken, dass die Eclipse-Installation aus den Ubuntu-Repositories genau das macht. Für alle, die etwas “wagemutiger” sind hier das Startskript mit export.

#!/bin/sh

# work around e#290395 / LP: #458703
export GDK_NATIVE_WINDOWS=true

xuldir=/usr/lib/xulrunner-$(/usr/bin/xulrunner-1.9.1 --gre-version)
LD_LIBRARY_PATH=$xuldir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} <your/eclipse/path>/eclipse
 "$@"

(Den Pfad <your/eclipse/path> bitte an das jeweilige Installationsverzeichnis von Eclipse anpassen.)

Dieses Skript am besten unter /usr/local/bin als eclipse ablegen und alle Links, die vorher direkt auf die Eclipse-Startdatei gegangen sind auf dieses Skript umbiegen.

Trac

Interessanterweise – was die primäre Motivation für diesen Kurzkommentar war – hat es auch das Bugtracking-Tool Trac erwischt. Zum Glück nicht so “heftig” wie Eclipse, aber dafür umso unverständlicher.

Ich habe lokal Trac mittels easy_install installiert gehabt. Nach dem Upgrade auf Ubuntu 9.10 erhielt ich plötzlich auf allen Trac-Seiten einen 500-Fehler. Nach kurzem Suchen fand ich in der error.log-Datei von Apache (Pfad unter Ubuntu: /var/log/apache2/error.log) folgenden Eintrag:

... [error] [client 127.0.0.1] ImportError: No module named genshi, ...

Warum das Modul plötzlich nicht mehr vorhanden war kann ich zum aktuellen Zeitpunkt nicht erklären, aber das war noch nicht das Ende.

Just in dem Moment wo ich mit

sudo easy_install Genshi

das entsprechende Modul installieren wollte, quittierte mir Ubuntu die Nachricht, dass easy_install nicht existiert. Ein kurzer Blick in Synaptic unter python-setuptools bestätigte die Vermutung, dass das Paket nicht installiert war. Ich möchte an dieser Stelle nicht ausschließen, dass es beim Upgrade in der Liste der zu deinstallierenden Pakete dabei war, aber dennoch ärgerlich. Also war auch hier nachinstallieren angesagt.

Zum Glück fügte sich dann alles sehr schnell. Das Genshi-Modul installierte sich ohne Probleme.

Abschließend musste noch der Workaround für einen Bug (http://trac.edgewall.org/ticket/7526) eingebaut werden. Einfach die compat.py im functional/tests-Ordner in testcompat.py
umbenennen und die compat.pyc in compat.pyc.old umbenennen oder gleich löschen.

Blog redesigned … to be continued

08. November 2009

Wartungsarbeiten sind Arbeiten, die ungnädig und ewig warten, bis man sie macht. So geschehen mit diesem Blog und demnächst auch mit der Webseite http://www.pincservices.de.

Da der Blog seit einiger Zeit Brach lag, musste eine Runderneuerung her. Nachdem die Software auf den aktuellen Stand gebracht wurde, fiel mein Augenmerk auf das Frontend. Ich weiss gar nicht mehr wie viele Jahre das alte Frontend ausgehalten hat, aber ich entschuldige mich hiermit nachdrücklich bei allen, die darunter leiden mussten.
Seit heute morgen erstrahlt der Blog in neuem Gewand und auch die Nutzbarkeit ist – nach eigenen Erfahrungen – erheblich gestiegen. Nebenbei sind diverse “Gimmicks” ins Template geflossen, wobei ich nicht verrate welche.

Dennoch stehen – jetzt, wo man alles lesen kann – die nächsten Umbauten an. Mit der Aktualisierung sind einige Probleme beim Encoding aufgetreten und einige Artikel müssen nochmal visuell nachgebessert werden. Ausserdem fehlen noch einige Zutaten zum “perfekten Glück”.

Ich werd mich dann mal weiter darum kümmern – Also viel Spaß beim stöbern.

Webapps become wicket …

26. September 2009

Im Allgemeinen sprießen Webframeworks wie Pilze aus dem Boden. Mal mehr und mal weniger erfolgreich. Allerdings haben die meisten immer den gleichen MVC-Ansatz. Daten werden im Model vorgehalten, der Controller kümmert sich um die Bereitstellung der Objekte und der Navigation und das View “holt” sich alles was es braucht aus dem Kontext. D.h. man hat häufig eine Mischung aus HTML und einer expliziten Expression Language im View (z.B. JSTL).
Der große Nachteil bei dieser Form von Aufteilung ist, dass man beim Testen häufig auf verschiedene Frameworks zurückgreifen muss um die Korrektheit der Anzeige zu gewährleisten bzw. stark auf manuelles Testen angewiesen ist. Im Speziellen sei hier die Problematik von Text-Lokalisierung erwähnt, die jedem Entwickler im Laufe seiner Arbeit schonmal “auf die Füße gefallen” sein wird.

Einen etwas anderen Ansatz verfolgt an dieser Stelle das Framework Wicket. Hier wird bewusst auf eine explizite Expression Language verzichtet – soweit ich sehen kann. Alle Referenzen zwischen Controller und View werden über eine Wicket-Id-Tag (<span wicket:id="myMessage"></span>) aufgelöst. Damit steht zwar der Controller in der Pflicht alle Inhalte entsprechend umfangreicher aufzubereiten (z.B. explizite Änderungs-Propagierung), aber diese Vorgehensweise hat den elementaren Vorteil, dass Seiten wesentlich einfacher auch mit z.B. Junit-Tests geprüft werden können.

Es wäre alles zu schön um wahr zu sein, wenn es nicht schon auf den/meinen ersten Blick ein kleines Problem gäbe. Natürlich geht Wicket von einem MVC-Ansatz aus und benötigt damit HTML-Dateien zum Rendern. Leider wird standardmäßig davon ausgegangen, dass die HTML-Dateien im gleichen Verzeichnis liegen wie die entsprechende Klasse.

“First we need to create a web page. Wicket has a WebPage class suited for this task. All webpages need to be subclasses of WebPage. Another requirement is that the actual HTML file and the class name are equal: Index.html and Index, IndexPage.html and IndexPage or HelloWorld.html and HelloWorld. They also need to be in the same place on the classpath. The best way to develop is to put them in the same directory. This might seem strange in the beginning, especially when you are accustomed to separate html files and java files. However, since all pages are actually just components, it makes perfect sense in terms of reusability.”

(siehe hierzu http://cwiki.apache.org/WICKET/newuserguide.html#Newuserguide-TheHTML)

Für jeden Webentwickler, der es gewohnt ist, dass HTML- respektive JSP(X)-Dateien gefälligst weit weg von den Klassen zu liegen haben, eine Umstellung die nur leidlich zu ertragen ist.
Zum Glück haben die Wicket-Entwickler hier aber ein Einsehen und bieten in der Referenz-Dokumentation auch gleich mehrere Lösungen an, wobei aus meiner Sicht, die Maven-Lösung zu empfehlen ist, da sie versionsunabhängig ist.

GIT geht nicht gern mit Fremden: *** Please tell me who you are.

15. August 2009

Für alle, die es leid waren, sich mit Subversion herumzuschlagen – oder einfach mal neugierig auf ein anderes Versionssystem sind, haben bestimmt schonmal etwas von GIT gehört.

Ich will an dieser Stelle weder die üblichen Lobeshymnen, noch die allseitsbeliebten Hasstiraden anstimmen, sondern nur eine kleine Hilfestellung geben, wenn man sich dazu entschließen will das GIT-Plugin von Hudson zu nutzen.

Die Installation ist zwar – wie immer – simpel, aber leider mag Hudson evtl. nicht gleich mit GIT “spielen”. Will man den Job starten kommt relativ schnell folgende Ausgabe in der Konsolenansicht.

Gestartet durch Benutzer anonymous
Checkout:workspace / /usr/local/apps/hudson_home/jobs/gitTest/workspace – hudson.remoting.LocalChannel@1063971
Last Build : #2
Checkout:workspace / /usr/local/apps/hudson_home/jobs/gitTest/workspace – hudson.remoting.LocalChannel@1063971
Fetching changes from the remote Git repository
Fetching upstream changes from file:///var/local/workspaces/experiments/sampleProject
[workspace] $ git fetch file:///var/local/workspaces/experiments/sampleProject +refs/heads/*:refs/remotes/origin/*
[workspace] $ git ls-tree HEAD
Seen branch in repository origin/master
Commencing build of Revision f7dc0e95f48a084d5192c3712f14b3353a98a876 (origin/master )
Checking out Revision f7dc0e95f48a084d5192c3712f14b3353a98a876 (origin/master )
[workspace] $ git checkout -f f7dc0e95f48a084d5192c3712f14b3353a98a876
[workspace] $ git tag -a -f -m “Hudson Build #3″ hudson-gitTest-3
FATAL: Could not apply tag hudson-gitTest-3
hudson.plugins.git.GitException: Could not apply tag hudson-gitTest-3
at hudson.plugins.git.GitAPI.tag(GitAPI.java:265)

[haufenweise stacktrace]

… 12 more
Caused by: hudson.plugins.git.GitException: Command returned status code 128:
*** Please tell me who you are.

Run

git config –global user.email “you@example.com”
git config –global user.name “Your Name”

to set your account’s default identity.
Omit –global to set the identity only in this repository.

fatal: empty ident <tomcat6@yourhost> not allowed

at hudson.plugins.git.GitAPI.launchCommandIn(GitAPI.java:297)
… 14 more

Offensichtlich ist GIT ein wenig schüchtern und mag nicht jeden an die digitale Wäsche ranlassen.

Leider findet man nicht wirklich eine Möglichkeit Hudson eine eigene Identität via Webfrontend zu geben. Der Trick liegt genau dahinter – und zwar im Dateisystem.

Genauer gesagt in der /etc/passwd. Aus irgendwelchen Gründen brauch GIT hier einen Eintrag im Feld für den fullName. Unter Ubuntu hat der Tomcat-User dort standardmäßig nichts gesetzt. Fügt man jetzt dort einfach ‘Tomcat 6,,,’ ein, läuft es auch mit dem CI-Nachbarn.

Alle, die sich über die Gründe Gedanken machen, seien auf die Git FAQs verwiesen.

So dann – viel Spaß beim Bauen.