IOException.de

Icon

Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm

Einfache Visualisierung von Geodaten – Teil 2: Leaflet & jquery.couch.js

Im vorherigen Teil haben wir gesehen, wie man Geodaten mithilfe von CouchDB abspeichern kann. Da diese Datenbank zugleich ein Webserver ist und die Daten im JSON-Format gespeichert werden, eignet sie CouchDB auch gut für AJAX-Abfragen. Hierfür gibt es eine auf jQuery aufbauende Library namens jquery.couch.js, die von den AJAX-Requests abstrahiert und direkt browser-seitige Interaktionen mit der Datenbank ermöglicht.

Im diesem Beitrag soll gezeigt werden, wie man mit der offenen Karten-Library Leaflet und jquery.couch.js geographische Daten aus CouchDB heraus auf einer Karten anzeigen kann.

Beispiel-Visualisierung von ulmapi.de, ebenfalls basierend auf CouchDB und Leaflet.

Wir verwenden die CouchApp aus dem ersten Teil weiter, und fügen zu den bisherigen Map/Reduce Views noch statische HTML- und Javascript-Dateien hinzu (im _attachments Ordner), die dann im Browser abgerufen werden können. Beim Aufruf dieser Webseite wird ein HTML-Grundgerüst übertragen, sowie eine JavaScript-Datei, die beim Aufruf die eigentlichen Datensätze via jquery.couch.js aus der CouchDB nachlädt.

Als Mapping-Library verwenden wir Leaflet, eine Open-Source Bibliothek für Kartendarstellungen im Browser. Leaflet abstrahiert von verschiedenen Kartenprovidern und erlaubt es somit, unterschiedliche Datenquellen zu verwenden, wie zum Beispiel auch Bing Maps oder Cloudmade. Letzteres ist ein Service, der auf Basis der Open Street Map Daten Kartenkacheln mit individuellen Stilen rendert und hostet – für Visualisierungen oft sehr hilfreich, da reguläre Karten meist zu viele Karteninformationen enthalten oder farblich überladen sind. In unserem Fall haben wir einen einfach Graustufenkarte gewählt. Leaflet selbst lässt sich relativ leicht verwenden, es muss eine CSS-Datei sowie eine JavaScript-Datei importiert werden, und ein div-Block im HTML enthalten sein, worin später die Karte gerendert werden soll. Somit sieht unser HTML-Gerüst zu Beginn so aus:

<!doctype html>
<html>
<head>
	<link rel="stylesheet" href="style/leaflet.css" />
	<script type="text/javascript" src="js/jquery.min.js"></script>
	<script type="text/javascript" src="js/jquery.couch.js"></script>
	<script type="text/javascript" src="js/leaflet.js"></script>
	<script type="text/javascript" src="js/maploader.js"></script>
</head>
<body>
	<div id="map"></div>
</body>
</html>

Es werden jQuery, jquery.couch.js und die Leaflet-Libs geladen, und die letzte importierte JavaScript-Datei soll nun unseren Code zum initialisieren der Karte und dem Laden der Daten aus der CouchDB enthalten. Zunächst erstellen wir eine Karte und rendern sie, sobald die Seite vollständig geladen wurde (jQuery Callback für document.ready):

$(document).ready(function(){

		var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/[YOUR_API_KEY]/33481/256/{z}/{x}/{y}.png';
		var cloudmadeAttribution = 'UlmApi.de / Shape Files: Stadt Ulm (cc-by-sa), Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade';
		var cloudmade = new L.TileLayer(
			cloudmadeUrl, {
				maxZoom : 18,
				attribution : cloudmadeAttribution
		});

		var map = new L.Map('map', {
			center : new L.LatLng(48.399976,9.995399),
			zoom : 12,
			layers : [ cloudmade ],
			zoomControl : false
		});
});

In der cloudmadeUrl muss für Cloudmade Karte ein korrekter API-Key angegeben werden, der nächste Parameter im Pfad identifiziert den Kartentyp. Beim Initialisieren der Karte wird dann die ID des divs angeben, bei uns ‘map’. Nun sollte unsere Karte bereits dargestellt werden, nachdem wir die CouchApp neu deployen (außerhalb des Fokus dieses Artikels, mehr dazu auf couchapp.org).

Was nun noch fehlt, ist das Nachladen der Geodaten aus der CouchDB und die Anzeige auf der Karte. Hierfür verwenden wir jquery.couch.js als Wrapper für die AJAX-Requests gegen CouchDB und die GeoJSON-Funktionalität von Leaflet:

$.couch.db('database_name').view('design_doc_name/view_name', {

	success: function(data){
		if(data && data.rows && data.rows.length){

			var geoJsonLayer = new L.GeoJSON();

			for(var i = 0;i<data.rows.length;i++){
				geoJsonLayer.addGeoJSON(data.rows[i].value.geometry);
			}

			map.addLayer(geoJsonLayer);
		}
	}
});

Das obige Snippet sollte im vorherigen Code hinter der Erzeugung der Karte eingefügt werden. Es ruft von der Datenbank ‘database_name’ den View ‘view_name’ des Design-Dokuments ‘design_doc_name’ auf, und iteriert bei erfolgreicher Abfrage über alle Zeilen. Von jeder Zeile wird dabei die geometry-Property zu einem GeoJSON-Layer hinzugefügt, der am Ende an die Karte übergeben wird. Da unser View aus Teil 1 bereits GeoJSON generiert, und Leaflet nativ GeoJSON lesen und darstellen kann, ist das Hinzufügen von Geodaten auf die Karte sehr einfach.

Hier noch ein paar weiterführende Links mit vertiefenden Inhalten zu den einzelnen Themen:

Whitespace

In einigen Vorlesungen der Uni Ulm ist es üblich Programmieraufgaben der Übungen online an ein Framework zu submitten, welches die Programmierlösung automatisch bewertet. Im letzten Semester war bei einer dieser Veranstaltungen die Abgabe in Whitespace erlaubt.
Whitespace ist eine esoterische Programmiersprache. Das Außergewöhnliche an ihr ist, dass ihr Quelltext ausschließlich aus Whitespace (also Leerzeichen u.ä.) besteht. Um genau zu sein aus den drei Zeichen „Leerzeichen“, „Tabulator“ und „Zeilenumbruch“. Jegliche lesbaren Zeichen werden ignoriert. Damit lassen sich interessante Dinge anstellen, wie zum Beispiel Quelltext in anderen Texten zu verstecken.

Und wenn es erlaubt ist Aufgaben in einer so interessanten Sprache abzugeben, dann kann ich es mir natürlich nicht nehmen lassen dies auch zu tun. Ich begann also Whitespace zu lernen und war erstaunt, wie schnell man sich gute Kenntnisse in dieser Programmiersprache aneignen kann. Wer schon einmal mit einer Assembler-Sprache gearbeitet hat, sollte mit Whitespace keine Probleme haben. Innerhalb weniger Stunden hatte ich bereits einen Whitespace Disassembler (whdisasm) geschrieben gehabt, der mir Whitespace Quelltext in lesbare Mnemonics übersetzt. Disassembler ist natürlich eigentlich keine korrekte Bezeichnung für ein Programm mit dieser Funktionalität. Aber der Name klingt meiner Meinung nach einfach besser als „Whitespace-zu-Mnemonics-Konverter“ oder so. Mit dem whdisasm konnte ich die im Web existierenden Beispiele leichter analysieren und damit lernen, wie man die Sprache verwendet.

Ich schrieb einige kurze Programme. Aber das Coden mit Whitespace ist doch nicht gerade einfach (obwohl für viele Editoren Syntax Highlighting möglich ist). Also machte ich mich daran noch einen Assembler, den whasm, zu entwickeln. Auch whasm war in kurzer Zeit lauffähig. In nur zwei Tagen hatte ich also eine neue Sprache gelernt, einen Disassembler und einen Assembler, sowie einige Libraryfunktionen, geschrieben und meine Programmieraufgabe in Whitespace abgegeben.

Viele esoterische Programmiersprachen sind zwar auf den ersten Blick furchtbar abschreckend aber in Wirklichkeit sehr einfache Konstrukte. Whitespace hat zudem eine ausgezeichnete Dokumentation, die meine einzige Informationsquelle für whasm und whdisasm gewesen ist.

