RRZE – Projekte & Prozesse (P&P)

Das Blog der RRZE Stabsstelle "Projekte & Prozesse"

Content

Message-ID & Grails Mail Plugin

Nach einem freundlichen Hinweis unserer Postmaster, dass die Message-ID unserer E-Mails eher suboptimal ist, einigten wir uns auf eine neue einheitliche Message-ID für unsere Anwendungen.

Bisher wurde die Message-ID autogeneriert und hatte die Form: <1234567890.12.1234567890123.JavaMail.”RRZE IdM-Portal”@smtp.uni-erlangen.de>

Wir haben uns nun auf die folgende Form geeinigt: <[TIMESTAMP].[RANDOM(10)].[appname]@[domain]>

In unseren GRAILS-Anwendungen nutzen wir das Mail-Plugin. Man kann die Message-ID durch folgende Anweisung neu setzen.
[groovy]
headers “Message-ID”: “<message-id@domain>”
[/groovy]

Es gibt allerdings ein Problem bei dieser Lösung: Man kann die Message-ID nicht anwendungsweit in der Konfiguration festlegen. Daher müsste man bei jedem Aufruf des Mail-Services die Message-ID neu setzen. Wir haben uns nun dafür entschieden, einen eigenen Service als Wrapper für den Mail-Service zu nutzen.
[groovy]class PpsaMailService {
def grailsApplication

def mail = { c ->
def domain = grailsApplication.config.mail.domain?:’idm.fau.de’
// generate random number

sendMail {
c.delegate = delegate
c.call()
headers “Message-ID”: “<${(System.currentTimeMillis())}.${randomNumber}.${grailsApplication?.metadata[‘app.name’]}@${domain}>”
}
}
}
[/groovy]

In der Grails Konfiguration kann man nun die Domain für die Message-ID festlegen, default ist idm.fau.de.

Folgendes Beispiel zeigt nun, wie der neue Service zu nutzen ist:

Mit Mail Plugin:
[groovy]
sendMail {
from “from@example.org”
to “to@example.org”
subject “Subject”
body “Body”
}
[/groovy]

Mit Wrapper:
[groovy]
def ppsaMailService
ppsaMailService.mail {
from “from@example.org”
to “to@example.org”
subject “Subject”
body “Body”
}
[/groovy]

 

Message-ID & Grails Mail Plugin

After a friendly advice from our postmaster, the Message-ID of our e-mails are rather suboptimal, we agreed on a new consistent Message-ID for our applications.

So far the Message-ID has been automatically generated and had the form: <1234567890.12.1234567890123.JavaMail.”RRZE IdM-Portal”@smtp.uni-erlangen.de>

We agreed now on the following form: <[TIMESTAMP].[RANDOM(10)].[appname]@[domain]>

In our GRAILS-applicatons we use the Mail-Plugin. You can set a new Message-ID with the following command:
[groovy]
headers “Message-ID”: “<message-id@domain>”
[/groovy]

However there is a problem with this solution: you can not define the Message-ID application wide in the configuration. Therefore the Message-ID would have to be set again at every request to the Mail-Service. We have now decided to use an own service as a wrapper for the Mail-Service.
[groovy]class PpsaMailService {
def grailsApplication

def mail = { c ->
def domain = grailsApplication.config.mail.domain?:’idm.fau.de’
// generate random number

sendMail {
c.delegate = delegate
c.call()
headers “Message-ID”: “<${(System.currentTimeMillis())}.${randomNumber}.${grailsApplication?.metadata[‘app.name’]}@${domain}>”
}
}
}
[/groovy]

In the Grails configuration, the domain for the Message-Id can be set. Default is idm.fau.de.

The following example shows how to use the service:

With Mail Plugin:
[groovy]
sendMail {
from “from@example.org”
to “to@example.org”
subject “Subject”
body “Body”
}
[/groovy]

With Wrapper:
[groovy]
def ppsaMailService
ppsaMailService.mail {
from “from@example.org”
to “to@example.org”
subject “Subject”
body “Body”
}
[/groovy]

Grails Plugins – lokal und global / Grails Plugins – local and global

English Version

Bei der Verwaltung von Grails Plugins in einem zentralen Repository gestaltet sich die lokale Weiterentwicklung der Plugins ggf. schwierig.

Mit dem folgenden Code-Block in der BuildConfig.groovy kann mittels des Kommandozeilenparameters -Dlive=... eines, oder mit
Komma getrennt, mehrere Plugins lokal geladen werden.

Wichtig ist dabei, dass neben dem Hauptprojekt auch alle Plugins mit diesem Code Block ausgestattet werden. So werden auch transitive Abhängigkeiten wie gewünscht mittels relativem Pfad oder aus dem Repository aufgelöst.

