Samstag, 22. Dezember 2012

Das war nicht gut

Neulich habe ich hier mal einen Workaround mit Gradle für das Classpath-Problem mit dem YUI-Compressor beschrieben.
Dieser Workaround funktioniert solange sehr gut, wie man in den Web-Archiv-Artefakten alle Java-Bibliotheken mitschleppt und dann beim Kopieren daraus auswählen und umbenennen kann.
Genau das habe ich gestern mit der Version 0.7 von Tangram aber endlich eingedämmt, da man jede Menge Code und damit Daten ständig von A nach B schiebt. Man kann eigentlich all die JAR-Dateien einfach weglassen. Sie kommen automatisch durch die Abhängigkeiten der restlichen Codes wieder rein. Alle JARs? Ein kleines, problematisches Archiv leider nicht: der YUI-Compressor. - Also habe ich die Mimik mal wieder umgebaut.
Dazu basteln wir uns eine weitere Configuration, wie das für die Google App Engine schon für das enhancen der JDO-Klassen notwendig war:

configurations {  .
  .
  .
  yui
}

Mit dieser Konfiguration belegen wir nur den YUI-Compressor:

dependencies {
  .
  .
  .
 providedCompile "com.yahoo.platform.yui:yuicompressor:$yui_version"
 yui "com.yahoo.platform.yui:yuicompressor:$yui_version"
  .
  .
  .
}

Er wird also aus den Abhängigkeiten entfernt und nur in der Konfiguration genutzt. Und die Elemente dieser Konfiguration können wir nun wieder beim Kopieren umbenennen, wen man das abschließende into('') etwas tuned.

war.doFirst {
  .
  .
  .
  for (f in configurations.yui.files) {
    copy {
      from f.absolutePath
      into "$buildDir/target/WEB-INF/lib"
      rename 'yui', 'aaa-yui'
    }
  }
 
  into ('') {
    from "$buildDir/target"
    exclude 'WEB-INF/lib/tangram-**'
  }
}
Und damit wäre dieser Bereich auch wieder heil und kann wieder als Vorlage für entsprechende Fälle mit dem YUI-Compressor und Gradle dienen.

Freitag, 21. Dezember 2012

Artefakte mit viel Liebe

Es liegt ganz sicherlich an mir, daß ich zwar nun für fast alles, was man so zu Entwicklung und Betrieb braucht, dienste in der "Cloud" gefunden habe, aber das Bereitstellen von Bibliotheken via Maven Repository noch nicht hingebekommen habe.
Aber im Grunde - so von der lesenden Seite - sind das ja nur Verzeichnisstrukturen mit einigen festgtelegten Dateien darin, die man per HTTP zugreifen kann. - Und das solle ist: Alle Daten dazu habe ich ja schon lokal auf der Platte, wenn ich meinen Build - bei mir ja immer mit Gradle - abgeschlossen habe.
Bis ich also verstanden habe, wie ich meine Projekte in den Prozeß in die Zentralen Maven Repositories einkippe, habe ich eher mal eben heute für die Zeit bis dahin ein Ad-hoc Maven Object Repository mit dem Tangram - in Version für die Google App Engine - geschrieben und es deshalb amor genannt.
Was mußte man dafür tun? Es gibt nur ein Datenmodell: Das Artefakt, wie es im Repository zugänglich gemacht werden soll. Der Rest ist URL-Format und eine kleine Listing-Seite - und damit sind das nur noch Dinge, die man dynamisch im Tangram mal schnell hinzufügen kann. Der Code findet sich auf github - mit Datenbank Abzug diesmal.
Die Beispiel-Site ist nun das Repository auf dem ich das aktuelle Tangram 0.7 zur Verfügung stelle: http://my-amor.appspot.com/

Donnerstag, 20. Dezember 2012

Neuer Frühling im Winter


