Donnerstag, 21. November 2013

Gradle Plugin for JPA, JDO, and EBean Bytecode Transformers

The plugin referenced here is meant for use with the tangram framework but there are a few calls which are helpfull for any user of Gradle and
It simply has easy to use wrappers for the enhancer/wever components for the persistence frameworks above since the solutions presented by those projects each for its own reason needed some cosmetic enhancement to fit into my gradle builds.

Prerequisites

Of course you will have some classes with persistence annotations. I'm assuming that you have a (sub)module containing the model classes for the API to be used (JDO, JPA with OpenJPA, DataNucleus, or EclipseLink, or Ebean with JPA annotations) which get compiled by some task of your project.

Preparation

The plugin can be obtained from the tangram snapshots repository.

// build.gradle
buildscript {
  repositories {
    mavenCentral()
    maven { url "http://repository-tangram.forge.cloudbees.com/snapshot" }
  }
  dependencies {
    classpath "tangram:gradle-plugin:0.9-SNAPSHOT"
  }
}


And of course it must be applied

apply plugin: 'tangram'

When using EclipseLink the bytecode transformer called weaver will already be contained in the compile dependencies of your module. The same applies for OpenJPA except that here the bytecode transformer is called enhancer.
For DataNucleus und Ebean the bytecode transformer is called enhancer again and is a dependency of the plugin itself. There is no generic need to have it in the compile dependencies of your module.
So in short, you will not have to modify your dependencies and the resulting package will not contain any additional classes or jars.

Solution

The plugin does not introduce any new tasks but just some methods that can be placed anywhere in the build process. In a standard gradle task-wiring of the java plugin the methods the following locations make sense:

// JDO with DataNucleus
compileJava.doLast {
  nucleusJdoEnhance()
}


// JPA with DataNucleus
compileJava.doLast {
  nucleusJpaEnhance()
}


// JPA with OpenJPA
jar.doFirst {
  openjpaEnhance()
}


// Ebean
compileJava.doLast { 
  ebeanEnhance()
}


// JPA with EclipseLink
compileJava.doLast {

  eclipselinkWeave()
}


But you may decide to use it at other points within your build process.

Background

For OpenJPA the wrapper presented here is a simple wrapper for the ant tasks provided by this project wired up for the given gradle build setup.
For EclipseLink the weaver the jar is included as a dependency for the plugin itself and the weaver is called called via its Java API. So woven codes can be generated independent of the using projects build setup.
For DataNucleus and Ebean the solution is somewhat more complicated since the enhancers are in separate jars which you most likely don't want to include in your resulting packages.
So those jars are also included as a dependency of the plugin, and again the plugin itself calls the Java APIs of the enhancers directly.

Mittwoch, 13. November 2013

mavenLocal() - remote and clean

Working in the cloud even for development tasks oftentimes needs what used to be the local maven artifact repository – referred to as mavenLocal() – available somewhere remote, accessible by your cloud continuous integration server.

The usual Suspect

A very easy way is to use an e.g. WebDAV accessible folder somewhere for every days snapshots.
For gradle users this has two drawbacks and for all others still at least one:
You will get a bunch of snapshots over time and the housekeeping there is as time consuming as with your local maven artifact repository, which – from time to time – needs some cleaning to avoid unreproducable build on your machine.

Cloudbees humming to a Gradle Blues

This is where my latest suggestion comes in: The cloudbees forge.
This is still a more or less normal WebDAV accessible storage but it has one important feature: Just with a check box in the administration panels you can ask for snapshot clean up to be done for you.
The one additional problem for Gradle users is the fact, that the latest maven-publish plugin from the distribution cannot publish to WebDAV resources until http://issues.gradle.org/browse/GRADLE-2919 is resolved.

Cloudbees Forge cleans my local Repository

As a workaround I'm publishing to a local folder and using a synchronisation software (https://github.com/mgoellnitz/JFileSync3 or AllwaySync) to bringt the stuff online. This in turn has the advantage that the clean up of cloudbees hums over to my local drive. Thus I'm not really sure if I'm waiting for a solution to the Gradle WebDAV publish problem...

Tangram Snapshot Artifact Repository

As a result I now – without any additional effords on my side – present public snapshots of the tangram system.

Tangram Snapshot Maven Artifact Repository now to be found at:
https://repository-tangram.forge.cloudbees.com/snapshot

And I myself am using these on any cloud platform I'm trying some remote build on, still having the latest changes for these plattforms available. All this avoiding the need to release to my old Ad-hoc Maven Artifact Repository at

http://my-amor.appspot.com/repository/

which still holds the releases.
I expect to be using the cloudbees solution for my releases some day soon as well. It's way easier to handle.

Sonntag, 10. November 2013

Splitter im Frühling

(English summary at the end)
Am Ende dieses Beitrags kommt eine universeller Konfigurations-Helper für das Springframework heraus.
org/tangram/spring/PropertySplittingPlaceholderConfigurer.java
Aber warum man so etwas brauchen könnte, wollte ich kurz an zwei oder vier (je nachdem wie man es zählen möchte) zeigen.
Bisher habe ich das Springframework und die jeweiligen Persistenzschicht immer komplett unabhängig voneinander genutzt: Java Persistence API (JPA) konfiguriert man über eine persistence.xml und Java Data Objects (JDO) über eine jdoconfig.xml. ORM Integrationen habe ich auch im zusammenspiel mit Spring MVC nicht benötigt. - Dachte ich.
Aber insbesondere durch Cloud-Umgebungen habe ich nun lernen müssen, daß der Weg über diese Dateien eigentlich nicht gerade "best practice" ist und eher für einfache Situationen taugt.
Wenn man dann endlich neben der Nutzung der Google App Engine auch mal einen Ausflug nach Cloudbees und OpenShift macht, tritt nämlich ein kleines Problem zutage: Die Werte in den oben genannten Dateien können nicht, wie alles andere, das ich in Spring "zusammenstecke", mit Platzhalter versehen werden, die erst zur Laufzeit des Systems aufgelöst werden.
Durch diese Ersetzung, wie sie z.B. Bei Spring quasi automatisch passiert - paßt sich ein ein grundsätzlich vorkonfiguriertes System dann in seine Laufzeitumgebung ein. - Bis auf die Persistenzschicht in meinem Fall.
Als einfachstes Beispiel nehmen wir mal die Verbindungsdaten zu einer Datenbank unter OpenShift. Diese sollte man am sinnvollsten aus den Umgebungsvariablen lesen, sagt die "best practice" von OpenShift.

JDO auf OpenShift

Also nimmt man beim Einsatz von JDO die Werte

<persistence-manager-factory name="transactions-optional">
  <property name="javax.jdo.PersistenceManagerFactoryClass"

            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>     
  <property name="javax.jdo.option.ConnectionURL"

            value="mongodb://localhost:8111/db"/>
  <property name="javax.jdo.option.ConnectionUserName" value="u"/>
  <property name="javax.jdo.option.ConnectionPassword" value="p"/>
</persistence-manager-factory>


aus der jdoconfig.xml komplett heraus und übergibt sie bei der Instanziierung der PersistenceManagerFactory mit:

factory = 
JDOHelper.getPersistenceManagerFactory(jdoConfigOverrides, 
                                       "transactions-optional");