[groovy]
/*
* PPSA PLUGIN CONFIGURATION
*/
def runtimePlugins = [
“:ppsa-menu:latest.release”,
“:ppsa-layout:latest.release”,
]

/* —– BEGIN: no changes past here —– */
def live = System.getProperty(“live”)?System.getProperty(“live”).split(‘,’):[]
def filteredLivePlugins = []
if (live) {
filteredLivePlugins = live.inject([]) { list, lib ->
runtimePlugins.grep { it.contains(lib) } ? list << lib : list
}
if (filteredLivePlugins) {
println “[YOUR_PROJECT_NAME_HERE] Loading some plugins from relative paths in the workspace: ” + filteredLive
filteredLivePlugins.each { pluginName -> grails.plugin.location.”${pluginName}” = “../${pluginName}” }
}
}
/* —– END: no changes past here —– */

grails.project.dependency.resolution = {
plugins {
def filteredRuntimePlugins = runtimePlugins.inject([]) { list, lib ->
filteredLivePlugins.grep { lib.contains(it) } ? list : list << lib
}
if (filteredRuntimePlugins) {
println “[YOUR_PROJECT_NAME_HERE] Loading plugins from release repository: ” + filteredRuntimePlugins
filteredRuntimePlugins.each { pluginDef -> runtime “${pluginDef}” }
}
}
}
[/groovy]

Beispiel, wie man zur live Umgebung für das Plugin ppsa-layout wechseln kann:
[bash]
grails clean -Dlive=ppsa-layout
grails run-app -Dlive=ppsa-layout
[/bash]

Grails Plugins – local and global


With the management of Grails Plugins in a central repository the local further development of plugins could become quite difficult.

With the following Code Block in the  BuildConfig.groovy one or more plugins — sepearted via commas — can be loaded locally instead of from the repository with the help of the command line parameter  -Dlive=....

In order to also resolve transitive dependencies in this way it is important that beside the main project all plugins are also provided with this code block. This way also transitive dependencies can be loaded from a relative path or from the repository.

[groovy]
/*
* PPSA PLUGIN CONFIGURATION
*/
def runtimePlugins = [
“:ppsa-menu:latest.release”,
“:ppsa-layout:latest.release”,
]

/* —– BEGIN: no changes past here —– */
def live = System.getProperty(“live”)?System.getProperty(“live”).split(‘,’):[]
def filteredLivePlugins = []
if (live) {
filteredLivePlugins = live.inject([]) { list, lib ->
runtimePlugins.grep { it.contains(lib) } ? list << lib : list
}
if (filteredLivePlugins) {
println “[YOUR_PROJECT_NAME_HERE] Loading some plugins from relative paths in the workspace: ” + filteredLive
filteredLivePlugins.each { pluginName -> grails.plugin.location.”${pluginName}” = “../${pluginName}” }
}
}
/* —– END: no changes past here —– */

grails.project.dependency.resolution = {
plugins {
def filteredRuntimePlugins = runtimePlugins.inject([]) { list, lib ->
filteredLivePlugins.grep { lib.contains(it) } ? list : list << lib
}
if (filteredRuntimePlugins) {
println “[YOUR_PROJECT_NAME_HERE] Loading plugins from release repository: ” + filteredRuntimePlugins
filteredRuntimePlugins.each { pluginDef -> runtime “${pluginDef}” }
}
}
}
[/groovy]

Example on how to switch to the live environment for the plugin ppsa-layout:
[bash]
grails clean -Dlive=ppsa-layout
grails run-app -Dlive=ppsa-layout
[/bash]

Grails Plugins in eigenem Maven Repository verwalten / Manage Grails Plugins in own Maven Repository

English Version

In diesem Beitrag zeige ich kurz wie man Grails 2 Plugins als Releases in einem eigenen Maven Repository wie Archiva oder Artifactory verwalten kann. Dafür sind grundsätzlich zwei seperate Konfigurationen für Upload und Download vom Repository nötig.

Upload

Für das Releasen neuer Versionen in das eigene Repository bietet Grails 2 das Release Plugin, das man allerdings erst selbst nachinstallieren muss:
[bash]
grails install-plugin release
[/bash]

Die nötige Konfiguration dazu wird in die ~/.grails/settings.groovy ausgelagert. Einträge in dieser Datei gelten für alle Grails Projekte und sind äquivalent zu Einträgen in der BuildConfig.groovy.

Hier ein Beispiel zur Einrichtung von Archiva als Release Repository:
[groovy]
/*
* THIS IS FOR UPLOADING TO THE REPO (via release plugin)
* To use this do:
* grails2 install-plugin release
*/
grails.project.repos.default = “rrzeArchiva”

grails.project.repos.rrzeArchiva.url = “http://REPOSITORY:PORT/archiva/repository/internal/”
grails.project.repos.rrzeArchiva.username = “USERNAME”
grails.project.repos.rrzeArchiva.password = “PASSWORD”