Eventuell können wir die Diskussion ein wenig aufschieben, ob es nicht eigentlich kalendarisch noch Herbst wäre, aber es liegt nun einmal genug Schnee und trotzdem ist das Spring Framework in Version 3.2 herausgegeben worden.
Diesen Text könnte ich glatt auch einfach auf meine TODO Liste schreiben. Ein nettes, aber nicht aufregendes Update für das Spring Framework. Es ist ein extrem Umfangreiches, nicht immer komplett intuitiv und einfach zu benutzendes Framework, aber es tut einfach an so viele Stellen das, was man - mindestens im Web-Kontext - so braucht und wird von so vielen Menschen benutzt, daß ich für Tangram und fast alle kommerziellen Projekte irgendwo Spring im Einsatz habe.
Also sollte Tangram auch mal so schnell wie möglich den neuen Unterbau nutzen - wenn das mit Spring Security für den RDBMS-Bereich vereinbar ist. - Dort stehen wir im Moment noch auf dem "Development Release"  M1. Die Sanduhr tickt aber schon.
So richtig schön wird es, wenn man dann noch sieht, daß auch das Spring Framework in Sachen Source-Code-Verwaltung und Bau genau da angekommen ist, wo Tangram ganz klein und in der Ecke auch liegt: github und dem Build Tool gradle. Die Mavenfinsternis scheint langsam zu beginnen, wenn man auch in diesen Dimensionen sich auf das neuere Tool stützen kann.
Jetzt warte ich eigentlich nur noch darauf, daß die Google App Engine sich auf die Servlet Version 3 einschießt, damit man auch die qualitativ neuen Features dort gut nutzen kann.

Freitag, 14. Dezember 2012

My App Engine

Der Schock am Wochenende, was sich bei Google mit dem Gesamtszenario aus Google App Engine und Google Apps, um eigenen Domains der App-Engine Anwendung zuordnen zu können, so verändert, ist ja wieder immer heilsam. Panik ist sicherlich übertrieben und die spontane Sicht etwas pointiert, aber es ist nicht der erste Schritt, bei dem Google Einschnitte im Bereich App Engine und Google Apps vornimmt. Und so war es wieder einmal Zeit, sich die Optionen anzusehen. 
Mit Tangram steht man nicht gerade festgelegt da und kann die Anwendungen normalerweise direkt auf einen Servlet/JSP Container umstellen und sich eine Datenbank suche, die von DataNucleus' JDO Implementierung unterstützt wird.
Das ist natürlich mit einer Umstellung der Codes verbunden. Außerdem hat sich ja gerade die Frage nach der Datenmigration als einer der verschlechterten Punkte in der Google App Engine herausgestellt. Es ist also eine gute Option für Tangram aber eventuell eine schlechte für ein bereits damit bestehenden Projekt.
Beim Stöbern im Netz stößt man als zusätzliche Option mitterweile aber auch auf zwei Projekte, die es sich zum Ziel gesetzt haben, eine zur Google App Engine kompatible Infrastruktur auf anderen Systemen als denen von Google zum Laufen zu bekommen.
AppScale und TyphoonAE wollen sich dabei wie ein "Plug-In-Replacement" anfühlen, sodaß die Anwendungen ohne Änderungen laufen können. Dafür muß man dann eben mit unterschiedlichen Mitteln - entweder entsprechenden Computern oder Cloud-Infrstrukturen - die App Engine Infrastruktur nachstellen - also statt an der Anwendung gewaltig and er Umbgebung arbeiten.
Um es gleich ganz deutlich zu sagen: Für keine der beiden Umgebungen sind alle APIs umgesetzt und keine ist in einem Zustand, daß man über einen produktiven Einsatz wirklich nachdenken sollte. Es ist nur jetzt an der Zeit, sich prototypisch anzusehen, ob man mit dem Weg etwas anfangen kann, und ihn mitzugehen oder nicht.
Die wesentliche Warnung an dieser Stelle ist aber eine, die bleiben wird: Die APIs und Lösungen der Google App Engine müssen bei beiden Lösungen nachprogrammiert werden. Und wenn sich bei Google etwas ändert, das man in der Anwendung nachführen muß (trotz der recht stabilen APIs ist das natürlich schon vorgekommen in den letzten Jahren), muß man bei den beiden Emulationen darauf warten, daß es dort auch entsprechend umgesetzt wird - oder es selbst tun. Es sind ja Open Source Lösungen und man kann die Erweiterungen auch wieder einbringen. Der Zeitliche Versatz bleibt so oder so und es wird immer ein Hinterherprogrammieren bleiben.
Von den beiden Lösungen kommt man mit der "Kleinen" TyphooeAE schneller an ein lauffähiges System aus dem bereitgestellten Code heraus. Hier wird ein Pyhthon-basiertes Build System bereitgestellt und man kann from Scratch bauen.
Die AppScale Lösung ist größer und zielt in jeder Hinsicht auf die größeren Installationen. Zum Ansehen gibt es hier dafür allerdings fertige VMs für Xen und KVM. Dumm nur, daß ich mich gerade wegen der Nutzung unter Windows und Linux für die Oracle Virtual Box entscheiden mußte.

