Samstag, 18. April 2009

Wie kann man sich vor Phorm schützen?

Über die britische Unternehmung Phorm gibt es z. Z. viel Aufregung in den Medien, z. B. auch in diesem Artikel auf Heise Online.

Man kann es aber auch mit Angst zu tun bekommen, wenn man liest, das einem in Zukunft Cookies nicht mehr nur von den besuchten Webseiten (und den unzähligen dort ggf. integrierten GoogleAds, Bannern, Trackern usw.) untergeschoben werden können, sondern auch von einem ‘Dienst’, mit dem man nie etwas zu tun haben wollte und der offenbar auch noch mindestens temporär die Rolle des angesurften Webservers übernimmt.

Grundsätzlich ist es offenbar im wesentlichen eine Frage, ob der eigenen Internetprovider mit Phorm kooperiert.

Freitag, 17. April 2009

IE 8: 'Die Datei konnte nicht in den Zwischenspeicher geschrieben werden'

P1080645b Heute meldete sich eine Nutzerin unserer Dienste, die ein Problem beim Download einer Worddatei aus unseren Webseiten hat. Sie verwendet den Internetexplorer (Version und Betriebssystem unbekannt). Das Problem lässt sich aber im MSIE 8.0.6001 unter Windows XP nachvollziehen.

Eingrenzung 1: Problem nur mit SSL

Mit einer neu angelegten Testdatei kann das Problem ebenfalls verifiziert werden, es liegt also nicht an der speziellen Datei, welche die Nutzerin verwendet hat. Allerdings tritt das Problem nur dann auf, wenn der Zugriff auf die Webseite per SSL (HTTPS) erfolgt. Bei einem HTTP Zugriff gibt es kein Problem.

Eingrenzung 2: Problem nur mit bestimmten Formaten

Das Problem tritt mindestens auf bei Word (Endung *.doc) und PDF Dateien (Endung *.pdf). Bei Bilddateien (Endung *.jpeg) tritt es nicht auf.

Internetquellen

Es gibt eine Microsoft Seite, welche sich um ein ähnliches (identisches?) Problem kümmert, allerdings nur bis zum IE 6 reicht.

By the way: diese Seite ist ein interessantes Beispiel für die manchmal rätselhaften Ergebnisse automatischer Übersetzungen. So kann man über Wortschöpfungen wie ‘Cache-control:no- speichert’ grübeln:)

Hier ist noch eine englische MS Seite, die das Thema behandelt (auch nur bis zum IE 6).  Eine erhellendere Beschreibung findet man in the downside.

Lösung

Wie in dem von the downside zitierten Blog beschrieben folgende Zeilen in das Servlet eingefügt:

response.setHeader("Content-Disposition", "attachment; filename=\"" + dateiName + "\"");
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "max-age=0");

Damit funktioniert es dann im IE 8 und auch weiterhin im Firefox und Chrome.

Donnerstag, 9. April 2009

Kleine Javascript/CSS Bastelei: Zusammenhängende Einträge in einer großen Tabelle kennzeichnen

P1020986 Wir haben eine oft recht umfangreiche Tabelle in der eKVV Suche nach freien Räumen. In jeder Zelle werden die freien Räume gezeigt, wobei der gleiche Raum in unterschiedlichen Zellen an unterschiedlicher Position stehen kann. Dies lässt sich nicht verhindern, da die Tabelle sonst noch größer werden würde.

Damit die BenutzerInnen trotzdem einfach sehen können, ob der Raum in anderen Zellen (also zu anderen Zeiten) verfügbar ist wird mit Javascript und CSS folgende Funktionalität implementiert:

  • Sobald der Mauszeiger auf einen bestimmten Raum geht werden alle Vorkommnisse des Raums in der Tabelle anders formatiert.
  • Sobald der Mauszeige den Raum wieder verlässt wird die alte Formatierung wieder hergestellt.

Vorbereitung der gemeinsamen Formatierung: CSS Klassen

Damit die Formatierung aller Vorkommnisse eines Raumes leicht per CSS gemacht werden kann, erhalten die Links der Räume eine weitere CSS Klasse, die sich aus dem Raumnamen ableitet. Beispiel:

<a href=”..” class=”… raum_h1”>…</a>