// disable nagging about not having committed all changes…
grails.release.scm.enabled = false
[/groovy]

Das Beispiel Konfiguriert das rrzeArcchiva Repository als Default für Uploads und deaktiviert die SVN Überprüfung für nicht eingecheckte Änderungen.

Fertig.

Neue Releases können dann mit folgenden Build Targets ins Repository geladen bzw. in den lokalen Maven Cache installiert werden.
[bash]
grails maven-deploy
grails maven-install
[/bash]

Download

Für den automatischen Download von Plugins aus dem Repository sind folgende Einträge in der BuildConfig.groovy des Projektes nötig.
[groovy]
grails.project.dependency.resolution = {
repositories {
mavenLocal()
mavenRepo name:”rrzeArchiva”, root:”http://REPOSITORY:PORT/archiva/repository/internal”
}
plugins {
runtime “:ppsa-menu:latest.release”
}
}
[/groovy]

Der Repositories Abschnitt Konfiguriert den lokalen Maven Cache und das Archiva Repository als zu durchsuchende Quellen für Plugin Abhängigkeiten.
Die Plugin Abhängigkeiten selbst werden im eigenen Abschnitt Plugins verwaltet. Der Beispieleintrag lädt immer die aktuellste Version des Plugins ppsa-menu.

Eigentlich würde das nun schon reichen, um Plugins auf dem Repository zu installieren.

Die allermeisten internen Repositories werden jedoch noch zusätzlich eine Authentifizierung erfordern. Hierfür muss noch der folgende Eintrag zur ~/.grails/settings.groovy hinzugefügt werden.
[groovy]
/*
* THIS IS FOR DOWNLOADING FROM THE REPO
* To use this add this to your BuildConfig.groovy:
* mavenRepo name:”rrzeArchiva”, root:”http://REPOSITORY:PORT/archiva/repository/internal”
*/
grails.project.ivy.authentication = {
credentials {
realm = “Repository Archiva Managed internal Repository”
host = “REPOSITORY(ohne Port!)”
username = “USERNAME”
password = “PASSWORD”
}
}
[/groovy]

—- WICHTIG —-

Für Archiva muss der Realm wie angegeben konfiguriert werden. Sonst geht nix!

Außerdem muss der Host hier ohne Port angegeben werden. Wenn das Archiva also z.B. auf Port 8080 läuft ist diese Angabe hier wegzulassen.
—- WICHTIG —-

Managing Grails Plugins in own Maven Repository


 

In this post I will briefly show how to manage Grails 2 Plugins as releases in an own Maven Repository such as Archiva or Artifactory. Therefore basically two separate configurations for Upload and Download from the repository are needed.

Upload

To release new versions in the own repository Graisl 2 offers the Release Plugin, which though must be installed additionally by yourself:
[bash]
grails install-plugin release
[/bash]

The necessary configuration for this is stored in the  ~/.grails/settings.groovy. Entries in this data are valid for all Grails projects and equivalent entries in the BuildConfig.groovy.

Here is an example of installing Archiva as release repository:
[groovy]
/*
* THIS IS FOR UPLOADING TO THE REPO (via release plugin)
* To use this do:
* grails2 install-plugin release
*/
grails.project.repos.default = “rrzeArchiva”

grails.project.repos.rrzeArchiva.url = “http://REPOSITORY:PORT/archiva/repository/internal/”
grails.project.repos.rrzeArchiva.username = “USERNAME”
grails.project.repos.rrzeArchiva.password = “PASSWORD”

// disable nagging about not having committed all changes…
grails.release.scm.enabled = false
[/groovy]

The example configures the rrzeArcchiva repository as Default for Uploads and deactivates the SVN proof for not-checked-in changes.

Done.

New releases can be loaded with the following Build Target in the Repository, respectively installed the local Maven Cache.
[bash]
grails maven-deploy
grails maven-install
[/bash]

Download

For the automatic download of plugins from the repository the following entries in the  BuildConfig.groovy of the project are needed.
[groovy]
grails.project.dependency.resolution = {
repositories {
mavenLocal()
mavenRepo name:”rrzeArchiva”, root:”http://REPOSITORY:PORT/archiva/repository/internal”
}
plugins {
runtime “:ppsa-menu:latest.release”
}
}
[/groovy]

The repository paragraph configures the local Maven Cache and the Archiva Repository as sources for plugin dependencies which have to be browsed.
The plugin dependencies themselves are managed in the own paragraph plugins. The example entry is always loading the most current version of the plugin ppsa-menu.

Actually this would be enough to install plugins on the repository.

