Ich hoffe mal, der Pilot hat Glück gehabt….

Das normale Vorgehen in GWT fuer die Erstellung einer Oberflaeche ist es, die Struktur der Oberflaeche mit GWT-Mitteln festzulegen (hart verdrahtet im Code oder mittels UIBuilder). Dies hat den Nachteil, dass die Oberflaeche dynamisch zur Laufzeit (per Javascript) aufgebaut wird. Dies fuehrt dazu, dass die Inhalte, der so erstellten Seite, nicht indizierbar sind fuer Suchmaschinen.

Ein andere Variante ist es, die HTML-Seiten serverseitig zu erstellen und GWT an die jeweiligen Element zu “kleben”. Die meisten GWT-GUI-Elemente besitzen eine statische “wrap( Element )” Methode, mit der man aus einem HTML-Element das passende GWT-GUI-Objekt erzeugen kann.

Nun ein einfaches Beispiel fuer ein Eingabefeld.

In der HTML-Datei findet sich folgende Zeile:

<input id=”username” type=”text” name=”signin_username” >

Im GWT-Code wird kann daraus wie folgt ein GWT TextBox erzeugt werden:

TextBox textboxUsername = TextBox.wrap( DOM.getElementById( “username” ) );

Verknuepfungspunkt ist id=”username” des HTML-Elements. textboxUsername entspricht nun dem passenden HTML-Element und es kann z.B. der eingebene Wert abgefragt werden.

Im Beispiel mit Zugriff auf eine Oracle-DB. Der passende Oracle-Treiber muss im Classpath mit eingebunden sein.

import oracle.jdbc.driver.OracleConnection
import groovy.sql.Sql
import java.sql.DriverManager
Class.forName ("oracle.jdbc.OracleDriver")
OracleConnection connection = DriverManager.getConnection( 'jdbc:oracle:thin:@server:1521:SID', 'username', 'password' )

def dbHandleSource = new Sql( connection )
dbHandleSource.eachRow( 'SELECT * FROM PRODUKT' )
   { produkt ->
   println produkt.id + ', ' + produkt.name
   }
import oracle.jdbc.driver.OracleConnection
import groovy.sql.Sql
import java.sql.DriverManager
OracleConnection connectionSource = DriverManager.getConnection( 
'jdbc:oracle:thin:@server:1521:SID', 'username', 'password' )

def dbHandleSource = new Sql( connectionSource )

def produktSourceSet = dbHandleSource.dataSet( 'PRODUKT' )
produktSourceSet.each
   { produkt ->  
   println produkt.id + ', ' + produkt.name  
   }

Auf developerWorks gibt es eine Artikelreihe zum Thema Grails.

Growl ist DAS Benachrichtigungssystem unterm Mac, wenn es darum geht, dem Nutzer über neue Mails, abgeschlossene Downloads oder neue Chatnachrichten zu informieren. Was ich bisher nicht wusste, man kann Growl auch über Java ansteuern und entsprechende Benachrichtigungen an Growl schicken.

Ich zeige anhand eines klitzekleinen Beispielprogramms, wie man Growl nutzen kann.

Zuerst muss man sich das SDK von http://growl.info/downloads_developers.php herunter laden. Darin enthalten ist die API-Dokumentation und eine Klasse (com.growl.Growl), die den Zugriff auf Growl wrappt. Falls das heruntergeladenen DMG nicht automatisch gemountet wird, dies nun machen. Unter Bindings/Java finden sich die für Java-Entwickler interessanten Dateien. Im Verzeichnis Bindings/Java/docs befindet sich die API-Dokumentation, die kann man sich lokal kopieren, ist aber nicht unbedingt notwendig. Unter Bindings/Java/Source befindet sich die Wrapper-Klasse für Growl, diese wird gleich benötigt.

In der IDE seines Vertrauens erstellt man nun ein neues Java-Projekt. In das Source-Verzeichnis kopiert man die Growl-Klasse (aus Bindings/Java/Source) inklusive der Verzeichnisse, also com markieren und per Copy & Paste ins Sourceverzeichnis kopieren. Im Normalfall sollte die IDE jetzt meckern, dass sie die Klassen und Packages (com.apple.cocoa…), die im Import-Teil der Growl-Klasse stehen, nicht auflösen kann. Dazu muss in den Klassenpfad des Projektes noch folgendes Verzeichnis mit aufgenommen werden: /System/Library/Java. Nun sollte es keinen Fehler mehr geben, die Import-Anweisung dürften aber noch markiert sein, da die verwendeten Klassen laut Apple deprecated sind.