Da in den Raumnamen prinzipiell alle Zeichen zugelassen sind muss eine Bereinigung durchgeführt werden. In der Bereinigung werden alle Zeichen bis auf a-z und 0-9 entfernt.

Mit dieser Vorbereitung können über eine CSS Anweisung wie der Folgenden alle Stellen mit dem gleichen Raum markiert werden:

a.raum_h1 {
     border: 1px solid black;
}

Hier sind der Phantasie natürlich keine Grenzen gesetzt. Die Manipulation des Stylesheets hat gerade bei großen Tabellen Performancevorteile im Vergleich zur Manipulation jedes einzelnen zu formatierenden Elements, z. B. durch Hinzufügen/Entfernen einer besonderen Klasse zu allen Links des gleichen Raums in den onmouseover/onmouseout Events.

Javascriptfunktionen für die Events onmouseover und onmouseout

Alles was wir jetzt noch brauchen sind zwei Eventhandler auf dem Anchortag:

  1. onmouseover: Beim ‘Betreten’ des Raumbereiches mit dem Mauszeiger muss die Markierung per CSS aktiviert werden
  2. onmouseout: Beim Verlassen muss die Markierung deaktiviert werden, damit nicht nach und nach alle Räume gekennzeichnet bleiben

Da es sehr viele Links gibt lagern wir die beiden Aktionen in eigene Funktionsdefinitionen aus. Das spart viel Platz im generierten HTML Code. Die Anchortags sehen dann so aus:

<a class=".. raum_h11"
          onmouseover="javascript:zr('raum_h11');"
          onmouseout="javascript:vr();" …

Jetzt müssen wir nur noch die Funktionen zr (für zeige Raum) und vr (für verberge Raum) implementieren und im HTML Code der Seite unterbringen. Warum beim Aufruf von vr kein Parameter übergeben wird? Das sehen wir gleich.

Nützlich Hinweise zur Manipulation der Stylesheets per Javascript finden sich z. B. im DADABase Blog und in den javascript.faqts. Mit diesen Hilfen bekommt man schnell die notwendigen Funktionen hin. Das wesentliche DOM Element ist dabei document.styleSheets:

<!-- Dieses leere STYLE Tag wird gebraucht für die folgenden JS Funktionen -->
<style></style>
<script type="text/javascript">
    function zr(cssClass) {
        vr();
        var sSheet = document.styleSheets[document.styleSheets.length-1];
        var selector = "a." + cssClass;
        var rule = "border: 1px solid black;";
        sSheet.insertRule("" + selector + " { " + rule + " }", 0);
    }
    function vr() {
        var sSheet = document.styleSheets[document.styleSheets.length-1];
        while (sSheet.cssRules && sSheet.cssRules.length > 0) {
            sSheet.deleteRule(0);
        }
    }
</script>

Wir machen es uns hier einfach und greifen mit document.styleSheets[document.styleSheets.length-1] direkt auf das extra für diesen Zweck angelegte, zunächst leere STYLE Tag zu. Falls noch andere STYLE Tags folgen sollten funktioniert das natürlich nicht so einfach!

Dieses Konstrukt funktioniert wunderbar in Firefox 3.0.8, in Chrome 2.0.x, in Safari 4 Public Beta und in Opera 9.64. Aber natürlich funktioniert es deshalb noch lange nicht im Internet Explorer. Und das tut es auch in der Tat nicht im MSIE 8.

Der Grund liegt darin, dass der MSIE kein insertRule kennt, sondern eine addRule Funktion. Das gleiche gilt auch für die Funktionen zum Entfernen von Rules. Ein Beispiel, wie man damit umgehen kann, findet sich in CodingsForums.com. Ein noch raffinierterer Trick wird auf TUTORIALHELPDESK.COM beschrieben. Dort wird für den MSIE das Stylesheetobjekt einfach um die noch fehlenden Funktionen ergänzt. Dieser Ansatz der einheitlichen Methoden hat nur beim Hinzufügen der Rules seine Grenze, da insertRule weniger Parameter erwartet als addRule