Aber wie funktioniert denn jetzt Whitespace?

Ich verwende ab jetzt für Leerzeichen die Abkürzung [space], für Tabulatoren [tab] und für Zeilenumbrüche [lf]. Diese Darstellung wird auch im Whitespace Tutorial so verwendet.

Ich habe bereits erwähnt, dass Whitespace eine Art Assemblersprache ist. Der Whitespace Quelltext (also die Leerzeichen, Tabs und so) werden üblicherweise interpretiert. Im Netz sind dafür leicht verschiedene Interpreter zu finden. Bestimmt gibt es auch irgendwo einen Compiler (und wenn nicht, ist das hier vielleicht ein Anstoß dafür einen zu schreiben).
Whitespace kennt zwei Arten von Speicher: Stack und Heap. Für den Stack gibt es Operationen, wie „auf den Stack pushen„, „vom Stack poppen“, „duplizieren des obersten Elements“ und einige weitere. Im Heap können Daten an beliebigen Adressen gespeichert und gelesen werden. An dieser Stelle passt es vielleicht zu erwähnen, dass Whitespace keine Datentypen kennt, wie sie sonst üblich sind. Die einzige Unterscheidung ist „Zahl“ oder „(ASCII-) Zeichen“. Druckbare Zeichen haben dabei 8 Bit. Zahlen können beliebig lang(!) sein. Eine positive Zahl beginnt mit einem [space] und eine negative mit einem [tab]. Ansonsten ist ein [space] die binäre 0 und ein [tab] die binäre 1.

Hier ein Beispielprogramm, dass zwei Zahlen aus dem Heap liest, diese addiert und das Ergebnis auf dem Bildschirm anzeigt. Ich habe für bessere Lesbarkeit statt echten Leer-/Tab-/Zeilenumbruchszeichen sichtbare Abkürzungen verwendet ;)


// Achtung! Zeilenumbrüche sind nur da, wo [lf] steht. Ich habe die Zeilen im Beispiel
// lediglich für bessere Lesbarkeit umgebrochen.
// Zuerst wollen wir zwei Zahlen vom Heap auf den Stack laden.
// Um eine Zahl zu laden, pushen wir zunächst ihre Adresse auf den Stack
[space] // das erste [space] bedeutet, dass wir den Stack manipulieren wollen
[space] // das zweite [space] sagt, dass wir die Zahl nach dem Befehl auf den Stack pushen wollen
[space][tab][tab][tab][lf]
// [space][tab][tab][tab] ist die binäre 0111 (also 7)
// das [lf] markiert das Ende des Zahlenparameters

[tab][tab] // [space] sagt, dass wir aus dem Heap lesen wollen (an der Adresse im Stack)
[space] // [tab][tab] bedeutet, dass wir auf den Heap zugreifen

// Das Selbe nochmal mit der zweiten Zahl:
[space][space][space][tab][space][space][space][lf] // diesmal die Adresse 8 (01000)
[tab][tab][space]

[tab][space] // leitet eine arithmetischen Befehl ein
[space][space] // Addition der zwei obersten Werte auf dem Stack

[tab][lf] // beginnt eine I/O Operation
[space][tab] // Bildschirmausgabe der Zahl oben auf dem Stack

[lf][lf][lf] // beendet das Programm

Das selbe Programm würde in (mit whasm übersetzbaren) Mnemonics so aussehen:

push 7
retrieve

push 8
retrieve

add
printnum

end

Fazit

Esoterische Programmiersprachen machen Spaß! Das ist zumindest meine Meinung. Ich habe in der Zeit, in der ich mich damit beschäftigt habe, einiges gelernt. Nicht nur Whitespace selbst, sondern den Umgang mit stackbasierten Sprachen oder auch, wie leicht sich solche Konstrukte parsen lassen.

Der Code für whasm und whdisasm sind auf github verfügbar. Dort habe ich auch begonnen Libraryfunktionen zu implementieren, die mit whasm in Whitespace übersetzt werden können.

Einfache Visualisierung von Geodaten – Teil 1: CouchDB/GeoCouch

Die Hochschulgruppe Open Data Ulm hat es sich zur Aufgabe gemacht, offene und öffentliche Daten rund um die Region Ulm zu aggregieren, aufzuarbeiten und zu visualisieren. Näheres zu diesem Projekt sowie bereits gesammelte Datensätze gibt es unter UlmApi.de

Für unser Vorhaben habe ich als Persistenzlösung die dokumentenorientierte Datenbank CouchDB gewählt, da sie für uns mehrere interessante Features bietet:

  • schemalos: Anders als relationale Datenbanken benötigen schemalose Datenbanken keine im Voraus fest definierte Struktur der Einträge. Für unsere Geodaten ist dies sehr hilfreich, da außer einer ID und den Geodaten noch beliebige zusätzliche Daten pro Eintrag mitgespeichert werden können.
  • JSON: Für die Speicherung strukturierter Daten stellt dieses Format eine leichtgewichtige Alternative zu XML dar.
  • webbasiert: Die Datenbank ist zugleich ein Webserver und der Zugriff auf die Daten läuft somit über HTTP.
  • verteilt/replizierend: Ein wichtiges Konzept von CouchDB ist die einfache aber mächtige Replikation zwischen verschiedenen Instanzen. Im Kontext unserer offenen Datensammlungen ermöglicht dies, dezentralte Kopien der Daten anzulegen, diese lokal zu editieren oder erweitern und wieder auf unsere Hauptdatenbank zu laden.
  • Attachments: Neben strukturierten Daten lassen sich auch ganze Dateien speichern. Dies ist vor allem für archivierte Rohdaten in proprietären Formaten interessant.
  • CouchApps: Neben der Speicherung der Daten sind vor allem einfache Anwendungen interessant, die diese visualisieren oder aufbereiten. Das Konzept der CouchApps ermöglicht es uns, simple Webanwendungen direkt auf der Datenbank zu deployen und verfügbar zu machen.
  • räumliche Indizes: Dank Volker Mische besitzt CouchDB einen zusätzlichen Index (GeoCouch), der statt eindimensionaler B-Bäume zweidimensionale R-Bäume benutzt. Damit lassen sich Dokumente mit räumlichen Daten abspeichern, indizieren und effizient abfragen.

Von der Stadt Ulm haben wir als ersten Datensatz Shapefiles der Ulmer Stadtteile und Stadtviertel bekommen. Diese wurden zunächst vom Ausgangsformat (Gauss-Krueger-Shapefiles) in das GeoJSON-Format mit WGS84-Koordinaten konvertiert. Mithilfe eines kleinen node.js Skriptes wurden die einzelnen Shapes dann als Dokumente auf die Couch geladen.

Ein Dokument hat hierbei folgende Form (Originaldokument):

{
   "_id": "ul-st14",
   "_rev": "1-797187e292d93b6d661ca8f7fec3f6c9",
   "type": "stadtteil",
   "name": "Weststadt"
   "geometry": {
       "type": "Feature",
       "properties": {
           "identifier": "ST 14",
           "name": "Weststadt"
       },
       "geometry": {
           "type": "Polygon",
           "coordinates": [
               [
                   [
                       9.981459,
                       48.395751
                   ],
                   …
              ]
           ]
       }
   },
   "creator": "Stadt Ulm",
   "license": {
       "name": "Creative Commons - Namensnennung-Weitergabe unter gleichen Bedingungen 3.0 Deutschland (CC BY-SA 3.0)",
       "link": "http://creativecommons.org/licenses/by-sa/3.0/de/"
   },
}

Die _id bestimmt das Dokument eindeutig, das _rev Feld ist für die Versionskontrolle verantwortlich. Als ID haben wir einen internen Identifier der Stadt genommen und noch mit einem “ul” Präfix versehen. Der Rest des Dokuments kann frei strukturiert werden. In unserem Fall verwenden wir noch ein type Feld, durch das wir später Dokumente unterschiedlichen Typs unterscheiden können (z.B. stadtteil oder stadtviertel). Das geometry Feld (hier gekürzt) enthält die geografischen Daten im GeoJSON-Format. Die sonstigen Felder beschreiben noch den Urheber und die Lizenz der Daten sowie den Namen des Eintrags.