Growl zeigt bei den Benachrichtigungen noch ein Symbol an, dies sollte in der Regel das Symbol der Anwendung sein, die die Benachrichtigung an Growl sendet. Dazu ist es in unserem kleinen Beispiel notwendig, eine PNG-Grafik mit 32 x 32 Pixeln zu erstellen. Im Codebeispiel wird angenommen, die Grafik ist über folgenden Pfad auf dem System erreichbar: /image/GrowlTest.png.

Hier nun das Codebeispiel, anschliessend noch ein paar Erklärungen zum Code:

package de.yellowshoes.growl.example;
import com.growl.Growl;
public class GrowlExample
    {
    public static void main(String[] args) throws Exception
        {
        String GROWL_EXAMPLE_NOTIFY_01 = "ExampleNotification_01";
        String[] notifications = { GROWL_EXAMPLE_NOTIFY_01 };
        Growl growl = new Growl( "MyApp", notifications, notifications );
        growl.register();
        growl.notifyGrowlOf( GROWL_EXAMPLE_NOTIFY_01,
                             "Ein Beispiel",
                             "Hier steht die Beschreibung des Events" );
        growl.notifyGrowlOf( GROWL_EXAMPLE_NOTIFY_01,
                             "/image/GrowlTest.png",
                             "Zweites Beispiel",
                             "Und noch eine Beschreibung" );
        }
   }

Die Variable GROWL_EXAMPLE_NOTIFY_01 bezeichnet einen möglichen Benachrichtigungsevent, der von Seiten der Applikation an Growl geschickt werden kann. Bei der Initialisierung der Wrapperklasse (Growl growl = new Growl….) übergibt man zuerst den Namen der Applikation (MyApp), die die Benachrichtigungen an Growl schicken wird, anschliessend einen Array mit allen möglichen Benachrichtigungsevents der Applikation und als dritten Parameter ein Array mit allen Benachrichtungsevents, die per default aktiviert sein sollen.

Das Abschicken der Benachrichtigungsevents (growl.notifyGrowlOf….) sollte eigentlich selbsterklärend sein. Der Unterschied besteht hier darin, das beim zweiten Aufruf das eigene Symbol mit angezeigt wird.

Eine Sammlung von Links zur Google App Engine for Java

Einleitung

Es kommt immer wieder vor, dass man keine speziellen ORM-Frameworks für den Zugriff auf Datenbank nutzt oder nutzen kann und zum direkten JDBC-Zugriff zurückgreifen muss oder will. Wie werden die entsprechenden SQL-Statements im Code hinterlegt? Meistens als Konstanten, manchmal wird das Statement auch dynamisch aufgebaut. Was mich immer wieder gestört hat bei der Variante mit den Konstanten, ist die Lesbarkeit der SQL-Statements innerhalb des Codes. Werden die SQL-Statements umfangreicher, so ist es kaum noch nachzuvollziehen, was innerhalb des Statements passiert….wenn Einrückungen und Klammerungen dann auch noch vernachlässigt werden ist es ganz aus. Daher habe ich mir jetzt eine Variante ausgedacht, die eine Lösung darstellt: auslagern der SQL-Statements in Textdateien, die innerhalb der Packages liegen und zur Laufzeit eingelesen werden.

Umsetzung

Die SQL-Statements sollen innerhalb der Klassen als “Konstanten” abgelegt sein, dazu werden sie zur Laufzeit beim Laden der Klasse durch den Classloader initialisiert. Hierzu wird statischer Code verwendet. Eine entsprechende Utitlityklasse, die sich um das Einlesen der Textdateien kümmert, wird hier als Code aufgeführt, ebenso Beispielcode, wie es genutzt werden kann.

package de.maicoda.utilities;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;

import org.apache.log4j.Logger;