Most of all intern repositories will though need further authentication. Therefore the following entry has to be added ~/.grails/settings.groovy.
[groovy]
/*
* THIS IS FOR DOWNLOADING FROM THE REPO
* To use this add this to your BuildConfig.groovy:
* mavenRepo name:”rrzeArchiva”, root:”http://REPOSITORY:PORT/archiva/repository/internal”
*/
grails.project.ivy.authentication = {
credentials {
realm = “Repository Archiva Managed internal Repository”
host = “REPOSITORY(ohne Port!)”
username = “USERNAME”
password = “PASSWORD”
}
}
[/groovy]

—- IMPORTANT —-

For Archiva the realm must be configured as shown. Or nothing will work!

Also, the Host must be stated here without Port. If the Archiva is also running e.g. on Port 8080 these information can be omitted here.
—- IMPORTANT —-

Postgres, Serials und Rules: Achtung!

Achtung bei folgender Konstellation:
[sql]
CREATE TABLE source
(
id serial NOT NULL,
“text” character varying(255),
CONSTRAINT id_pkeyPRIMARY KEY (id)
)
[/sql]

mit Regel:
[sql]
CREATE RULE “insert_rule” AS
ON INSERT TO source DO  INSERT INTO dest (sourceid)
VALUES (NEW.id);
[/sql]

Serial und Bigserial sind in Postgres intern so konzipiert, dass der Wert für das Feld durch einen Aufruf von
[sql]
nextval(‘source_id_seq’)
[/sql]
erzeugt wird. Entgegen der Erwartung wird nach Ausführen der Regel aber nicht source.id in die Tabelle dest eingetragen, sondern source.id +1. Bug oder einfach so gewolltes Verhalten von Postgres? In der Postgres-Dokumentation zu Regeln findet sich zumindest ein entsprechender Kommentar:
“Beware that CREATE RULE insert_foo AS ON INSERT TO foos DO ALSO INSERT INTO bar (foo_id) VALUES (NEW.id); won’t work if foo.id is a serial (and foo_id REFERENCES foos ofcourse) because nextval() is evaluated twice.”

Sauberste Lösung: Hier keine Regel, sondern einen Trigger verwenden.

Postgres, Serials and Rules: Caution!

Caution with the following constellations:
[sql]
CREATE TABLE source
(
id serial NOT NULL,
“text” character varying(255),
CONSTRAINT id_pkeyPRIMARY KEY (id)
)
[/sql]

with rule:
[sql]
CREATE RULE “insert_rule” AS
ON INSERT TO source DO  INSERT INTO dest (sourceid)
VALUES (NEW.id);
[/sql]

Serial and Bigserial are so in Postgres internally designed that the data for the field can be generated with the request of
[sql]
nextval(‘source_id_seq’)
[/sql]
Against all expectations after carrying out the rule it is not source.id which is filled into the table dest but source.id +1. Bug or simply unwanted behavior of Postgres? In the Postgres-Documentation the rules can be found at least in on depending commentary:
“Beware that CREATE RULE insert_foo AS ON INSERT TO foos DO ALSO INSERT INTO bar (foo_id) VALUES (NEW.id); won’t work if foo.id is a serial (and foo_id REFERENCES foos ofcourse) because nextval() is evaluated twice.”

Cleanest solution: Do not use a rule here but a trigger.

Properties einer Grails-Domänenklasse auslesen

Bei der Deklaration einer Domänen-Klasse in Groovy wird auf die Bean-Notation für Variablen (Variable deklariert als private, zusätzlich Getter und Setter) verzichtet. Stattdessen werden sogenannte Properties definiert, die durch Groovy intern in Java-Beans umgewandelt werden.
[groovy]
class MyTest {
Long id
String val1, val2
}
[/groovy]
Oftmals ist es nötig, die Liste der Properties auszulesen, etwa um ein Objekt in ein anderes zu mappen. Hierfür bietet Groovy die Methode getProperties():
[groovy]
def x = new MyTest()
x.properties.each { key, value ->
println “Property ${key}. ${value}”
}
[/groovy]

Hier tauchen aber plötzlich Properties auf, die in MyType gar nicht definiert wurden, wie version, class, metaClass, constraints! Grails Magic, hier wurden durch das Grails-Framework weitere Properties hinzugefügt!

Wie kommt man nun an die Liste nur der selbst definierten Properties? Die Antwort liegt bei den persistentProperties, die in der Klasse liegen. Diese erreicht man über die DefaultGrailsDomainClass.
[groovy]
def persistentProps = new DefaultGrailsDomainClass(MyTest.class).persistentProperties
persistentProps.each {
println “${it.name}”
}
[/groovy]
Et Voila: Die Liste der persistenten Properties, in aller Regel der selbstdefinierten.

 

Read Properties of a Grails Domain Class

