Hat mich einwenig Zeit gekostet, aber Apple hat scheinbar Maven3 mit dem neusten Java Update ausgeliefert. Daher führte bei mir der Aufruf von mvn zu der besagten NoClassDefFoundError: org/codehaus/plexus/classworlds/launcher/Launcher-Exception
Dank dem Artikel habe ich einen Blick in das /usr/share Verzeichnis geworfen und tatsächlich einen Link auf Maven3 gefunden.
$ ls -la /usr/share/maven maven -> java/maven-3.0.2
Ich habe den Link entfernt und auf meine alte Maven Installation gesetzt – schon geht es
$ sudo rm /usr/share/maven $ sudo ln -s /Library/Maven/2.2.1 /usr/share/maven
Für Snäckbox wollte ich heute eine Architekturentscheidung treffen: Lasse ich das Projekt weiter aufblähen oder versuche ich es weiter zu modularisieren. Das Projekt wächst täglich, aber es sind noch lange nicht alle Features implementiert, die ich auf der Liste stehen habe. Ich möchte mich früh genug um eine saubere Architektur bemühen, damit ich später nicht in Schwierigkeiten gerate.
Ich setzte bisher auf die Kombination von Grails, GWT und Maven. Die Modularisierung von reinem Java Quellcode ist mit Maven sehr praktisch, doch wie werden Grails Anwendung modularisiert?
Ganz konkret möchte ich einige Domain Klassen auslagern, um sie gleichzeitig in einer Frontend- und einer Backend-Anwendung zu nutzen, muss diese Klasse den Grails Konventionen gehorchen. Sprich sie muss sich unter grails-app/domain befinden. Sie einfach zu kopieren ist eine denkbar dumme Idee, sie in ein Maven Modul zu packen und in ein Repository zu installieren wird leider nicht klappen. Denn wenn Grails zur Laufzeit die Domain Klassen unter grails-app/domain um dynamische Methoden erweitert, wird meine ausgelagerte Domain Klasse übergangen und verfügt folglich nicht über die save(), delete() und find*() Methoden.
Mir scheint ein Grails-Plugin eine sinnvolle Möglichkeit zu sein, meine Anwendung zu strukturieren. Was mich zu Beginn einwenig stocken ließ ist der automatisierte Build-Prozess, der auf meinem TeamCity Server läuft. Alle Module werden mit Maven gebaut. Diese Linie wollte ich beibehalten und habe einwenig geforscht, wie ich die Brücke zwischen meinem Grails-Plugin, der bestehenden Grails Anwendung und meinem Maven-Buildprozess schlagen kann.
Das Maven Publisher Plugin ist ein guter Anfang. Es bündelt das Plugin und installiert es in ein Maven kompatibles Repository.
Als erstes erzeuge ich ein Grails Plugin:
$ grails create-plugin sb-cms-base
Und installiere das Maven Publisher Plugin:
$ cd sb-cms-base
$ grails install-plugin maven-publisher
Da ich gewohnt bin mit Maven zu arbeiten, habe ich eine pom.xml erzeugen lassen, was sich als Fehler herausgestellt hat. Ich habe die install-Phase um die Ausführung von dem maven-install erweitert und versucht das Modul zu installieren. Das hat leider nicht funktioniert. Für jedes Maven Modul wird die Packaging-Methode angegeben <packaging>jar|war|ear|...</packaging> => zip ist aber nicht dabei. Die Folge ist, dass die pom.xml den Dateinamen des Ziel-Artefakts überlädt. Habe ich also also als Packaging-Methode war angegeben, baut mir das maven-publisher Plugin ein Archiv mit dem Namen sb-cms-base.zip, kopiert wird es aber nach sb-cms-base.war
Da ich dieses Plugin trotzdem automatisiert bauen und installieren will, benötige ich ein geeignetes Build-Werkzeug – z.B. das gute alte Ant
Die build.xml und die ivy.xml kann Grails prima selbst erzeugen:
$ grails integrate-with --ant
Und noch die build.xml um ein neues Target ergänzen, welches das Plugin direkt in das lokale Maven Repository als *.zip installiert
... <target name="maven-install" depends="-init-grails"> <grails script="MavenInstall"/> </target> ...
Hier ist übrigens ein netter Post über das Deployment in ein Remote-Repository.
Nun habe ich zwei Dinge erreicht. Erstens kann das neue Plugin in das bestehende Maven Repository deployed werden und zweitens hat mein TeamCity Server dank der Ant-Integration keine Schwierigkeiten dies für mich zu übernehmen.
Jetzt möchte ich dieses Plugin in einer anderen Grails Anwendung nutzen. Dazu definiere ich in der grails-app/conf/BuildConfig.groovy eine Abhängigkeit zu meinem Plugin und….
grails.project.dependency.resolution = { repositories { ... mavenLocal() mavenCentral() ... } dependencies { plugins { compile 'org.grails.plugins:sb-cms-base:0.1' } } }
…, da ich mich auf Maven-Boden befinde, installiere es über die Kommandozeile in die zweite Grails Anwendung:
$ mvn grails:install-plugin -DpluginName=sb-cms-base
(Ich weis nicht genau ob das ein Caching Problem meines IDEA’s, oder ob mir ein Fehler unterlaufen ist, aber in der application.properties fehlte nach der Plugin-Installation der plugins.sb-cms-base=0.1 Eintrag – habe ich folglich ergänzt.)
Nun kann ich beginnen Domain Klassen, Services und weitere Komponenten, die von mehreren Grails Anwendungen genutzt werden sollen, in das Plugin auszulagern. Schön ist, dass sich diese Lösung in den Maven Buildprozess integrieren lässt.
Übrigens, sollte eine Exception kommen, die so etwas als Fehlermeldung enthält…
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any
… dann könnte es an der Ant Version liegen. Grails kann mit 1.8.x nicht umgehen und benötigt eine 1.7.x
Update 1
War eine kleine Herausforderung, aber jetzt wird das Plugin auf dem TeamCity Server gebaut. Grails ist nicht installiert und die Abhängigkeiten werden über Ivy aufgelöst. Ich hatte Schwierigkeiten gehabt Spring Abhängigkeiten aufzulösen und kam nicht weiter. Ein ähnliches Problem wird bei stackoverflow.com beschrieben, aber der Lösungsvorschlag brachte bei mir nichts. Für mich hat die folgende Anpassung funktioniert:
Eine wichtige Veränderung ist der Methodenaufruf ebr() in der BuildConfig.groovy, der das SpringSource Enterprise Bundle Repository hinzuzieht. Zusätzlich habe ich ein paar Abhängigkeiten von vornherein ausgeschlossen. Die jsp-api stellt mein Container bereit, mit den xml-apis kollidiert der mit Groovy gebündelte XML-Parser, und Grails loggt über SLF4J, was das commons-logging vieler Apache Bibliotheken überflüssig macht.
grails.project.dependency.resolution = { inherits("global") { excludes 'jsp-api', 'xml-apis', 'commons-logging' } ... repositories { grailsPlugins() grailsHome() grailsCentral() ebr() mavenLocal() mavenCentral() mavenRepo "http://repository.codehaus.org" mavenRepo "http://download.java.net/maven/2/" } plugins { runtime 'org.grails.plugins:maven-publisher:0.7.5' } dependencies { ... } }
Die ivy.xml habe ich um einige Grails-Abhängigkeiten erweitert, insbesondere die grails-scripts, ohne die das Ant-Buildskript nicht funktionieren würde.
<dependencies> <dependency org="org.grails" name="grails-bootstrap" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-core" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-crud" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-gorm" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-web" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-test" rev="1.3.6" conf="build"/> <dependency org="org.grails" name="grails-scripts" rev="1.3.6" conf="build"/> </dependencies>
Und schließlich habe ich das Target maven-install inder der build.xml um die compile Abhängigkeit erweitert, da Grails sonst keine Chance hat das maven-publisher Plugin zu installieren:
<target name="maven-install" depends="-init-grails, compile"> <grails script="MavenInstall"/> </target>
Update 2
Es wollte einfach nicht… Irgendwie hat sich die slf4j-api:1.5.2 in das deployte WAR eingeschlichen. Lokal auf einem Rechner nicht, aber auf dem TeamCity Server schon. Grund war das hibernate:1.3.4-Plugin, das eine Abhängigkeit auf die slf4j-api:1.5.2 definiert. Egal was ich gemacht habe, ich habe sie nicht rausbekommen und musste schmerzlich erfahren, dass egal wie viel Zeit Grails + Maven + Ivy auch sparen, ein Großteil davon geht auf die Suche nach mysteriösen Fehlern wieder drauf.
Meine derzeitige Lösung sieht so aus, dass ich slf4j-api:1.5.2 global in der BuildConfig.groovy ausschließe:
grails.project.dependency.resolution = { inherits("global") { excludes 'jsp-api', 'xml-apis', 'commons-logging', 'slf4j-api' } ... dependencies { provided 'org.slf4j:slf4j-api:1.5.2' } }
Kommt ja ab und an vor, dass ein PDF Dokument erstellt werden soll, das u.a. einen maschinenlesbaren Code enthält. Z.B. ein Barcode. Ich habe mich gestern in der Grails-Plugins Landschaft umgesehen. Auf anhieb fand ich das Rendering Plugin, mit dem es u.a. möglich ist PDF’s aus XHTML zu erzeugen. Nach anfänglichen Schwierigkeiten mit den Abhängigkeiten, habe ich eine Konstellation gefunden, die für mich funktioniert hat:
... <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>core-renderer</artifactId> <version>R8</version> </dependency> <dependency> <groupId>com.lowagie</groupId> <artifactId>itext</artifactId> <version>2.0.8</version> </dependency> <dependency> <groupId>net.sf.barcode4j</groupId> <artifactId>barcode4j</artifactId> <version>2.0</version> <exclusions> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> </exclusions> </dependency> ...
Ich habe Abhängigkeiten zu XHTML Renderer, iText und Barcode4j definiert. Die Idee ist ganz einfach:
- Einen EAN-13 Barcode als Bild erzeugen und in dem Images Verzeichnis ablegen, das dem Webserver zugänglich ist.
- Ein GSP Template schreiben, aus dem später das PDF erzeugt wird
- In der GSP das Barcode Bild verlinken
- PDF rendern
Schritt 1
Das Rendering Plugin installieren: mvn grails:install-plugin -DpluginName=rendering und einen PdfController erstellen.
Schritt 2
Eine Methode schreiben, die mit Hilfe von Barcode4j einen Barcode erzeugt und in eine Datei unter web-app/images ablegt
class PdfController { static DPI = 600 ... /** * Erstellt einen neuen Barcode und legt in die Datei ab * * @param code Der EAN Code * @param file Die Zieldatei */ private void createBarCode(code, File file) { def bean = new EAN13Bean(fontSize: 3.0, barHeight: 6) bean.doQuietZone(false); bean.moduleWidth = UnitConv.in2mm(6.0f / DPI) file.withOutputStream { out -> def canvas = new BitmapCanvasProvider(out, "image/x-png", DPI, BufferedImage.TYPE_BYTE_BINARY, false, 0); bean.generateBarcode(canvas, code); canvas.finish(); } } }
Um mit Barcode4j einen bestimmten Barcode Typen zu erzeugen, instanziiere ich die entsprechende Bean. In diesem Fall EAN13Bean aber natürlich sind auch andere möglich – z.B. 2-dimensionalle. Mit dem BitmapCanvasProvider wird der Barcode erzeugt und in den OutputStream der Datei geschrieben.
Schritt 3
Eine Methode schreiben, die ein Barcode Bild erzeugt und lokal speichert, wenn es noch nicht existiert und anschließend eine URL liefert, die über dem Webserver zugänglich ist.
class PdfController { static LOCAL_PATH = "${new File("").absolutePath}/web-app/images/barcodes" ... /** * Liefert eine öffentlich zugängliche URL zu dem Bild, * das den übergebenen Code als Barcode darstellt. * * @param code Der EAN-13 code * @return Die URL zu dem erzeugten Barcode */ private def asImageUrl(code) { if (!code || code.size() != 13) { throw new IllegalArgumentException("EAN 13 code not given or length is not 13: $code") } def filename = "${code}.png" def file = new File("${LOCAL_PATH}/${filename}") if (!file.exists()) { createBarCode(code, file) } resource(dir: 'images/barcodes', file: filename) } ... }
Schritt 4
Eine GSP schreiben, die den Barcode als Bild einbindet.
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <style type="text/css"> @page { size: A4; } </style> </head> <body> <div> <img src="${barcode}" alt="Barcode" width="200px"/> </div> </body> </html>
Letzter Schritt
Die PDF rendern und dem Client schicken
class PdfController { ... def index = { def ean = "9781590599952" def target = asImageUrl(ean) renderPdf(template: "/pdfs/barcode", model: [barcode: target], filename: "barcode.pdf") } ... }
Webserver starten, im Browser die URL des Controllers eingeben (http://localhost:8080/CONTEXT/pdf/index) und es sollte ein PDF geladen werden, das ein Barcode als Bild enthält:
Heute morgen habe ich nano auf 3sat geschaut; einen Bericht mit dem Titel Spermien in Gefahr.
Demnach lässt die Qualität der Spermien zu wünschen übrig.
Bei einem Drittel der Männer ist sie so eingeschränkt, dass es länger dauerte, bis die Paare schwanger werden.
Und dafür gibt es einige Gründe auf die wir (in)direkt Einfluss haben. Das eine Frau während der Schwangerschaft das Rauchen sein lassen sollte, ist nichts Neues. Leider ist es bei vielen nicht angekommen und so schädigen sie bereits in der Schwangerschaft die Hoden ihrer Ungeborenen Jungen.
Dann spielt unsere Ernährung eine große Rolle. Unsere Lebensmittel kommen mit Verpackungen in Kontakt, in denen Weichmacher verarbeitet wurden. Manche Weichmacher wirken wie Östrogen und stören dabei die Testostäron-gesteuerten Abläufe im männlichen Körper. Ein Blick bei Google offenbart einige Presseberichte wie Plastikflaschen machen weiblich. Das Thema ist also nicht neu. Dabei sind Weichmacher allgegenwärtig. Sie sind in unserem Haushalt, in unserem Essen, Trinkwasser, und sogar in der Hausstaub-Luft.
Als dritter Grund wird Fettleibigkeit angeführt. Als heute Morgen mein Radiowecker anging, vernahm ich als erstes Franky’s Stimme von FFN, der meinte das 60% aller Männer in Niedersachsen Übergewicht haben. Das Bauchfett speichert und gibt Östrogen frei, das ebenfalls den Hormonhaushalt der Männer beeinflusst.
Die Folge davon ist, dass unsere Gesellschaft verweiblicht. In dem nano-Bericht wurde ein Forscher gezeigt, der Kaulquappen im Wasser züchtet, das Weichmacher enthält – so wie z.B. unser Trinkwasser. Dabei dominiert das weibliche Geschlecht auffallend. Und was im Tierreich funktioniert, kann auch bei uns ähnlich aussehen.
Ist eine Weile her seit meinem letzten Eintrag
Gestern habe ich mich mit einem Kollegen über die Kommunikation zwischen einer Android App und einem Grails-Backend unterhalten. Ich freue mich immer über eine Herausforderung, also habe ich ein kleines Projekt aufgesetzt, das dieses Problem löst.
Das Command-Pattern ist eines meiner liebliengs-Entwirfsmuster. Es eignet sich hervorragend zum Bau von Schnittstellen zwischen zwei unterschiedlichen Systemen. In diesem Fall zwischen Grails (serverseitig) und Android (clientseitig). Der Vorteil des Command-Patterns ist, dass es wunderbar mit dem Open/closed principle harmoniert. Denn die Schnittstelle umfasst, platt gesagt, einen Service mit einer execute Methode, die einen Request bekommt und einen Response liefert. Wie die konkreten Implementierungen der einzelnen Request/Response Paare aussehen, ist nicht wichtig, die Schnittstelle bleibt die Selbe.
Die Funktionsweise ist wie folgt:
- Auf der Android-Seite wird eine Anfrage in Form einer
Actionerzeugt und an einen asynchronen Service übergeben. - Dieser Service serialisiert die Anfrage nach JSON und schickt die Nachricht per POST zum Server.
- Auf Server Seite wird die Nachricht deserialisiert und an einen synchronen Service übergeben, der damit was anzufangen weis.
- Die Antwort serialisiert der Server ebenfalls nach JSON und schickt sie zurück.
- Auf Android Seite wird die Antwort deserialisiert und das Callback des asynchronen Service aufgerufen.
Dies ist nichts Revolutionäres und funktioniert so oder so ähnlich in einigen Projekten. Ich möchte nun mit dem Code konkret werden. (Mir fehlte die Fantasie für einen tollen Namen, also habe ich das Projekt MDI – mobile data interface – genannt) Um das alles beisammen zu halten, habe ich ein neues Maven Projekt mit drei Modulen aufgesetzt:
... <groupId>de.fivews.mdi</groupId> <artifactId>mdi</artifactId> <packaging>pom</packaging> <version>0.1</version> <modules> <module>core</module> <module>server</module> <module>android</module> </modules> ...
Wichtig ist in dem server- und android-Modul eine Abhängikeit zu dem core-Modul zu definieren.
... <dependency> <groupId>${pom.groupId}</groupId> <artifactId>core</artifactId> <version>0.1</version> </dependency> ...
Core – gemeinsame Code-Basis
Das erste Modul core ist das umfangreichste und bildet die Schnittstelle zwischen der Client-App und dem Server. (Ich habe die Bezeichner Action, Result und ActionHandler an das Dispatch Modul in GWT angelehnt.)

In diesem Modul sind Interfaces und Klassen definiert, die sowohl auf der Client- als auch für die Serverseite genutzt werden. Das Result Interface wird von allen Beans implementiert, die ein Ergebnis enthalten, das für den Client bestimmt ist.
public interface Result extends Serializable {}
Analog zu dem Result gibt es das Action Interface. Dieses Interface sorgt für zweierlei.
- Die Bindung an einen bestimmten
Result. Es wird von der konkreten Implementierung vorgegeben, von welchem Typ das Ergebnis sein muss. - Die
getResultClass-Methode liefert die Klasse desResults. Wir werden später sehen, dass ich den Typen benötige, da die Generics mir zur Laufzeit nicht weiter helfen.
public interface Action<T extends Result> extends Serializable { public Class<T> getResultClass(); }
Weiter geht es mit dem ActionHandler. So ein Handler ist jeweils für ein Action/Result Pärchen verantwortlich. Mit anderen Worten, nimmt ein Handler eine bestimmte Action entgegen, macht irgendwas damit und liefert ein bestimmtes Result-Objekt. Zu jedem Action/Result Paar gibt es genau einen ActionHandler. Es ist sinnvoll für jede Action gleich einen Mock-ActionHandler zu implementieren, der per Dependency Injection in eine Testumgebung eingebunden werden kann. Zudem liefert die getActionClass-Methode die konkrete Klasse der Action, für die der Handler verantwortlich ist. Auch dazu später…
public interface ActionHandler<A extends Action<R>, R extends Result> { /** * Handles an action and returns the appropriate result * * @param action The action to handle * @return The result of the action * @throws Exception If an action could not be executed */ public R execute(A action) throws Exception; /** * @return The class of the action this handler is responsible for. */ public Class<A> getActionClass(); }
Nun benötigen wir eine Instanz, die darüber entscheidet, welche Action, von welchem ActionHandler bearbeitet wird. Dafür habe ich mir den Dispatcher ausgedacht. Der Dispatcher führt eine interne Liste über die verfügbaren ActionHandler und delegiert jede Action zu dem entsprechenden Handler, sofern denn einer verfügbar ist.
public interface Dispatcher { /** * Executes an action and returns the result * * @param action The action to execute * @param <R> The result type * @param <A> The action type * @return The result * @throws DispatchException If an action raises an exception */ public <R extends Result, A extends Action<R>> R execute(A action) throws DispatchException; }
Passend dazu existiert auch eine asynchrone Variante für den Client. (Es ist nichts ärgerlicher als eine blockierende UI.) Der Unterschied liegt in der Rückgabe des Ergebnisses. In der Asynchronen Variante wird das Ergebnis(oder der Fehler) in einem Callback zurück gegeben.
public interface AsyncDispatcher { /** * Executes an action and returns the result * * @param action The action to execute * @param callback The callback with the result * @param <R> The result type * @param <A> The action type */ public <R extends Result, A extends Action<R>> void execute(A action, AsyncCallback<R> callback); }
Zu guter letzt habe ich auch noch einen konkreten Anwendungsfall modelliert und zwar die Abfrage nach der Schnittstellen-Version.
// GotVersion.java public class GotVersion implements Result { private Version version; ... } // GetVersion.java public class GetVersion implements Action<GotVersion> { public Class<GotVersion> getResultClass() { return GotVersion.class; } } // MockGetVersionHandler.java public class MockGetVersionHandler implements ActionHandler<GetVersion, GotVersion> { public GotVersion execute(GetVersion action) throws Exception { return new GotVersion(new Version("1.0")); } public Class<GetVersion> getActionClass() { return GetVersion.class; } }
Damit ist die Arbeit an der Schnittstelle fürs erste abgeschlossen. Die Erweiterung findet durch die Implementierung von jeweils Action + Result + ActionHandler statt. Danach zähle ich die Version in der pom.xml hoch, installiere das Modul in meinem Maven Repository und damit steht es dem Server und Client zur Verfügung.
Server – Grails
Der Server ist eine Grails Anwendung. Damit die Kommunikation mit dem Client funktioniert muss ich drei Dinge tun:
- Einen Dispatch Service implementieren
- Dem Dispatch Service alle Handler mitgeben
- Einen Controller implementieren, der die JSON Nachricht verarbeitet und an den Dispatch Service delegiert.
Die Implementierung der Dispatcher Schnittstelle ist denkbar einfach. Es wird eine Liste der verfügbaren ActionHandler im Konstruktor erwartet. Sie werden in einer Map auf die Klasse der Action abgebildet, für die der ActionHandler verantwortlich ist. Wird die execute-Methode aufgerufen, sucht sich der Service den passenden Handler raus und delegiert den Aufruf.
class DispatchService implements Dispatcher { def handlers = [:] def DispatchService(handlerList = []) { handlerList.each { handler -> handlers.put(handler.actionClass, handler) } } Result execute(Action action) { def handler = handlers.get(action.getClass()) if (handler == null) { throw new DispatchException("Handler not found for action class " + action.getClass()) } try { return handler.execute(action) } catch (Exception e) { throw new DispatchException(e) } } }
Die zu verwendenden ActionHandler konfiguriere ich über die config/spring/resources.groovy
beans = { /** * The service that delegates the action requests to the responsible handler instances. * To extend the service, just instantiate a new handler bean and put it in the list */ dispatchService(DispatchService, [ new MockGetVersionHandler() ]) }
Schließlich ist der Controller zu implementieren, der die JSON Nachricht verarbeitet. Da aus dem JSON String nicht erkennbar ist, welche Action gerade übertragen wird, schickt der Client den voll qualifizierten Klassen Namen als Parameter mit. Ich kenne keine Bibliothek, die so einfach wie GSON funktioniert, wenn es um die (De)serialisierung von JSON Daten geht. Um eine Action zu deserialisieren, benötigt GSON die konkrete Klasse der Action. Diese lädt der Controller über Class.forName()
package de.fivews.mdi.server.controller import com.google.gson.Gson class DispatchController { static packagePrefix = "de.fivews.mdi" def dispatchService def gson = new Gson() /** * Interface entry point for mobile data requests. * Expects the following parameters: * - a The full qualified action class name * - d The JSON data that will be transformed into an instance * of the action class */ def index = { /* Action package must start with de.fivews.mdi */ if (params.a && !params.a.startsWith(packagePrefix)) { render(text: gson.toJson(new Error(error: 'action not supported'))) } /* Is JSON data given? */ else if (!params.d) { render(text: gson.toJson(new Error(error: 'data missing'))) } /* Prerequisites are given. Continue processing... */ else { try { def actionClass = params.a ? Class.forName(params.a) : null /* Is action name given? */ if (!actionClass) { render(text: gson.toJson(new Error(error: 'action missing'))) } /* Process request and returns the result as JSON text */ else { render text: processRequest(params.d, actionClass) } } catch (Exception e) { /* Don't show too much information */ render(text: gson.toJson(new Error(error: "unexpected error"))) } } } /** * Handles the given JSON payload by decoding the action and executing * the action request. The result will be encoded as JSON and returned * * @param data The JSON payload * @param actionClass The action class of the JSON payload * @return The result as JSON */ private def processRequest(d, actionClass) { def action = gson.fromJson(d, actionClass) def result = dispatchService.execute(action) gson.toJson(result) } /** * An instance of this class will be returned if an error is raised */ private static class Error { def String error; } }
Sobald der Request im index-Closure angekommen ist, überprüft der Controller ob die Daten (params.d) und der Klassen-Name(params.a) der Action die Voraussetzungen erfüllen. Wenn nicht, erzeugt er eine Fehlermeldung. Soweit alles passt, lädt der Classloader mit Class.forName() die Klasse der Action und der Controller deserialisiert mit Hilfe von GSON die Nachricht in die entsprechende Action. Der Dispatcher delegiert die Action an den verantwortlichen ActionHandler und liefert das Ergebnis an den Controller, der das Ergebnis wieder nach JSON serialisiert und an den Client liefert. Puhh
Client – Android
An dieser Stelle ist die Arbeit schon fast erledigt. Auf der Clientseite ist es ratsam eine asynchrone Variante des Dispatcher's zu nutzen. So wird die Anwendung nicht blockiert. Meine Android-Activity sieht deshalb wie folgt aus:
... public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // URL = "http://localhost:8080/server/dispatch" final AsyncDispatcher dispatcher = new HttpAsyncDispatcher(URL, new DefaultHttpClient()); dispatcher.execute(new GetVersion(), new AsyncCallback<GotVersion>() { public void onSuccess(GotVersion result) { Log.i(TAG, "result = " + result); } public void onError(Throwable throwable) { Log.e(TAG, "throwable = " + throwable, throwable); } }); } ...
Nichts Spektakuläres… Eine neue Instanz der HttpAsyncDispatcher Klasse erzeugen und die GetVersion-Action abfeuern. Sobald der Server eine Antwort schickt, ruft der Dispatcher eine der beiden Methoden im AsyncCallback auf. Zu guter letzt die Implementierung des asynchronen Dispatchers:
public class HttpAsyncDispatcher implements AsyncDispatcher { private final BlockingQueue<Runnable> queue; private final ThreadPoolExecutor executor; private final HttpClient client; private final String url; public HttpAsyncDispatcher(String url, HttpParams params) { this(url, new DefaultHttpClient(params)); } public HttpAsyncDispatcher(String url, HttpClient client) { /* The queue contains all tasks this dispatcher should handle */ this.queue = new LinkedBlockingQueue<Runnable>(); /* A thread pool for processing the tasks in the queue */ this.executor = new ThreadPoolExecutor(2, 6, 10000, TimeUnit.MILLISECONDS, queue); /* Server target url */ this.url = url; /* An instance of org.apache.http.client.HttpClient */ this.client = client; } public <R extends Result, A extends Action<R>> void execute(final A action, final AsyncCallback<R> callback) { /* Creates a new Task and add it to the queue */ executor.execute(new Runnable() { public void run() { try { /* The action and data parameters that will be processed by the server */ final List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("a", action.getClass().getName())); params.add(new BasicNameValuePair("d", new Gson().toJson(action))); final UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8"); /* Create the post request an set the message payload */ final HttpPost post = new HttpPost(url); post.setEntity(entity); /* Executes the request and wait for the response */ final HttpResponse httpResponse = client.execute(post); final String content = EntityUtils.toString(httpResponse.getEntity()); /* Decode the json request and execute the callback */ final R result = new Gson().fromJson(content, action.getResultClass()); callback.onSuccess(result); } /* In case something went wrong */ catch (Exception e) { callback.onError(e); } } }); } }
Da der Aufruf auf dem HttpClient synchron abläuft, habe ich mir überlegt einen Thread Pool zu nutzen und die auszuführenden Aufgaben in eine Queue zu schreiben, die vom Thread Pool abgearbeitet wird. (Was genau in dem Service passiert, bitte ich den Kommentaren im Quelltext zu entnehmen.)
Fazit: Allgemein bin ich mit meiner Lösung zufrieden. Einmal geschrieben, kann sie schnell erweitert werden. Auch ist sie nicht besonders komplex. Was einwenig Disziplin erfordert ist das Design der Beans, die zwischen Client und Server ausgetauscht werden. Es gibt keine Saubere Möglichkeit zyklische Beziehung zwischen einzelnen Objekten in JSON abzubilden. Deshalb würde ich persönlich mit eher flachen Strukturen arbeiten und keinen komplexen Objektgraphen übertragen. Das ist natürlich von Fall zu Fall unterschiedlich, aber ich muss bedenken, dass ich mit JSON keine Referenzen auf Objekte übertragen kann und ein doppelt referenziertes Objekten u.U. zwei mal erzeugt wird. Damit kann das Objekt auf der Clientseite 2x existieren.
Gestern Abend sprach mit meiner Bruder über die vielen Ideen, die wir in den letzten Jahren hatten. Manche von denen sind gleich in Vergessenheit geraten und manche habe ich heute noch im Kopf. Aber wirklich viele davon, habe wir nicht umgesetzt. Um ehrlich zu sein nur wenige. Es ist dieses Gefühl, wenn eine Idee geboren wird und bei einem guten Gespräch wächst, das ich wirklich spannend finde. Zu beginn nur eine Waage Vorstellung, dann immer detaillierter und irgendwann eine finale Version mit dem Nachsatz:
So machen wir es.
Wenn es dann an die Arbeit gehen soll, fühlt sich es wie ein Rückschlag an. In der Realität kommen dummerweise einige “Vorher muss ich noch…” ‘s oder “Gleich nachdem…” ‘s zuvor und das stark brennende Feuer von eben wird mit einem kräftigen Schank Wasser abgeschreckt. Ich dem Augenblick wünschte ich mir mich klonen zu können, damit sich mein geklontes Ich voll und ganz auf das neue Projekt konzentrieren kann. Keine Arbeit bei der er jeden Morgen antreten, keine Familie und Freunde um die er sich kümmern muss, und auch keine aktuell laufenden Projekte, die einen beträchtlichen Teil seiner Zeit in Anspruch nehmen. Mit der Voraussetzung müsste es mein Klon packen.
Im Moment kann ich keine Klone erschaffen und muss mir daher anders helfen. Interessant fand ich einen Blog-Eintrag, in dem sich jemand für eine Ideenbörse ausgesprochen hat. Er empfand es als schade, dass viele gute Ideen verloren gehen, weil die Menschen nicht die Zeit oder nicht den Ehrgeiz besitzen diese umzusetzen. Wieso also nicht mit jemandem teilen und der Idee eine Chance geben verwirklicht zu werden? Mein erster Gedanke war: “Ja klar, damit sich jemand mir meiner Idee die Taschen vollstopfen kann”.
Heute denke ich ganz anders darüber.
- Nicht jede Idee hat Potenzial. Sie kann ganz nett sein, aber ohne einen echten Zuspruch, der sich erst in der Praxis zeigt, leider nicht überlebensfähig.
- Im Nachhinein bin ich immer schlauer. Wenn also jemand meine Idee umsetzen sollte, können zwei Dinge passieren. Entweder die Umsetzung wird erfolgreich, oder nicht. Wenn sie erfolgreich wird, dann ärgere ich mich über mich selbst, weil nicht ich die Idee umgesetzt habe. Sollte sie kein Erfolg haben, dann tue ich so, als ob ich es geahnt und deshalb mit dem Nichtstun richtig gelegen habe.
- Wir leben den Kapitalismus. Wir werden dazu erzogen, dass mit jeder unser Bewegung, jeder unserer Tat, Geld verbunden ist – egal ob Einnahme oder Ausgabe. Dadurch herrscht eine – Jeder ist sich selbst der Nächste – Mentalität. Das hat einen interessanten Effekt, der mir erst kürzlich bewusst wurde: Wenn ich jemanden eine Idee erzähle, und der wirtschaftliche Aspekt nicht deutlich wird, dann kommt in 90% der Fälle die Frage: “Und wie verdienst du damit Geld?”. Wenn ich mit “Ich weis es noch nicht” antworte, dann ist die Idee schlecht.
In meiner Vorstellung einer Ideenbörse sind also auch Ideen erlaubt, die nicht sofort die Millionen bringen – oder auch gar nicht. Denn genau genommen ist eine Idee eine potenzielle Lösung für ein vorangegangenes Problem. Sie ist das Ergebnis einer Kette von Gedankengängen, die sich ein Mensch auf der Suche nach einer Lösung baut.
Deshalb würde ich auch mal behaupten, dass der Satz “Man muss nur die richtige Idee haben..” nichts als eine bequeme Ausrede für jeden ist, der Angst vor der Arbeit hat. Denn, Probleme hat ja wahrscheinlich jeder.
Ich baue derzeit an einer Webanwendung in Grails und bin an einem Punkt angekommen, an dem ich unheimlich gern auf GWT zurückgreifen würde. Ich sehe in beiden Technologien viele Vorteile und würde nicht behaupten, dass es sich zwischen den beiden entscheiden sollte. Vielmehr denke ich, dass sich Grails und GWT sehr gut ergänzen können.
Es existiert ein Grails / GWT Plugin mit dem es möglich ist GWT im Frontend und Grails im Backend zu verwenden. Aber es nicht komplett das was ich suche. Denn ich will keine reine GWT-Applikation bauen. Was mir vorschwebt ist eine vollfunktionsfähige, in Grails geschriebene Webseite, die ohne JavaScript auskommt. Auch wenn die breite Masse der Browser JavaScript unterstützt, gibt einige Benutzergruppen die nicht von der clientseitigen Dynamik profitieren können – Nutzer eines Screen-Readers z.B.
Meine Idee ist es nun die Anwendung in Grails zu schreiben und die zusätzliche clientseitige Funktionalität durch GWT zu ergänzen. Ich habe mir ein Szenario ausgedacht, das wie ich denke, ziemlich deutlich macht, was ich will: Meine Webanwendung stellt eine Liste von POI’s dar. Also geo-referenzierte Punkte, die bestimmte Lokalitäten markieren. Ein Klick auf einen der POI Namen öffnet mir eine Karte mit einem Marker auf der geografischen Position des POI’s. Je nach dem ob JavaScript aktiviert ist oder nicht, erscheint die Karte als ein in JS erstelltes Popup oder es folgt der Verweis auf eine seperate URL, auf der eine statische Karte gezeigt wird.
Für das Beispielprojekt verwende ich Grails 1.2.2, GWT 2.0.3, GWT-Query 1.0.0, GWT-Maps 1.0.4, Maven2 und schreibe den Code in IntelliJ Idea. Die Grails Anwendung erzeuge ich mir mit Hilfe des Grails-Maven-Plugins:
$> mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate \
-DarchetypeGroupId=org.grails\
-DarchetypeArtifactId=grails-maven-archetype \
-DarchetypeVersion=1.2.2 \
-DgroupId=org.example \
-DartifactId=gwt-with-grails
$> cd gwt-with-grails
$> mvn initializeDie erzeuge pom.xml beinhaltet ein Minimum an Konfiguration und zumindest in meinem Fall musste ich aufgrund einiger Exceptions noch einige Abhängigkeiten nach pflegen.
<dependency> <groupId>org.grails</groupId> <artifactId>grails-test</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.8</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency>
Laut der Maven Konvention gehören die Quellen in das ${basedir}/src/main/java Verzeichnis, in dem ich mir das org.example.gwt.share Package anlege und eine Poi Klasse definiere. Der Aufbau ist denkbar einfach: Eine eindeutige Id, ein Name und die geografischen Koordinaten.
public class Poi { private final Long id; private String name; private double lat; private double lng; /* Konstruktoren, getter und setter */ ... }
Der Einfachheit halber arbeite ich in diesem Beispiel mit Dummy-Daten und erzeuge mir ein Grails Controller mit einigen, statischen POI’s. In dem index Closure wird die index View gerendert und alle POI’s an die View weitergegeben.
class PoiController { static def POIS = [:] static { POIS[1] = new Poi(1, "Hannover", 52.423, 9.72) POIS[2] = new Poi(2, "Dortmund", 51.516, 7.43) POIS[3] = new Poi(3, "Dresden", 51.048, 13.75) POIS[4] = new Poi(4, "Stuttgart", 48.800, 9.20) } def index = { render view: 'index', model: [pois: POIS.collect { k, v -> v }] } ... }
Damit ich zu einem späteren Zeitpunkt auf die Daten der einzelnen POIs zugreifen kann, hinterlege ich mir die wichtigen Informationen im Markup – und zwar in versteckten Feldern, denen ich die display:none; CSS Eigenschaft über eine Klasse zuweise. Ich bin gerade dabei zu evaluieren, wie man Elemente vor Screen-Readern versteckt…
Die versteckten Elemente benötige ich später, um von GWT aus an die wirklich interessanten Informationen zu gelangen. Diese Vorgehensweise erscheint mir besser zu sein, als inline-JavaScript zu platzieren und damit den Code über mehrere Views zu verteilen. Um den POI Namen habe ich ein Link gezogen, der auf die show-Action des selben Controllers verweist. Bei einem Klick auf den Link wird eine statische Karte mit der Position des POI’s gezeigt. Dieser Link spielt später noch eine wichtige Rolle.
<%@ page contentType="text/html;charset=UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <meta name="layout" content="poi" /> <title>Some interesting Poi's</title> ... </head> <body> <g:each in="${pois}" var="poi"> <div class="poi"> <span class="hidden id" >${poi.id}</span> <span class="hidden name">${poi.name}</span> <span class="hidden lng" >${poi.lng}</span> <span class="hidden lat" >${poi.lat}</span> <g:link class="link" controller="poi" action="show" id="${poi.id}"> ${poi.name} </g:link> </div> </g:each> </body> </html>
Klickt der Anwender auf den Link, landet der Request in der show-Action. Hier wird anhand des POI’s der Link zu dem Google Static Maps Server gebaut und zusammen mit dem POI an die View übergeben – die entsprechend schlicht gestrickt ist.
class PoiController { ... static def KEY = "MY_GOOGLE_MAPS_KEY" def show = { def poi = POIS[params.id ? params.id.toInteger() : 1] def link = new StringBuffer() link << "http://maps.google.com/staticmap" link << "?zoom=13" link << "&size=512x512" link << "&markers=${poi.lat},${poi.lng},blue" link << "&key=${KEY}" link << "&sensor=false" render view: 'show', model: [link: link, poi: poi] } }
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <meta name="layout" content="poi" /> <title>${poi.name}</title> </head> <body> <img src="${link}" alt="Map of ${poi.name}" title="${poi.lng},${poi.lat}" /> </body> </html>
Nun starte ich die Grails Anwendung über den grails:run-app Goal und sehe im Browser wie erwartet eine Liste der POI’s. Klicke ich auf einen der Einträge, wird die statische Karte als Bild im Browser geladen. Bis hier verhält sich meine Anwendung wie gewollt und ein Anwender mit deaktiviertem JavaScript wird die Anwendung bedienen können.
Nun geht es darum den Klick-Handler des zuvor erwähnten Links mit JavaScript zu überschreiben. Nicht die show-Action soll aufgerufen werden, sondern es soll ein mit JavaScript erzeugtes Popup erscheinen, in dem eine dynamische Google Maps Karte mit einem Marker geladen ist.
Ich erweitere meine pom.xml Datei um die GWT-Abhängigkeiten:
... <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-servlet</artifactId> <version>2.0.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-user</artifactId> <version>2.0.3</version> </dependency> ... <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>gwt-maven-plugin</artifactId> <version>1.2</version> </plugin> ...
Nachdem das erledigt ist, erstelle ich eine neues GWT Modul und nenne es org.example.gwt.Poi. Hier ist drauf zu achten, dass die Java-Quellen für Client und Server sich im ${basedir}/src/main/java-Verzeichnis und die Modulbeschreibungen *.gwt.xml im ${basedir}/src/main/resources-Verzeichnis befinden. So sieht momentan meine Verzeichnisstruktur aus:

(Übrigens, die HTML und CSS Datei, die für ein GWT Modul normalerweise erzeugt wird, benötigen wir nicht.) In meinem GWT Modul habe ich mir ein EntryPoint erstellt und es unter PoiEntryPoint gespeichert. Nun kommt ein wichtiger Schritt. Das GWT Modul muss einmal übersetzt werden. Dafür rufe ich den gwt:compile Goal auf und kopiere anschliessend das Kompilat, das sich irgendwo unter ${basdir}/target befindet nach ${basedir}/web-app/js. Das muss nur ein mal gemacht werden, damit der Browser die Einstiegs-JavaScript Datei findet und die GWT Anwendung sich auf dem gewohnten Weg starten und debuggen lässt. Die einzelnen Schritte können auf der GWT Seite nachgelesen werden.
In Grails muss ich nun dafür sorgen, dass der Einstiegspunkt in die GWT Anwendung geladen wird. Das ist die org.example.gwt.Poi.nocache.js Datei, die ich zuvor kopiert habe. Es bietet sich an diesen Import in einer Layout-View durchzuführen.
<html> <head> <title><g:layoutTitle default="My POIs"/></title> <g:layoutHead/> </head> <body> <g:layoutBody/> <!-- Wichtiger Schritt --> <g:javascript library="org.example.gwt.Poi/org.example.gwt.Poi.nocache"/> </body> </html>
Starte ich meine Grails-Anwendung erneut, muss der Browser die org.example.gwt.Poi.nocache.js Datei geladen haben. Dafür am besten mal mit dem Safari Inspector oder Firebug den Inhalt der Datei betrachten. Befindet sich minifizierter JavaScript Code im Inhalt, ist alles gut gegangen.
An dieser Stelle ist die Brücke zu GWT bereits geschlagen und ich kann mich an die Implementierung des EntryPoints machen. Für GWT existiert ein jQuery Nachbau, mit dem es unheimlich einfach ist Elemente aus dem DOM abzugreifen und Operationen wie Event-Handler, Animationen oder CSS auf den Elementen durchzuführen. Das Modul heißt GwtQuery. (Ich habe das Modul in mein lokales Maven Repository installiert, weil ich kein öffentliches Repository fand, aus dem ich das ziehen konnte.)
public class PoiEntryPoint implements EntryPoint { private MapWidget map = null; private PopupPanel mapsPanel; public void onModuleLoad() { $(".poi").each(new Function() { public void f(Element e) { final Long id = Long.parseLong($(e).find("span.id").text()); final Double lat = Double.parseDouble($(e).find("span.lat").text()); final Double lng = Double.parseDouble($(e).find("span.lng").text()); final String name = $(e).find("span.name").text(); $(e).find("a.link").click(new Function() { public boolean f(Event e) { displayPoi(new Poi(id, name, lat, lng)); return false; } }); } }); } ...
In dem EntryPoint iteriere ich über alle DIV-Container mit der Klasse poi und lese die versteckten Informationen aus. Der Klick-Handler des Links, der eigentlich auf die show Action verweist überschreibe ich und sorge mit dem return false; dafür, dass das Ziel nicht angesprungen wird. Stattdessen soll der POI auf einer Karte in einem Popup-Dialog erscheinen.
Dadurch, dass meine Grails Anwendung bereits in einem Container läuft und mir die HTML Seiten mit Inhalt rendert, benötige ich keinen weiteren Server, den GWT im Normalfall starten würde. Deshalb erstelle ich mir eine GWT Konfiguration mit der -noserver Direktive und übergebe als Start-URL die URL meines Grails-Servers. In meinem Fall die http://localhost:8080/gwt-with-grails/poi/index
Nun setzte ich in dem EntryPoint einen Breakpoint, starte die GWT Konfiguration und öffne den Browser. Alles läuft gut, wenn in der onModuleLoad Methode über meine POI Elemente iteriert wird und die Klick-Handler erfolgreich überschrieben sind.
Der Rest des Codes ist trivial, aber ich poste ihn der Vollständigkeit wegen:
... private void displayPoi(final Poi poi) { loadGoogleMaps(new MapsCallback() { public void doWithMap(MapWidget mapWidget) { final LatLng coords = LatLng.newInstance(poi.getLat(), poi.getLng()); map.clearOverlays(); map.setCenter(coords); map.setZoomLevel(12); map.addOverlay(new Marker(coords)); map.getInfoWindow().open(map.getCenter(), new InfoWindowContent(poi.getName())); mapsPanel.show(); } }); } private void loadGoogleMaps(final MapsCallback mapsCallback) { if (map != null) { mapsCallback.doWithMap(map); } else { mapsPanel = new PopupPanel(true, true); mapsPanel.setGlassEnabled(true); mapsPanel.setWidget(new Label("Loading...")); mapsPanel.setWidth("500px"); mapsPanel.setHeight("500px"); mapsPanel.setPopupPositionAndShow(new PopupPanel.PositionCallback() { public void setPosition(int offsetWidth, int offsetHeight) { int left = (Window.getClientWidth() - offsetWidth) / 2; int top = (Window.getClientHeight() - offsetHeight) / 2; mapsPanel.setPopupPosition(left, top); } }); Maps.loadMapsApi("", "2", false, new Runnable() { public void run() { map = new MapWidget(); map.setSize("100%", "100%"); map.addControl(new LargeMapControl()); mapsPanel.setWidget(map); mapsCallback.doWithMap(map); } }); } } private interface MapsCallback { public void doWithMap(MapWidget mapWidget); }
Klicke ich nun auf einen POI Namen, öffnet sich ein modaler Dialog mit der Karte und dem Marker.
In meiner App möchte ich ein Diagramm darstellen und benötige dafür eine Möglichkeit 2D Grafiken zu erzeugen. Die API von Titanium bietet derzeit keine Möglichkeit dies zu tun, weshalb ich mir überlegt habe WebKit zu nutzen und dann auf einem Canvas zu zeichnen. Die ersten Versuche im iPhone Simulator waren sehr vielversprechend, aber leider wird der Code auch mit der Geschwindigkeit meines MacBooks ausgeführt. Die Performanz, mit der die Diagramme auf dem Gerät erzeugt werden, ist eher ernüchternd.
Bevor ich auf die einzelnen Werte eingehe, erkläre ich erstmal einen möglichen Weg Diagramme mit Titanium darzustellen. Meine Ordnerstruktur sieht wie im folgenden Bild aus:

Die Titanium Skripte liegen bei mir alle in der Wurzel. In diesem Fall ist es nur die app.js. Sie ist einfach aufgebaut und erzeugt nur eine WebView, in der eine URL geladen wird.
1 2 3 4 5 6 7 8 9 10 | var window = Ti.UI.createWindow({ orientationModes: [Ti.UI.LANDSCAPE_RIGHT] }); var webkit = Ti.UI.createWebView({ url: 'web/chart.html' }); window.add(webkit); window.open(); |
Die WebView Komponente lädt eine lokale HTML Seite. Die HTML Seiten befinden sich in dem Ordner web und relativ dazu liegen die Skripte und die Stylesheets. Ein wesentlicher Unterschied zu einer normalen Webanwendung ist die Benennung der JavaScript Dateien. Sie enden bei mir mit *.javascript. Titanium liest in seinem Build-Vorgang rekursiv alle Dateien mit der Endung *.js ein und nimmt an, dass dies Titanium Skripte sein müssen. Der Parser geht strikt vor und erlaubt keine Fehler. In Bibliotheken wie jQuery oder Prototype fehlt aber ab und an ein Semikolon. Damit kommt Titanium nicht klar. Eine Möglichkeit das Problem zu umgehen ist die Umbenennung der Endung. Auf diese Weise müssen externe Bibliotheken nicht in die HTML Seiten eingebettet werden.
Wie zu sehen ist, nutze ich für dieses Beispiel Flot. Ein jQuery Plugin zur Erzeugung von Diagrammen. Dafür muss zuerst jQuery, dann Flot, dann mein Code geladen werden. Die HTML Datei, die die WebView Komponente also lädt, sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 | <html> <head> <link rel="stylesheet" type="text/css" href="css/style.css" /> <script type="text/javascript" src="scripts/jquery.min.javascript"></script> <script type="text/javascript" src="scripts/jquery.flot.min.javascript"></script> <script type="text/javascript" src="scripts/chart.javascript"></script> </head> <body> <!-- Render the chart in this container --> <div id="placeholder"></div> </body> </html> |
Was nun fehlt ist die Brücke zwischen Flot in der WebView und Titanium. Das Schöne ist, dass die WebView eine Methode bereitstellt, mit der sich JavaScript Code evaluieren lässt: evalJS. Ich bevorzuge den JavaScript Code, der für die WebView gedacht ist, an einer Stelle zu halten und habe daher mir die chart.javascript angelegt:
1 2 3 4 5 6 7 8 9 10 | function runChartDemo() { var d1 = []; for (var i = 0; i < 14; i += 0.5) { d1.push([i, Math.sin(i)]); } var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]]; var d3 = [ [0, 12], [7, 12], null, [7, 2.5], [12, 2.5]]; $.plot($("#placeholder"), [ d1, d2, d3 ]); } |
Die runChartDemo() Funktion steht mir spätestens dann zu Verfügung, wenn die WebView geladen und Einsatzbereit ist. Und das bekomme ich in Titanium durch einen EventListener mitgeteilt. Nebenbei: In den Docs steht geschrieben, dass entweder nur die Titanium Events oder die Events in dem JavaScript Universum der WebView genutzt werden sollen. Nach Möglichkeit nicht gemischt. Auf jeden Fall kann ich nun in der app.js das load-Event der WebView abgreifen und meine Funktionen ausführen:
1 2 3 | webkit.addEventListener('load', function() { webkit.evalJS('runChartDemo()'); }); |
Starte ich Titanium, sehe ich das folgende Ergebnis:
Nun zu den Nachteilen. Es ist langsam. Zumindest so langsam, dass es mir als Anwender keinen Spass machen wird, auf die Grafik zu warten. Alleine die nackte WebView wird auf meinem iPhone 3G in ca. 1000ms erzeugt. Dann wird jQuery und Flot geladen. Wie lange das dauert, hängt davon ab, ob es zum ersten mal in der App passiert oder zum x-ten mal (das iPhone scheint das zu cachen). Ich habe Werte zwischen 2000ms und 8000ms gehabt. Dann wird das Diagramm erzeugt. Ist es ein einfaches, dauert die Erzeugung 800ms, bei einem komplexeren mit vielen Daten auch schonmal 2000ms. Soll in der Summe heissen, dass diese Lösung im besten Fall nach 3500ms und im schlimmsten Fall nach 11000ms ein Diagramm zeigt.
Ich habe einige Diagramm-Bibliotheken gefunden, aber die meisten, die von der Optik in Betracht kommen, basieren auf einer anderen Bibliothek wie jQuery, MochiKit oder MooTools. Für mich persönlich sind 3500ms akzeptabel, aber 11000ms nicht. Deshalb denke ich, dass noch einiges an Forschungsbedarf auf mich zukommt.
Christian hat in einem Artikel das Titanium Framework von Appcelerator erwähnt. Ich kannte es vorher nicht und habe es mit die letzten Tage einwenig näher angesehen. Aktuell habe ich ein konkretes Projekt, das ich eigentlich wieder mit GWT umsetzen wollte, aber Titanium scheint mir eine echte, und vielleicht sogar bessere Alternative zu sein.
In Titanium wird der Programmcode in JavaScript geschrieben und als nativer Code auf der jeweiligen Platform (Android oder iPhone) ausgeführt. Das ist der große Vorteil. Während ich den Code nur einmal schreibe, ist er dank dem Framework auf mehreren Plattformen lauffähig.
Titanium übernimmt dabei die Rolle eines Proxies. Während bei GWT der Java Code von einem echten Cross-Compiler in JavaScript Code übersetzt wird, interpretiert Titanium den JavaScript Code auf der jeweiligen Plattform und delegiert die Funktionsaufrufe an Module. Diese Module setzen dann die Funktionsaufrufe nativ um. An sich kein schlechter Ansatz. Doch der Code wird interpretiert, deshalb ist er nicht sonderlich schnell.
Das Ergebnis ist, dass mit relativ wenig Code Standardfunktionalität umgesetzt werden kann. Standard deshalb, weil natürlich nur die Funktionen genutzt werden können, die dem Titanium Framework als Modul zu Verfügung stehen. Möchte der Entwickler Funktionen nutzen, die es in Titanium nicht gibt, kann er eigene Module implementieren und damit Titanium erweitern.
Ich habe beim Entwickeln sowohl ein freudiges Grinsen als auch Verzweiflung im Gesicht gehabt und bis jetzt bin ich mir immer noch nicht schlüssig.
Vorteile
- Schnelle Erfolgserlebnisse und Ergebnisse
- Code ist komplett in JavaScript. Daher kein Erlernen einer neuen Sprache
- Native Anwendung
- Großer Funktionsumfang, viele Module
- Automatisches Deployment auf das Gerät und den Simulator
- Schnelles Bauen, da nur wenig übersetzt werden muss
- Konsole mit Log-Ausgaben
Nachteile
- Keine Möglichkeit zum Debuggen Ich schreibe den Code in IDEA, wechsele dann in den Titanium Developer und führe mein Programm aus. Um den Code zu Debuggen, müsste es eine Integrationsmöglichkeit für die IDE geben. Da Titanium noch sehr jung ist, kann es aber durchaus sein, dass dies bald kommt.
-
Wenig Transparenz Bis auf die Log-Ausgaben sehe ich leider nicht, was mein Programm macht. Manchmal ist mein Simulator abgestürzt oder startete das Programm nicht. In vielen Fällen gab es keinen Anhaltspunkt zu der Ursache. Ein
exit(null)reicht als Fehlerbeschreibung nicht. -
Hohe Fehlertoleranz, wenig Feedback In Titanium werden statische “create” Methoden genutzt, um Instanzen anzulegen. Diese Instanzen werden durch Parameter konfiguriert. z.B.
1 2 3 4 5 6
var view = Titanium.UI.createView({ borderRadius:10, backgroundColor:'red', width:50, height:50 });
Titanium schluckt fast alles. Es gibt leider keine Überprüfung darüber, ob ein Parameter erlaubt ist, oder ob er einen gültigen Wert bekommen hat. Dadurch weis ich als Entwickler nicht, aber ich das Objekt falsch konfiguriert habe, oder ob der Fehler eine andere Ursache hat.
- Organisation von JavaScript Dateien in Ordnern ist zwar möglich, bringt aber Probleme mit sich. Ich habe meine “Views” in einem Unterordner liegen gehabt. Relativ zu der
app.js. Die Dateien wurden alle richtig gefunden und interpretiert. Als ich das Programm jedoch ausgeführt, von einem Fenster zu einem anderen und wieder zurück, navigiert habe, wurde auf einmal der Pfad zu der JavaScript Datei falsch zusammengebaut. Daraufhin stürzte mein Programm ab. - Performanz Obwohl die Ausführungszeit nicht schlecht ist, gibt es einige Stolperfallen. Beispiel TableView. In der
createTableViewMethode wird ein Array mit den Zeilen der Tabelle übergeben. Diese Zeilen sollten zuvor erzeugt und dann der TableView auf einen Schlag übergeben werden – was auch klappt. Leider ist die Erzeugung der Zeilen in einer Schleife alles andere als performant. Handelt es sich um einfache Texte, dann geht dies relativ zügig. In meinem Fall bestand eine Zeile aus einer View mit drei Labels. Für die Erzeugung von nur 25 Zeilen mit statischem Inhalt hat auf meinem iPhone 3g 2-3 Sekunden gedauert. Kommen die Daten von einem Server, muss die Latenz und Serverzeit addiert werden. Ich kann mir vorstellen, dass sich dieser Wert verdoppeln kann.
Insgesamt kann ich aber sagen, dass die Vorteile knapp überwiegen, so dass ich auf jeden Fall meine aktuelle App mit Titanium realisieren werde.
Um die Entfernung zweier Objekte in Metern zu berechnen eignet sich die distance Funktion leider nicht. Denn die Entfernung wird in der Einheit berechnet, in der die Punkte gespeichert sind. In diesem Fall als Lat/Lng Koordinaten, also Winkel in Grad. Das die Kalkulation nicht aufgeht, zeigt ein simples Experiment:
SELECT distance(geometry('POINT(0 -170)'), geometry('POINT(0 170)')) SELECT distance(geometry('POINT(0 -160)'), geometry('POINT(0 160)'))
Das Ergebnis der ersten Berechnung ist 340 und das der zweiten 320. Mathematisch richtig, bezogen auf unsere Erde aber leider falsch. Denn die Punkte der zweiten Berechnung liegen in der Realität – auf dem Globus betrachtet – weiter auseinander als die Punkte der ersten Kalkulation. Das liegt daran das die Punkte -180/0 und +180/0 ein und denselben Punkt markieren. Denn es handelt sich bei unserem Planeten um eine Kugel.
Um nun die Entfernung zweier Punkte zu einander zu berechnen, wird die kürsteste Strecke auf einer Kugel ermittelt. Mehr dazu unter Orthodrome
Die Implementierung sieht so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class GeoUtils { private static final double RADIUS = 6378137.0d; /** * Calculates the distance between two points in meters and returns * the value as result. * * @param p1 The first point * @param p2 The second point * @return The distance in meters */ public static double distance(Point p1, Point p2) { double lng1 = p1.getX() * Math.PI / 180; double lat1 = p1.getY() * Math.PI / 180; double lng2 = p2.getX() * Math.PI / 180; double lat2 = p2.getY() * Math.PI / 180; return RADIUS * Math.acos( Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) ); } } |
Postgis bietet dafür die distance_sphere Funktion. Leider definiert Hibernate Spatial diese Funktion nicht im Dialekt. Das führt dazu, dass die Funktion per native SQL aufgerufen werden muss – möchte man sie nutzen.
Die einfachste Möglichkeit dies zu umgehen ist die Erweiterung des Postgis Dialektes:
1 2 3 4 5 6 7 8 9 10 | import org.hibernate.Hibernate; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernatespatial.postgis.PostgisDialect; public class MyPostgisDialect extends PostgisDialect { public MyPostgisDialect() { super(); registerFunction("distance_sphere", new StandardSQLFunction("distance_sphere", Hibernate.DOUBLE)); } } |
Die Verwendung ist dann in HQL denkbar einfach. In dem folgendem Beispiel habe ich eine Funktion definiert, die mir zu einem bestimmten Punkt den nächst möglichen Flughafen findet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * Returns the nearest airport to the given point. * * @param point The reference point * @return The nearest airport */ static Airport findNearestTo(Point point) { return withSession { session -> def hql = """from Airport a order by distance_sphere(setsrid(geometry(:point), 0), a.location)""" session.createQuery(hql) .setParameter("point", point.toText()) .setMaxResults(1) .uniqueResult() } } |

