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.