Nun ist die Datenbank gefüllt, später sollen aber die Daten auch wieder abgefragt werden können. Als “NoSQL” Datenbank bietet CouchDB hierfür aber keine SQL-Statements an, stattdessen muss man mithilfe von MapReduce beschreiben, wie aus den Daten Indizes gebildet werden sollen:

function(doc) {
	if (doc.type) {
		if(doc.type === 'stadtviertel'){
		    emit(['stadtviertel', doc._id], {
		    	'geometry' : doc.geometry,
		    	'label' : "<b>Stadtviertel "+doc.name+"</b><br/>ID: "+doc._id+"<br/>(Stadteil "+doc.stadtteil+")"
		    });
		}
		else if(doc.type === 'stadtteil'){
		    emit(['stadtteil', doc._id], {
		    	'geometry' : doc.geometry,
		    	'label' : "<b>Stadtteil "+doc.name+"</b><br/>ID: "+doc._id
		    });
		}
	}
};

Damit erzeugen wir eine sortiere Liste von Schlüssel-Wert-Paaren. Der Schlüssel ist selbst wieder komplex und besteht aus zwei Teilen. Der erste Teil ist der Typ, der zweite Teil die ID des Dokuments. Damit kann man später durch die sogenannte View Collation Abfragen durchführen, die sich auf einen bestimmtem Typ beschränken (zur Wiederholung: ohne SQL gibt es hier auch kein WHERE Statement). In diesem Fall werden bisher nur Dokumente des Typs stadtteil oder stadtviertel eingetragen, und als Wert eines Eintrages wird bereits die spätere Nutzung auf einer Karte vorbereitet – es werden die Geodaten sowie ein Label indiziert. Damit lassen sich nun schon Stadtteile/Stadtviertel abfragen.

Ergänzt man dies noch um einen räumlichen Index, so werden auch räumliche Abfragen ermöglicht. Hierfür werden in den Index als Schlüssel die Geodaten (unverändert im GeoJSON Format) eingetragen, den Wert selbst bliebt leer, da das Feld _id sowieso eingetragen wird und vorest keine weiteren Daten mehr benötigt werden:

function(doc){
	if(doc.geometry && doc.geometry.geometry){
		emit(doc.geometry.geometry, null);
	}
};

(Das etwas merkwürdig anmutende doc.geometry.geometry entstand einerseits dadruch, dass unser Feld mit dem GeoJSON-Objekt geometry heißt, das GeoJSON-Objekt selbst aber komplex ist und nur in einem Teil davon die eigentlichen Geodaten hinterlegt sind.)

Mithilfe dieses Index lässt sich nun bei einem gegebenen geografischen Raum überprüfen, welche Objekte darin enthalten sind. Also zum Beispiel ausgehend von einer Koordinate, ob sie sich in Ulm befindet und wenn ja, in welchem Stadtteil/Stadtviertel.

Im nächsten Teil wird näher betrachtet, wie die nun abgespeicherten und indizierten Geodaten im Browser auf einer Karte dargestellt werden können, und zwar direkt aus CouchDB heraus.

Präsentieren mit HTML5-Foliensätzen

Kurzversion: HTML5 Foliensatz, basierend auf einem Google Template, das zusätzlich Notizen und einen Presenter Mode bietet: Demo / Code

Nicht nur die Frage, wie man richtig präsentiert (Stichwort Zen vs. Death by PowerPoint), sondern auch die Frage, mit welchen Anwendungen man präsentiert, ist oft umstritten. Ich persönlich konnte mich bisher mit Powerpoint und Konsorten eher wenig anfreunden – vor allem Typografie und Einschränkungen bei der Gestaltung waren problematisch. Als Alternative habe ich bisher oft LaTeX Beamer verwendet, was allerdings je nach visueller Komplexität auch oft relativ zeitaufwendig ist, sich aber zumindest bei Grafiken in Vektorformaten und mathematischen Inhalten auch schnell auszahlt.

HTML5-basierte Foliensätze

Mit dem Aufkommen von HTML5 entstand eine zusätzliche Möglichkeit. Dank der neuen Multimedia-Tags wie <audio> und <video> sowie mächtigeren CSS Stilen bietet HTML nun die Grundlagen für browser-basierte Präsentationen. Mittlerweile gibt es hierfür auch schon mehrere Templates:

Noch mehr Alternativen gibt es in dieser Auflistung. Ein weiteres sehr schönes Beispiel ist ein Foliensatz zu HTML5, der selbst quasi eine Technologiedemonstration enthält: http://slides.html5rocks.com

Der Vorteil von HTML-basierten Präsentationen ist die hohe Anzahl von Medien (u.a. auch SVG, Flash Videos oder ganze Webseiten als IFrames), die man einbetten kann. Ein einfaches, weitläufig bekanntes Markup ermöglicht das schnelle Erstellen von Folien, und mit einer Kombination aus HTML, CSS und JavaScript lassen sich dennoch auch komplexe Spezialfunktionen realisieren.

Mir persönlich hat das html5slides Template ganz gut gefallen, das Google entwickelt und für die Google I/O Slides eingesetzt hat. Da das Template unter einer Apache License veröffentlicht wurde, habe ich zunächst damit begonnen, es an das Uni Ulm Corporate Design anzupassen. Außerdem hatte ich ein paar kleine Änderungen am Code vorgenommen, um zum Beispiel eine Nummerierung der Folien zu ermöglichen.

Presenter Mode?

Prinzipiell war das Ergebnis schon mal akzeptabel, allerdings wurden die oft genannten Probleme bei solchen HTML-Foliensätzen schnell offensichtlich – fehlende Notizen für den Vortragenden und nur eine Ausgabe.

Eher durch Zufall bin ich auf ein anderes Konstrukt gestoßen, dass seit HTML5 Cross-Frame-Communication erlaubt, also den Austausch von Nachrichten zwischen zwei verschiedenen Frames (mit einigen Einschränkungen): window.postMessage()

Die Möglichkeit, zwischen Frames zu kommunizieren, ist natürlich auch ideal dafür, Daten zwischen Frames zu synchronisieren. Übertragen auf zwei verschiedene Präsenationsframes ermöglicht dies beim Weiterschalten der Folien in einem Frame, den zweiten Frame zu aktualisieren. Schematisch sieht das so aus:

(CC-BY-NC) Icons by picol.org / w3.org

Im Hauptfenster kann per Tastendruck ein zusätzliches Popup geöffnet werden (1). Das neue Popup öffnet die gleiche URI der Präsentation und wird auf dem Bildschirm des Vortragenden platziert. Schaltet der Vortragende nun im Hauptfenster weiter zu nächsten Folie, so erzeugt dies ein Nachricht an das zweite Frame (2), das dann ebenfalls weiterschaltet.

Eine weitere Ergänzung war die Unterstützung von Notizen als Overlay über die Folien. Kombiniert mit dem Dual-Screen-Ansatz ermöglicht dies, dem Publikum die Folien zu zeigen, dem Vortragenden auf einem zweiten Bildschirm die Folien plus den verfügbaren Notizen.

Ausführliche Beispiele mit Code gibt es in einer Beispielpräsentation, den kompletten Code auf github: https://github.com/berb/html5slides-uulm

Auf der Feature Wishlist steht noch ein alternativer CSS-Stylesheet für den Druckexport. Außerdem ein Tool, dass externe Daten wie Bilder per Base64 encoding als Data URI integriert und JavaScript sowie Stylesheets inline einbindet, sodass die Präsentation als einzelne HTML5 Datei ohne externe Abhängigkeiten abgespeichert werden kann.

Funktionale Ansätze in einer imperativen Programmiersprache

Im Zuge der Vorlesung “Paradigmen der Programmierung” kam ich erstmals mit dem alternativen Programmierkonzept der funktionalen Programmierung in Berührung, das sich von dem der weiter verbreiteteren imperativen Programmiersprachen wie etwa Java und C grundsätzlich unterscheidet. Als Übung auf die Klausur versuchte ich die Ideen der funktionalen Programmiersprache Haskell in einer imperativen Sprache umzusetzen. In Javascript ist die Nutzung von anonymen Funktionen und ihre Übergabe selbst als Parameter einer anderen Funktion bereits verbreitet, womit sie per se bereits einige Konzepte der funktionalen Programmierung umsetzt. Um weitere Dinge wie partielle Funktionsaufrufe ebenfalls zu ermöglichen, habe ich ein Javascript-Modul “functionalJS” geschrieben, das mit dem Konstruktor Functional() ein Äquivalent zum Javascript-eigenen Function() bietet. Der Quellcode sowie einige Beispiele finden sich auf github. Im Folgenden möchte ich die Ideen der funktionalen Programmierung kurz vorstellen und erklären, wie sie in dieser Klasse umgesetzt wurden.