und diese jdoConfigOverrides bezieht man dann aus der Spring-Configuration, wo sie von den Ersetzungen auf Basis von Umgebungswerten profitieren:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

      value="mongodb://${OPENSHIFT_MONGODB_DB_HOST}:${OPENSHIFT_MONGODB_DB_PORT}/test"/>
      <entry key="javax.jdo.option.ConnectionUserName"

             value="${OPENSHIFT_MONGODB_DB_USERNAME}"/>
      <entry key="javax.jdo.option.ConnectionPassword"

             value="${OPENSHIFT_MONGODB_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

JPA auf OpenShift

Entsprechend geht man bei JPA vor und nimmt die Werte

<persistence-unit name="openjpa" transaction-type="RESOURCE_LOCAL">
  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
    <property name="javax.persistence.jdbc.url"

              value="jdbc:postgresql://localhost:5432/postgres"/>
    <property name="javax.persistence.jdbc.user" value="un"/>
    <property name="javax.persistence.jdbc.password" value="pw"/>
  </properties>
</persistence-unit>

auch hier aus der Konfigurationsdatei heraus und fügt sie in die Spring-Konfiguration ein:

<bean id="jpaConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="
javax.persistence.jdbc.url"
  value="jdbc:postgres://${OPENSHIFT_POSTGRESQL_DB_HOST}:${OPENSHIFT_POSTGRESQL_DB_PORT}/db"/>
      <entry key="
javax.persistence.jdbc.user"
             value="${OPENSHIFT_POSTGRESQL_DB_USERNAME}"/>
      <entry key="
javax.persistence.jdbc.password"
             value="${OPENSHIFT_POSTGRESQL_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

denn auch hier gibt es entsprechende Parameter beim Erzeugen in diesem Fall der EntityManagerFactory:

factory = 
Persistence.createEntityManagerFactory(persistenceUnitName,
                                       jpaConfigOverrides);

Cloudbees

Über die Vorgehensweise hier stolperte ich erst, als ich meine Anwendungen mit Tangram auf OpenShift betreiben wollte, nachdem sie bei cloudbees schon liefen, da es für MySQL auf run@cloudbees eine "managed" Lösung mit einer DataSource gibt

 <!-- jndi datasource example (run@cloudbees) -->
 <property name="datanucleus.ConnectionFactoryName" 

           value="java:comp/env/jdbc/mydb" />

Das Problem besteht also mit der "hauseigenen" MySQL Datenbank dort überhaupt nicht.
Aber auch in dieser Umgebung werden ganz allgemein Werte der Betriebsumgebung an die Anwendungen durchgereicht und sollten von dieser auch benutzt werden.

URL Splitting in der Spring-Konfiguration

Das geht leider nicht ganz genau wie oben beschrieben, da unter Cloudbees z.B. für MongoDB die Verbindungdaten in einer URL übergeben werden (die gibt es auf OpenShift auch, aber man kann dort auch direkt auf die Einzelteile zurückgreifen).
Die Lösung ist hier mit ein wenig Programmieraufwand (s.o.) verbunden, da ich mich entschlossen habe, die Property-Ersetzungen durch Spring an dieser Stelle ein wenig aufzubohren und ganz allgemein URLs in ihren Teilen nutzbar zu machen.
Den

<bean id="propertyConfigurer" 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

ersetze ich also durch einen eigenen

<bean id="propertyConfigurer" 
      class="org.tangram.spring.PropertySplittingPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

Danach steht von jeder URL, wie z.B. ${MONGOHQ_URL_MYBD} für den Service MongoHQ auf Cloudbees mit der verbundenen Datenbank MYDB, die Teile zur Verfügung:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

value="mongodb:${MONGOHQ_URL_TANGRAM.host}:${MONGOHQ_URL_MYBD.port}/${MONGOHQ_URL_MYDB.uri}" />
      <entry key="javax.jdo.option.ConnectionUserName" 

             value="${MONGOHQ_URL_MYDB.username}" />
      <entry key="javax.jdo.option.ConnectionPassword" 

             value="${MONGOHQ_URL_MYBD.password}" />
    </map>
  </constructor-arg>
</bean>

Das macht die Implementierung für alles, was sie für eine URL hält, und damit wird sie zu einem recht universellen Werkzeug bei der Spring-Konfiguration.
Jede URL in einer Property-Datei

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

wird zerlegt in

url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db 

Diese Platzhalter fügt der PlaceholderConfigurer dann an allen gewünschten Stellen ein. Das sollte für einen ganzen Bereich von Anwendungen erst einmal mit einem Werkzeug ausreichen.

English (sort of) Summary

For easier parameter passing through the springframework down into the JDO or JPA persistence layers of you app - especially on the plattforms of CloudBees and OpenShift - I introduced a PropertySplittingPlaceholderConfigurer, which splits everything it consideres a URL into the parts host, port, username, password, protocol, and uri.
So anything in the form of

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

gets exploded as if it would read

url.protocol=mongo
url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db

These generated properties can subsequently be used as placeholders in your springframework XML configuration files. So, what started as a little helper to connect to the databases of OpenShift in the best practice way, ended as a small but universal helper class.

Donnerstag, 3. Oktober 2013

Wolkig aber sonnig

Bereits im Sommer konnte ich hier verkünden, daß ein Tangram Deployment in der Wolke bei den Bienen von CloudBees ohne weiteres - und ohne weitere Änderungen möglich - und sehr einfach ist.
Dabei sind die 5MB, die dort bei MySQL Instanzen kostefrei zur Verfügung gestellt werden jedoch auch für einen Playground, z.B. auf Basis des RDBMS Beispiels, sehr eingeschränkt. Beim Versuch, diese Grenze zu erweitern, stieß ich in den Services bei CloudBees auf die MongoDB, die man mir ja schon lange an's Herz gelegt hat - z.B. im CoreMedia Kontext. Schade nur, daß Tangram ja keine MongoDB unterstützt... Außerdem war die Beschreibung, die damals gegeben wurde zwar vollständig, aber für eine vollen Integration der Möglichkeiten der CloudBees Plattform sehr knapp.

MongoDB für Tangram

Das Fehlen der MongoDB-Unterstützung ließ sich mit wenigen Handgriffen beseitigen, sodaß es nun ein entsprechendes Modul gibt. Lerneffekt dabei: Die separaten RDBMS und MongoDB Layer oberhalb von JDO sind eventuell nicht nötig, und man könnte die Entscheidung der konkrekten Anbindung auf die Konfiguration auslagern. Ich lege mich damit nur auf die datanucleus dataaccess Plattform fest und verschiebe alle weiteren Fragen auf diese Ebene. Bevor ich aber soweit gehen will, sollte ich evtl. noch ein paar kleinere Ecken bei der Behandlung von strukturierten Texten und Blobs angehen.

Keine Beispielanwendung

Die Minimalversion aus dem Sommer sollte nun anhand etwas realistischerer Szenarien erweitert werden. Im Moment haben wir statt einer weiteren Beispielanwendung erst einmal unsere eigene (sehr kleine) Website, die in diesem Fall sogar eine Webseite ist - und das mit Absicht - auf CloudBees, MongoDB und Tangram umgezogen.
Dabei wird außer CloudBees nichts weiter als die lokale Entwicklungsumgebung genutzt und gegebenenfalls ein separater GIT-Client.
Nur die eingegebauten, privaten Maven-Repositories blieben bisher außen vor, und wir haben das Release 0.8 beschleunigt und gleich auf Tangrams amor abgelegt.
Die Tatsache, daß man bei CloudBees registriert sein muß, kann man hier voraussetzen und die unterschiedliche Optionen dabei ignorieren wir hier. Man muß allerdings damit rechnen, ab und zu drollige Support-Mails zu bekommen, die einen dazu animieren sollen, sich mehr mit der Plattform zu beschäftigen oder die Hilfe anbieten, wenn das CRM-System das Gefühl bekommt, das wäre notwendig.

