Samstag, August 18, 2012

Migration auf Spring Annotations

Heute war der große Tag. Die alten XML Konfigurationsdateien wurden entfernt und durch die Spring Annotationen @Repository, @Service und @Component ersetzt.

Teil 1: @Repository und die DAOs

Dabei bin ich auf ein erstes Problem gestossen: Die HibernateDaoSupport Klasse wird nicht mehr unterstützt. Weiter wird empfohlen, auf die Klasse org.springframework.orm.hibernate.HibernateTemplate zu verzichten und statt dessen direkt org.hibernate.Session zu verwenden. Das ganze funktioniert ab Hibernate 3.0.1. Für die DAOs habe ich eine gemeinsame Oberklasse definiert: AbstractCommonDao. Diese Klasse definiert die typischen Datenoperationen #findById(...), #delete(...), #save(), etc.. Ein erster Migrationsschritt sieht folgendermaßen aus:

public class AbstractCommonDao extends
        org.springframework.orm.hibernate3.support.HibernateDaoSupport {
    ...
    public final void save(final T t) {
        getHibernateTemplate().saveOrUpdate(t);
    }
}
wird ersetzt durch
public class AbstractCommonDao {
    private org.hibernate.SessionFactory sessionFactory;

    SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public final void save(final T t) {
        getSessionFactory().getCurrentSession().saveOrUpdate(t);
    }
}
Im ersten Schritt sind also alle Codestellen #getHibernateTemplate() durch getSessionFactory().getCurrentSession() zu ersetzen. Problematisch sind im Anschluss die #finder Methoden, da diese keine 1:1 Entsprechung in der HibernateSession besitzen. Die Methode #createSQLQuery(...) findet sich in der Klasse HibernateTemplate wie in der Klasse HibernateSession (Mit dieser Methode können native SQL Anfrage formuliert werden). Bei der Umstellung habe ich alle namenlosen SQL Parameter ersetzt. Das Spring HibernateTemplate unterstützt Konstrukte wie
from match where id = ?
Die HibernateSession kennt keine namenlosen Parameter. Diese Ausdrücke habe ich ersetzt durch
from match where id = :matchId
Für den umgebenden Java Code ändert sich der Aufruf:
public SessionFactory getSessionFactory() {
    return sessionFactory;
}

public Team findByName(final String teamName) {
    List teams = getSessionFactory().getCurrentSession()
            .createQuery("from team where name = :teamName")
            .setParameter("teamName", teamName)
            .list();

    if (teams.size() == 0) {
        return null;
    } else {
       return teams.get(0);
    }
}
Erleichtert also sogar die Lesbarkeit.

Teil 2: Server Start

Nach dem die Konfiguration auf Annotationen komplett umgestellt war, alle JUnit Tests erfolgreich durchliefen, ging es an den Server Start. Meine Anwendung besteht grob aus zwei Artefakten: Der Kern mit der Abstraktion für die Datenbank (Dao, Repository, Service-Schicht) und die Web-Anwendung mit JSPs und Servlets. Die Anwendung läuft auf einem Tomcat 7. Nach Deployment der Anwendung trat dabei beim Hochfahren des Servers die folgende Ausnahme auf:

14.08.2012 17:22:23 org.apache.catalina.startup.ContextConfig
    checkHandlesTypes
WARNUNG: Unable to load class [javax.xml.parsers.SecuritySupport12]
    to check against the @HandlesTypes annotation of one or
    more ServletContentInitializers.
java.lang.IllegalAccessError: class javax.xml.parsers.SecuritySupport12
    cannot access its superclass javax.xml.parsers.SecuritySupport
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
        ...
Gefolgt von der Meldung:
INFO: No Spring WebApplicationInitializer types detected on classpath
log4j:WARN No appenders could be found for logger
    (org.springframework.web.context.support.StandardServletEnvironment).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
14.08.2012 17:22:28 org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'betofficeform'
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
Und die Lösung ist die folgende: In der web.xml muss das XML Attribut metadata-complete="true" eingetragen sein!
<web-app id="WebApp_9" version="3.0"
  xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  metadata-complete="true">
Das verhindert, dass die deployten JARs nach Annotationen aus dem Package javax.servlet.annotation durchsucht werden. Ein ähnliches Problem ist in diesem Forum Eintrag beschrieben. Das Problem wurde durch das Löschen der activation.jar aus der Maven Dependency Liste gelöst.

PHP Horror

Bei der Installation von PHP, Pear, PHPUnit bin ich auf verschiedene Probleme gestossen. Wie üblich haben die Versionen der einzelnen Module nicht mit einander harmoniert. Hier einfach mal ein Auszug meiner Installationskommandos:
sudo apt-get install php-pear
sudo pear channel-update pear.php.net
sudo pear upgrade-all
sudo pear channel-discover pear.phpunit.de
sudo pear install -a phpunit/PHPUnit
pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit
Bzw. auch eine Empfehlung...
sudo apt-get remove phpunit
sudo pear channel-discover pear.phpunit.de
sudo pear channel-discover pear.symfony-project.com
sudo pear channel-discover components.ez.no
sudo pear update-channels
sudo pear upgrade-all
sudo pear install --alldeps phpunit/PHPUnit
sudo pear install --force --alldeps phpunit/PHPUnit
Und falls Curl fehlt vielleicht auch das:
sudo apt-get install php5-curl
Und noch den hier:
sudo apt-get install php-pear
sudo pear channel-update pear.php.net
sudo pear upgrade-all
sudo pear channel-discover pear.phpunit.de
sudo pear channel-discover components.ez.no
sudo pear channel-discover pear.symfony-project.com
sudo pear install -a phpunit/PHPUnit
Und falls du unter PHP 5.3 arbeitest, kann es evt. Sinn machen, den strikten PHP Übersetzer etwas zu lockern:
error_reporting = E_ALL & ~E_DEPRECATED
Das hat zumindest einige Probleme mit den Pear Bibliotheken behoben. Die Einstellung muss natürlich in der php.ini hinterlegt werden.