Mit diesem Wissen ausgestattet kann man nun die Funktionen so erweitern, dass sie auch im MSIE funktionieren. Hier das Ergebnis, welches nun auch im MSIE 8 funktioniert:

   <!-- Dieses leere STYLE Tag wird gebraucht für die folgenden JS Funktionen -->
   <style></style>

    <script type="text/javascript"> 
        function zr(cssClass) {
        vr();
        var sSheet = provideSSheet();
        var selector = "a." + cssClass;
        var rule = "border: 1px solid black;";
        try {           
            sSheet.insertRule("" + selector + " { " + rule + " }", 0);
        } catch (msie) {
            try {
                sSheet.addRule(selector, rule);
            } catch (err) {
            }
        }
    }
    function vr() {
        var sSheet = provideSSheet();
        while (sSheet.cssRules && sSheet.cssRules.length > 0) {
            sSheet.deleteRule(0);
        }
    }
    function provideSSheet() {
        var sSheet = document.styleSheets[document.styleSheets.length-1];
        // Check to see if we are operating in Internet Explorer
        if (sSheet.rules)
        {
            // Map the standard DOM attributes and methods to the internet explorer
            // equivalents
            sSheet.cssRules = sSheet.rules; 
            sSheet.deleteRule = function(ruleIndex)
            {
                 this.removeRule(ruleIndex);
            };
        }
        return sSheet;
    }   
</script>

Damit sind wir fertig! Und so sieht es dann aus:

ScreenShot_eKVV_Raumfreisuche

Erfahrungen

Die Lösung scheint in verschiedenen Browsern unterschiedlich schnell zu reagieren. Hier ein paar erste Erfahrungen:

Firefox: Grundsätzlich am problemlosesten. Alles ist flüssig.

Opera: Ähnlich wie Firefox.

Chrome/Safari: Der allererste Aufruf der JS Funktionen braucht eine gewisse Zeit, danach ist alles flüssig

MSIE 8: Nie wirklich schnell, das rasche Scrolling über die Seiten wird manchmal behindert durch die JS Funktionen

Montag, 6. April 2009

Wie kam die schöne Flasche auf den Hankenüll

P1170443 Bei der letzten Wanderung mit dem Hund im nahen Teutoburger Wald fand ich eine alte Getränkeflasche mit interessanter Form. Ich habe sie bis nach Hause getragen um zu sehen wie weit ich mit Hilfe des Internets der Frage nachgehen kann:

Wie kam die schöne Flasche auf den Hankenüll?

Zum Fundort: Was  ist der Hankenüll?

Der Hankenüll ist meines Wissens die höchste Erhebung des Teutoburger Waldes, abgesehen von den Bergen an Anfang und Ende. Laut dem spärlichen Artikel in der Wikipedia ist er 307m hoch. Er wird gekennzeichnet durch einen alten Grenzstein, der im 19. Jhrd. die Grenze zwischen Hannover und Preußen kennzeichnete.

Über den Hankenüll  führt der Hermannsweg, jener Wanderweg, der immer entlang des Kammes des Teutoburger Walds verläuft. In einer schlammigen Stelle dieses Wanderwegs lag die Flasche nicht mehr als 10m vom ‘Gipfel’ entfernt.

Was ist das für eine Flasche?

P1170445 Die Form der Flasche ähnelt eine langgezogenen Variante der Orangina-Flasche. Es ist eine 0.2l Flasche. Am Hals hat sie jedoch eine 10 flächige Abflachung, ähnlich wie ein Schraubansatz. Gibt es eigentlich so etwas wie ein Flaschenformenarchiv oder –museum im Internet?

Leider hat das ‘europaweit einzigartige’ Flaschenmuseum von "Flaschen-Sepp" in der Käserei Kappelimatt offenbar keinen eigenen Internetauftritt. Da ist das Baby Bottle Museum schon viel besser aufgestellt, aber ich bin mir einigermaßen sicher, dass es sich bei meiner Flasche nicht um ein Utensil für Babies handelt.

Da ist die Googlesuche nach ‘Flaschenformen’ schon ergebnisreicher. So findet man viele Seiten, die sich mit Formen von Weinflaschen befassen. Dort kann man z. B. lernen was ein Fiasco ist. Meine Flasche ist allerdings kein Fiasko.

Am Boden der Flasche findet sich ein in Glas geprägter Herstellername:

P1170446

P1170447 