Mittwoch, 12. Dezember 2012

Die Welt kriegt nicht genug

Heute möchte die Welt einen denkwürdigen Tag für Premium Content im Internet ausrufen. Ab jetzt gibt es nur noch einen Apetithappen von 20 Artikeln im Monat kostenlos.
Persönlich und auch als Fachmann für Online-Medien halte ich das in der gegebenen Form für ein "fail". Es ist ein lohnendes Experiment, aus dessen Ausgang ich gerne lernen möchte, das man aber evtl. nicht gerade mit einem der renommiertesten und reichweitenstärksten Online-Angebote - so hat es mir ivw auch gerade erst geflötet - eingehen sollte. Ich werde also schon aus beruflichen Gründen die zukünftige Reichweite im Auge behalten. Wobei Reichweite natürlich nur für den werbenden Bereich relevant ist.
Für alles weitere gibt es Abo-Modelle.
Dieser Schritt war angekündigt worden, sodaß ich die auf dem Mobilportal - das mit der Werbung, die sich dreht, sodaß man es kaum benutzen kann - beworbene App lieber schon im Vorwege nicht installiert habe. (Vermutlich war das vom Verlag so nicht gemeint, oder?) Man weiß ja nie, was so eine App dann In-App kaufen möchte.
Diese Abo-Modelle sind preislich durchaus fair. Den Preisen steht Leistung gegenüber und sie sind aus der Sache heraus nicht überzogen und sie liegen deutlich unter einem Print-Abo. Wie üblich ist mit dem Print-Abo das Online-Abo abgegolten - man muß sich nur registrieren. (Für so etwas habe ich mich das letzte bei ftd.de registriert, aber lassen wir das...)
Bisher gab ich so ca. 30€ bis 40€ im Monat für Print-Abos aus, da sollten doch die 7€ bis 13€ ein Schnäpchen sein.
Doch genau hier beginnt für mich der professionelle Denkfehler: Das Online-Angebot konkurriert nicht mit Print-Abos sondern mit meinem gesamten online Informationsbedürfnissen. Und hier will jede Site mit "Premium"-Anboten - als letztlich Abo-Diensten - von mir immer nur 5€ bis 10€ im Monat sehen. Kleine Beträge! Aber im Internet nutze ich nicht nur eine Zeitung sondern - so ist das Medium strukturiert - viele Sites quer durcheinander entweder Technisch oder Persönlich im Mesh-Up. Bei nur einem halben dutzend Sites sind das in der Summe schon mal eben 60€. - Und dann muß man wohl das, was aus der GEZ wird, gleich noch mit dazuaddieren, weil deren Content-Angebot ja auch kostenpflichtig und ungbefragt in's Internet gepustet wird.
In der Summe werde ich wohl - und ich denke, ich bin auch in Zukunft nicht alleine - lieber vernünftige Werbeformate in Kauf nehmen und meine strikte "Zero-Payment" Strategie allen Sites gegenüber fortsetzen. - Und das paßt dann ja auch wieder zum teschnischen Fokus dieses Blogs.

Dienstag, 11. Dezember 2012

On the Fly: URL Formate dynamisch anpassen