Schritt für Schritt

Alle weiteren Schritte, bis die Site mit der neuen Technik aktiv war, finden sich nun hier in Bild und Text. Der grobe Ablauf ist:
1. Anwendung erstellen
2. Datenbank erstellen
3. GIT-Repository erstellen
4. Anwendung lokal erstellen
5. Jenkins Build erstellen
6. Anwendung einchecken
7. Konfiguration abstimmen
Für die Einrichtung einer Anwendung beschäftigt man sich zunächst mit dem run@cloud Bereich.

Bereich Applications

Von der Home-Page aus wählen wir Apps aus und erstellen eine Anwendung für Java/JVM.

Dialog Create abApplication 
 
Für den Moment muß in der Anwendung nichts weiter konfiguriert werden. Viele Texte beschreiben hier auch nur die Optionen, die man nun für die Entwicklung hat. Später kann man hier seine Domain (unten) aufschalten. Dafür muß man natürlich nicht nur hier den Domain-Namen angeben, sondern auch entsprechend seinen DNS mit einem weiteren CNAME konfigurieren.

Seite Manage Application

Für die Datenbanken hat man mehrere Optionen. Zum einen findet sich unter DBs MySQL. Dort kann man eine MySQL Datenbank anlegen und verwalten.

 Dialog Create MySQL Database

Seite Manage Database

Eine MySQL Datenbank sollte man mit dem Kommandozeilenwerkzeug bees mit der Anwendung verbinden. Alternativ kann man natürlich immer den Datenbankzugang direkt in der Anwendung einstellen, wie es auch Tangram-RDBMS erlaubt. Durch die Verbindung löst man die konkreten Einstellungen zur Datenbank von der Anwendung und verlagert sie in die Systembetreuung - keine schlechte Idee.

bees app:bind -db <DBNAME> -a <APPID> -as tangramdb

Zum anderen gibt es MongoDB, die man als Service im entsprechenden Bereich findet und sich gegebenenfalls erst abonnieren muß. Unter dem MongoDB Service legt man sich eine neue Datenbank an.

Bereich Services

Für die weiteren Schritte hier haben wir MongoDB gewählt. Jetzt wird es Zeit, die Anwendung vorzubereiten. Dazu legt man sich ein GIT Repository an und davon einen lokalen Clone auf dem Entwicklungsrechner.

 Bereich Repositories

Hier hinein kopiert man sich das MongoDB Beispiel (oder das RDBMS-Beispiel). 
Unter CloudBees ist eine Sache anders als in allen anderen bisher probierten Plattformen: Es gibt ein Problem mit den Bibliotheken, die als „provided“ angesehen werden. Also fügen wir sie explizit über die build.gradle hinzu.

configurations {
  libs
  webapp
  // add this:
  reallyNeeded
}
...
dependencies {
  webapp "tangram:tangram-mongo:$tangram_version:war@war"
 
  compile "tangram:tangram-mongo:$tangram_version"
 
  providedCompile "javax.servlet:servlet-api:$servlet_spec"
  providedCompile "javax.servlet:jsp-api:$jsp_spec"
 
  // add this:
  reallyNeeded "org.ow2.asm:asm:4.0"
 
  testCompile "junit:junit:$junit_version"

  providedCompile "org.apache.ant:ant:1.8.4"
  providedCompile "org.datanucleus:datanucleus-enhancer:$datanucleus_enhancer_version"
}
...
war {
  // change this:
  classpath = jar.outputs.files
  + configurations.runtime
  - configurations.providedRuntime
  + configurations.reallyNeeded
  excludes = [ "classes/**" ]
}

Für MongoDB kann man nicht den eleganten Weg über eine Verbindung der Datenbank in der Konfiguration gehen und trägt den konkreten Zugang im Code in src/main/resources/jdoconfig.xml ein. Aus der Konfiguration der MongoDB Datenbank extrahiert man die notwendigen Informationen für Benutzernnamen, Kennwort und Zugangs-URL. 

 Seite Manage MongoDB

Unter Show Config findet man eine Zeichenkette der Form 

mongodb://<username>:<password>@<host>:<port>/<database> 

Diesen Text muß man entsprechend zerlegen, um die Teile für den Zugang für die Datei src/main/resources/jdoconfig.xml passend einfügen zu können.

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

<persistence-manager-factory name="transactions-optional">
  <property name="javax.jdo.PersistenceManagerFactoryClass"    
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

  <!-- jndi datasource for mongodb -->
  <property name="datanucleus.ConnectionURL" value="mongodb:host:port/database" />
  <property name="datanucleus.ConnectionUserName" value="username" />
  <property name="datanucleus.ConnectionPassword" value="password" />

  <property name="datanucleus.autoCreateSchema" value="true" />
  <property name="datanucleus.validateTables" value="true" />
  <property name="datanucleus.manageRelationships" value="true" />
  <property name="datanucleus.validateConstraints" value="true" />
</persistence-manager-factory>

</jdoconfig>
 
Für den ersten Schritt ist ein erweitertes Logging in src/main/webapp/WEB-INF/log4j.properties hilfreich.

# Daily rolling file appender
log4j.appender.file=org.apache.log4j.ConsoleAppender
# log4j.appender.file.File=build/tangram.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout

Bevor wir das Ergebnis nur in das GIT-Repository pushen, legen wir uns lieber gleich vorher einen Job im Jenkins Continous-Integration Server an, der auf unseren Push mit dem ersten Deployment-Versuch reagieren kann.


Bereich Builds

Wir legen also eine Freestyle Job in Jenkins an.

 Seite Neuen Job Anlegen

Diesen neuen Job konfigurieren wir dann. Zunächst wählt man unter Source-Code Management git aus und fügt die ssh-basierte Zugangs-URL ein.

 Seite Job konfigurieren

Im Buildverfahren sind zwei Schritte nötig, die über Build-Schritt hinzufügen... eingefügt werden können: Invoke Gradle Script und Deploy applications. In beiden Schritte kann die weitgehend leere Basiskonfiguration beibehalten werden.

Seite Job konfigurieren - Buildverfahren

Alle weiteren Anpassungen und Entwicklungen sind jetzt nur noch dem konkreten Bedarf und nicht mehr der Technik geschuldet.
Nun wird jedesmal, wenn man das GIT-Repository pusht ein Build im Jenkins angestoßen und die aktuelle Version versucht zu bauen und zu deployen.
Im Erfolgsfall kann man dann sein Logging tunen und eine eigene Domain aufschalten.

Sicherheitsfragen

Sicherheitsbedenken? In diesem Fall ist ein ohnehin veröffentlichtes Framework mit nichts weiter als der Beispielanwendung - ebenfalls öffentlich zugänglich - und ein paar eigenen Templates für ohnehin öffentlich zugänglichen Inhalt zu sehen. Dies ist nicht die Stelle, an der uns die Cloud Sorgen machen kann, aber die Cloudbees Plattform nimmt uns die Betreuung aller Komponenten von der Entwicklung bis zum produktiven System ab. Physische eigene zu wartende Rechner finden sich nur noch im Form der Entwicklermaschinen. Alles andere kann abgegeben werden. Die eingesetzte Technik erlaubt es uns aber, jeden Schritt auch anders umzusetzen.