Alles ist eine Funktion

Beim funktionalen Programmierstil besteht das Programm einzig aus Funktionen. Auf den ersten Blick mag das bei imperativen Programmiersprachen kaum anders sein, auch hier bilden Funktionen bzw. Methoden einen wesentlichen Bestandteil. Es gibt jedoch Unterschiede bei der Art der Aufrufe: Imperative Programme führen die Funktionen als eine Folge von Anweisungen aus (“Tu das! Dann tu das solange dies gilt!” – imperativ eben), bei der funktionalen Programmierung treten die Funktionen nur ineinander geschachtelt auf – das Ergebnis einer Funktion ist also zugleich Parameter der nächsten. Dadurch sind Variablen unnötigt, was einen Vorteil der funktionalen Programmiersprachen begründet: Es gibt keine Seiteneffekte! Während in der imperativen Programmierung das Resultat vom Programmzustand abhängt, hängen hier die Rückgabewerte einzig von den Parametern ab und etwa nicht vom Zeitpunkt des Aufrufes.

Für die Umsetzung in Javascript bedeutet dies, dass alle Funktionen ohne lokale Variablen auskommen, sondern stattdessen einzig ein return-Statement beinhalten. In diesem können nach dem eben genannten Konzept Funktionen aufgerufen werden. Dadurch ist Rekursion möglich, die nötig wird, um Schleifen zu vermeiden. Jede Funktion muss zudem mindestens einen Parameter und genau einen Rückgabewert besitzen, um dem funktionalen Anspruch gerecht zu werden.

Beispiel: Durchschnittsfunktion für eine übergebene Liste in Javascript (Ausschnitt der functional.js)

function(l) { return ratio(sum(l), length(l)); }

Mustervergleiche (Stichwort: Pattern matching)

Haskell erlaubt die mehrfache Definition einer Funktion. Dies unterscheidet sich wesentlich vom “Überladen” bei Java, wo die Unterschiede bei der mehrfachen Definition in der Methodensignatur liegen. In funktionalen Programmiersprachen werden dagegen semantische statt syntaktische Muster für die Eingabeparameter angelegt, d.h. es wird quasi eine Musterbasis aufgebaut, die dann beim Aufruf der Funktion von oben nach unten getestet wird. Sobald das erste Muster mit den übergebenen Parametern zusammenpasst, wird dessen Ergebnis oder Funktionsrumpf genutzt. Das Ergebnis hängt also wesentlich von der Reihenfolge der Musterdefinitionen ab. Basisfälle für Rekursionen werden so stets als erstes definiert, der allgemeinste Fall zum Schluss.

Beispiel: (Primitive) Definition der Fibonacci-Funktion in Haskell:

fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n-1) + fibonacci (n-2)

Die beiden Basisfälle der Fibonacci-Funktion werden also vor der allgemeinen Rekursionsvorschrift definiert. In Javascript ist ein Überladen von Funktionen nicht möglich, auch bei der functionalJS-Klasse müssen so die Muster gleich dem Konstuktor übergeben werden. Eine äquivalente Definition der Fibonacci-Funktion mittels der functionalJS-Klasse sieht so aus:

Beispiel: Definition der Fibonacci-Funktion in Javascript (modifizierter Ausschnitt der test.js)

fibonacci = new Functional("fibonacci",
	[0, 0],
	[1, 1],
	["n", function(n) { return fibonacci(n-1)+fibonacci(n-2); }]
);

Die Muster werden als Arrays dem Functional übergeben. Dabei sind für reine Funktionen wie der letzte Fall auch Verkürzungen möglich. Näheres dazu ist in der Dokumentation aufgeführt. Ansonsten gilt: Das letzte Array-Element ist das Resultat oder die aufzurufende Funktion bei Übergabe dieses Musters. Optional kann als einfacher String der Name der Funktion übergeben werden, was für die interne Realisierung der Rekursion nötig ist.

Wird nun die Fibonacci-Funktion aufgerufen, so werden die Muster mittels Pattern matching nacheinander mit den tatsächlichen Parametern verglichen. Dabei sind durchaus auch kompliziertere Muster möglich, die die Haskell-typischen Listen erkennen. So wird die einfache length-Funktion, die die Anzahl der Elemente einer Liste zurückgibt, in beiden Sprachen wie folgt definiert:

Beispiel: Definition der length-Funktion in Haskell

length []	=  0
length (x:xs)	=  1 + length xs

Analog sieht eine solche Definition mittels functionalJS aus, mit dem Unterschied, dass hier für Listen (egal ob leer oder nicht) stets die eckigen Klammern genutzt werden:

Beispiel: Definition der length-Funktion in Javascript (Ausschnitt der functional.js)

length = new Functional("length",
	["[]", 		0],
	["[x:xs]", 	function(x, xs) { return 1+length(xs); }]
);

Über vordefinierte special operators ist es auch nach der Definition eines Functionals möglich, die Musterbasis zu erweitern. Dies könnte in diesem Beispiel etwa wie folgt geschehen, wodurch die Funktion bei Übergabe einer leeren Liste 42 zurückgibt:

Beispiel: Nachträgliche Erweiterung der Musterbasis in Javascript

length('__addOnTop',
	["[]", 42]
);

In Haskell ist ein solches Verhalten nachträglich etwa im GHCi, dem Haskell-Interpreter, nicht möglich. Dort könnten weitere Muster stets nur am Ende das Basis hinzugefügt werden. Ein solches Verhalten lässt sich bei functionalJS mittels des Keywords ‘__add’ realisieren.

Partielle Aufrufe (Stichwort: Currying)

Haskell erlaubt das partielle Aufrufen von definierten Funktionen, d.h. das Übergeben zu weniger Parameter um daraus eine neue Funktion zu erhalten. Dieses Verhalten ist sinnvoll, um eigene und vordefinierte Funktionen mehrfach nutzen zu können und insbesondere in der Verwendung als Funktional (siehe nächsten Punkt). Anders als bei imperativen Programmiersprachen stellen Übergabeparameter einer Funktion also kein Tupel dar (param1, param2, param3), sondern lassen sich eher als Liste param1 -> param2 -> param3 -> result auffassen. Werden alle Parameter übergeben, so wird ganz normal das Funktionsresultat zurückgegeben. Wird die Funktion dagegen nur mit zwei Parametern aufgerufen, so wird ein neue Funktion zurückgegeben, die von einem Parameter abhängt. Man sagt, dass eine Funktion mit einer Stelligkeit von 3 bei Übergabe von zwei Parametern eine Funktion der Stelligkeit 1 zurückliefert.

Das Verhalten, dass die Anzahl der Parameter einer Funktion in Javascript variabel sein kann, kann durch die Nutzung des arguments-Objekts realisiert werden. Schwieriger gestaltet sich die Realisierung der partiellen Auswertung (Ausschnitt). Während bei der normalen Auswertung, also bei Übergabe aller Parameter, einfach das erste Muster gesucht wird, welches zutrifft, müssen bei der partiellen Auswertung alle zutreffenden Muster gesucht werden, da diese in dieser Reihenfolge Teil des zurückzugebenen Functionals werden. Anhand dieser Muster wird ein neues Functional erzeugt, in deren Funktionsrümpfe die gebundenen Variablen durch die gematchten Parameter ersetzt werden. So wird also aus der zweistelligen add-Funktion (Definition), die die Summe zweier Zahlen berechnet, bei Übergabe des Parameters 1 ein einstelliges Functional, das eine Zahl erwartet und als Resultat den Nachfolger der Zahl zurückliefert.

Beispiel: Definition der Nachfolger-Funktion durch partiellen Aufruf in Javascript

succ = add(1); // add :: a -> a -> a

Anders als in Haskell können Funktionsnamen in functionalJS nur aus Buchstaben und Zahlen bestehen, eine Funktionsdefinition als “<” ist so etwa nicht möglich, stattdessen gibt es eine lessthan-Funktion. Dies erschwert partielle Aufrufe von nicht-kommutativen Funktionen.