public class ResourceReader
{
static Logger log = Logger.getLogger( ResourceReader.class );

/**
* Liest aus dem Classpath eine Resource ein und liefert den Inhalt als String zurück.
* Besonders gedacht zum Einlesen von Textdateien, die innerhalb von Packages liegen.
*
* @param resource Pfad und Name der Resource zusammen, z.B. de/sinform/dwh/Test.sql
* @return Inhalt der Resource als Text
*/
public static String readResourceAsString( String resource )
 {
 ClassLoader classLoader = ResourceReader.class.getClassLoader();
 LineNumberReader reader = new LineNumberReader( new InputStreamReader( classLoader.getResourceAsStream( resource ) ) );

 String zeile = null;
 StringBuffer puffer = new StringBuffer();

 try
    {
    if( reader.ready() )
       {
       while( ( zeile = reader.readLine() ) != null )
          {
          puffer.append( zeile );
          puffer.append( "\n" );
          }
       }
    }
 catch( IOException e )
    {
    log.error( e );
    }
 finally
    {
    if( reader != null )
       {
       try
          {
          reader.close();
          }
       catch( IOException e1 )
          {
          log.error( "Fehler beim Schliessen des Readers für die Datei " + resource, e1 );
          }
       }
    }

 return puffer.toString();
 }

/**
* Liest aus dem Classpath eine Resource ein und liefert den Inhalt als String zurück.
* Besonders gedacht zum Einlesen von Textdateien, die innerhalb von Packages liegen.
*
* @param pfad Pfad (Package), in dem die Resource liegt, z.B. de/sinform/dwh/
* @param resource Name der Resource, z.B. Test.sql
* @return Inhalt der Resource als Text
*/
public static String readResourceAsString( String pfad, String resource )
 {
 if( pfad.endsWith( "/" ) == false )
    {
    pfad += "/";
    }

 return readResourceAsString( pfad + resource );
 }

/**
* Liest aus dem Classpath eine Resource ein und liefert den Inhalt als String zurück.
* Besonders gedacht zum Einlesen von Textdateien, die innerhalb von Packages liegen.
*
* @param packagename   Referenz auf das Package der Resource
* @param resource Name der Resource, z.B. Test.sql
* @return Inhalt der Resource als Text
*/
public static String readResourceAsString( Package packagename, String resource )
 {
 String pfad = packagename.getName().replaceAll( "\\.", "/" ) + "/";
 return readResourceAsString( pfad, resource );
 }
}

Diese Utilitieklasse kann nun wie folgt genutzt werden. Dabei wird davon angenommen, dass die die Textdatei mit dem SQL-Code (NeuerKunde.sql) im gleichen Package liegt wie die Klasse SqlKunde.

private static final String sqlNeuerKunde = ResourceReader.readResourceAsString( SqlKunde.class.getPackage(), "NeuerKunde.sql" );

Das Ergebnis ist nun eine initialisierte Konstante sqlNeuerKunde, in der das entsprechende SQL-Statement zu finden ist. Die Initialisierung findet beim Laden der Klasse statt.

Um in VirtualBox unter Mac OS X eine Festplatte (das ist noch nicht die virtuelle Maschine!!!) zu klonen, muss man auf die Konsole wechseln und folgendes ausführen:

/Applications/VirtualBox.app/Contents/MacOS/VBoxManage clonehd /Users/johndoo/Library/VirtualBox/HardDisks/YourBaseInstallation.vdi /Users/johndoo/Library/VirtualBox/HardDisks/YourClone.vdi

Anschliessend kann normal eine neue virtuelle Maschine angelegt werden, die geklonte Harddisk muss dann noch eingebunden werden und der neuen virtuellen Maschine zugeordnet werden.

In einem Projekt bekam ich die folgende Fehlermeldung: java.util.zip.ZipException: Cp437

Diese Fehlermeldung wurde durch die Apache Ziptools geworfen, die im Package org.apache.tools.zip beheimatet sind. Genutzt werden sie, um eine Zip-Datei zu erstellen.
Suchen im Netz gaben keine konkreten Hinweise darauf, was genau das Problem sein könnte. Klar, Cp437 bringt man mit Codepages in Verbindung….aber was genau sagt einem die Fehlermeldung? Da es auch keinen StackTrace gab, musste ich tiefer Code debuggen, zu dem ich keinen Sourcecode habe…und siehe da, irgendwann war ich an der Stelle, an der der eigentliche Fehler auftrat. Jetzt tauchte als Fehler kurz folgende Meldung auf, bevor sie in eine ZipException gekapselt wurde: java.io.UnsupportedEncodingException: Cp437.
Mit der Information im Netz gesucht und den Hinweis gefunden, dass die Jar-Datei charsets.jar eingebunden werden muss. Diese findet sich im lib-Verzeichnis der JRE-Installation, ich hatte allerdings das JDK genutzt, dort ist die Datei nicht vorhanden.

Nach einbinden der charsets.jar in den Klassenpfad war der Fehler behoben.