Danke Gradle!

Evtl. bin ich nach über 25 Jahren professioneller Softwareentwicklung und Projektgeschäft etwas abgebrüht, desillusioniert und blind gegen die Schönheit des Neues geworden, aber ich finde Maven nicht besser als ANT und ANT nicht grundlegend mächtiger als make. Bisher habe ich noch mit jedem Build-Werkzeug meine Ziele erreichen können und mich dabei immer in dem Sinne verrenken müssen, daß ich nie aufscheiben konnte, was ich eigentlich meine.

"Das war schon immer so", "Da kann ja jeder kommen" und andere Leitsätze

Egal, ob es make, ant oder maven waren: Meist bestimmte das Werkzeug den Build und nicht die Entwickler oder die Art der Software, die es zusammenzubauen galt. Im wesentlichen nutze ich die Werkzeuge daher immer nur deshalb, weil sie Verfügbar im jeweiligen Kontext sind, oder weil es einfach eine breite Masse von Nutzern gibt, sodaß man nicht so alleine ist.
Im Moment nutze ich in jeden professionellen Projekt Maven und weiß daher, welche Kosten es entfesselt, um seinen Nutzen zu bringen. Den Nutzen der vernünftigen Abhängigkeitsumsetzung will ich nicht missen, aber der Rest ist weder gut lesbar noch gut im Ablauf verständlich. Ich finde die Schreibeweisen nach wie vor unleserlich und das Werkzeug unverständlich in seiner Sprache und Komplexität.

Peak Maven?

Nachdem ich also nun viele Jahre lang leidenschaftslos beim Buildwerkzeug war, sehe ich jetzt auf die umfassenden Lösungenmit Maven in großen Projekten (wie dem CoreMedia 7 Blueprint) wie auf die letzten Werke des Maven-Barock. Großartige umfassende Lösungen, die mir netto sehr helfen (weil ich sie nicht selbst schreiben mußte), Maven und das Projekt im Griff zu halten, und die so vieles bedacht haben, das ich nicht einmal bemerke.

Kleine Danksagung zum 1.8er Release

Den Grund, warum sich meine Sicht auf Buildwerkzeuge nun geändert hat, möchte ich anhand einer kleinen Begebenheit illustrieren: Immer wenn ich kleinere Softwareprojekte anderen Menschen bereitstellen will, reicht es nicht, daß ich reprodzierbar aus meiner IDE getestet eine Software purzeln lassen kann. Bevor ich also beschreibe, was ich getan habe und was eventuell bei anderen dann gerade einmal nicht genauso aussieht, schreibe ich lieber gleich die notwendigen Build-Skripte, um den Vorgang zu dokumentieren und dabei gleich zu automatisieren.
Bei tangram habe ich mich noch lange mit der Auswahl des Werkzeugs gequält, weil ich mich nicht auch noch in meiner Freitzeit mit Maven herumschlagen wollte und kein anderes Tool mir spontan unter "das machen alle so" oder "das war einfach da" vorlag. Letztlich hat Gradle über Maven gewonnen und ich habe mich ahnungslos in die Umsetzung gestürzt. Heute verstehen wir uns recht gut.
Als ich vor kurzem wieder ein paar Zeilen Code herausgeben wollte, stand ich mit einem kompletten Fremdprojekt und einem alten ANT-Script da. JFileSync sollte - weit vor der NSA-Affäre, die mir keine neuen Erkenntnisse brachte - um für mich notwendige Features erweitert werden. Das Ergebnis wollte ich wieder gerne reprodzierbar als nutzbares Programm erzeugen können. Und das ging mit Gradle so erschreckend gut und einfach - und mit wenigen Worten, daß ich mich gleich wieder in Goodies für ein schöneres Ergebnis stürzen konnte.

Gradle-Deutsch / Deutsch-Gradle

Wir haben also ein Java-Programm

apply plugin: 'java'

Und wir hätten den Code gerne sauber in aktueller Java-Version, mit UTF-8 und ohne alten Kram.

sourceCompatibility = 1.7
targetCompatibility = 1.7
compileJava.options.encoding = 'UTF-8'
compileJava.options.deprecation = true

Der Source-Code und die Ressourcen finden sich zusammen im Verzeichnis src.

sourceSets {
  main {
    java {
      srcDir 'src'
    }
    resources {
      srcDir 'src'
    }
  }
}

Damit man das ganze kompilieren kann, muß man aus diesen Quellen

repositories {
  maven { url "http://repo1.maven.org/maven2" }
  maven { url "http://sardine.googlecode.com/svn/maven/" }
  maven { url "http://repo2.maven.org/maven2/org/bouncycastle" }
}

diese Dinge besorgen

dependencies {
  compile 'org.slf4j:slf4j-api:1.5.8'
  compile 'org.slf4j:slf4j-log4j12:1.5.8'
  compile 'org.apache.httpcomponents:httpclient:4.2.5'
  compile 'commons-lang:commons-lang:2.6'
  compile 'org.apache.commons:commons-compress:1.3'
  compile 'org.bouncycastle:bcprov-jdk16:1.46'
  compile 'javax.servlet:servlet-api:2.5'
}

Ach, und ich würde gerne weiterhin Eclipse nutzen

apply plugin: 'eclipse'

Könnt Ihr Euch bitte auf ein Vereichnis für den Output einigen?

eclipse.classpath.conventionMapping.defaultOutputDir = { 
  new File(project.projectDir, 'build/classes/main')
}
Du solltest aber sauberer Arbeiten als meine IDE!

defaultTasks 'clean', 'build'

Das ganze soll am Ende ein benutzbares Programm werden.

apply plugin: 'application'

Den Einstieg findest Du dabei in dieser Java-Klasse:

mainClassName = 'jfs.JFileSync'

Wow! Du hast ja automatisch Start-Scripts für Unix und Windows erzeugt! Danke! Ich hätte da noch ein paar Verbesserungsvorschläge...

startScripts {
  doLast {
    // Add some more reasonable memory settings to JFileSync3
    unixScript.text = 

      unixScript.text.replace('DEFAULT_JVM_OPTS=""', 
                                     'DEFAULT_JVM_OPTS="-ms1280m -mx1536m -Xms1280m -Xmx1536m"')
    windowsScript.text = 

      windowsScript.text.replace('DEFAULT_JVM_OPTS=', 
                                        'DEFAULT_JVM_OPTS=-ms1280m -mx1536m -Xms1280m -Xmx1536m')
    windowsScript.text = windowsScript.text.replace('java.exe', '%JAVA_CMD%')
    windowsScript.text = 

      windowsScript.text.replace('@rem Find %JAVA_CMD%', 
                                 'set JAVA_PREFIX=start ... java.exe')
    windowsScript.text = 

      windowsScript.text.replace('"%JAVA_EXE%" %DEFAULT_JVM_OPTS%', 
                                 '%JAVA_PREFIX% "%JAVA_EXE%" %DEFAULT_JVM_OPTS%')
  }
}
Und ich habe da gerade diesen netten Launcher für Java-Anwendungen unter Windows gefunden

buildscript {
  repositories {
    maven { url "http://repo.smokejumperit.com" }
    ivy {artifactPattern 'http://gradle-launch4j.googlecode.com/files/[module]-[revision].[ext]'}
  }
  dependencies {
    classpath 'com.smokejumperit:gradle-plugins:0.8.2'
    classpath 'edu.sc.seis:gradle-launch4j:1.0.5'
  }
}