Beispiel: Falsche Definition der negative-Funktion durch partiellen Aufruf in Javascript

negative = lessthan(0);

Die oben genannte Definition der negative-Funktion liefert eben nicht true zurück für Zahlen kleiner null, sondern genau das Gegenteil. Dies liegt an der Definition von lessthan:

Beispiel: Definition der standardmäßigen lessthan-Funktion (Ausschnitt der functional.js)

lessthan = new Functional("lessthan",
	function(a, b) { return a < b; }
);

Der partielle Aufruf lessthan(0) liefert also ein neues einstelliges Functional zurück, dass stets 0<x prüft. Dies entspricht also nicht dem gewünschten Verhalten. In Haskell sind dagegen auch partielle Aufrufe wie oben gewünscht möglich:

Beispiel: Definition der negative-Funktion in Haskell

negative = (<0)

Daher sind in den standardmäßigen Funktionen der functionalJS-Klasse zu jeder zweistelligen nicht-kommutativen Funktion auch eine entsprechende mit verdrehten Parametern definiert, die den gleichen Namen endend auf “Re” trägt. Unser obiges Beispiel müsste also korrekt lauten:

Beispiel: Korrekte Definition der negative-Funktion durch partiellen Aufruf in Javascript

negative = lessthanRe(0);

Funktionen als Parameter (Stichwort: Funktionale)

Die Möglichkeit des partiellen Aufrufs einer Funktion ist bereits ein mächtiges Instrument. Seine volle Wirkung können solche Funktionen aber bei Übergabe an andere entfalten. Anders als in vielen anderen imperativen Programmiersprachen ist die Übergabe von Funktionen als Parameter in Javascript möglich und ein häufiger Einsatzzweck, insbesondere als Callbacks für Events. Javascript-Anhängern ist ein solches Verhalten also bereits bekannt. In der funktionalen Programmierung bezeichnen wir Funktionen, die als Eingabe oder Ausgabe selbst Funktionen aufnehmen, als Funktionen höherer Ordnung oder Funktionale. So ist es etwa bei der Map-Funktion:

Beispiel: Definition der map-Funktion in Javascript (Ausschnitt der functional.js)

map = new Functional("map",
	["f", 	"[]", 		function(f) { return []; }],
	["f", 	"[x:xs]", 	function(f, x, xs) { return unshift(f(x), map(f, xs)); }]
);

Wie wir sehen, erwartet die map-Funktion also zwei Parameter: Zum einen eine Funktion f, zum anderen eine Liste. Das erste Muster beschreibt den Basisfall der leeren Liste, während der zweite den Rekursionsfall nennt. Kurz gesagt: Im Falle der leeren Liste wird eine leere Liste zurückgegeben, ansonsten wird das Kopfelement genommen, die Funktion f darauf angewendet, und das ganze mit dem Rest der Liste wiederholt und zusammengefügt. Die map-Funktion führt also eine Funktion f auf alle Elemente einer Liste aus. Bekannte Haskell-Beispiele der Funktionale sind auch die fold-Funktionen, die ebenfalls in dem Modul definiert wurden.

Unter Anwendung der Funktionen höherer Ordnung sowie partieller Aufrufe lassen sich so sehr schnell nette Funktionen zusammenstellen:

Beispiel: Anwendungen (Ausschnitt der test.js)

foldr(mult, 1, [1,2,3,4]); // 24, berechnet das Produkt aller Listenelemente

all(lessthanRe(5), [1,2,3,4]); // true, da alle Elemente kleiner 5

until(greaterthanRe(80), mult(2), 1); /* 128, da der Initialwert 1 solange mit 2 multiplitziert wird, bis der Schwellenwert 80 erreicht ist */

Fazit

Den funktionalen und imperativen Programmierstil zu vereinen ist schwierig und mag in der Frage münden: Warum? Zum einen beschneidet man die mächtigen Instrumente von Javascript, indem etwa Variablen und Schleifen komplett verboten werden, zum anderen kann man nicht aus den vollen Vorteilen von Haskell schöpfen, etwa bei der Nutzung unendlicher Listen. Und dennoch bieten die Functionals einige Vorteile, allen voran die partiellen Aufrufe. Ob durch die strikte Nutzung des funktionalen Stils tatsächlich auch in Javascript Seiteneffekte ausbleiben, wäre noch zu testen. Insbesondere, da sich einige Funktionen derzeit nicht atomar realisieren lassen (es gibt etwa keinen “(x:xs)”-Operator zur Listenerzeugung), darf in der aktuellen Fassung nicht davon ausgegangen werden. Prinzipien wie anonyme Funktionen und Funktionale sind in Javascript bereits enthalten, wodurch es sich zum Teil bereits ähnlich anfühlt. Das oben geführte until-Beispiel würde man mit normalen Javascript-Mitteln vermutlich wie folgt lösen:

Beispiel: Aufruf einer herkömmlichen until-Funktion in Javascript

until(function condition(x) { return x > 80; }, function do(x) { return x*2; }, 1);

Während ein solches Verhalten nicht in allen imperativen Programmiersprachen möglich ist, bietet Javascript offensichtlich bereits etwas ähnliches an. Auf partielle Aufrufe und die einfache Funktionsdefinition mittels Pattern matching muss man dennoch verzichten.

In der vorgestellten Klasse fehlen noch Haskell-typische Elemente, allen voran die Implementierung von Tupeln. So ist derzeit die bekannte zip-Funktion noch nicht eingebaut. Daneben ist die Implementierung der Mengendefinition, also das einfache Erzeugen einer Liste anhand einer Bildungsvorschrift, umsetzbar. Komplizierter wird es bei der Realisierung von unendlichen Listen, wo Haskell etwa in der Berechnung großer Fibonacci-Zahlen imperativen Programmiersprachen überlegen ist. Schlussendlich lassen sich dann auch ganz grundsätzliche Dinge, wie etwa die Nachbildung der lazy-evaluation als Auswertungsstrategie nicht nachträglich in Javascript umsetzen.

Einfache Mobilapplikation mit Sensordaten und Real-Time-Streaming

Im Rahmen der Vorlesung “Mobile & Ubiquitous Computing” (bin derzeit mitbetreuender Hiwi) waren wir auf der Suche nach passenden Übungsaufgaben. Eine Übung davon sollte verschiedene Aspekte aktueller Mobilapplikationen (Sensorkontext, Web Services, Live Notifications) einbeziehen, ohne dabei allzu komplex zu werden.

Als ansatzweise reales Szenario hierfür dient die Mensa. Zu Stoßzeiten ist es häufig schwierig, in einer größeren Gruppe gemeinsam zu essen. Spätestens an den Kassen teilt sich die Gruppe auf und es ist schwierig, die Anderen zu finden. Ein Teil sitzt vielleicht auch schon an irgendeinem Tisch, während andere immer noch an der Essensausgabe warten. Was man also unbedingt braucht, ist ein Mensafinder. Der Mensafinder ermöglicht es, anderen seine grobe Position in der Mensa mitzuteilen oder aufzuzeigen, wo andere Leute sitzen. Aufgrund der Einschränkungen vor Ort (kein GPS-Empfang, WLan-Ortung zu ungenau) haben wir uns auf eine einzige Kontextinformationen beschränkt, die bereits eine ausreichende Lösung bietet – die Kompassausrichtung. Anstatt die genaue Position zu ermitteln, verwenden wir eine grobe Richtung abhängig von einem Fixpunkt im Zentrum des Raums (Wendeltreppe). Bereits sitzende Personen richten ihr Mobilgerät in Richtung des Fixpunktes aus, suchende Personen können ausgehend vom Fixpunkt den Richtungen folgen.

Technisch besteht der Mensafinder aus einem Webservice und mobilen Anwendungen. Der Webservice basiert auf REST und bietet als besonderes Feature das ‘Streamen’ neuer Events (neue/aktualisierte Peilungen oder Abmeldungen). Hierfür wird durch den Client eine HTTP-Verbindung geöffnet und serverseitig nicht direkt geschlossen. Stattdessen werden neue Events via Chunked Encoding in die offene Verbindung geschrieben, ähnlich wie bei Streaming API von Twitter. Der Service wurde mit node.js implementiert, Quellcode sowie Dokumentation der REST API sind auf github verfügbar.