Nachdem nun einmal Groovy Codes jederzeit als Klassendefinitionen in das Tangram System eingefügt werden können, bietet es sich an
Mit Groovy kann man in Tangram neben den Ergänzungen der "Bean Schicht" - des Models - durch Shims sehr einfach URL-Formate schreiben und dynamisch jederzeit anpassen.
Dazu wird wieder Code mit MimeType "application/x-grooy" geschrieben. Die Annotation ist diesmal aber "org.tangram.view.link.LinkScheme" oder gleich der Klassenname der zu erstellenden Klasse.
Diese Klasse muß das Interface org.tangram.view.link.LinkScheme implementieren, d.h. Instanzen müssen sowohl URLs interpretieren können als auch aus Objekten URLs generieren können.
Das Tangram-System bietet dabei neben den URLs zur reinen Ansicht der Inhalte sogenannte Action-URLs, mit denen Aktionen - also Controller-Elemente - angesprochen werden sollten. Am Ende so einer Aktion sollte ein Redirekt auf eine reinen Ansichts-URL erfolgen. (Sonst würde bei "Reload" durch den Benutzer die Aktion erneut ausgeführt)

package org.tangram.solution.web.links;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.tangram.Constants;
import org.tangram.content.Content;
import org.tangram.content.BeanFactory;
import org.tangram.controller.DefaultController;
import org.tangram.view.Utils;
import org.tangram.view.TargetDescriptor;
import org.tangram.view.link.Link;
import org.tangram.view.link.LinkScheme;

import org.tangram.solution.web.RootTopic;
import org.tangram.solution.web.Topic;
import org.tangram.solution.web.ImageData;

public class DefaultLinkScheme implements LinkScheme {
 
  private static Log log = LogFactory.getLog(DefaultLinkScheme.class);

  private BeanFactory beanFactory;
 
 
  public void setBeanFactory(BeanFactory factory) {
    beanFactory = factory;
  } // setBeanFactory()
 
 
  public void setDefaultController(DefaultController defaultController) {
    // Automagically set default view
    defaultController.getCustomLinkViews().add(Constants.DEFAULT_VIEW);
  } // setDefaultController ()
 
 
  public Link createLink(HttpServletRequest request, 

                         HttpServletResponse response, Object bean, 
                         String action, String view) {   
      Link result = null;
      if ((action==null)&&(view==null)) {
          if (bean instanceof RootTopic) {
              result = new Link();
              result.setUrl("/");
          } else {
              if ((bean instanceof Topic)||(bean instanceof ImageData)) {
                  String title = "-";
                  String keywords = "-";
                  if (bean instanceof Topic) {
                      try {
                          title = Utils.urlize(((Topic)bean).getTitle());
                          keywords = Utils.urlize(((Topic)bean).getKeywords());
                      } catch (UnsupportedEncodingException uee) {
                          log.error("createLink()", uee);
                      } // try
                  } // if
                  String url = "/"+keywords+"/"+title+

                                    "/"+((Content)bean).getId();
                  result = new Link();
                  result.setUrl(url);
              } // if
          } // if
      } // if
      return result;   
  } // createLink()

 
  private TargetDescriptor rootDescriptor = null;;
 
 
  public TargetDescriptor parseLink(String url, HttpServletResponse response) {
    TargetDescriptor result = null;
    if (url.equals("/")) {
        try {
            if (rootDescriptor==null) {
                List<RootTopic> rs = 

                   beanFactory.listBeans(RootTopic.class, null);
                RootTopic root = null;
                if (rs.size()==1) {
                    root = rs.get(0);
                } else {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, 

                                "Have "+rs.size()+" RootTopics in data store");
              } // if
                rootDescriptor = new TargetDescriptor(root, null, null);
            } // if
            result = rootDescriptor;
        } catch (Exception e) {
            result = new TargetDescriptor(e, null, null);
        } // try/catch
      } else {
          String id = null;
          Object bean  = null;
          String[] elements = url.split("/");
          for (String element : elements) {
            if (element.indexOf(":") > 0) {
              id = element;
              bean = beanFactory.getBean(element);
            } // if
          } // for
          if (bean != null) {
              result = new TargetDescriptor(bean, null, null);
          } else {
              throw new RuntimeException("no content with id "+id+

                                         " in repository.");
          } // if
      } // if
      return result;
  } // parseLink()
   
} // DefaultLinkScheme