den will ich haben!

apply plugin: 'launch4j'

Schreib doch bitte einfach rein, wer das gemacht hat, danke.

launch4j {
    mainClassName = project.mainClassName
    version = '3.0.0'
    copyright = '(C) 2002-2013, J. Heidrich, M. Goellnitz'
    downloadUrl = 'https://www.dropbox.com/s/3n4snlbw9tyjgec/JFileSync3.zip'
    supportUrl = 'https://github.com/mgoellnitz/JFileSync3'
    icon = "$project.projectDir/win/JFileSync.ico"
    dontWrapJar = true
    xmlFileName = 'JFileSync3-launcher.xml'
    initialHeapSize = 1024
    maxHeapSize = 1536
}
Ach, und wo Du gerade da bist: Pack das doch noch mal etwas anders ein, als sonst

distZip {
  into(project.name) {
    from "$buildDir/launch4j"
    include '*.exe'
    include '*.xml'
  }
  into(project.name) {
    from '.'
    include 'legal/*.*'
    include 'profiles/*.*'
    include 'README.md'
  }
}

Oh, äh. Testen. Machst Du das noch schnell? Ich habe da vier Beispiele abgelegt.

apply plugin:com.smokejumperit.gradle.ExecPlugin

task(encryptionTest) << {
  // Extract distribution
  println "Extracting distribution"
  ant.unzip(src: "$buildDir/distributions/${project.name}.zip", dest: "$buildDir")
  // Extract test data
  println "Extracting test data"
  ant.unzip(src: "test/test-folders.zip", dest: "$buildDir/${project.name}")

  println "Encrypting"

  String cmd = "";
  cmd = "cmd /c bin${File.separator}${project.name} -config 1encrypt.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "Decrypting taking every folders metadata into account"
  cmd = "cmd /c bin${File.separator}${project.name} -config 2decrypt1.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "Decrypting ignoring every folders metadata"
  cmd = "cmd /c bin${File.separator}${project.name} -config 3decrypt2.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "And now please compare by hand"
  cmd = "cmd /c bin${File.separator}${project.name} -config 4compare.xml -nohistory"
  project.exec(cmd, "$buildDir/${project.name}")
}


Macht das bitte nicht nur in der Kürze sondern auch in so kurzer Zeit und so, daß man es immer wieder lesen kann, mit anderen Werkzeugen. Für mich ist Gradle nun weiterhin die erste Wahl in allen Projekten, in denen ich die Wahl habe.

Sonntag, 29. September 2013

Tangram 0.8 Release - Mehr Dynamik bitte

Es wurde langsam Zeit für ein neues Tag auf dem Tangram Repository. Die Änderungen gegenüber der letzten Version sind doch umfangreich und viele Anwendungen, die Tangram nutzen - und dringend Version 0.8 brauchen, sollten wieder eine Stabile Basis bekommen.
Die aktuelle Fassung auch von den Beispielen gibt es als Code wie immer bei github und die Dependencies bezieht man aus dem amor.

Was gibt's neues?

Version 0.8 beschäftigt sich viel mit Dynamik. Dazu bedurfte es eines Erheblichen Tunings - insbesondere auf der Google App Engine - allgemein für die Nutzung in der Cloud. Aber etwas anderes kommt unter der Überschrift Dynamik ja auch nicht in Frage.
Zu den umfangreicheren Caching Techniken ebenfalls mit Zielgebiet Cloud werde ich hier noch einen separaten Beitrag veröffentlichen.
Mit Version 0.8 ist die Programmierung von Tangram-Anwendungen nun endgültig in die Datenbank gewandert und wird dort mit Apache Velocity und Groovy umgesetzt, wobei einige Schwächen und Fehler bereinigt wurden.
Dabei sind nun neben den bekannten URLs auch Benutzeraktionen auf der Weboberfläche mit (in den sogenannten Shims) Groovy umsetzbar.
Das dynamische Zusammenstellen der Inhalte auf der Site ist nun ausreichend performant, in der API vollständig und ebenfalls mit Groovy handhabbar.
Um das alles benutzbar zu halten wurde der Editor - eigentlich nur ein Stiefkind in Tangram - in wichtigen Details verbessert und in den Abläufen einfacher gestaltet.
Technisch wurde die RDBMS Umsetzung vom Proof of Concept neben der Google App Engine zu der Hauptumsetzung und das System um die Anbindung MongoDB erweitert. Dabei wurde der JDO-Layer von den historischen Altlasten früher Google App Engine Zeiten befreit und auf API-Level 3.0 gehoben.
Natürlich sind alle genutzen Komponenten auf aktuelle Versionen umgestellt und auch das Buildsystem mit Gradle - jetzt in Version 1.8 - konsistent weiterentwickelt.

Neuer Standard-Startpunkt

Wer nun eine neue Web-Idee ausprobieren möchte und kein Geld in die Hand nehmen will, nimmt nicht mehr die Tangram und die Google App Engine und den Google Apps sondern Tangram und Cloudbees - ggf. mit MongoDB als Backend, an dieser Stelle sogar mit integrieten git-SCM und Continous-Integration-Server über Jenkins.
Auch hier gilt: Wenn die Idee fliegt, besteht ein professionelles Angebot für eine kommerzielle Nutzung der Plattform.
Ersatz für die Google Apps, die wir in der Vergangenheit für den Rest des Auftrittes wie Mails, Calender, Dokumentablage etc. genutzt haben, findet man bei zoho.

Freitag, 27. September 2013

CoreMedia Blueprint für Solaris

Evtl. kennt das noch der ein oder andere: Wenn man den ganzen Tag auf einen dieser alten grünen Monochrommonitore geschaut hat, sieht man abends einen rosa Augenhintergrund. - In unserem Projekt hier ist es gerade sehr ähnlich - nur andersherum.
Und so müssen die neuen Dinge von CoreMedia, die richtig Spaß machen und gut zu handhaben sind (natürlich nicht ohne Einarbeitung), auch ein wenig Altlast tragen, denn einige Dinge sollen so bleiben, wie sie seit viele Jahren bewährt laufen.
Z.B. sind die Server immernoch Sparc Maschinen mit Oracle Solaris. Und der der Kunde erwartet, daß das einfach so funktioniert, da es ja auch im Handbuch steht.
Der CoreMedia Blueprint und sein kleiner Bruder verlassen sich aber darauf, daß die damit erstellten Pakete auf einer bestimmten Sorte Linux laufen (eine Frage von LSB, BSD, POSIX usw.). Also haben wir da doch mit einer gewissen Lernkurve Punkte entdeckt, an denen man Änderungen einbauen muß. Die Projektlösung war dabei doch recht speziell, umfangreich - und hatte mehr Klippen zu umschiffen als für eine Blueprint nötig.
Daher wollte ich nun auf Basis des reinen Blueprint Workspaces noch einmal eine saubere, schlanke Neu-Umsetzung durchführen. Bereits bei den Spielereien mit Debian/Ubutunu habe ich gesehen, wie sich einige Skripte doch auf Details verlassen, die nicht einmal unter allen Linux-Distributionen so sind. Bei Solaris war mir aus Jahrzehntelanger Erfahrung klar, daß selbst bei gleicher Shell einige System-Tools sich nicht so verhalten, wie ihre meist erweiterten GNU-Freunde. - Und das ist auch hier wieder relevant.
Der große Skripte-Zoo im CoreMedia Blueprint ist aber gerade ein wichtiger Teil für den Weg vom Entwickler-Rechner in die Produktion. Diese Zusammenstellung wollte ich möglichst minimal anpassen und mit einer Solaris Paketierung zusammenführen.
Schritt eins dabei war natürlich die Herstellung der Pakete selbst. Wegen schlechter Erfahrungen mit entsprechenden Maven-Plugins, die auch seit Jahren nicht mehr gewartet werden und das Alpha-Stadium nicht verlassen haben, kam diesmal der Einsatz der System-Tools auf die Tagesordnung. Solaris-Pakete lassen sich damit auch nur unter Solaris erstellen, sorry.
Dabei integriert mal die Skripte aus dem Workspace oder verpackt. D.h. man holt sich die preinstall.sh und portuninstall.sh als preinstall und postuninstall in das Paket während man die postinstall.sh und preuninstall einfach verpacken kann:
  # postinstall
  if [ -f ${INSTALL_ROOT}/${APPLICATION_NAME}/INSTALL/postinstall.sh ] ; then
    ${INSTALL_ROOT}/${APPLICATION_NAME}/INSTALL/postinstall.sh 1
  fi