Da es sich nur um eine Demo-Applikation handelt, fehlen einige wichtige Features. Es gibt keine Authentisierung der Benutzer und es werden alle Peilungen aller Benutzer übertragen (es gibt keine Kontaktlisten). Interessant wäre natürlich die Anbindung an bestehende Dienste, die bereits eine Authentisierung und Kontaktlisten bereitstellen, so wie beispielsweise Facebook Connect.

Die UNIX-Philosophie

Das UNIX-Betriebssystem wurde in den sechziger Jahren von Bell Laboratories entwickelt. Ein Großteil der heute populären Betriebssysteme wie GNU/Linux oder Mac OS X verwenden grundlegende Konzepte, die aus dem ursprünglichen UNIX-Betriebssystem stammen. Die BSD Familie oder Solaris sind direkte UNIX-Nachfolger. Man bezeichnet diese Systeme daher als unixoide Betriebssysteme. Es ist kein Zufall, dass die effizientesten, sichersten und fortschrittlichsten Betriebssysteme UNIX-Derivate sind. Unix-Systeme sind ideal für die Zukunft gerüstet, da sie die Grundannahme haben, dass sich alles ständig ändert. Der Aufbau ist deshalb äußerst flexibel gehalten.

Mit der Unix-Philosophie bezeichnet man die Konzepte, die beim Erstellen des ursprünglichen Unix-Systems von den Entwicklern angewandt wurden. UNIX wurde als System für Experten entworfen. Für Leute, die wissen was sie tun und was sie wollen. Auf Einsteigerfreundlichkeit wurde keinen Wert gelegt. Die Benutzerschnittstelle zur Interaktion mit dem Betriebssystem ist die Shell.

Es gibt einige Konzepte, die allgemein unter die Unix-Philosophie fallen. Ich möchte in diesem Beitrag einige der wichtigsten herausgreifen um einen Einblick zu geben.

UNIX-Typografie

Make each program do one thing well.
Anstatt ein großes monolithisches Programm zu entwickeln sollte ein Programm genau eine Sache tun — und das richtig! Ein solches Programm hat viele Vorteile:

Programme die genau eine Sache tun können speziell für diese Aufgabe konzipiert werden.

Viele einzelne Programme, die genau eine Sache machen, können sehr einfach kombiniert werden! Aus der Kombination von vielen einzelnen Dingen ergibt sich leicht etwas neues, viel größeres. Wie in einem Werkzeugkasten ist es sehr einfach sich die richtigen Programme für eine Aufgabe zu suchen und diese passend zu kombinieren. All das ermöglicht einen hohen Grad an Abstraktion, der es ermöglicht sehr schnell und effizient ein Ziel zu erreichen.

Um etwa herauszufinden, wie viele verschiedene Dateien in einem Verzeichnis liegen und welchen Typ diese haben können folgende Kommandos in der Shell kombiniert werden: file -b * | sort | uniq -c.
Pipes | sind ein effizientes Konzept um die Programmausgabe direkt als Eingabe an ein anderes Programm weiterzuleiten. Das Programm, an das weitergeleitet wird, beginnt mit der Verarbeitung sobald eine Eingabe vorliegt.
Summiert man die Quelltexte der einzelnen Programme auf kommt man auf mehrere tausend Zeilen Quelltext, die in dieser einen Zeile verwendet werden. Jedes dieser Programme wurde für genau eine Aufgabe entworfen, über Jahre konstant verbessert, ausgebessert und angepasst.

Bei unixoiden Systemen zieht sich dieser Aufbau durch, so sind viele Benutzeroberflächen oft einfach nur grafische Frontends für Kommandozeilentools (diskutil/Festplattendienstprogramm unter Mac OS X oder tcpdump/Wireshark etwa).

Small programs are beautiful.
Programme sollten klein sein. Das macht es einfach sie zu entwickeln, zu pflegen und zu verstehen. Für neue Entwickler ist es leichter sich in das Programm einzuarbeiten, die Funktionalität des Quelltextes ist überschaubar. Die Komplexität eines Programms wird so klein gehalten. Es fällt leichter Fehler zu finden und zu beheben.

Ein weiterer Vorteil ist, dass kleine Programme ressourcenschonend sind: Sie können vom Betriebssystem schnell geladen werden, swapping und paging sind seltener nötig. Im Endeffekt ergibt sich so ein schnelleres Gesamtsystem.

Wie aber legt man fest was ein “kleines” Programm ist? Mike Gancarz gibt in seinem Buch “The UNIX Philosophy” einige Anhaltspunkte: Funktionsdeklarationen, die aufgrund von vielen Parametern umgebrochen werden müssen oder auftretende Probleme, die nicht mehr augenblicklich einem Kontext zugeordnet werden können sind Warnsignale. Strikt der Unix-Philosophie folgend sollte ls keinen Flag zur Formatierung oder Sortierung der Ausgabe haben — hierfür gibt es andere Programme (column, sort, …).

Dieser Aufbau von vielen verschiedene kleinen Programmen die zusammenarbeiten, lässt sich dem Konzept “Divide & Conquer” zuordnen: Dadurch, dass große, komplexe Probleme in einfachere, kleine zerlegt werden wird es einfach diese einzeln zu lösen.

Avoid captive user interfaces.
Programme sollten so geschrieben sein, dass sie problemlos in einem Skript verwendet werden können. Die Schnittstelle muss für Maschinen entworfen sein — nicht für Menschen. Das bedeutet beispielsweise, dass Programme nach dem Aufruf nicht noch auf Benutzereingaben angewiesen sein sollten (“Sind Sie sicher, dass Sie …? y/n”), sondern nur über die Kanäle STDIN, STDOUT & STDERR kommunizieren sollten.
Das macht Automatisierung einfacher, es ist sehr einfach Programme miteinander zu verknüpfen, die eben nicht irgendwann auf eine Benutzereingabe warten und deswegen blocken. Das UNIX-Konzept macht es sehr einfach, Programme zu kombinieren.

Zum Vergleich: Befehle wie dir erzeugen unter Windows-Systemen auf ein leeres Verzeichnis angewandt eine Ausgabe mit mehreren Zeilen — ohne relevanten Informationen. Diese Ausgabe weiterzuverarbeiten oder zu parsen ist nicht ohne weiteres machbar. Programme, die strikt der Unix-Philosophie folgen verhalten sich dagegen nach dem Grundsatz “Silence is golden”.

Build a prototype as soon as possible.
In der Unix-Philosophie nehmen Prototypen eine zentrale Rolle ein. Die Sichtweise ist hier, dass viele traditionelle Softwareentwicklungsprozesse durch Anforderungsanalyse, Spezifikationen, etc. darauf abzielen von Anfang an das ideale System zu den Anforderungen zu bauen. Oder anders ausgedrückt: Beim ersten Mal alles richtig zu machen.

Die Sichtweise der Unix-Philosophie ist hier, dass sich ein solches System nie in einem Zyklus bauen lässt. Systeme müssen verschiedene Phasen durchlaufen und konstant, iterativ verbessert werden um Anforderungen ideal zu erfüllen. In jeder Phase muss dazu gelernt werden. Die Alternative ist einen Prototypen zu bauen und diesen iterativ auszubauen. Da Programme klein und überschaubar sein sollen fällt der Prototypenbau leichter.

Automate everything
Aufgaben, die heute von Hand ausgeführt werden, obwohl sie auch das System übernehmen könnte, sind nüchtern betrachtet Zeitsverschwendung. Da unter unixoiden Systemen Programme darauf ausgelegt sind mit anderen Programmen zu interagieren — und nicht mit Menschen, können Abläufe beispielsweise mittels Shellskripten einfach automatisiert werden. In Shellskripten können Programme und Kommandos wie Funktionen verwendet werden. Die Ausgabe kann direkt in den Programmfluss integriert weren.

Fazit
Die wichtigsten Konzepte zusammengefasst: Dinge einfach halten, Komplexität vermeiden. Eine Sache richtig machen. Programme miteinander kombinieren.

Das war jetzt lediglich ein kleiner Einblick in die Unix-Philosophie. Es gibt noch deutlich mehr Konzepte und man kann die Ideen auch nicht nur auf Betriebssysteme anwenden. Für weitere Informationen kann ich folgende Quellen sehr empfehlen:

Natürlich gibt es auch Probleme eines solchen Systemaufbaus. Diese habe ich hier absichtlich nicht thematisiert. Wer sich hierfür interessiert sollte sich etwa zu Plan 9 oder Kernelproblemen informieren.

GUIs & Threads in Java ME

Obwohl moderne Plattformen wie Android und iOS als auch alternative Ansätze wie mobile Webanwendungen zunehmend den Markt mobiler Applikationen beherrschen, spielt auch nach über 10 Jahren Java ME noch eine Rolle in diesem Bereich. Vor allem für Nicht-Smartphones und Low-End-Geräte ist Java ME eine verbreitete Technologie. Dieser Beitrag soll erläutern, wie man mit Java ME Anwendungen implementieren kann, die aufwendige oder länger dauernde Operationen im Hintergrund ausführen und somit eine “responsive” GUI bereitstellen.

Bei interaktiven Anwendungen, die Inhalte aus dem Internet laden oder komplexe Berechnungen durchführen, ist es besonders wichtig, die Benutzerschnittstelle durch diese Aktionen nicht vollständig zu blockieren. So können zum Beispiel HTTP Requests in mobilen Netzwerken mehrere Sekunden benötigen – die Anwendung sollte trotzdem benutzbar bleiben oder zumindest dem Anwender über den Ladevorgang informieren.

Wie auch bei Desktop-Applikationen liegt hierbei der Schlüssel im Umgang mit Nebenläufigkeit. So wie es in gewöhnlichen Java-Anwendungen mit GUI einen AWT-Thread gibt, der mit einer Event-Queue auf GUI-Ereignisse wartet und diese verarbeitet, gibt es auch in Java ME ein ähnliches Konstrukt für die Benutzerschnittstelle. Wichtig ist es nun, länger dauernde Operationen nicht in dem jeweiligen Thread auszuführen, sondern in einem separaten Thread. So können auch weiterhin GUI-Ereignisse abgefangen werden, auch wenn die Operation noch andauert. Die eigentliche Aufgabe – I/O Operationen, Berechnungen etc. – wird in einem eigenen Thread oder einem Pool von Threads durchgeführt. Die Aktion wird aus dem GUI-Thread heraus gestartet (z.B. nach dem Click auf einem Button), allerdings eben nicht im GUI-Thread, sondern separat. Somit wird das Starten der Operation asynchron und somit nicht blockierend durchgeführt, und auch mit dem Resultat sollte ähnlich umgegangen werden. Das Ergebnis der Operation sendet nach Bearbeitung das Ergebnis wiederum an den GUI-Thread, der dies dann darstellt. Für den letzteren Fall gibt es bereits ein fertige Methode: javax.microedition.lcdui.Display.callSerially(Runnable r). Diese Methode reiht das angegebene Runnable an das Ende der Event-Queue ein. Der GUI-Thread arbeitet wiederum die Events und Runnables der Reihe nach ab.

Für das Absenden von Hintergrundaktionen aus dem GUI-Thread bietet sich ein ähnliches Vorgehen an: Mithilfe einer blockierenden Queue sollten neue Runnables angelegt und eingetragen werden. Ein Thread oder ein Pool von Threads sollte nun aus der Queue lesen und die dortigen Runnables ausführen. Am Ende der run()-Methode jedes Runnables sollte nun das Resultat der Aufgabe wieder an die GUI zurückgegeben werden. Hierfür ist nun die Display.callSerially(Runnable r) hilfreich.

Die folgende Grafik illustriert das grundsätzliche Vorgehen:

Git als Update-Mechanismus

In diesem Beitrag möchte ich einen einfachen Mechanismus beschreiben, um in einer Client-Server Infrastruktur Updates an die Clients auszuliefern. Ich hatte bei einem Projekt einige Clients in ubiquitären Umgebungen und war dafür auf der Suche nach solch einem System. Die Clients sind einfache Kleincomputer mit einer schlanken Linux-Distribution und müssen ohne technische Wartung auskommen.

Anforderungen an den Update-Mechanismus sind:

  • Authentifizierung
    Die Verbindung zum Update-Server muss authentifiziert sein.
    Das war in den großen Betriebssystemen früher ein großes Problem und ist in vielen Programmen noch immer ein Schwachpunkt. Der Instant Messenger-Exploit Anfang dieses Jahr war etwa ein Beispiel, in dem der Update-Server nicht authentifiziert wurde.
  • Fallbacks
    Falls ein Update schiefgeht sollte es sehr einfach möglich sein den alten Stand wiederherzustellen. Es ist nicht akzeptabel, dass Daten verloren gehen. Alles muss abgespeichert werden.
  • Scripting
    Ich will an jeden Zeitpunkt des Updateprozesses einen Hook setzen können (vor dem Update, danach, etc.). Dies kann später benutzt werden um beispielsweise das Gerät nach dem Einspielen von Neuerungen zu rebooten, um zu überprüfen ob das Update erfolgreich verlaufen ist oder um etwa Datenbankänderungen durchzuführen.
  • Autorisierung
    Der Updateserver darf nicht öffentlich verfügbar sein — Clients müssen sich ihm gegenüber autorisieren um Zugriff auf Updates zu bekommen. Die Option später eine gruppenbasierte License-Policy für verschiedene Softwareversionen nachrüsten zu können soll offengehalten werden.

Ich habe mich letztlich dazu entschieden hierfür git zu verwenden. Die Versionsverwaltung deckt bereits im Vornherein einen großen Teil der Anforderungen ab. Git bot sich an, da ich auch sonst sehr gerne mit dem Tool arbeite und damit also schon umgehen konnte. Der Aufbau meines Systems sieht inzwischen so aus:

 

System-Aufbau

 

Auf der Serverseite

  • Webserver, Zugriff nur über HTTPS möglich, selbstsigniertes X.509 Zertifikat.
  • Ein “bare” git repository, geschützt über HTTP-Authentifizierung: https://updates.foo.net/bar.git.
    Neue Updates werden zu diesem Repository gepusht.
  • hooks/post-update: Wird ausgeführt nachdem neue Updates gepusht wurden.

    # wird von git für HTTP repositories benötigt
    git update-server-info  
    
    # korrekte Zugriffsrechte
    find ~/bar.git -exec chmod o+rx '{}' \;
    

 

Auf der Client-Seite

  • Cronjob. Überprüft regelmäßig ob neue Updates vorliegen. check.sh enthält:
    #!/bin/sh
    
    # unser Update-Repository
    cd "~/bar.git"
    
    # neue Updates fetchen, git legt diese dann in FETCH_HEAD ab
    git fetch
    
    # liegen im origin-Repository neue Änderungen vor?
    newUpdatesAvailable=`git diff HEAD FETCH_HEAD`
    
    if [ "$newUpdatesAvailable" != "" ]
    then
            # fallback erstellen
            git branch fallbacks
            git checkout fallbacks
    
            git add .
            git add -u
            git commit -m `date "+%Y-%m-%d"`
            echo "fallback created"
    
            git checkout master
            git merge FETCH_HEAD
            echo "merged updates"
    else
            echo "no updates available"
    fi
    
  • Unter hooks/post-merge können Aktionen definiert werden, die nach dem Einspielen der Änderungen ausgeführt werden (Reboot, Validierung des Updateverlaufs, etc.).

Der Client hat eine Kopie des Server-Zertifikats und benutzt dieses um die Verbindung zu authentifizieren. So können die Geräte sicher sein, dass sie zum richtigen Server verbunden sind. Die passende ~/bar.git/.gitconfig sieht in etwa so aus:

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = https://user@updates.foo.net/bar.git
[http]
        sslVerify = true
        sslCAInfo = ~/server.crt
[core]
        askpass = ~/echo_your_https_authorization_pw.sh

 
Was sind Nachteile dieses Aufbaus?
Ich habe mich bewusst für ein Versionierungssystem entschieden, da meine wichtigste Anforderung ist, dass die Geräte auf keinen Fall Daten verlieren. Dies ist gleichzeitig auch ein Nachteil des Systems, da letztlich alles archiviert wird kann dies bei Geräten mit geringem Speicher kritisch sein. Ein ähnliches Problem tritt auf, falls häufig ein großer Teil des Systems geändert werden muss. In diesem Fall würde eine Lösung mittels rsync oder ähnlichen Tools eventuell mehr Sinn machen. rdiff-backup führt etwa inkrementelle Backups durch und bietet dazu noch die Möglichkeit die Anzahl der Versionen die behalten werden zu beschränken.