Die ‘Süssmoster Genossenschaft’! Leider scheint sie nicht im Internet vertreten zu sein. Hier findet man nur diverse Telefonbucheinträge der ‘Deutsche Süßmoster-Genossenschaft eG’. Ob die überhaupt etwas mit meiner Flasche zu tun hat?

Bleiben als letzter Hinweis noch die Reste des Etiketts. Es zeigt in einer Art Strahlenkranz ein flaches Römerglas. Darüber ist gerade noch der Schriftzug APFELSAFT in zwei Zeilen zu sehen, wobei ein Apfelblatt aus dem Text entspringt. Gibt es wohl so etwas  wie Etikettenarchive im Internet? Offenbar nicht…

Da sich die Identität der Flasche nicht wirklich feststellen lässt bleiben nur Vermutungen: Ich halte die Flasche für schon etwas älter. Das Etikettendesign wirkt nicht mehr top-aktuell und die Form der Flasche kommt mir auch unbekannt vor.

Wie könnte die Flasche zum Hankenüll gelangt sein?

P1170448

Hier bleibt dann wohl nur noch ganz wilde Spekulation.

Theorie A: Da die Flasche direkt am Hermannsweg lag könnte sie von Wanderern, die keine rechten Naturfreunde waren, einfach weggeworfen worden sein. Das müsste dann wohl vor der Einführung des Flaschenpfands gewesen sein.

Theorie B: Auch am letzten Samstag waren im Wald etliche Leute unterwegs, die ihrer Kettensäge etwas Auslauf gönnten und das Brennholz für die nächsten Winter fertig machten. Da man bei dieser anstrengenden und gefährlichen Arbeit kein Bier trinken kann wäre die zweite Theorie, dass die Flasche ein Überbleibsel von Holzarbeiten im Wald ist. Als Untertheorie erscheint es noch plausibel, dass die Waldarbeiter nicht mit den Waldbesitzern identisch waren, da diese wohl kaum ihren eigenen Wald vermüllt hätten.

Theorie C: Auch bei den Jagden in Herbst und Winter werden die beteiligten Personen durstig. Allerdings erscheint diese Theorie nicht ganz so plausibel, da hier eher Heißgetränke gereicht werden.

Nun, ich werde wohl keine dieser Theorien jemals verifizieren können. Falls mir aber jemand eine plausible Geschichte zu der Flasche präsentieren kann, so werde ich ihm einen Cappuccino in unserem Café ausgeben:)

GWT Lernen: Wie funktionieren die RPCs?

DSCF0005c

Die RPCs (Remote Procedure Calls), die das GWT bietet, sind leicht zu nutzen. Aber wie funktioniert die ganze Sache eigentlich? Und sollten wir die RPCs einsetzen, oder lieber auf vorhandene bzw. allgemeinere Services setzen?

Wie funktioniert die GWT RPC ‘Magie’?

Das Diagramm, welches man in der Doku zum Thema RPCs findet, ist schon recht hilfreich.  Der GWT Compiler arbeitet gerade bei der clientseitigen Programmierung eines RPCs mit Namenskonventionen:

So muss das asynchrone Serviceinterface immer den Namen des synchronen Interfaces plus Async tragen und im gleichen Package liegen. Auch müssen hier die gleichen Methoden definiert werden, allerdings mit einem zusätzlichen Callback Parameter und mit Ergebnistyp void. Auf Java Ebene gibt es ansonsten keine Beziehung zwischen den beiden Interfaces.

Der Callback Parameter AsyncCallback<T> ist ein generisches Element.  Für ‘T’ soll hier der Ergebnistyp der Methode im synchronen Serviceinterface gesetzt werden, dann ist dieser Typ in der onSuccess Methode direkt ohne Casting nutzbar. In der Implementierung verwendet man von AsyncCallback abgeleitete Klassen um mit Erfolg oder Misserfolg des Serviceaufrufs umzugehen.