Im Gegensatz zu Linux Varianten kennt hier Solaris anscheinend keine Parameter an diese Skripte. Preinstall und Postuninstall sind also schon einmal anzupassen, da vor dem Installieren und nach dem Entfernen ein Wrapper in's Leere greifen würde:
  #!/bin/bash
  set -e
  # $1 == 1 --> initial installation
  # $1 == 2 --> upgrade
  SELECTOR=$1

  # Solaris
  if [ -z $SELECTOR ] ; then
    SELECTOR=1
  fi

  if [ $SELECTOR -eq 1 ] ; then
    ...
  (preinstall.sh)Die Solaris Paketierung stellt man auch gerne wieder generisch als Maven Modul unter packages/ bereit und legt sich hierin Prototypen für die Paketbeschreibung inklusive der Skripte und ein Ant-Script zu Steuerung der Paketierung bereit:
  PKG=${APPLICATION_NAME}
  NAME=${APPLICATION_NAME}
  DESC="CoreMedia 7 Component - ${APPLICATION_NAME}"
  VERSION=${project.version}
  BASEDIR=${INSTALL_ROOT}
  PSTAMP=${timestamp}
  CATEGORY=application
  ARCH=sparc
  CLASSES=none
  VENDOR="CoreMedia AG"
  HOTLINE="+49-40-325587-777"
  EMAIL="support@coremedia.com"
  ISTATES=S s 1 2 3
  RSTATES=S s 1 2 3

  pkginfo

  i pkginfo
  i preinstall=preinstall
  i postinstall=postinstall
  i preremove=preuninstall
  ! postremove=postuninstall
  !include generated-prototype

  prototype

<?xml version="1.0" encoding="UTF-8" ?>
<project>
  <target name="pkg">
    <copy file="${project.build.directory}/${PACKAGE_DIR}/INSTALL/preinstall.sh"
          tofile="${project.build.directory}/solaris-cooked/preinstall" 

          overwrite="true" failonerror="false" />
    <copy file="${project.build.directory}/${PACKAGE_DIR}/INSTALL/postuninstall.sh"
          tofile="${project.build.directory}/solaris-cooked/postuninstall"     

          overwrite="true" failonerror="false" />
    <exec executable="/bin/bash" failonerror="true">
        <arg value="-c" />
        <arg value="pkgproto ${project.build.directory}/${PACKAGE_DIR}=${APPLICATION_NAME}/ > ${project.build.directory}/solaris-cooked/raw-prototype" />
    </exec>
    <exec executable="/bin/bash" failonerror="true">
        <arg value="-c" />
        <arg value="sed -e s/${user.name}/${INSTALL_USER}/g ${project.build.directory}/solaris-cooked/raw-prototype | sed -e s/[a-zA-Z0-9]*$/${INSTALL_GROUP}/g | grep -v dll= | grep -v exe= | grep -v bat= > ${project.build.directory}/solaris-cooked/generated-prototype" />
    </exec>
    <exec executable="/bin/bash" failonerror="true">
      <arg value="-c"/>
      <arg value="mkdir ${project.build.directory}/pkg"/>
    </exec>
    <exec executable="/bin/bash" failonerror="true">
        <arg value="-c" />
        <arg value="pkgmk -f ${project.build.directory}/solaris-cooked/prototype -d ${project.build.directory}/pkg -o -b ./" />
    </exec>
    <exec executable="/bin/bash" failonerror="true">
        <arg value="-c" />
        <arg value="pkgtrans ${project.build.directory}/pkg ../${APPLICATION_NAME}-${project.version}.sparc.pkg ${APPLICATION_NAME}" />
    </exec>

    <exec executable="/bin/bash" failonerror="true">
      <arg value="-c" />
      <arg value="gzip -9 ${project.build.directory}/${APPLICATION_NAME}-${project.version}.sparc.pkg" />

    </exec>
  </target>
</project>

(ANT build-pkg.xml)

Wenn man diese Pakete dann installiert, fällt die Installation recht herb auf die Nase. Unter Solaris funktioniert das automatische Anlege der Benutzer doch etwas anders und hat keine impliziten Randbedingungen wie bei Linux.
   # Add the "@INSTALL_USER@" user
  # This will fail on Solaris
  /usr/sbin/useradd -c "system user to run coremedia services and applications" \

       -s /bin/bash -m -d @INSTALL_ROOT@ -r @INSTALL_USER@ 2> /dev/null || :
  # For Solaris create group manually
  /usr/sbin/groupadd @INSTALL_GROUP@ 2> /dev/null || :
  # So try it again without -r parameter but group (s.a.) instead
  /usr/sbin/useradd -c "system user to run coremedia services and applications" \

       -s /bin/bash -m -d @INSTALL_ROOT@ \
       -g @INSTALL_GROUP@ @INSTALL_USER@ 2> /dev/null || :
(postinstall.sh)
Wenn man jetzt noch das mktemp in reconfigure.sh umformuliert kommt man schon sehr viel weiter. Und nach einer Anpassung der Service-Integration in /etc/init.d ist man sehr nahe am Ziel.
  # Register as service if possible
  if [ -x /sbin/chkconfig ] ; then
    logger -p syslog.info -t coremedia/rpm \

           -s "Enabling the service @APPLICATION_NAME@, ..."
    chkconfig @APPLICATION_NAME@ on
  else
    echo "To start call \"/etc/init.d/@APPLICATION_NAME@ start\""
  fi
  (z.B. postinstall.sh)

Auch hier wieder ist zur (etwas) besseren Lesbarkeit einiges ausgelassen. Die volle GIT-Patch-Sequence gibt's gerne auf Anfrage.

Montag, 23. September 2013

CoreMedia 7 Blueprint als Debian/Ubuntu Pakete