Do not use SSH.
Ein einfacherer Weg solch ein System aufzubauen wären SSH-Zugänge mit eingeschränkten Dateirechten für die Clients. Das Problem hierbei ist, dass Geräte Zugriff auf den Server bekommen. Ein Angreifer könnte sich mit dem private key des Clients zum Server verbinden. Es ist nicht aussergewöhnlich, dass in Betriebssystem-Kernen oder in System-Bibliotheken Sicherheitslücken entdeckt werden. Im schlimmsten Fall könnte ein Angreifer so die Dateien innerhalb des Repositories manipulieren. Die Clients würden automatischen den manipulierten Inhalten fetchen. Das manipulierter Inhalt ausgeliefert wird, ist auch in meinem Setup nicht ausgeschlossen, dazu muss der Angreifer aber über einen Zugang zu dem Server verfügen.

Warum wir eine Wave brauchen

Das Problem

E-Mail ist alt und kaputt. Es gehört durch modernere Kommunikationsarten ersetzt. Warum? Was erwarten wir von Kommunikation über das Internet?

  • Confidentiality, Integrity, Authentication
  • Perfect Forward Secrecy (PFS)
  • Plausible Deniability (PD)
  • Asynchrone und synchrone Kommunikationsmöglichkeit
  • Gruppenkommunikation
  • Kollaboration
  • Multimediale Kommunikation
  • Dezentrale Struktur

Wenn wir von Verschlüsselung sprechen, dann ist damit Ende-zu-Ende-Verschlüsselung gemeint und nicht die Verschlüsselung der Transportwege. Das Gleiche gilt für Integrity und Authentication.

Wir können E-Mails verschlüsseln und digital signieren (z. B. durch Verwendung von OpenPGP). Leider kann dann für immer und von jedem bewiesen werden, dass wir diese Nachricht geschrieben haben. Ein weiteres Problem entsteht, wenn einer der Keys in falsche Hände gerät. Dann sind sämtliche bisher für ihn verschlüsselten Daten kompromittiert.

E-Mail ist langsam und immer asynchron. E-Mail-Verkehr kostet unnötig Zeit.[0]

E-Mail ist nicht kollaborativ.

E-Mail-Anhänge ermöglichen zwar die Übertragung multimedialer Inhalte, sind aber unglaublich ineffizient und ressourcenfressend.

Was gibt es also für Alternativen? Wie kann besser kommuniziert werden?

Soziale Netzwerke sind Privacy-Desaster.

Instant Messaging erfüllt die meisten Zwecke. Bei vielen Protokollen ist sowohl asynchrone als auch synchrone Kommunikation möglich. Confidentiality, Integrity, Authentication, Perfect Forward Secrecy und Plausible Deniability wird durch Verwendung von OTR[1] erreicht (allerdings nur bei Kommunikation mit einem Partner). Einige Protokolle erlauben Gruppenkommunikation durch Chaträume oder multimediale Kommunikation, wie das Übertragen von Dateien oder Video-/Audio-Telefonie.

Warum reichen bestehende Instant-Messaging-Systeme nicht aus? Was fehlt uns?

  • Unterhaltungen in Echtzeit
  • Flexibles Hinzufügen von Teilnehmern zur Gruppenkommunikation
  • Verbessertes Sharing und Darstellung von Multimediainhalten
  • Verschlüsselte, möglicherweise asynchrone, Gruppenkommunikation
    (mit PD und PFS)

Die ersten drei Punkte kann Googles Wave[2,3]. Das Problem der verschlüsselten Gruppenkommunikation mit den beschriebenen Bedingungen ist leider darin bisher nicht gelöst worden. Zudem existieren keine zufriedenstellenden Clients für Wave.

Wir wollen, dass die Infrastruktur nicht von einem einzigen Knotenpunkt abhängig ist. Dezentrale Struktur wird von den Protokollen, die E-Mail bzw. Wave zugrunde liegen, unterstützt. Fast alle IM-Dienste erlauben diese nicht.

Die Lösung

Wir sehen mehrere Lösungsansätze.

Komplett neues Protokoll mit Implementierung und Infrastruktur

Auch bekannt als: „Das Rad neu erfinden“. Machen wir nicht. Das Rad gibt es schon. Dieser Ansatz wäre sehr aufwändig und würde sich schwer etablieren.

Verwendung bestehender Protokolle

Bedingung ist, dass das Protokoll offen und leicht erweiterbar ist. Hier halten wir nur XMPP für sinnvoll. Der Vorteil dieser Lösung ist, dass genau die gewünschten Features implementiert werden können und keine Kompromisse notwendig sind. Voraussichtlich wird bereits bestehende Infrastruktur verwendet werden können.

Erweiterung von Wave

Wave hat bereits die meisten Features, welche wir uns wünschen. Verschlüsselung fehlt aber und es existieren keine guten Clients.

Der Plan

Wir evaluieren die letzten beiden Lösungsansätze und implementieren dann die sinnvollste Lösung. Dazu müssen wir zunächst XMPP und das Google Wave Federation Protocol verstehen.

Auf jeden Fall werden wir einen Client schreiben, der im Vergleich zu bestehenden Clients besser bedienbar ist und unsere beschriebenen Anforderungen erfüllt. Für Legacy-Clients ist eine (teilweise) Umsetzung der Features, beispielsweise durch Plugins, denkbar.

Die Unzufriedenheit mit vorhandenen Kommunikationsmöglichkeiten und die Idee diese zu verbessern schlummert schon seit einiger Zeit in uns. Hiermit möchten wir die Sache ins Rollen bringen. Stay tuned!

–nico, phil, matou

[0] http://www.heise.de/newsticker/meldung/Unternehmen-will-Mitarbeitern-die-E-Mail-abgewoehnen-1184596.html
[1] http://www.cypherpunks.ca/otr/
[2] http://wave.google.com/
[3] http://www.waveprotocol.org/

ioexception.de

Benjamin Erb [] studiert seit 2006 Medieninformatik und interessiert sich insbesondere für Java, Web-Technologien, Ubiquitous Computing, Cloud Computing, verteilte Systeme und Informationsdesign.


Raimar Wagner studiert seit 2005 Informatik mit Anwendungsfach Medizin und interessiert sich für C++ stl, boost & Qt Programmierung, Scientific Visualization, Computer Vision und parallele Rechenkonzepte.


David Langer studiert seit 2006 Medieninformatik und interessiert sich für Web-Entwicklung, jQuery, Business Process Management und Java.


Sebastian Schimmel studiert seit 2006 Informatik mit Anwendungsfach Medizin und interessiert sich für hardwarenahe Aspekte, Robotik, webOs, C/C++ und UNIX/Linux.


Timo Müller studiert seit 2006 Medieninformatik. Er interessiert sich allen voran für Mobile and Ubiquitous Computing, systemnahe Enwticklung und verteilte Systeme, sowie Computer Vision.


Achim Strauß studiert seit 2006 Medieninformatik. Seine Interessen liegen in Themen der Mensch-Computer Interaktion sowie Webentwicklung und UNIX/Linux.


Tobias Schlecht studiert seit 2006 Medieninformatik und interessiert sich vor allem für Software Engineering, Model Driven Architecture, Requirements Engineering, Usability Engineering, Web-Technologien, UML2 und Java.


Fabian Groh studiert seit 2006 Medieninformatik. Seine Interessengebiete sind Computer Graphics, Computer Vision, Computational Photography sowie Ubiquitos Computing.


Matthias Matousek studiert seit 2007 Medieninformatik und interessiert sich besonders für Skriptsprachen, Echtzeitsysteme und Kommunikation.


Michael Müller [] studiert seit 2009 Medieninformatik. Er interessiert sich vor allem für Web-Technologien, Ubiquitous Computing, User-Interfaces, UNIX und Creative Coding.


Falco Nogatz [] studiert seit 2010 Informatik mit Anwendungsfach Mathematik. Er interessiert sich für Web-Technologien, Programmierparadigmen und theoretische Grundlagen.

Archiv

Februar 2012
M D M D F S S
« Dez    
 12345
6789101112
13141516171819
20212223242526
272829