Das Beispiel hier zeigt zum einen das Erzeugen von URLs mit createLink() und das Verarbeiten mit parseLink(). Es ist im Grunde schon gar nicht so simpel sondern erfüllt eine ganze Reihe von Anforderunen:
  • Es sind sprechende URLs umgesetzt, die den Title der Objekte benutzen
  • Die Startseite wird mit / aufgerufen
  • Aktionen laufen auf den DefaultController aus dem System

Montag, 10. Dezember 2012

Nie wieder keinen Shim-mer!

Tangram wie viele Systeme besteht aus zwei Elementen: dem Code, der sowohl das Model als auch das Verhalten (den Controller) beschreibt und der View Schicht mit ihren Templates. Ich nenne das im Moment zwei Element, weil sie mit unterschiedlichen Techniken bedient werden: Code ist bei uns typischerweise in Java geschrieben und die Templates in JSP.
Schnell ist man dabei, Ergänzungen zu den Templates wie CSS und JavaScript im Repository anzulegen, damit man sie schnell und einfach ohne Deployment ändern kann. In Tangram Websites war das nicht dadurch gegeben, daß Deployments ein großer Aufwand sind, sie ware auch für die kleinen Sites und das einfache Deployment mit der Google App Engine, schlicht gelegentlich in dem Moment wo es notwendig war technisch nicht durchführbar.
Wenn man dann die Elemente CSS und JavaScript im Repository hat, will man schnell das Template auch dort haben. Das ist im Tangram mit Apache Velocity umgesetzt und ist schnell zur bevorzugten Technik geworden, sodaß JSP-Templates nur noch für die mitgelieferten Teile wie den Editor eine Rolle spielen, nicht aber für die Webanwendungen selbst.
Beim Schreiben der Templates stößt man dann aber unweigerlich immer wieder an die Limitierung der Implementierungsteile in Java, da diese ja fest im Deployment verankert sind.
Also wurde im Tangram Groovy integriert, daß als dynamische Sprache in die Java Welt integriert ist und somit sehr gut in das Szenario paßt.
Mit Groovy kann man in Tangram sehr einfach Funktionen auf Basis des Models zu den Java-Klassen hinzufügen. Da diese Codes im Repository als kleine Erweiterungen gedacht sind, heißen sie Shims. Diese kleinen Shims können zu jeder Klasse definiert werden, indem man einen Code mit dem MimeType "application/x-grooy" erstellt und als Annotation den Namen der Java-Klasse wählt, zu der dieser Code hinzugefügt werden soll.
Der Code selbst implementiert das Interface Shim oder ViewShim, wobei der Unterschied nur ist, daß ein ViewShim auch mit den Informationen des aktuellen Request versorgt wird und damit in den Bereich fällt, in dem Tangram nicht mehr cachen kann.

package org.tangram.solution.web.shims;

import javax.servlet.http.HttpServletRequest;

import org.tangram.logic.AbstractShim;
import org.tangram.gae.solution.Topic;

public class TopicShim extends AbstractShim<Topic> {

    public TopicShim(Topic topic) {
        super(topic);
    } // TopicShim()
   
    public String getNonsense() {
      return "Hallo";
    } // getNonsense()
   
    public String getReverseTitle() {
      return new StringBuilder(delegate.title).reverse().toString();
    } // getReverseTitle()
   
} // TopicShim


Der Inhalt dieses Shims ist relativ sinnlos, zeigt aber das Wesentliche: Wir muß die Groovy-Klasse aufgebaut sein, und wie greift man über den Delegate auf die jeweilige Instanz der Java-Klasse zu.
Bleibt nur noch eine Frage zu klären: Wie kommt man dann an diesen Code heran? Zusammen mit den Instanzen der Java-Klassen wird in Tangram jeweils eine Instanz der View-Klasse angelegt und unter dem Klassen-Namen ohne Package-Angabe den Templates zur Verfügung gestellt.
Im Apache Velocity Template im Repository - JSP haben wir ja für die Anwendung selbst längst zu den Akten gelegt - sähe das dann so aus:

  <h1>$TopicShim.reverseTitle | $self.title</h1>