Bei der serverseitigen Implementierung des RPCs ist hingegen weniger ‘Magie’ im Spiel: Im Grunde geht nur darum ein spezielles Servlet zu implementieren, welches das synchrone Client(!) Interface implementiert und von der Klasse RemoteServiceServlet abgeleitet ist. An dieser Stelle spielt der Klassenname keine Rolle, es gibt hier keine ‘magischen’ Namenskonventionen. Man kann das Servlet also auch im Gegensatz zu den GWT Beispielen komplett anders benennen. Wichtig ist nur, dass diese Klasse nicht im gleichen Package landet wie die Clientklassen/-interfaces.

Die Adresse des Servlets (aka Service) sollte dann noch der Clientseite durch Einträge in der <Projekt>.gwt.xml Datei (der sogn. Modul XML Datei , module XML file) bekannt gemacht werden. Diese Verdrahtung ist gewöhnlichen Servletverknüpfungen in der web.xml nicht unähnlich, kommt aber mit einer einzigen Anweisung pro Service aus. Wichtig: Diese Verdrahtung ist nur für den Test im hosted mode notwendig. Sie hat keinen Einfluss auf die produktive Installation.

Dieser Onlinevortrag enthält eine Darstellung der RPC Programmierung, die an manchen Stellen die GWT Seiten etwas ergänzt.

GWT RPC vs. XML Services plus Struts

In der mit dem GWT  erstellten BIS Anwendung gäbe es zwei denkbare Pfade, wie die Serverkommunikation mit dem Ajax Client durchgeführt werden könnte:

A) Wir setzen komplett auf die GWT RPCs

Vorteil wäre eine zunächst einheitliche und vermutlich rasche Entwicklung, die komplett in der GWT ‘Welt’ bleibt. Hier gibt es Lösungen für Exception Handling und all die anderen kleinen Dinge, die eine Anwendung erst ausreichend robust für den Einsatz machen.

Allerdings müssten einige Teile der Anwendungslogik erneut implementiert werden, die an anderer Stelle schon vorhanden sind. Auch bei dieser Lösung würden auf der untersten Ebene wieder die Struts und iBatis nutzenden Basisklassen eingesetzt werden.

Aufgaben in der Programmierung um eine Datensatzart abzudecken:

  • Erstellung einer GWT Modellklasse, z. B. ‘Person’. Wichtig ist hier die Serialisierbarkeit
  • Erstellung einer RPC Klasse für lesende Zugriffe. Muss aus den vorhandenen Strutsklassen die Instanzen der GWT Modellklassen erzeugen
  • Erstellung einer RPC Klasse für schreibende Zugriffe. Hier müssen Rechtemanagement und Logging eingefügt werden sowie das Rückmapping des GWT Modells auf die Strutsklassen

Beim Deployment der Anwendung würde man wie in der GWT Doku beschrieben vorgehen und müsste nur der Anwendung den Connectionpool zur Datenbank bekannt machen, damit die Basisklassen arbeiten können.

B) Erstellung allgemeiner XML Services und Nutzung der vorhandenen Struts Programmierung

Eine Alternative könnte in der Erstellung von ‘allgemeinen’ XML Services sein, die nicht nur für die GWT Anwendung nutzbar wären. Ein Beispiel ist der schon existierende Service für die Publikationsdaten. Auch für andere Daten werden solche XML Services nachgefragt und müssen so oder so implementiert werden.

Im GWT lassen sich solche Services ebenfalls relativ einfach verarbeiten, auch wenn der Aufwand im Vergleich zu den RPCs höher erscheint, insbesondere wenn der XML Service nur für das GWT erstellt werden müsste.

Beim Schreiben der Daten auf den Server könnte aus dem GWT heraus ein ‘normaler’ Formularsubmit ausgelöst werden, welcher dann die entsprechende Strutsklasse populiert. Ab hier würde es ‘wie gewohnt’ weiter gehen.

Aufgaben in der Programmierung um eine Datensatzart abzudecken:

  • Implementierung der lesenden XML Schnittstelle
  • Implementierung einer GWT Modellklasse, die hier aber nur auf Clientseite verwendet wird (könnte u. U. ausgelassen werden, wenn alle Daten sofort in Widgets gesteckt werden, aber dies erscheint zu unflexibel)
  • Abbildung der XML Daten auf die Modellklasse
  • Aufbau einer kompletten Strutsanwendung, die mindestens in der Lage ist die schreibenden Operationen aufzunehmen. Das Rechtemanagement kann wie bisher über Filter erfolgen oder auch direkt in den Actionklassen.