With the declaration of a domain class in  Groovy it is done without a Bean-Notation for variables (variable declared as  private, furthermore Getter and Setter). Instead so called properties are defined which are changed internally into Java-Beans by Groovy
[groovy]
class MyTest {
Long id
String val1, val2
}
[/groovy]
It is often necessary to read out the list, for example to map an object into another. Therefore Groovy offers the methode getProperties():
[groovy]
def x = new MyTest()
x.properties.each { key, value ->
println “Property ${key}. ${value}”
}
[/groovy]

At this points properties suddenly appear which haven’t been defined like version, class, metaClass, constraints! Grails Magic, further properties have been added by the Grails-Framework!

So how do you get the list of the self defined properties only? The answer lies within the persistentProperties, which are within the class. You can reach it via DefaultGrailsDomainClass.
[groovy]
def persistentProps = new DefaultGrailsDomainClass(MyTest.class).persistentProperties
persistentProps.each {
println “${it.name}”
}
[/groovy]
Et Voila: The list of persistent properties, usually the self defined.

Logging von Grails-Applikationen in JBoss

Kurzanleitung:

  • Aus Classloader-Gründen darf die Applikation nicht eine eigene log4j-xxx.jar beinhalten. Diese kann aus dem war-file entfernt werden durch folgenden Eintrag in BuildConfig.groovy:
    [groovy]
    grails.war.resources = { stagingDir ->
    delete(file:”${stagingDir}/WEB-INF/lib/log4j-1.2.16.jar”)
    delete(dir:”${stagingDir}/WEB-INF/classes/org/grails/tomcat”)
    }
    [/groovy]
    (Der Eintrag mit tomcat ist für das Thema hier nicht relevant, entfernt aber unnötige Klassen aus dem war-file.)
  • Die Grails-Log4j-Konfiguration aus Config.groovy wird im JBoss nicht ausgewertet. Daher sind die Einträge in der JBoss-Log4j-Konfiguration ${JBOSS}/server/default/conf/jboss-log4j.xml nachzuziehen.
    Beispiel für Controller:
    [xml]
    <category name=”grails.app.controller.MyController”>
    <priority value=”INFO”/>
    </category>
    [/xml]
    Man beachte auch das “grails.app.controller“-Präfix!
  • Eine weitere Seltsamkeit ist, dass nach einem Undeploy oder Redeploy eines Grails-war-files das Logging des JBoss komplett beendet wird, d.h. absolut nichts mehr in die Logdatei geschrieben wird. Den Grund und eine saubere Lösung dafür habe ich noch nicht gefunden, aber als Workaround ist es möglich, ein touch auf ${JBOSS}/server/default/conf/jboss-log4j.xml auszuführen. Da diese Konfiguration regelmäßig eingelesen wird, erscheinen die Logmeldungen wieder.

Logging of Grails application in JBoss

Brief administration:

  • Because of  Classloader reasons the application can not have an own  log4j-xxx.jar. It can be deleted from the war-file with the following entry in  BuildConfig.groovy:
    [groovy]
    grails.war.resources = { stagingDir ->
    delete(file:”${stagingDir}/WEB-INF/lib/log4j-1.2.16.jar”)
    delete(dir:”${stagingDir}/WEB-INF/classes/org/grails/tomcat”)
    }
    [/groovy]
    (The entry with tomcat is not relevant for this topic, but deletes unnecessary classes from the war-file.)
  • The Grails-Log4j-configuration from Config.groovy is not evaluated in JBoss. Therefore the entries need to be moved in the JBoss-Log4j-onfiguration ${JBOSS}/server/default/conf/jboss-log4j.xml.
    Example for Controller:
    [xml]
    <category name=”grails.app.controller.MyController”>
    <priority value=”INFO”/>
    </category>
    [/xml]
    Keep the “grails.app.controller“-Prefix in mind!
  • Another oddity is that after an undeploy or redeploy of a Grails-war-file the logging of the JBoss will be completely closed,  which means that absolutely nothing is written in the log file. We haven’t found yet the reason and a clean solution for this problem, but as a Workaround it is possible to make a touch to ${JBOSS}/server/default/conf/jboss-log4j.xml. Because this configuration will be importet regularly the logging messages will reoccur.

Promovierendenverwaltung wird selbständig

English Version

Mit Version 5.1 von mein campus wird die Promovierendenverwaltung der Graduiertenschule der Friedrich-Alexander-Universität auf einen eigenen Internet-Auftritt ausgegliedert. Sie ist seit Mittwoch, 17.11. unter der URL https://www.docdaten.uni-erlangen.de zu erreichen.

Unter dieser Adresse können Promotionen registriert werden, registrierte und freigeschaltete Promovenden haben Einsicht in ihre Daten (Benutzername und Passwort bleiben hier unverändert). Zugleich mit der Ausgliederung hat sich auch der Funktionsumfang erweitert:

Neue Features bei der Registrierung von Promotionen:

  • Datenübernahme aus mein campus für (ehemalige) Studierende der FAU ist möglich!Nach Eingabe von Benutzername und Passwort werden persönliche Daten sowie sowie Informationen über Studiengang und -abschluss aus mein campus ausgelesen und in das Registrierungsformular übertragen.
  • Die Registrierung kann zwischengespeichert werden, woraufhin ein Link per E-Mail zugesandt wird, unter dem sie innerhalb zwei Wochen fortgesetzt werden kann. Nach zwei Wochen werden nicht vollständige Registrierungen gelöscht, um Datenmüll zu vermeiden.

Neue Features bei Dateneinsicht:

  • Eine PDF-Ansicht der Daten kann erzeugt und heruntergeladen werden.

Zudem gibt es einige neue Funktionen für die Administratoren des Systems

Wir wünschen viel Spaß mit Docdaten!

PhD Administration becomes independent

With version 5.1 of mein campus the PhD administration of the grad school of the Friedrich-Alexander-Universität will be removed to its own internet appearance. You can reach it since Wednesday, 17.11. under the URL  https://www.docdaten.uni-erlangen.de  .

Under this adress PhD-students can be registered, already registered and activated PhD-students can view their data (username and password remain unchanged here). At the same time with the new appearance the functional range has expanded:

New features at the registration of PhD-students:

  • Data transfer from mein campus for (former) students of the FAU is possible! After inserting username and password, personal data as well as information on the study course and degree can be read from mein campus and be transfered into the register form.
  • The registration process can be saved within, whereupon a link is sent vie e-mail under which the process can be continued within two weeks. After two weeks uncomplete registrations will be deleted to avoid data trash.

New features for data overview:

  • A PDF overview of data can be generated and downloaded.

Further there are some new functions for the admins of the systems.

We hope you have a lot of fun with Docdaten!

Spring, Maven und Variablen

Das Ziel ist: In der Spring-Konfiguration Platzhalter verwenden, die je nach Maven-Profil anders belegt werden.

pom.xml, hier muss Filterung konfiguriert werden:

  • Properties definieren:
    [xml]
    <properties>
    <location>remote</location>
    </properties>
    [/xml]
  • Ersetzung aktivieren, im build-Baum der POM bzw. des jeweiligen Profils. Dies ersetzt in den spezifizierten Dateien Einträge der Form ${location} durch die entsprechenden Werte:
    [xml]
    <resources>
    <resource>
    <directory>src${file.separator}main${file.separator}resources</directory>
    <filtering>true</filtering>
    <includes>
    <include>**${file.separator}*.xml</include>
    <include>*.xml</include>
    <include>**${file.separator}*.properties</include>
    <include>*.properties</include>
    </includes>
    </resource>
    <resource>
    <directory>src${file.separator}main${file.separator}resources</directory>
    <filtering>false</filtering>
    <excludes>
    <exclude>**${file.separator}*.xml</exclude>
    <exclude>*.xml</exclude>
    <exclude>**${file.separator}*.properties</exclude>
    <exclude>*.properties</exclude>
    </excludes>
    </resource>
    </resources>
    [/xml]

Spring-Konfiguration (applicationContext.xml in WEB-INF/):

  • placeholderConfig definieren:
    [xml]
    <bean id=”placeholderConfig”
    class=”org.springframework.beans.factory.config.PropertyPlaceholderConfigurer”>
    <property name=”locations”>
    <list>
    <value>classpath:ejb-configuration.properties</value>
    </list>
    </property>
    </bean>
    [/xml]
  • Variable verwenden:
    [xml]
    <jee:jndi-lookup id=”gradschoolManager” jndi-name=”GradschoolManagerBean/${location}”>
    <jee:environment>
    java.naming.factory.initial=org.jboss.naming.NamingContextFactory
    java.naming.provider.url=${java.naming.provider.url}
    java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
    </jee:environment>
    </jee:jndi-lookup>
    [/xml]

Jetzt muss nur noch eine entsprechende Properties-Datei angelegt werden, z.B. unter /src/main/resources:

[xml]
java.naming.provider.url=${java.naming.provider.url}
location=${location}
[/xml]

Wie läuft das ab?

Bei einem Maven-Lauf werden zunächst die Variablen in der Properties-Datei ersetzt. Spring ist so konfiguriert, dass die (ersetzten) Werte aus dieser Datei gelesen und genutzt werden.

Log4j Appender mit automatischem Zip-nach-Rotate

English version

Um Pattenplatz bei der Benutzung von Log4j zu sparen wurden zwei alternative Appender implementiert die neben der üblichen Rotation der Logdatei, diese auch gleich mittels zip komprimiert.