Samstag, 8. Dezember 2012

Tür zu zum Talentschuppen

Wer gute Ideen in's Web bringen will, muß erst einmal aussortieren, was gute und was schlechte Idee sind. D.h. man muß leider all die schlechten auch in's Web bringen und dann aussortieren, was funktioniert und was nicht.

Alle, die nur wirtschaftlich garantiert erfolgreiches in's Web bringen, leben von denen, die vorher einmal mit Idee gescheitert sind.

Ganz in diesem Sinne hatte Google für die Google App Engine und die Goople Apps kostenlose Angebote, mit denen man schnell, einfach und ohne Risiko Idee in's Web bringen konnte. - Ja, konnte. Denn zeitgleich mit der FTD hat Google heute die kostenlose Version der Google Apps eingestellt - und damit den Weg verbaut, in der gegenwärtigen Fassung Anwendungen aus der Google App Engine mit einer eigenen Domain zu versehen.

Das bedeutet deutlich mehr, als daß man eben keine kostenlosen Mail- und Office-Anwendungen etc. für seine Domains mehr bekommt! Eigene Domains für die Google App Engine sind - und das auch jetzt noch - nur mit den Google Apps zu realisieren.

Auch der in diesem Blog beschriebene Weg, klein anzufangen, und dann im Erfolgsfall einfach wachsen zu können mit der Google Infrastruktur ist damit verbaut. Man kann keine Websites mehr erstellen, die mit - hoffentliche - innovativen, selbst programmierten Ideen - und damit ohne die fertigen Versatzstücke Blog, Sites etc. von Google - kostenlos starten und wenn es viele Teilnehmer gibt, technische wie wirtschaftlich - dann natürlich auch gerne für Google - anfangen zu fliegen.

Jetzt kann jeder für sich durchrechnen, ob die konsteplflichtigen Angebote von Google in diesem Zusammenhang einen Testbalon für die ein oder andere Sache trotzdem sinnvoll erscheinen lassen.

Hier beginnt nun erst einmal die Suche nach den alternativen, die das kostenlose Szenario - und damit auch für Projekte ohne Gewinnabsicht - wieder bedienen.

Bisher war die Rechnung wie folgt:

- Coole Idee mit Google App Engine umsetzen (kostenlos)
- Domain kaufen (Mindestpreis .de Domain 4,65€ pro Jahr)
- Google Apps für die Domain einrichten (kostenlos)

Ich geh dann mal suchen...

Update:
Mini Google-Apps gehen noch, sind aber sehr klein geworden und evtl. zeitlich nur noch begrenzt verfügbar.

Montag, 3. Dezember 2012

Singing the YUI Blues

Im Web gehört es ja zum guten Ton, spätetens seit Google die PageSpeed Tools herausgebracht hat, zusätzlich zur Kompression des HTTP-Inhaltes CSS und JavaScript zu "minifizieren" (ich sage mal lieber minimieren).

Es gehört sogar nicht nur zum guten Ton sondern bringt meßbar bessere Reaktionen in den Browsern und vermindert für Betreiber größerer Websites signifikant die Traffic-Menge. Das alte Argument, daß nachbearbeiten wäre zu CPU-belastend, gilt heute wirklich nicht mehr, wie ich an einer der größten deutschen (europäischen?) Website erfahren und messen konnte. Hier hat man eher Probleme, es nicht zu tun, da irgendwann auch GBit-Bündel von Netzanschlüssen am Kapazitätsende ist - nicht jedoch die CPUs.

Im Tangram sind natürlich alle statischen Dateien im Bereich CSS und JavaScript bereits beim Zusammenbau minifiziert und beim Ausliefern sollte der JSP/Servlet Container komprimieren - oder ein HTTP-Server davor. In der Google App Enginge erledigt das die Infrastruktur von Google auf irgendeiner Ebene für uns ebenfalls automatisch mit. (Und wenn auch die es im großen tun, ist es wohl wirklich kein Lastproblem mehr...) Für Inhalte aus dem Repository ist eine Lösung enthalten, die das CSS oder JavaScript zur Laufzeit minimiert.

