Posted on March 27th, 2010 by Reiner.
Categories: Grails, English.
As outlined at How to Deploy a Grails Application to JBoss 5, deploying to JBoss 5 just requires to remove some logging jars and disable Grails logging configuration. With GlassFish however, it is required to supply certain logging jars (e.g. within your war file) in order to use its native logging system.
I’ll give a full source example on how to achieve this, using Grails 1.2.1 and GlassFish v3. The code should as well run a-is with Grails 1.2.2 and GlassFish v2 (and even with Tomcat 6 using its default Java Util Logging configuration). Watch-out: GlassFish currently refuses to deploy Grails 1.2.2 applications - see Links below.
I won’t cover using JNDI data sources here, as their implementation is straight forward and nicely covered by GlassFish’s administration console. You should always use JNDI and JDBC pools provided by GlassFish though, as they offer superior reliability and shield your application from deployment site details.
Note that with GlassFish v3, it may be feasible to deploy skinny wars, i.e. instead of including them within your war, let GlassFish supply the jars required for running a Grails application. This pattern reduces resource usage when running multiple Grails applications within a single GlassFish instance. I haven’t looked into this, as for me the potential gain did not justify the effort when building and keeping track of possibly conflicting Grails versions.
GlassFish uses Java Util Logging as its native logging implementation. Thus your Grails war should delegate its logging to Jul as well in order take advantage of GlassFish built-in web administration and alert features. Components shown in green have to be added to your war and the ones in red are to be removed:
How to achieve this? Just supplying an additional Grails configuration file and changing a single configuration attribute will do the trick.
First within scripts/_Events.groovy we’ll remove offending jars, add required jars and disable Grails logging configuration components:
import groovy.xml.StreamingMarkupBuilder
/**
*** TODO GLASSFISH - Remove log4j configuration stuff (when running with JBoss or GlassFish a.s.o) ***
*/
eventWebXmlEnd = {String tmpfile ->
def root = new XmlSlurper().parse(webXmlFile)
// When running with JBoss (or GlassFish a.s.o) remove log4j configuration stuff
def log4j = root.listener.findAll {node ->
node.'listener-class'.text() == 'org.codehaus.groovy.grails.web.util.Log4jConfigListener'
}
log4j.replaceNode {}
def log4jFile = root.'context-param'.findAll {node ->
node.'param-name'.text() == 'log4jConfigLocation'
}
log4jFile.replaceNode {}
webXmlFile.text = new StreamingMarkupBuilder().bind {
mkp.declareNamespace("": "http://java.sun.com/xml/ns/j2ee")
mkp.yield(root)
}
}
/**
*** TODO GLASSFISH Remove log4j and use jul as used by GlassFish instead ***
*/
eventCreateWarStart = { warName, stagingDir ->
if (grailsEnv == "production") {
String log4jVer = "1.2.15"
String slf4jVer = "1.5.8"
[
"lib/log4j-${log4jVer}.jar", // log4j not used with GlassFish
"classes/log4j.properties", // logging conf done in GlassFish Admin only
"lib/slf4j-log4j12-${slf4jVer}.jar", // log4j not used with Glassfish
"lib/jul-to-slf4j-${slf4jVer}.jar", // not required, native JUL with by GlassFish
// you might want to remove JDBC drivers when using server supplied JNDI...
// "lib/hsqldb-1.8.0.10.jar",
].each {
println "*** GLASSFISH *** _Events.groovy removing ${it}"
Ant.delete(file: "${stagingDir}/WEB-INF/$it")
}
// Grails+Ivy (as yet) do not provide war-only dependencies - thus we fetch them manually
Ant.mkdir(dir: "${basedir}/glassfishlibs") // to this dir (in case your internet breaks down)
[
"slf4j-jdk14", // from http://repo1.maven.org/maven2/org/slf4j/slf4j-jdk14/1.5.8/slf4j-jdk14-1.5.8.jar
"log4j-over-slf4j", // from http://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.5.8/log4j-over-slf4j-1.5.8.jar
].each { artifactId ->
String fileName = "${artifactId}-${slf4jVer}.jar"
if (!(new File("${basedir}/glassfishlibs/${fileName}")).file ) {
println "*** GLASSFISH *** _Events.groovy getting ${fileName}"
Ant.get(dest: "${basedir}/glassfishlibs/${fileName}",
src: "http://repo1.maven.org/maven2/org/slf4j/${artifactId}/${slf4jVer}/${fileName}")
}
println "*** GLASSFISH *** _Events.groovy copying ${fileName}"
Ant.copy(file: "${basedir}/glassfishlibs/${fileName}",
tofile: "${stagingDir}/WEB-INF/lib/${fileName}")
}
}
}
Unfortunately, I found no way to cause Grails+Ivy dependency resolution to include artifacts solely when building a war (the other way around appears to be feasible though). Thus the script outlined above uses Ant to fetch the required Jars from Maven’s Central repository and saves them to a folder within your project (glassfishlibs) in order to have subsequent project builds succeed, even if there’s no connectivity to the Internet.
Watch out: The jar versions as stated within the script might need to be adopted for Grails versions other than 1.2.1 or 1.2.2.
Next, within grails-app/conf/Config.groovy we’ll instruct Grails not to enable the Jul-to-Slf4j bridge, as this bridge would either alter GlassFishs loggings setup and cause infinite looping or class-not-found errors (as we removed the bridge jar from our war):
// *** TODO GLASSFISH doesn't use jul bridge, as GlassFish uses native jul *** grails.logging.jul.usebridge = false // instead of true
You are now set to control logging using GlassFish’s excellent Web GUI.
Links:
Posted on March 20th, 2010 by Reiner.
Categories: Deutsch, Uncategorized.
Abstrakt: Bei einer - aus rechtlichen Gründen - hier nicht namentlich genannten - Bank reicht das Gedächtnis für Kontoauszüge nur 3 Monate zurück. Und selbst für in diesem Zeitraum ausgeführte Dauerüberweisungen sind keinerlei Kontodaten des Begünstigten mehr verfügbar, sobald die Dauerüberweisung endet.
Ich muss mir hier meine Wut und mein Entsetzen vom der Seele schreiben…
Ok, Stefan bekommt noch meinen Anteil am gemeinsamen Urlaubsflug (den er gebucht und bezahlt hat) von mir. Nichts einfacher, als das - dachte ich.
Also schnell ins Online-Banking und in den Kontoauszügen nach den letzten Überweisungen an Stefan gesucht.
Huch, was ist das? Keine Kontonummer, nirgends?! Das Online-Gedächtnis meiner Bank ist dem Wählerverhalten angepasst und hat (trotz stetig fallender Preise für Massenspeicher) nur Platz für 3 Monate. Egal…
Krieg ich schon - denke ich - da ist ja erst vor ein paar Wochen noch ein Dauerauftrag ausgeführt worden. Fehlanzeige! Es gibt den zugehörigen Dauerauftrag gar nicht mehr, da er jetzt bereits abgelaufen ist - im Ergebnis habe ich mein Geld einem schwarzen Loch übereignet!
Papierauszugsstapel durchwühlt - auch da: Bei Daueraufträgen gibt’s keine Kontonummer. Die, die ich selbst irgendwann mal eingetippt hatte, wurde in bester Ministry-of-Truth-Manier aus der Weltgeschichte getilgt. Und das, obwohl die aktuellen AGB der Banken die Ausführung nur noch nach BLZ+Konto ausführen - Namen sind inzwischen nur noch völlig überflüssiges Beiwerk - nur BLZ und Kontonummer sind für die Ausführung verbindlich.
Im Ergebnis kann ich an Hand des Kontoauszugs nicht mal prüfen, an wen ich denn nun wirklich Geld überwiesen habe.
Ich wünsche mir eine Google-Bank. Und bis dahin: Nur Bares ist Wahres ![]()
Posted on March 6th, 2010 by Reiner.
Categories: Hibernate, Grails, English.
As outlined at Grails - How to Use Native Server Logging Configuration (e.g. Tomcat GlassFish JBoss), it may be advisable to remove any conflicting logging implementation when deploying a Grails application into a production environment. I’ll give a full source example on how to achieve this and how to work-around an issue which prevents a Grails 1.2.0 and 1.2.1 applications from starting.
Unfortunately, each server provides a distinct set of logging frameworks. Still worse, servers can be reconfigured to use alternate logging frameworks (e.g. JBoss can be configured to use Java Logging instead of its default Log4J implementation and Tomcat 6 can be configured to use Log4J instead of its default Java Logging implementation).
Thus you either have to prepare a war which has been set-up for a specific target environment or completely remove any logging implementation from your Grails war and add missing jars to the lib directory of your target server instead. Which one to choose may depend on whether access to the server configuration is permitted (e.g. its lib directory), how many different servers your application is to be deployed to and how hard it is for your build process to reliably produce variants specifically tailored for a particular deployment site.
Before presenting the code for a deployment to JBoss 5.1.0GA, let’s have a look at Grails’ logging architecture. In its infancy Grails just used Apache Commons Logging (JCL) to provide an abstraction level and Log4J to implement the actual logging. Although even now, all log instances injected into Grails applications still implement org.appache.commons.logging.Log, Grails no longer uses JCL, but meanwhile has moved on to SLF4J:
Starting with Grails 1.2, an additional bridge jul-to-slf4j which delegates Java Logging (Jul) to Slf4j is enabled by setting grails.logging.jul.usebridge = true. Be warned though, that for performance reasons this bridge should not be used when expecting heavy logging.
Now for the good news: With JBoss 5, all of the above (but excluding jul-to-slf4j) has already been integrated into the server configuration and libraries. In order to deploy a Grails application to JBoss 5, we’ll just need to get rid of the jars within your war and prevent Grails from trying to configure the logging. How to achieve this?
Within grails-app/conf/BuildConfig.groovy we’ll remove offending jars when building the war:
// TODO JBOSS - Remove own log4j and use the one supplied by JBoss instead
grails.war.resources = {stagingDir ->
def toRemove = [
"$stagingDir/WEB-INF/lib/log4j-1.2.14.jar", // log4j supplied by JBoss
"$stagingDir/WEB-INF/lib/log4j-1.2.15.jar", // log4j supplied by JBoss
"$stagingDir/WEB-INF/classes/log4j.properties", // logging conf done in JBoss only
"$stagingDir/WEB-INF/lib/slf4j-api-1.5.6.jar", // slf4j supplied by JBoss 5+
"$stagingDir/WEB-INF/lib/slf4j-api-1.5.8.jar", // slf4j supplied by JBoss 5+
"$stagingDir/WEB-INF/lib/slf4j-log4j12-1.5.6.jar", // slf4j supplied by JBoss 5+
"$stagingDir/WEB-INF/lib/slf4j-log4j12-1.5.8.jar", // slf4j supplied by JBoss 5+
"$stagingDir/WEB-INF/lib/jcl-over-slf4j-1.5.6.jar", // jcl supplied by JBoss as well
"$stagingDir/WEB-INF/lib/jcl-over-slf4j-1.5.8.jar", // jcl supplied by JBoss as well
// see also Config.grails.logging.jul.usebridge - shouldn't be used
// http://www.slf4j.org/legacy.html#jul-to-slf4j
// "$stagingDir/WEB-INF/lib/jul-to-slf4j-1.5.6.jar",
// "$stagingDir/WEB-INF/lib/jul-to-slf4j-1.5.8.jar",
// you might want to remove JDBC drivers when using server supplied JNDI...
// "$stagingDir/WEB-INF/lib/hsqldb-1.8.0.5.jar",
].each {
delete(file: it)
}
}
Within scripts/_Events.groovy we’ll disable Grails logging configuration components:
import groovy.xml.StreamingMarkupBuilder
/**
* TODO JBOSS - Remove log4j configuration stuff (when running with JBoss or GlassFish a.s.o)
*/
eventWebXmlEnd = {String tmpfile ->
def root = new XmlSlurper().parse(webXmlFile)
// When running with JBoss (or GlassFish a.s.o) remove log4j configuration stuff
def log4j = root.listener.findAll {node ->
node.'listener-class'.text() == 'org.codehaus.groovy.grails.web.util.Log4jConfigListener'
}
log4j.replaceNode {}
def log4jFile = root.'context-param'.findAll {node ->
node.'param-name'.text() == 'log4jConfigLocation'
}
log4jFile.replaceNode {}
webXmlFile.text = new StreamingMarkupBuilder().bind {
mkp.declareNamespace("": "http://java.sun.com/xml/ns/j2ee")
mkp.yield(root)
}
}
You are now set to control logging within JBoss jboss-log4j.xml.
If you’re using Grails 1.2.0 or 1.2.1, JBoss still refuses to deploy your war. This is caused by version conflicts within Hibernate jars (used to be work-arounded by deleting all Hibernate jars from JBoss lib directory) and they have been fixed by GRAILS-5606 for (not yet released) Grails 1.2.2.
Graeme Rocher as well supplies a work-around for 1.2.x:
Fixed in latest hibernate plugin 1.3.0.BUILD-SNAPSHOT
It seems in order to use the latest version of Hibernate on JBoss you must include Hibernate validator otherwise you run into this classloading error. This means the hibernate plugin is forced to include hibernate-validator as a dependency even if we don’t use it which is a pretty annoying.
Anyway you can fix this yourselves in 1.2.x by adding the following dependency to BuildConfig.groovy:
runtime('org.hibernate:hibernate-validator:3.1.0.GA') { excludes 'sl4j-api', 'hibernate.core', 'hibernate-commons-annotations', 'hibernate-entitymanager' }Be sure to uncomment the jboss maven repository so the dependency resolvers
This work-around is included within sample sources. Just search for TODO tags.
Links:
Posted on February 10th, 2010 by Reiner.
Categories: Maven, Grails, English.
A mavenized Grails project has 2 competing sources stating the version of the application
If 2. does not match 1. the grails-maven-plugin will abort the build, urging you to manually fix the version mismatch. Although somewhat clumsy, the release will still succeed if you edit application.properties and re-start the failing mvn release:prepare. However, you’re somewhat stuck, if the release is to be performed from within a continous integration server (e.g. Hudson).
Thus I devised a work-around, that will compare both versions and update the version within application.properties if required. My work-around is far from perfect, as I was unable to integrate it within the Maven lifecycle defined by the grails-maven-plugin. This lifecycle will always and unconditionally bind its own code to the validate phase first (aborting on version mismatches). Therefore I resorted to binding my repair action to the clean phase and changed the goals of the maven-release-plugin to perform a clean before invoking the deploy. Note that this will produce a consistent war (both pom and application.properties set to the release version), but will not resolve the discrepancy within the tagged sources (i.e. application.properties will still state the snapshot version).
Here’s my code. It uses a profile, which is only activated, if the file application.properties is actually present. Thus the code can be included within a parent pom.xml that might be used with non-Grails projects as well:
<profiles>
<profile>
<id>enable-reset.application.properties</id>
<activation>
<activeByDefault>false</activeByDefault>
<file>
<exists>application.properties</exists>
</file>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<!-- invoke clean first - see below -->
<goals>clean deploy</goals>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!--
Allow release:prepare/perform to succeed with Grails
app. Reset application.properties.app.version to
project.version Sorta hack on clean, as
grails-maven-plugin:validate ALWAYS runs first and
connot be preceeded by any other execution
-->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>reset.application.properties</id>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<taskdef resource="net/sf/antcontrib/antlib.xml"
classpathref="maven.plugin.classpath" />
<if>
<available file="application.properties" />
<then>
<property file="application.properties"
prefix="application.properties" />
<if>
<not>
<equals arg1="${project.version}"
arg2="${application.properties.app.version}" />
</not>
<then>
<propertyfile file="application.properties"
comment="Grails Metadata file">
<entry operation="=" type="string" key="app.version"
value="${project.version}" />
</propertyfile>
</then>
</if>
</then>
</if>
</tasks>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-nodeps</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>1.0b3</version>
<exclusions>
<exclusion>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Links:
Posted on February 7th, 2010 by Reiner.
Categories: Maven, English.
I want to share a misconception that caused Maven to fail when looking for artifacts. Im my example I was upgrading from Grails 1.2.0 to 1.2.1 and including a dependency to grails.org:grails-core:[1.2.1,1.2.1]. Maven would just complain with Couldn’t find a version in …, although I verified that this particular version is indeed available at the remote repository.
My misconception was, that when declaring a remote release repository <updatePolicy>never</updatePolicy> would only apply to existing artifacts, which - of course - need not be checked for remote changes (release artifacts must not change once they are released).
However, updatePolicy also applies to Maven meta data, i.e. which versions are available (and which one is the most current one). Thus, if there are any artifact versions already present in your local Maven repository, Maven will never download any version that is not yet present in your local repository
See also Maven does not always download artifacts from specified repos.
With release repositories, the default value for updatePolicy is <updatePolicy>daily</updatePolicy>, which should be appropriate for most external Maven release repositories.
As we’re using a local Nexus for both hosting our own company repositories and for proxying external repositories as well, updatePolicy daily is too slow to avoid confusion when accessing internal releases from the Public Repositories group. So I chose to use <updatePolicy>always</updatePolicy> instead and let Nexus settings determine the interval at which it refreshes external repository artifacts:
<repository> <!-- Use our local Nexus Public Repositories Group --> <id>public</id> <name>Public Repositories</name> <url>http://ournexus.mycompany.com:8081/nexus/content/groups/public</url> <releases> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository>
Posted on February 7th, 2010 by Reiner.
Categories: Grails, English.
Grails comes complete with logging out-of-the-box. However, if a Grails application is being deployed to a production servlet container, it may be advisable or even required for Grails to step aside and let the server admins do their jobs
Update: There’s a follow up that includes full source and a ready-to-deploy war. See How to Deploy a Grails Application to JBoss 5.
You need not avoid the loggers injected by Grails in order to use the native logging provided by servlet containers (e.g. Tomcat, GlassFish, JBoss). Grails loggers (always have implemented and now still) implement org.apache.commons.log.Log through jcl-over-slf4j delegating to some slf4j implementation adapter. You’re free to choose which logging system will ultimately perform the “real” logging. This is what we do:
Step 2 can be implemented within grails-app/BuildConfig.groovy similar to:
grails.war.resources = {stagingDir ->
def toRemove = [
"$stagingDir/WEB-INF/lib/log4j-1.2.14.jar", // Logging -> native GlassFish/Tomcat/JBoss
"$stagingDir/WEB-INF/lib/log4j-1.2.15.jar", // "
"$stagingDir/WEB-INF/classes/log4j.properties", // "
"$stagingDir/WEB-INF/lib/slf4j-log4j12-1.5.6.jar", // "
"$stagingDir/WEB-INF/lib/slf4j-log4j12-1.5.8.jar", // "
].each {
delete(file: it)
}
}
Step 3 can be achieved by altering the web.xml, either verbatim or by means of a grails-app/scripts/_Events.groovy which might looks similar to:
import groovy.xml.StreamingMarkupBuilder
eventWebXmlEnd = {String tmpfile ->
def root = new XmlSlurper().parse(webXmlFile)
// remove some log4j stuff
def log4j = root.listener.findAll {node ->
node.'listener-class'.text() == 'org.codehaus.groovy.grails.web.util.Log4jConfigListener'
}
log4j.replaceNode {}
def log4jFile = root.'context-param'.findAll {node ->
node.'param-name'.text() == 'log4jConfigLocation'
}
log4jFile.replaceNode {}
webXmlFile.text = new StreamingMarkupBuilder().bind {
mkp.declareNamespace("": "http://java.sun.com/xml/ns/j2ee")
mkp.yield(root)
}
}
Posted on February 6th, 2010 by Reiner.
Categories: Grails, English.
There are numerous articles describing charset problems when using Tomcat to run Grails applications that are supposed to accept UTF-8 characters (e.g. German umlauts äöüÄÖÜß). Most of them don’t mention a subtle cause that may jeopardize all your efforts to properly interpret UTF-8 form input. And fiddling around with Tomcat’s defaults was not an option for me, as the same Tomcat instance is to run other non-Grails legacy applications that rely on ISO-8859-1 being the default character set.
It took me some time to find out that Grails applications (1.2.0) behave just as expected under Tomcat (6.x) without any modification whatsoever, provided you don’t use Tomcat’s RequestDumperValve within your server.xml or context.xml.
<!-- Reiner: If you include this in your context.xml or server.xml,
form input will always be interpreted as ISO-8859-1
<Valve className=”org.apache.catalina.valves.RequestDumperValve”/>
–>
The reason for RequestDumperValve to garble form input is that it causes POST-parameters to be evaluated (using default ISO-8859-1) before Grails request filters have had a chance to set the request encoding to UTF-8. This behaviour is documented and RequestDumperValve is now deprecated due to its side effects.
The ultimate cause to this problem is that most browsers do not bother to state the character set that has been used to encode POST-parameters, thus leaving it up to the server to guess. Grails does this out-of-the-box within its applicationContext.xml:
<bean id="characterEncodingFilter"
class="org.springframework.web.filter.CharacterEncodingFilter">
<property name="encoding">
<value>utf-8</value>
</property>
</bean>
Credits: Ales at Grails User forum.
Posted on January 16th, 2010 by Reiner.
Categories: English, Computers.
Have a look at Google Trends to see who’s likely to survive
Ok, I’ll admit to be a Maven fan - because it’s the only one that has a holistic concept.
…that will never make it into the heads of people that attempt to reduce complexity to a hammer and all problems to the shape of a nail.
Posted on January 13th, 2010 by Reiner.
Categories: Hibernate, Java, Grails, English.
Working on a Grails project that needs to access a legacy database, it proved quite time-consuming to implement optimistic locking in a way that can be handled by Hibernate. Optimistic locking used to be implemented within (legacy) application code and should now be handled by Hibernate completely on its own. For each entity, the legacy schema uses two nullable columns to both trace creation and last updated times as well as to provide optimistic locking capabilities. TS_INSERT holds the Timestamp the entity has been created and TS_UPDATE (initially null) records subsequent mutations to an entity. Just disabling optimistic locking (as well being advertised here) keeps away run time errors but results in an application that cannot meet production quality demands without additional (awkward) application code.
Annotating the TS_UPDATE column with @Version almost did the trick, except it was unable to successfully update entities that had not yet been updated (TS_UPDATE is null). The reason for this behavior is burried within method toStatementString Hibernate Update- and Delete-Code that use TS_UPDATE=? as part of the sql (prepared) statements in order to check for matching old version values. Of course, this condition will always yield false for null values (remember: null = anything including null = null always yields false).
Short of changing Hibernate source code I designed a work-around, that effectively changes the TS_UPDATE=? fragment so that it yields true, even for null values. The work-around replaces the TS_UPDATE=? with COALESCE(TS_UPDATE,{ts ‘1900-01-01 00:00:00′})=COALESCE(?,{ts ‘1900-01-01 00:00:00′}). I use the COALESCE function, because it is specified by Ansi SQL 92 Standard and thus should be implemented by most database vendors.
Hibernate provides a rather sophisticated set of interceptor methods, that will be called (back) at certain life cycle stages. Using the onPrepareStatement event, arbitrary changes can be applied to the sql statements that are then sent to JDBC for execution. This is what my interceptor looks like (note that it looks for and… as the version condition will always be appended to the primary key condition - todo: this still needs to be verified for persisting collections):
package com.carano.fw.hibernate.util.centura;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.EmptyInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Fixes a shortcoming within Hibernate: Hibernate cannot handle NULL values
* within optimistic version columns. The where clause will always include
* e.g. TS_UPDATE=?. We'll try to fix this by changing the sql to e.g.
* COALESCE(TS_UPDATE,{ts '1900-01-01 00:00:00'})=COALESCE(?,{ts '1900-01-01 00:00:00'}),
* so that NULL values match.
*/
public class DBcenturaInterceptor extends EmptyInterceptor {
private static final String PS = "(and) (ts_update)=?";
private static final String RS = "$1 COALESCE($2,{ts '1900-01-01 00:00:00'})=COALESCE(?,{ts '1900-01-01 00:00:00'})";
private static final Pattern P = Pattern.compile(PS, Pattern.CASE_INSENSITIVE);
private final static Logger LOG = LoggerFactory.getLogger(DBcenturaInterceptor.class);
public DBcenturaInterceptor() {
super();
LOG.info("new DBcenturaInterceptor()");
}
/**
* @param sql the sql to be prepared
* @return and TS_UPDATE=? replaced by
* and COALESCE(TS_UPDATE,{ts '1900-01-01 00:00:00'})=COALESCE(?,{ts '1900-01-01 00:00:00'})
*/
@Override
public String onPrepareStatement(final String sql) {
final Matcher m = P.matcher(sql);
final String result = m.replaceAll(RS);
if (LOG.isTraceEnabled() && !result.equals(sql)) {
LOG.trace("onPrepareStatement(" + sql + ") = " + result);
}
return result;
}
}
When creating a session, an interceptor can by passed to the Hibernate SessionFactory.openSession(…). As my application is a Grails application, I choose to provide my Interceptor by passing it to the Grails Hibernate session factory, so my interceptor will be used for all and every Hibernate session created within my Grails application. Starting from Grails 1.2, just a single statement is needed within grails-app/conf/spring/resources.groovy:
import com.carano.fw.hibernate.util.centura.DBcenturaInterceptor
// Place your Spring DSL code here
beans = {
entityInterceptor(com.carano.fw.hibernate.util.centura.DBcenturaInterceptor)
}
For native Spring applications, have a look at Using a Hibernate Interceptor To Set Audit Trail Properties.
That’s all, take care and have fun ![]()
Posted on January 9th, 2010 by Reiner.
Categories: Hibernate, Java, English.
Working on a Grails project that needs to access a legacy database, I’ve used the Hibernate Tools for Eclipse and Ant to create annotated EJB3 entity classes.
As the database model uses some peculiar concepts (e.g. char(1) for booleans), I ‘ve created Hibernate custom types that properly map database types to Java types. The Hibernate Reveng Tool can be configured to map certain database columns to custom types, but it fails to create the @Type annotation that is required for custom user types. And as numerous reverse engineering cycle are about to be executed, I didn’t want to fix the generated sources by hand.
There already exists a bug for this problem, but there has been no attempt to fix it during the past two years
Thus I’ve designed a work-around and documented it at Issue HBX-849 - Tools does not insert @Type in POJOs for user types defined in reveng.xml ![]()