Es ist hier Off-Topic, aber es paßt gut in die Zielrichtung Entscheidungsfreiheit bei der Plattformwahl zu bekommen und dennoch einheitlich die benötigten Features zur Verfügung zu haben.
Dies ist ein Thema, das man eben nicht nur mit Tangran adressieren kann, sondern auch z.B. mit dem CoreMedia CMS.
Mehr durch einen Bedienerfehler inspiriert als mit Zielrichtung versehen, habe ich das CoreMedia 7 Blueprint mit Debian Paketbau versehen. Damit kann man - auch wenn es nicht suppported ist - das CoreMedia 7 System auch z.B. mit einem Ubuntu LTS (es sollte ja auch ein Enterprise Linux sein können, damit man bei Bedarf auch kommerziell Support einkaufen kann) einsetzen.
CoreMedia selbst bietet uns ein fertiges Setup zum Bau von RPM-Paketen für die einzelnen Komponenten, das sogar unter Windows ohne weitere Werkzeuge läuft. Das ist eine Meßlatte, die ich nicht unterschreiten wollte. Also war ich auf der Suche nach einer reinen Java-Lösung und stieß auf diese Liste: Maven plugin for building debian package.
Leider werden CoreMedia Workspaces immernoch mit Maven zu fertigen Lösungen zusammengebaut und zwei der Tools auf der Liste lassen sich nicht einfach integrieren oder benötigen wieder externe Werkzeuge auf der Unix-Kommandozeile.
Einzig jdeb brachte schnell erste Ergebnisse.
Wer das Ganze in sein Projekt eingepaßt haben möchte oder einfach den fertigen Patch gegen dan Blueprint in Version 26 haben möchte, möge sich einfach mal melden. Hier folgt jetzt die lesbare aber damit nicht Code-Zeilen-genaue Version.
Was ist zu tun?
Nachdem man in der root-POM jdeb mit Version (hier 1.0.1) bekannt gemacht hat, wendet man sich einzig dem Bereich packages im Workspace zu, wo alles zunächst ganz einfach aussieht.
Mit einer Konfiguration für alle Pakete
  <plugin>
    <artifactId>jdeb</artifactId>
    <groupId>org.vafer</groupId>
    <configuration>
      <deb>[[buildDir]]/${APPLICATION_NAME}_[[version]]_all.[[extension]]</deb>
      <controlDir>${project.build.directory}/deb</controlDir>
      <dataSet>
        <data>
          <type>template</type>
          <paths>
            <path>${INSTALL_ROOT}/${APPLICATION_NAME}</path>
          </paths>
        </data>                  

        <data>
          <src>${project.build.directory}/${APPLICATION_NAME}</src>
          <type>directory</type>
          <excludes>**/*.bat,**/*.exe,**/*.dll</excludes>
          <mapper>
            <type>perm</type>
            <user>${INSTALL_USER}</user>
            <group>${INSTALL_GROUP}</group>
            <prefix>${INSTALL_ROOT}/${APPLICATION_NAME}</prefix>
          </mapper>
        </data>
      </dataSet>
    </configuration>
  </plugin>

entstehen sehr schnell .deb Dateien, wenn man sich vorher eine Dependency auf ein ZIP erzeugt und im ${project.build.directory}/deb auspacken läßt. In dieser Dependency (hier genannt debian-packaging) braucht man mindestens ein control-File und Filtering, damit man nicht für jedes Paket ein eigenes erstellen muß.
  Package: ${APPLICATION_NAME}
  Version: ${project.version}
  Section: httpd
  Priority: optional
  Architecture: all
  Depends: ${APPLICATION_PREFIX}-tomcat-installation
  Maintainer: Main Tainer <maintainer@example.com>
  Description: CoreMedia 7 - ${APPLICATION_PREFIX} ${project.artifactId}
  Homepage: http://www.coremedia.com/

Bis diese Pakete auch nur fast auf dem Niveau der RPM-Pakete sind, ist allerdings noch ein wenig Feinarbeit notwendig.
Die Installation- und Entfernungs-Skripten, die Dateiberechtigungen und die Unterschiede zwischen dem Bereich tools, services und der tomcat-installation bringen dann noch ein wenig Arbeit, die einige Round-Trips erfordert, bis alles an seiner Stelle steht und richtig zusammenspielt.
Man sieht es schon an den data-Template oben, wo der Basis-Pfad des gesamten Paketes noch einmal explizit erwähnt werden muß. Außerdem haben Debian-Pakete andere Aufruf-Parameter für die Skripten, wo z.B. install statt 1 und upgrade statt 2 übergeben wird. Desweiteren verlassen sich die mitgelieferten Skripten von CoreMedia auch fest auf das Vorhandensein von Werkzeugen, die nun einmal nun auf jedem Linux verfügbar sind. Diese mitgelieferten Skripten werden durch Wrapper in den Pakete aufgerufen - außer natürlich dem Skript preinst (preinstall.sh), das beim Wrapper in's leere greifen würde und daher angepaßt werden mußte - in allen drei Varianten, die im Workspace existieren:
  # $1 == 1 --> initial installation
  # $1 == 2 --> upgrade
  SELECTOR=$1


  # Debian
  if [ "a$SELECTOR" = "ainstall" ] ; then
    SELECTOR=1
  fi
  if [ "a$SELECTOR" = "aupgrade" ] ; then
    SELECTOR=2
  fi

  if [ $SELECTOR -eq 1 ] ; then
    # initial installation

Am Ende der ganzen Prozedur kommt noch ein wenig Fleißarbeit: In jedem aber auch jedem Paket - das ist beim CoreMedia Application Maven Plugin letztlich nicht anders - muß nun der Paketbau als Referenz auf die übergreifenden Konfiguration eingefügt werden.
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
      <execution>
        <phase>initialize</phase>
        <goals>
          <goal>unpack</goal>
        </goals>
        <configuration>
          <artifactItems>
            <artifactItem>
              <groupId>${project.groupId}</groupId>
              <artifactId>debian-packaging</artifactId>
              <version>${project.version}</version>
              <type>zip</type>
              <outputDirectory>${project.build.directory}/deb</outputDirectory>
            </artifactItem>
          </artifactItems>
        </configuration>
      </execution>
    </executions>
  </plugin>

  ...
  <plugin>
    <artifactId>jdeb</artifactId>
    <groupId>org.vafer</groupId>
    <executions>
      <execution>
        <phase>package</phase>
        <goals>
          <goal>jdeb</goal>
        </goals>
      </execution>
    </executions>
  </plugin>

Auch hier sind natürlich wieder ein paar Schritte ausgelassen, um den Text hier noch einigermaßen lesbar zu halten.

Mittwoch, 18. September 2013

NATURINSPIRIERT.org mit Tangram

Die Meldung kommt eigentlich etwas spät, aber es gibt einmal wieder eine neue Site, die mit Tangram realisiert wurde. Das kleine Foto-Blog naturinspiriert.org ist mit dem Tangram Framework auf der Google App Engine realisiert worden.
Das ganze ist insofern technisch dennoch nicht "yet another" und "nur klein", da hier zum ersten Mal live die dynamsichen Content-Zusammenstellungen genutzt werden. Die einzelnen Rubriken der Site befüllen sich mit den passenden Artikeln von selbst. Alle anderen Projekte, die diese Feautures von Tangram 0.8 nutzen sind leider noch nicht online oder können hier nicht öffentlich erwähnt werden.
Was bei der Umsetzung aufstieß sind allerdings die Probleme mit der Google App Engine: Man darf sich nicht mehr blenden lassen von veralteten Hinweisen und Links: Ohne kostenpflichtigen Google Apps Zugang bekommt man keine eigene Domain mehr für Google App Engine Anwendungen geschaltet. Punkt. Die erwähnten Links gibt es nicht mehr. Wir haben sie in keinem Szenario aus Google App Engine Anwendungen und Google Accounts nachstellen können.Wir werden also nun weiter an den alternativen Arbeiten.
Wenn's Geld kosten darf, ist Google Apps sicherlich eine gute Lösung und Google Appengine eine sehr attraktive Plattoform. Wenn man allerdings noch nicht weiß, ob eine Lösung Geld verdient, ist es nach erfolgreichen Jahren seit 2013 nur noch zweite Wahl.

Montag, 8. Juli 2013

Mehr Auswahl für die Plattform - Tangram auf Cloudbees

Wie es aussieht, läuft Tangram sehr einfach auch auf der Infrastruktur von Cloudbees. Im Gegensatz zu der Google App Engine benötigt man bei dieser Variante keine spezielle Anpassung, es müssen nur die vorhandenen Konfigurationsparameter passend eingestellt werden:

1. Anlegen einer neuen Anwendung

Entweder über die Web-Schnittstelle oder die Kommandozeile legt man eine neue Anwendung an.

2. Erstellen einer Datenbank

Unter Cloudbees kann man MySQL Datenbanken anlegen und benutzen.
Eine solche Datenbank könnte man natürlich nun direkt in die Tangram-Anwendung hineinkonfigurieren.  Dann könnte man den folgenden Schritt überspringen und direkt die Anwendung mit Schritt 4 deployen. Server, Port, Schemaname und Benutzername sind ja aus dem GUI bekannt und können einfach in die jdoconfig.xml übernommen werden. Wir gehen hier jedoch über einen Zwischenschritt vor.

3. Verbinden der Datenbank als Datasource

Die Verbindung der Datenbank mit einem Namen, der über JNDI erreichbar ist, kann nur über die Kommandozeile erledigt werden:

examples\rdbms-example>bees app:bind -db <DBNAME> -a <APPID> -as tangramdb

Im Tangram rdbms example lautet die jdoconfig.xml dann

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

   <persistence-manager-factory name="transactions-optional">
       <property name="javax.jdo.PersistenceManagerFactoryClass"
           value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
       <property name="datanucleus.ConnectionFactoryName" 

                 value="java:comp/env/jdbc/tangramdb" />
       <property name="datanucleus.autoCreateSchema" value="true" />
       <property name="datanucleus.validateTables" value="true" />
       <property name="datanucleus.manageRelationships" value="true" />
       <property name="datanucleus.validateConstraints" value="true" />
   </persistence-manager-factory>

</jdoconfig>


Statt der "normalen" Verbindungsparameter findet sich hier also nur eine Zeile mit dem Eigenschaftsnamen "datanucleus.ConnectionFactotyName". Die anderen Parameter entsprechen den Einstellungen, die im Zusammenhang mit Tangram immer verwendet werden.

4. Deployen

Entweder benutzt man wieder das GUI und deployed das Web-Archiv unter build\libs\rdbms-example.war oder erledigt dies über die Kommandozeile (BEES_HOME muß gesetzt sein und das Cloudbees SDK im Pfad erreichbar):

examples\rdbms-example>bees app:deploy build\libs\rdbms-example.war  
    -a <account>/<appid>

5. Benutzen

Das war's dann auch:

http://<appid>.<account.>.cloudbees.net/s/list?cms.editor.class.name=org.tangram.rdbms.solution.RootTopic

führt einen nun auf nach dem Login an den einfachen Tangram Editor - und es kann losgehen, mit dem Anlagen von Codes und (im Beispiel) Topics und anderen Objekten,

Mittwoch, 9. Januar 2013

JDO 3.0 in der Google App Engine

Es ist nicht gerade ganz neu, aber ich habe es nun einmal bis jetzt aufgeschoben, meine Google App Engine nutzenden Anwendungen auf JDO 3 zu heben, obwohl das ja nun angeblich möglich ist. 
Das wichtigste dabei wären die "unowned relationsships" (was ein Wort-Ungetüm), da sie der Grund sind, daß wir bisher keine referenzielle Integrität auf Datenebene haben und diese merkwürdigen Umwege über die IDs als Strings in in den Datenfeldern nutzen müssen. In Tangram gibt es da einen Haufen Methoden (getIds, getContent(), getContents()), die einem das Leben einfach machen, aber eigentlich sollte das alle ersatzlos gestrichen werden.
Google beschreibt die Umstellung als manuellen Weg, die Anwendung zu modifizieren.
An andere Stelle kursieren immer wieder Anleitetungen, wie man das mit dem Eclipse Plugin für die Google App Engine bewerkstelligen soll. Nur genau dieses Plugin nutze ich natürlich in einem Projekt nicht, das ich auch anderen Menschen zur Verfügung stellen will, da dort mein Anspruch ist, daß man einfach ein Build-Systam hat, das alles genauso zusammenbaut wie ich bei mir.
Mit einer IDE habe ich so etwas noch nie erreicht, also bleibe ich nach make, ant, maven nun bei gradle und will hier einmal für mich und andere Aufzählen, was man zu Umstellung machen muß:
Schritt eins ist, die Abhängigkeit der JDO API zu ändern. In Gradle-Notation wird
javax.jdo:jdo2-api:2.3-eb zu
javax.jdo:jdo2-api:3.0.1
(Die letzten Maven-Nutzer werde das auch lesen können)
Und dann brauch noch die jdoconfig.xml ein paar kleinere Anpassungen:
<property name="javax.jdo.PersistenceManagerFactoryClass"
          value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
wird zu
<property name="javax.jdo.PersistenceManagerFactoryClass"
          value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

Außerdem kommen noch zwei kleine Zeile dazu
<property name="datanucleus.appengine.query.inMemoryWhenUnsupported" value="true"/>
<property name="datanucleus.appengine.relationDefault" value="unowned"/>
Die obere Zeile ergibt sich aus den geänderten Paketen für die aktuelle datanucleus Version und die beiden anderen Zeilen versuchen, die performance zu erhöhen und die "unowned relationsships" als default zu setzen, wie es auch bei allen anderen Stores außer der Appengine der Falle ist.
Wenn man das getan hat, funktioniert bis auf die paar Semantik-Änderungen alles wie gehabt und man kann beginnen die Model-Klassen umzuschreiben.
Google beschreibt, daß auch noch die JPA-Anbindung in datanucleus hinzugefügt werden sollte, aber die nutzen ich nicht und so entsteht die Abhängigkeit nicht. Desweitere fehlt noch geronimo-jpa_2.0_spec-1.0, was aber für JDO wieder keine Rolle spielen sollte..
Aus 
package org.tangram.gae.solution;
 
import java.util.List;

import org.tangram.jdo.JdoContent

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Container extends JdoContent {

    private List<String> contentIds;


    public List<Topic> getContents() {
        return getContents(Topic.class, contentIds);
    }


    public void setContents(List<Topic> contents) {
        contentIds = getIds(contents);
    }

} // Container
wird dann das deutlich lesbarere
package org.tangram.rdbms.solution;

import java.util.List;

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Container extends Linkable {

    @Unowned
    private List<Topic> contents;


    public List<Topic> getContents() {
        return contents;
    }


    public void setContents(List<Topic> contents) {
        this.contents = contents;
    }

} // Container

wie wir es schon in der rdbms Variante nutzen. Endlich...
(Daß ich dabei Probleme in der rdbms-Variante entdecken mußte steht auf einem anderen Blatt und damit in einem späteres Posting ;-) )