Die beiden Appender Klassen sind entsprechend ihren Pendants auf die Namen DailyRollingZipAppender und RollingZipAppender getauft und im repo unter ppsa-log4j-zip zu finden.
Beide Klassen erweitern die ursprünglichen Appender Implementierungen und können deshalb 1:1 ohne zusätzlichen Konfigurationsaufwand ausgetauscht werden.

Um eine Logdatei beim rotieren automatisch zu zippen müssten zum Beispiel folgende Anpassungen vorgenommen werden:

     <appender name="FILE" class="org.jboss.logging.appender.DailyRollingFileAppender">

ersetzen durch

     <appender name="FILE" class="de.rrze.ppsa.log.DailyRollingZipAppender">

Außerdem muss die ppsa-log4j-zip-0.0.1.jar im Classpath liegen.

Die Implementierung wurde inspiriert von einem Blogbeitrag auf ralfhaug.de.

Log4j appender with automatic zip-after-rotate

In order to save precious disc space when using Log4j, we developed to alternative appender implementations with zip functionality. In addition to the usual rotation of the log files, these appenders also compress the rotated logs using the zip algorithm.

The two appender classes are named after their respective counterparts: DailyRollingZipAppender and RollingZipAppender. They are available in our repo under ppsa-log4j-zip.
Both classes extend the oiginal appender implementations and can be used as plug-in replacements without any additional configuration.

An example configuration to automatically zip a logfile after rotate would be done something like this:

     <appender name="FILE" class="org.jboss.logging.appender.DailyRollingFileAppender">

must just be replaced with

     <appender name="FILE" class="de.rrze.ppsa.log.DailyRollingZipAppender">

Additionally the ppsa-log4j-zip-0.0.1.jar has to be in the classpath.

The implementation was inspired by this blog entry on ralfhaug.de.

Sortierbare Elemente in Tapestry per jQuery UI

Problembeschreibung

 
English Version
Zur Sortierung von Elementen in einem Webfrontend kann man sich verschiedene Realisierungen vorstellen. Beispielsweise könnte man für jedes Element eine Textbox bereitstellen, in denen der Benutzer den Index innerhalb der Sortierreihenfolge definiert. Das ist zwar sehr barrierefrei, aber auch unhandlich, wenn man sich vorstellt, dass in einer bestehenden Sortierung das letzte Element weit nach oben verschoben werden soll, da dann viele Indizes angepasst werden müssen. Eine andere Realisierung wären Links mit denen die Elemente jeweils eine Position nach oben oder unten verschoben werden können. Das erfordert jedoch häufiges Neuladen der Seite und ist für den Benutzer nicht sonderlich ästhetisch anzusehen.

Daher wurde für eine konkrete Realisierung des Problems in FAU.ORG auf die wunderbare Welt der AJAX/Javascript Komponenten von jQuery zurückgegriffen. Konkret auf die Komponenten von jQuery UI. Diese Komponentensammlung umfasst sowohl Interactions (also Elemente wie Draggable, Droppable, Sortable, etc.) als auch Widgets (Datepicker, Progressbar, etc.). Die Sortable-Komponente entsprach genau den Anforderungen des oben beschriebenen Problems.

Integration von jQuery:Sortable in Tapestry