Obwohl man das Minimieren dadurch mit dem Komprimieren des gesamten HTTP-Inhaltes verwechseln kann, wird es auch gerne "Kompression" genannt und ist so der Namensgeber des YUICompressors. Dieser ist - s.o. - lange im Tangram integriert, aber nie ohne Probleme, die wir hier endlich einmal nicht elegant aber funktionierend lösen müssen.

Der YUICompressor bietet sehr gute Ergebnisse, ist eigentlich einfach zu benutzen und sehr weit verbreitet. Damit wäre alle in Ordnung und erledigt, da er auch eine einfach zu benutzende Programmierschnittstelle hat, die im Tangram sowohl im Buildsystem als auch in der späteren Ausspielung zur Laufzeit genutzt wird.

Der erste unangenehme Punkt ist, daß aktuelle Versionen des YUICompressors (mindestens für mich) bisher nicht in öffentlichen Maven-Artefakt-Repositories zu finden sind und wir so mit einer nicht ganz neuen Version unterwegs sind.

Der zweite Punkt tut mehr weh: Im JAR-Archiv des YUICompressors befinden sich Klassen, die einige Klasse aus der eingesetzten JavaScript-Bibliothek - einer ebenfalls älteren Fassung von Rhino - überschreiben sollen. Das ist so natürlich eine recht dreckige Lösung, und sie funktioniert auch normalerweise nicht. Das liegt ganz einfach daran, daß im classpath ein JAR mit Namen yui... selten vor einem JAR mit Namen js... zu finden ist. Eine relativ saubere Behandlung der dreckigen Lösung wäre das umschreiben der Archive beim Zusammenbau des Tangram.

Da aber Tangram eher die möglichkeiten bestehender Software nutzen als zu viel Räder neu erfinden soll, Greifen wir lieber in den Werkzeugkasten und sehen, was unser Werkzeugbestand leisten kann.

In diesem Fall ist Gradle - endlich einmal wieder - unser Freund und bietet Möglichkeiten zur Umbenennung beim Zusammenbau. Da alle Zielsysteme den classpath alphabetisch sortiert zusammestellen, reicht es für uns aus, den YUICompressor einfach ein wenig nach vorne zu schieben. Damit sind dieselben Klassennamen immernoch zweimal im classpath vertreten, aber es wird - Erfahrung im Tangram und großen Systemen (s.o.) - nur die funktionierende Version genutzt.

Damit alle Abhängigkeiten im Buildsystem sauber formuliert bleiben, kleben wir unser Pflaster nur in den Anwendungen auf, die mit Tangram gebaut sind - nicht im System selbst.

Im Detail sieht das so aus:

Wir müssen nun den Anwendungen die genaue Version bekannt machen, die bisher nur im System einfach enthalten war:

ext.yui_version="2.4.6"

Das entsprechen JAR lösen wir aus dem automatisch zusammegestellten classpath heraus:

dependencies {
  .
  .
  .
  // To exclude original YUICompressor jar from output war
  providedCompile "com.yahoo.platform.yui:yuicompressor:$yui_version"
  .
  .
  .
}

Jetzt sind wir sowohl den YUICompressor als auch seine Dependencies los und müssen diese nun "von Hand" wieder einfügen. Da tun wir im war.doFirst Zusatz, der in Tangram-Anwendungen "traditionell" schon angepaßt ist:

war.doFirst {
  // Strange way of overwriting things - it must be the first webapp dependency
  if (configurations.webapp.dependencies.size() > 0) {
    String archiveFileName = configurations.webapp.asPath
    int idx = archiveFileName.indexOf(';')
    if (idx >= 0) {
      archiveFileName = archiveFileName.substring(0, idx)
    } // if
    println "unzipping $archiveFileName"
    ant.unzip(src: archiveFileName, dest: "$buildDir/target") 
  } // if
 
  // Missing: YUICompressor - see base system
  // It's not really that important since we don't have any
  // static Stylesheets or JavaScripts are in the examples.
  copy {
    from 'src/main/webapp'
    into "$buildDir/target"
    include '**/**'
  }
 
  // ...and to again include those YUICompressor's dependencies with a different name

  copy {
    from "$buildDir/target"
    into "$buildDir/target"
    include '**/yui*'
    rename 'yui', 'aaa-yui'
  }
  into ('') {
    from "$buildDir/target"
    include 'WEB-INF/lib/aaa-*'
    include 'WEB-INF/lib/js-*'
  }
 
  into ('') {
    from "$buildDir/target"
    exclude 'WEB-INF/lib/**'
  }
}