Das Deployment einer solchen Anwendung wäre ähnlich wie das Deployment einer reinen Strutsanwendung. Es müsste nur das Verzeichnis mit den statischen GWT Inhalten in die Anwendung kopiert werden.

Wie ist die Trennung von Client- und Serverseite möglich?

Für die zuvor diskutierten BIS Szenarios wäre es teilweise sinnvoll, wenn wir RPC Services, die nicht weiter geschützt werden müssten, und die auch in anderen Anwendungen genutzt werden könnten, sofort in eine allgemeine Anwendung verlagern würden.

Die Trennung müsste eigentlich vergleichsweise einfach sein:

  • Die serverseitige Implementierung braucht nur Zugriff auf das synchrone Clientinterface. Dies kann über die gemeinsame Codebasis erreicht werden.
  • Die clientseitige Implementierung findet ihre Serviceendpunkte für das Testen durch die in der <Projekt>.gwt.xml definierten Adressen. Diese können sich auf den Serverkontext beziehen und nicht nur auf den Webapplikationskontext. Sobald die Clientimplementierung in JS übersetzt wurde wird beim RPC Aufruf der in den jeweiligen ServiceDefTarget.setServiceEntryPoint(String) Kommandos gesetzte Pfad verwendet. Hier spielen die Servletdefinitionen in der Modul XML Datei keine Rolle mehr. Daher:
  • Es müssen zu den auf der Clientseite definierten Adressen passende web.xml  Verdrahtungen der Serviceservlets eingerichtet werden.

Auf diese Weise ist eine gekoppelte Entwicklung, aber ein getrenntes Deployment im Produktivsystem möglich.

Ein denkbares Szenario ist folgendes:

  • Die übersetzte Clientseite, die ja nur aus statischen Inhalten besteht (HTML Startseite, Javascript, CSS, Bilder), wird auf den Apache Servern in einem Unterverzeichnis unter den htdocs abgelegt. Eine neue Version  der Anwendung wird einfach durch Hochladen dieser Dateien installiert.
  • Die serverseitige RPC Installation erfolgt in einer J2EE Anwendung auf den Tomcat Servern (WAR Archiv).

Eine Eigenschaft dieser Lösung ist die Möglichkeit Client- und Serverseite getrennt zu aktualisieren. Bei Änderungen an den RPC Interfaces müssen natürlich beide Seiten zugleich aktualisiert werden.

GWT Lernen: Deployment einer GWT Anwendung

Die Installation (das ‘Deployment‘) einer GWT Anwendung zerfälP1090013lt im Grunde in zwei Teile:

  • Deployment der clientseitigen statischen Inhalte, welche aus Bildern und CSS Inhalten, vor allem aber aus den vom GWT aus Java generierten Javascript Dateien bestehen
  • Deployment der serverseitigen RPC  Klassen

Clientseite

Die Dateien der Clientseite müssen nur ‘irgendwie’ vom Webbrowser erreicht werden können. Man kann sie also auf einem Webserver wie Apache HTTPD unterbringen oder in der J2EE Anwendung irgendwo, nur nicht im /WEB-INF-Verzeichnis.

Wo man diese Dateien genau ablegt ist im Grunde frei. Man muss sich nur ggf. Gedanken über die SOP – Same Origin Policy machen.

Serverseite

Alle serverseitigen Klassen müssen für das Deployment wie üblich in das /WEB/classes/Verzeichnis kopiert werden, oder, falls sie als JAR gebündelt sind, in das /WEB-INF/lib/ Verzeichnis.

Im Falle der RPCs handelt es sich letztlich um Servlets. Diese müssen wie gewohnt in der /WEB-INF/web.xml ‘verdrahtet’ werden. Dabei müssen die Pfade verwendet werden, die die Clientseite auch kennt.

Hier die genaue Beschreibung dazu in der GWT Doku.

Die Verbindung zwischen Client- und Serverseite

Die Clientseite der GWT Anwendung findet ihre RPCs über die Adressen, die in der <Projekt>.gwt.xml konfiguriert sind. Hier müsste es im Prinzip auch möglich sein, Adressen zu verwenden, die nicht innerhalb der gleichen Webanwendung liegen.