Um das tangram-coma Modul wieder mehr in meinen Fokus zu bringen, brauche ich zum Testen immer einen CoreMedia CMS Content Server als backend.
Bisher war dieser Server entsprechend einer Anleitung und mit der Lieferung des Produktes als ZIP-Datei von Hand herzustellen. Das paßt natürlich nicht so richtig in die Tangram Beispiele, die in sich abgeschlossen sein sollten, und die bisher genutzte Version CMS 2008 (5.2) läuft nun unwidderruflich auch aus.
Mit den aktuellen Versionen wird das Produkt handlich in Form von Artefakten in einem Maven-Repository geliefert und es stehen Maven-Module zur Verfügung, daraus komplette Server zu erstellen, zu customizen und zu bestücken.
Leider ist dieser Bereich eben noch mit Maven formuliert und außerdem müßte ich mich dann - und dafür ist es ein wenig früh - vom Minimalbeispiel MenuSite verabschieden.
Also habe ich mich gefragt, ob ich fit genug bin, die Baupläne von CoreMedia mal im ganz kleinen nach gradle zu übersetzen und mir so den Content Management Server mit Build-Script in die Tangram Beispiele zu integrieren. Dabei habe ich natürlich wieder einen Dienst menr aus der Cloud als Entwicklungsunterstützung hinzugezogen, wie es zum Entwicklungsmodell von Tangram am besten paßt: Gut funktionierende Dienste und Komponenten nutzen. Da ich mit CoreMedia in der aktuellen Version hsqldb nicht mehr nutzen kann und nicht mehr wie im bisherigen Beispiel postgresql lokal installieren und nutzen wollte, habe ich mir mal schnell eine Testdatenbank bei DB4Free besorgt, da das "default" Datenbanksystem im CoreMedia CMS derzeit MySQL ist.
Um's kurz zu machen: Das Zusammenstellen eines Content Management Servers geht ganz wunderbar einfach und auch die Maven-Vorlagen sind - für Maven's Verhältnisse - relativ kompakt (d.h. unlesbar, häßlich, langatmig aber nicht lang). Den Prototyp für einen komplett Maven-freien und nicht Tangram-bezogenen Content Management Server gibt's ab heute unter
https://github.com/mgoellnitz/cm-cms-webapp
Und mit diesem Werkzeug gehe ich nun bei Tangram in die nächste Runde. Außerdem sieht man, daß man langsam den Weg weg von Maven (unter Beibehaltung der großartigen Dependency und Repository-Bereitstellung) beschreiten kann hin zu Gradle. Das ist kein harter Schnitt, das ist ein Weg, sodaß ich nun auch im CoreMedia Kontext keinen Neubau mit Maven-Syntax beginnen werde. Gradle verspricht, uns dabei zu unterstützen, da sie die installierte Maven-Basis im Blick haben.
Das Entstehungs-Blog zu "Tangram". Einer Zusammenstellung von kleinere und größere Bausteinen, um schlank und elegant Ideen in's Web zu bringen. Es ist eine modulare Sammlung, um klein anzufangen ohne sich den Weg zur großen Webpräsenz zu verbauen. Außerdem ist das Ziel, vorhandene Räder nicht neu zu erfinden, sondern eher Versatzstücke zu integrieren, um ein schönes Gesamtbild zu erstellen. - Daher ja auch der Name.
Montag, 23. Juni 2014
Samstag, 5. April 2014
Why use JSR330 Dependency Injection annotations
I rather apologise to introduce another Dependency Injection Container for the Java world - dinistiq - a very minimalistic approach to the topic. It turned out to be easier to implement another one, than to use others listed here. Limited in features, easy to use, and still more configurable than other options I could think of. After some months of use, I now can invite other users to take a look at it and try it in their own projects.
Also this text gives you a "why" on the use of the JSR 330 annotations for Dependency Injection. It simply makes your code even more reusable in case your development or deployment environment changes.
Since tangram is much more about glueing together proven existing software components and frameworks than writing code, I felt the need to check if the existing code base was really fully dependent on the Spring Framework.
Despite the fact that spring more or less in many ways does what I need, it sometimes feels a bit bloated and does too much magic I don't understand in detail (which I still had to learn when debugging things). So I tried to isolate the spring code during the tangram 0.9 work and present at least a second solution for all the things I did with spring so far.
For tangram spring does three things
- Dependency Injection to plug the whole application together
- support a decent view layer with JSP and Apache Velocity views
- A concise way to map http requests to code - controller classes or methods
So I took a look at other view frameworks like Vaadin, GWT, Apache Wicket, Play, Struts, JSF/JEE, Stripes. Right at the moment I think Vaading, GWT, Wicket, and Play are no really good fit for tangram, Struts in my eyes is a fading technology, and only JSF/JEE is an obvious option. With Java Server Faces I only had unsatisfying project experiences and the rest of JEE goes for plain Servlet. So tangram had to be provided with a plain Servlet way of doing the view layer.
Since the modularity of tangram was achieved by the Spring way of plugging components together with Dependency Injection, the first thing to do was, to mark the generic components in a spring independent way and to look at the other options for the Dependency Injection part. Only then it would be possible to replace the spring view layer with a Servlet view layer during the startup and wire-up of the application.
So the list of relevant DI frameworks gets shortened to those supporting the generic Dependency Injection annotations from JSR330 which are intended for JEE and can e.g. also be used with Google Guice and the Spring Framework alike.
From the reading Google Guice seemed to be a good alternative for the proof of concept phase, but it took me that much work to get something to run with it (not everything can be plugged together programmatically in my case), that I came out faster with my own Dependency Injection Container. Rather minimalistic and only suited for the setup of components.
Its advantage over Guice is that it's smaller and easier configurable with properties files. Weeks later I discovered TinyDI as another option. While this container seems to be a lot cleverer about the search of annotated classes it seems to lack the needed option of extending the configuration aspects from the annotations with properties files - defaults and overridden values and references.
So right at the moment I still don't have a running tangram application but all of the tangram framework now can be used with dinistiq. This example shows that now over 90% of the classes of tangram are free of direct dependencies to the Spring Framework while still taking advantage of its features and runtime environment. The code definitely got cleaner and more reusable.
Labels:
Container,
Dependency Injection,
DI,
Dinistiq,
Framwork,
Google Guice,
Guice,
Java,
JSR330,
minimalistic,
small footprint,
Springframework,
Tangram,
TinyDI
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
// 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.
// 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.
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.
- EclipseLink
- OpenJPA
- DataNucleus (JDO or JPA style)
- Ebean
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.
Labels:
Datanucleus,
Ebean,
EclipseLink,
Gradle,
Java Data Objects,
Java Persistence API,
JDO,
JPA,
OpenJPA,
Plugin,
Tangram,
Tangram CMS
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.
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.
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.
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.
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.
<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>
<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);
<!-- 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.
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.
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.
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"/>
<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>
<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.
Labels:
Cloud,
Cloudbees,
Datasource,
GIT SCM,
JDO,
Maven,
MongoDB,
MySQL,
Tangram CMS
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.
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.
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.
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.
"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-Programmapply 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.
Abonnieren
Posts (Atom)