Hier wird die WAR-Depencency ausgepackt, ggf. statische Ressourcen minifiziert und dann alles außer den Abhängigkeiten sauber verpackt. Und das "alle außer den Abhängigkeiten" müssen wir nun wieder relativieren, da wir ja den YUICompressor aus den Abhängigkeiten entfernt hatte. Also kommt er explizit als "aaa-yuicompressor" wieder rein zusammen mit der Rhino-Bibliothek. Viola - das war's.

Was ist an der Lösung so toll? Nun, in Maven-Szenarien ist mir nichts besseren Eingefallen, als den YUICompressor unter dem albernen Namen in ein lokalen, eigenes Repository einzufügen. Würde ich das hier tun, wäre Tangram nicht mehr mit öffentlichen Maven-Artefakt-Repositories zu bauen. - Nicht gerade ein gute Idee und eigentlich auch keine für nicht öffentliche Projekte.

Sonntag, 2. Dezember 2012

OpenID - Closed Data on Google App Engine

Oder: Daten? Wer braucht schon Daten?

Relativ kürzlich hat ich in der Google App Engine mindestens für Java Anwendungen etwas recht störendes geändert.
Bisher war es immer einfach möglich, mit dem Python Werkzeug den kompletten Datenbestand aus dem Data Store der App Engine herunterzuladen oder einen kompletten Stand wieder hochzukopieren.
Solche Kopien sind für das Testen von Anwendungen lokal in meiner Welt essenziell. Es geht einfach meist nicht ohne einen wenigstens teilweisen Abzug von Produktionsdaten. Man kommt so einfach näher an Probleme heran, die man nicht entdeckt, wenn man nur auf einem zu kleinen, synthetischen lokalen Datensatz arbeitet. Außerdem zeichnet sich der Data Store in der Entwicklungsinstanz der Google App Engine als nicht gerade persistent heraus, wenn man bedenkt, daß das Verzeichnis bei jedem sauberen Build der Software eigentlich bei jedem Buildsystem gelöscht wird. Und umgekehrt kann man mit dem Werkzeug lokalen keinen Datenbestand erzeugen und als Basisdaten installieren. Dieser Weg war nie möglich, da man zwar lokal alle aus einer Datenbank einpielen konnte, sie aber nicht extrahieren konnte. - Seltsam aber dokumentiert.
Und selbst das geht nun nur noch, wenn die Anwendung nicht den OpenID Login benutzt. Dieser heißt zwar immernoch "Experimental", funktioniert aber seit geraumer Zeit sehr zufriedenstellend.
Was hat jetzt der Login mit den Datenkopien zutun? Eigentlich wenig, wäre da nicht die remote_api, die die Nutzung der Tools erlaubt hat. Diese braucht natürlich zu Sicherheit einen Login, und eben diesen Login erreicht sie nur über Google Accounts - nicht über OpenID.
Und seit kurzen kann man in der Google App Engine nicht mehr die Authentisierungsmethode umschalten.
Wege um dieses Problem herum ergeben sich aber realistisch nur für Python Anwendungen.
Schön, daß man die Daten zwischen Google Diensten hin- und herschieben kann, aber im Zweifel brauche ich vollen Zugriff auf meine eigenen Daten, nicht weil ich den Google Diensten zum Backup nicht traue oder so, sondern einfach um Anwendungen zu schreiben.
Bis das nicht wieder anders aussieht oder mir jemand einen Ausweg zeigt: Finger weg von der Google App Engine für Web-Anwendungen - also für allen, was wirklich einen Benutzerlogin braucht, es sei denn, man will ausgerechnet den selbst implementieren, was nun nicht mehr in den modernen "Mesh-Up" passen würde.