Virtual Server Update

Hier einige Merker und Tipps für ein Upgrade the Virtual Server Umgebung bei HostEurope.
  • Password für das Plesk Parallels Panel vergessen? Mit der Abfrage /usr/local/psa/bin/admin --show-password über eine Root-Shell kannst du dir das Password anzeigen lassen.
  • Wo finde ich das MySQL Password? Wieder gibt es dafür eine Abfrage cat /etc/psa/.psa.shadow;echo.
  • Installationshinweise für ein JDK 7 unter Ubuntu 10 habe ich hier gefunden. Hier gibt es eine weitere Beschreibung.
  • Aber auch ganz einfach:
    sudo apt-get install openjdk-7-jdk
  • Die aktuelle PEAR Version für PHP war auf meiner virtuellen Maschine bereits vorinstalliert. Bei der Ausführung der PHP Skripte kam es dann aber zu einer Fehlermeldung, dass die entsprechenden PEAR Skripte nicht gefunden werden können. Korrekt installiert finde ich PEAR im Verzeichnis /usr/share/php/. Als erstes habe ich in der Datei /etc/php5/apache2/php.ini den include Pfad angepasst: include_path = ".:/usr/share/php". Eventuell ist eine weitere include_path Zeile auszukommentieren, die meine Einstellung wieder überschreibt. Damit PHP Dateien öffnen und beschreiben kann, ist die Variable open_basedir ebenfalls anzupassen und der Pfad aus include_path hinzuzufügen.. Als letzte Einstellung sind die Dateirechte zu überprüfen.
  • Das Umstellen der Domain Namen auf die neuen IP Adressen kann bis zu 24 Stunden dauern. Unter Windows kann der DNS Cache mit dem folgenden Kommando gelöscht werden:
    ipconfig /flushdns
    Firefox besitzt einen eigenen DNS Cache, der mit der Eigenschaft network.dnsCacheExpiration=0 (Integer) gesteuert werden kann.
  • Firefox speichert die Eigenschaften in der Datei c/Users/winkler/Anwendungsdaten/Mozilla/Firefox/Profiles/xxxxxxx.default/prefs.js. Einige Eigenschaften für den Firefox lassen sich nur über diese Datei ändern.
  • Plesk legt die VHOST Logdateien unter den folgenden Pfaden ab: /var/www/vhosts/<domain_name>/statistics/logs/access_ssl_log und /var/www/vhosts/<domain_name>/statistics/logs/error_log.

Tomcat und UTF-8

Laut Dokumentation verarbeitet der Tomcat Container per Default das Encoding ISO-8859-1 (siehe Tomcat Wiki oder HTTP 1.1 Spezifikation des W3C). jQuery verwendet per Default für das Senden von Ajax Events das Encoding UTF-8 (Siehe dazu in der jQuery Beschreibung). Im ersten Schritt muss dem Tomcat beigebracht werden, Request mit dem für uns richtigen Encoding zu verarbeiten. Dazu muss die Datei server.xml im conf Verzeichnis der Tomcat Installation angepasst werden (Eine Beschreibung findet sich zu diesem Thema ebenfalls hier). Dazu wird die Connector Definition um das Attribut URIEncoding="UTF-8" erweitert.
<Connector port="8080"
    protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443"
    URIEncoding="UTF-8"/>
Im Anschluss ist dafür zu sorgen, dass das ausgelieferte Response ebenfalls mit UTF-8 kodiert wird. Dazu gibt es die folgende Möglichkeiten.
  • Innerhalb einer JSP Seite:
    <%@ page language="java"
        contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
        
  • Für HTML 5 (Angabe über das Meta Tag):
    <meta charset="UTF-8">
        
  • Für HTML 4:
    <meta http-equiv="Content-type"
        content="text/html;charset=UTF-8">
        
Dabei ist sicherzustellen, dass die ausgelieferten Daten ebenfalls UTF-8 kodiert sind. Im einfachsten Fall ist der HTML Editor auf das entsprechende Encoding einzustellen.
Zum Abschluss noch ein Link mit einer Erklärung über den Unterschied von GET und POST: GET versus POST

javax/validation/Validation

Heute ließ sich in einem meiner Java Web Projekte die JUnit Tests nicht ausführen mit der folgenden Fehlermeldung:

testXyz(de.betoffice.ws.HessianServiceTest):
  Error creating bean with name
  'mySessionFactory' defined in
  class path resource [betoffice-persistence.xml]:
  Invocation of init method failed; nested
  exception is java.lang.ClassFormatError:
  Absent Code attribute in method that is
  not native or abstract
  in class file javax/validation/Validation
Auf Stackoverflow habe ich eine Lösung gefunden. Ich frage mich nur, wieso benötigt das Projekt 'jetzt' ein JAR mit javax/validation/Validation?

UPDATE 30.12.2011 08:00 Uhr: Und heute funktioniert der JUnit Test wieder nicht. Trotz obiger skizzierter Lösung. ?!??? Irgendwas ist an meiner Umgebung faul.

UPDATE 30.12.2011 15:54 Uhr: Jetzt funktioniert der Test wieder. Ich habe die Reihenfolge der Dependencies in der pom.xml verändert. Die Einträge

  <dependency>
    <groupId>javax.validation>
    <artifactId>validation-api</artifactId>
    <version>1.0.0.GA</version>
  </dependency>
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.2.GA</version>
  </dependency>
stehen jetzt ganz vorne. Eine Erklärung habe ich erst einmal nicht.

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...