Zunächst müssen die notwendigen Javascript-Bibliotheken eingebunden werden:
[html]<link type=”text/css” href=”${asset:jqueryui/themes/base/jquery.ui.all.css}” rel=”stylesheet” />
<script type=”text/javascript” src=”${asset:scripts/jquery-1.4.1.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.core.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.widget.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.mouse.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.sortable.js}”></script>[/html]
Optional kann man die sortierbaren Elemente (die als unordered List gerendert werden) als Kästen formatieren:
[html]<style type=”text/css”>
#sortable { list-style-type: none; margin: 0; padding: 0; float: left; margin-right: 10px; }
#sortable li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.0em; width: 350px; }
</style>[/html]
Danach wird die Liste um Sortierfähigkeit erweitert und ein Handler entwickelt, der die Sortierergebnisse speichert:
[javascript]<script type=”text/javascript”>
$(function() {
$(“#sortable”).sortable();
$(“#sortable”).disableSelection();
});

function storeOrder() {
var result = $(‘#sortable’).sortable(‘toArray’);
var context = “”;
for (var id in result) {
if (context == “”) {
context = result[id];
} else {
context = context + “_” + result[id];
}
}
window.location = “/${pageName}” + “.save/” + context;
}
</script>
[/javascript]
Schließlich wird die Liste selbst noch eingebunden und ein Speichernlink bereitgestellt:
[html]<ul id=”sortable”>
<li t:type=”loop” source=”elements” value=”element” id=”${element.id}”>${element.name}</li>
</ul>
<a href=”#” id=”prepare” onClick=”storeOrder()”>${message:save}</a>
<t:actionlink t:id=”save” context=”literal:dummy”></t:actionlink>[/html]
Die einzelnen Listenelemente werden durch invisible Instrumentation der Loop-Komponente von Tapestry erzeugt. Die erste Funktion im Javascript-Block sorgt für die Verschiebbarkeit per Maus der Elemente innerhalb der Liste und verbietet deren Selektierbarkeit.

Beim Klicken auf den HTML-Link wird lediglich die Javascript-Funktion storeOrder() aufgerufen. Diese erzeugt ein Array der IDs der Elemente in ihrer vor dem Klicken aktuellen Reihenfolge. Da diese Liste per ActivationContext an einen Actionhandler auf Tapestry-Serverseite übergeben werden soll, kommaseparierte Liste in diesem Kontext jedoch nicht zugelassen sind, wird die Liste in einen String umgewandelt, in dem die Element-IDs per Unterstrich voneinander getrennt vorkommen. Dies kann auf Serverseite durch ein simples String.split() wieder zerlegt werden. Man beachte, dass dafür ebenfalls ein Actionhandler auf Serverseite bereitgestellt werden muss. Dafür wurde der pseudo-Actionlink erzeugt. Der Redirect der storeOrder()-Funktion ruft den Actionhandler mit dem vorbereiteten ActivationContext schließlich manuell auf.

Demo

Eine Demo der Sortable-Komponente von jQuery UI ist hier zu finden.

Sortable Elements in Tapestry with jQuery UI

Problem description

There are many ways to realize sorting elements in a web frontend. One could provide a textbox for every element, which holds the index within the sorting order. This is barrier free to the fullest, but also very inconvenient. Imagine a ordered list, where the last element should be moved to the very top. With this technique almost all element indexes must be adopted. Another realization would be two links on every element. One to move itself upwards and one downwards. This requires a lot of page reloading and is not really state of the art.

To solve the concrete problem within the FAU.ORG project the wonderful AJAX/Javascript components of jQuery were used. More specific the components of jQuery UI. This collection of components includes Interactions (elements like Draggable, Droppable, Sortable, etc.) and Widgets (Datepicker, Progressbar, etc.). The sortable component matched exactly the requirements of the above mentioned problem.

Integration of jQuery:Sortable in Tapestry

First of all one has to include the required Javascript-libraries:
[html]<link type=”text/css” href=”${asset:jqueryui/themes/base/jquery.ui.all.css}” rel=”stylesheet” />
<script type=”text/javascript” src=”${asset:scripts/jquery-1.4.1.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.core.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.widget.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.mouse.js}”></script>
<script type=”text/javascript” src=”${asset:jqueryui/ui/jquery.ui.sortable.js}”></script>[/html]
The next step comprises optional formatting of the elements (which are rendered as an unordered list):
[html]<style type=”text/css”>
#sortable { list-style-type: none; margin: 0; padding: 0; float: left; margin-right: 10px; }
#sortable li { margin: 0 5px 5px 5px; padding: 5px; font-size: 1.0em; width: 350px; }
</style>[/html]
Now the list is enriched to support sortability and a handler is developed, which stores the order of elements:
[javascript]<script type=”text/javascript”>
$(function() {
$(“#sortable”).sortable();
$(“#sortable”).disableSelection();
});

function storeOrder() {
var result = $(‘#sortable’).sortable(‘toArray’);
var context = “”;
for (var id in result) {
if (context == “”) {
context = result[id];
} else {
context = context + “_” + result[id];
}
}
window.location = “/${pageName}” + “.save/” + context;
}
</script>[/javascript]
Finally the list itself must be included and a link to store the changes provided:
[html]<ul id=”sortable”>
<li t:type=”loop” source=”elements” value=”element” id=”${element.id}”>${element.name}</li>
</ul>
<a href=”#” id=”prepare” onClick=”storeOrder()”>${message:save}</a>
<t:actionlink t:id=”save” context=”literal:dummy”></t:actionlink>[/html]
Every list element is created with invisible instrumentation of the Loop-component of Tapestry. The first function in the Javascript-block enables moving elements by mouse within the list and disables selections.

When clicking the HTML-link the storeOrder() Javascript-function is called. This function creates an array of the element-IDs in the most recent defined order. As the list will be handled over to an ActionHandler-function by ActivationContext on Tapestry server-side and comma-separated lists are forbidden within this context, the array is converted to a single string, where the IDs are separated by underscore. This string can easily be converted to an array again with the  String.split() Java-function.  Be aware, that therefore an ActionHandler has to be provided on server-side. To accomplish this a pseudo ActionLink is created within the Tapestry-Template. The redirect of the storeOrder()-function calls the ActionHandler with the given context manually.

Demo

A demo of the Sortable-component of jQuery UI can be found here.