Tuesday, June 30, 2009

Setting Maven Properties With Groovy

Goal


While I generally try to configure, rather than script, Maven builds, sometimes the publicly-available plug-ins do not provide enough flexibility to work-around limitations in third-party libraries through configuration alone. Fortunately, GMaven exposes the flexibility of Groovy in a Maven plug-in. This post demonstrates how to use a Groovy script to transform a Maven project property. Such a transformation is sometimes necessary, for example, for transforming a Windows-style path to Unix. For myself, I formalized this solution while trying to install a jar in PostgreSQL automatically during the artifact deployment phase.

Display a Maven Project Property


We can start by creating a simple m2eclipse project, by adding a single property with a default value and by configuring the pom.xml to display this property on the console during a lifecycle event (here, during compilation).

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>timezra.blog.maven.groovy.properties</groupId>
  <artifactId>timezra.blog.maven.groovy.properties</artifactId>
  <name>timezra.blog.maven.groovy.properties</name>
  <version>0.0.1-SNAPSHOT</version>
  <description>An example of setting Maven properties using Groovy.</description>
  <properties>
    <unixy_build_directory>${project.build.directory}</unixy_build_directory>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>show-unixy_build_directory!</id>
            <phase>compile</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <echo>unixy_build_directory: ${unixy_build_directory}</echo>
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>



If we run the compile goal, we will see the bound Maven property in the build output.

  > mvn compile
  ....
  [INFO] [antrun:run {execution: show-unixy_build_directory!}]
  [INFO] Executing tasks
       [echo] unixy_build_directory: C:\programming\workspaces\blog\timezra.blog.maven.groovy.properties\target
  [INFO] Executed tasks
  ....


Setting up the gmaven-plugin is straightforward, as is re-binding the property with Groovy in the pom.xml.

<project ....>
  ....
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.groovy.maven</groupId>
        <artifactId>gmaven-plugin</artifactId>
        <executions>
          <execution>
            <id>set-unixy_build_directory!</id>
            <phase>compile</phase>
            <goals>
              <goal>execute</goal>
            </goals>
            <configuration>
              <classpath>
                <element>
                  <groupId>commons-lang</groupId>
                  <artifactId>commons-lang</artifactId>
                  <version>2.4</version>
                 </element>
              </classpath>
              <source>
                if (org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS) {
                  project.properties.unixy_build_directory =
                  project.build.directory.replace("\\", "/");
                }
              </source>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ....
    </plugins>
  </build>
</project>


NB: This example takes a Windows file path and transforms the backslashes to forward slashes. If you are not running Windows, then this example is moot. Please, experiment with the bindings in the pom.xml to demonstrate clearly to yourself that the property has really, really, really been re-bound.

When compiling, we will now see output indicating that the Groovy script has mutated the Maven property.

  > mvn compile
  ....
  [INFO] [antrun:run {execution: show-unixy_build_directory!}]
  [INFO] Executing tasks
       [echo] unixy_build_directory: C:/programming/workspaces/blog/timezra.blog.maven.groovy.properties/target
  [INFO] Executed tasks
  ....


Conclusion


With a few lines of configuration and a simple Groovy script, we are able to modify Maven properties as part of a lifecycle event. This tool adds even more power to your Maven builds, but, as stated above, it should be used cautiously and only when absolutely necessary. If there is a solution already in the Maven toolkit, then that is generally better. Not all project requirements fit in the box, however, and this example exposes a simple way to handle the non-ideal.

Thursday, June 25, 2009

Maven And Weblogic

Goal


This post is a follow-up to a previous entry on running Weblogic Ant tasks without calling setDomainEnv. The purpose is to perform the same actions from Maven. Fortunately, the weblogic-maven-plugin does not have the same requirement for setting environment variables from outside the build script. Unfortunately, development on the mojos appears to be a few versions behind the latest release of Weblogic. The code and examples here demonstrate how to use this Maven plug-in to run goals for deloying a WAR, building a web service skeleton and generating a web service client for Weblogic 10.3.

Listing the Applications Deployed to Weblogic


We can begin by creating an m2eclipse Maven project, timezra.blog.maven.weblogic, with the webapp-jee5 archetype. The Wizard materializes a simple hello world application that will be packaged as a WAR.

In order to list the applications deployed to Weblogic, we first need to setup a Weblogic domain if we do not already have one. For this example, we can create one called maven_example using the defaults provided by the Configuration Wizard.
There is already a good starting point for configuring the weblogic-maven-plugin in our pom.xml, but for 10.3 these instructions are incomplete. One problem becomes evident when we compose an Eclipse Run Configuration for the weblogic:listapps goal (being sure to use the External Maven 2.10 Runtime).
An Eclipse Run Configuration for the weblogic:listapps goal.

In our console, we would see a stack trace.


java.lang.NoClassDefFoundError: weblogic/utils/Debug
 at weblogic.Deployer.(Deployer.java:23)
 at org.codehaus.mojo.weblogic.DeployMojoBase.executeDeployer(DeployMojoBase.java:509)
 at org.codehaus.mojo.weblogic.ListAppsMojo.execute(ListAppsMojo.java:51)
 at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:483)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:678)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeStandaloneGoal(DefaultLifecycleExecutor.java:553)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:523)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:371)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:332)
 at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:181)
 at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:356)
 at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:137)
 at org.apache.maven.cli.MavenCli.main(MavenCli.java:356)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
 at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
 at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
 at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
Caused by: java.lang.ClassNotFoundException: weblogic.utils.Debug
 at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at org.codehaus.classworlds.RealmClassLoader.loadClassDirect(RealmClassLoader.java:195)
 at org.codehaus.classworlds.DefaultClassRealm.loadClass(DefaultClassRealm.java:255)
 at org.codehaus.classworlds.DefaultClassRealm.loadClass(DefaultClassRealm.java:274)
 at org.codehaus.classworlds.RealmClassLoader.loadClass(RealmClassLoader.java:214)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
 ... 21 more



We must manually install a few libraries in addition to those the Wiki entry lists into our local Maven repository.


  mvn install:install-file -DgroupId=weblogic -DartifactId=weblogic -Dversion=10.3 -Dpackaging=jar -Dfile=C:\webservers\bea\wlserver_10.3\server\lib\weblogic.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=webservices -Dversion=10.3 -Dpackaging=jar -Dfile=C:\webservers\bea\wlserver_10.3\server\lib\webservices.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.rmi.client -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.rmi.client_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=javax.enterprise.deploy -Dversion=1.2 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\javax.enterprise.deploy_1.2.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.management.core -Dversion=2.3.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.management.core_2.3.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.security.identity -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.security.identity_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.security -Dversion=1.0.0.0_5-0-2-0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.security_1.0.0.0_5-0-2-0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.security.wls -Dversion=1.0.0.0_5-0-2-0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.security.wls_1.0.0.0_5-0-2-0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.workmanager -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.workmanager_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.transaction -Dversion=2.5.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.transaction_2.5.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.logging -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.logging_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.utils.classloaders -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.utils.classloaders_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.timers -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.timers_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.socket.api -Dversion=1.0.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.socket.api_1.0.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.common.security.api -Dversion=1.0.0.0_5-0-2-0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.common.security.api_1.0.0.0_5-0-2-0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.security.digest -Dversion=1.0.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.security.digest_1.0.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.lifecycle -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.lifecycle_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.workarea -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.workarea_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.utils.wrapper -Dversion=1.3.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.utils.wrapper_1.3.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.store -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.store_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.management.jmx -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.management.jmx_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor.wl -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor.wl_1.1.0.0.jar



We will configure the plug-in to depend on these and other publicly available libraries in our project pom.xml.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>timezra.blog.maven.weblogic</groupId>
    <artifactId>timezra.blog.maven.weblogic</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>timezra.blog.maven.weblogic JEE5 Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>weblogic-maven-plugin</artifactId>
                <version>2.9.2-SNAPSHOT</version>
                <configuration>
                    <adminServerHostName>localhost</adminServerHostName>
                    <adminServerPort>7001</adminServerPort>
                    <adminServerProtocol>t3</adminServerProtocol>
                    <userId>weblogic</userId>
                    <password>weblogic</password>
                    <upload>false</upload>
                    <remote>false</remote>
                    <verbose>false</verbose>
                    <debug>false</debug>
                    <targetNames>AdminServer</targetNames>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.utils.full</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.i18n</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.rmi.client</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>javax.enterprise.deploy</artifactId>
                        <version>1.2</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.management.core</artifactId>
                        <version>2.3.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.security.wls</artifactId>
                        <version>1.0.0.0_5-0-2-0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.security</artifactId>
                        <version>1.0.0.0_5-0-2-0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.security.identity</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.workmanager</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.transaction</artifactId>
                        <version>2.5.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.logging</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.utils.classloaders</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.timers</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.socket.api</artifactId>
                        <version>1.0.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.common.security.api</artifactId>
                        <version>1.0.0.0_5-0-2-0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.security.digest</artifactId>
                        <version>1.0.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.lifecycle</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.workarea</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.utils.wrapper</artifactId>
                        <version>1.3.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>javax.transaction</groupId>
                        <artifactId>jta</artifactId>
                        <version>1.1</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.store</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.management.jmx</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor.wl</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
        <finalName>timezra.blog.maven.weblogic</finalName>
    </build>
    <pluginRepositories>
        <pluginRepository>
            <id>Maven Snapshots</id>
            <url>http://snapshots.repository.codehaus.org/</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>
</project>


The output from our Eclipse weblogic:listapps launcher should now show

  There is no application to list.


Deploying a WAR


Our Maven archetype has already configured the packaging for our application, and building the WAR is not Weblogic-specific. Deploying to the AdminServer is, however.
If we were to create another Eclipse Run Configuration, here called full_deploy, which calls the package and deploy goals, we would see an error.


[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to configure plugin parameters for: org.apache.maven.plugins:maven-deploy-plugin:2.4

check that the following section of the pom.xml is present and correct:

<distributionManagement>
  <!-- use the following if you're not using a snapshot version. -->
  <repository>
    <id>repo</id>
    <name>Repository Name</name>
    <url>scp://host/path/to/repo</url>
  </repository>
  <!-- use the following if you ARE using a snapshot version. -->
  <snapshotRepository>
    <id>repo</id>
    <name>Repository Name</name>
    <url>scp://host/path/to/repo</url>
  </snapshotRepository>
</distributionManagement>


For this example, it is sufficient for the distribution manager to point to our local repository and to hook into the weblogic:deploy goal in our pom.xml.

<project ....>
    ....
    <build>
        <plugins>
            ....
            <plugin>
                <executions>
                    <execution>
                        <id>deploy.to.weblogic</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                </executions>
                <groupId>org.codehaus.mojo</groupId>
                ....
            </plugin>
        </plugins>
    </build>
    ....
    <distributionManagement>
        <snapshotRepository>
            <id>localRepository</id>
            <name>Local Repository</name>
            <url>file://${HOMEDRIVE}/Docume~1/${USERNAME}/.m2/repository</url>
          </snapshotRepository>
    </distributionManagement>
</project>



If you are developing on Windows, at this point you may see a misleading error about an incorrect protocol.


[INFO] Weblogic Deployment parameters [-adminurl, t3://localhost:7001, -username, weblogic, -password, weblogic, -name, timezra.blog.maven.weblogic, -targets, AdminServer, -source, C:\programming\workspaces\blog\timezra.blog.maven.weblogic\target/timezra.blog.maven.weblogic.war, -deploy]
weblogic.Deployer invoked with options: -adminurl t3://localhost:7001 -username weblogic -name timezra.blog.maven.weblogic -targets AdminServer -source C:\programming\workspaces\blog\timezra.blog.maven.weblogic\target/timezra.blog.maven.weblogic.war -deploy
<Jun 24, 2009 4:36:08 PM PDT> <Info> <J2EE Deployment SPI> <BEA-260121> <Initiating deploy operation for application, timezra.blog.maven.weblogic [archive: C:\programming\workspaces\blog\timezra.blog.maven.weblogic\target\timezra.blog.maven.weblogic.war], to AdminServer .>
no protocol: and



The problem is that weblogic-maven-plugin goals do not properly handle spaces in the classpath. The solution is simply to eliminate these spaces in the ~/$USER/.m2/settings.xml file.

<settings>
    <localRepository>${HOMEDRIVE}/Docume~1/${USERNAME}/.m2/repository</localRepository>
</settings>



The output from our Eclipse weblogic:listapps launcher should now show


   timezra.blog.maven.weblogic
  Number of Applications Found : 1


If we open http://localhost:7001/timezra.blog.maven.weblogic/ in a browser, we will see Hello World!

Compile A Web Service


The trial-and-error process for deploying a Web Service is very similar to publishing our packaged WAR: setup project dependencies, implement the service in Java, call the weblogic:jwsc goal, work out any unresolved dependencies and deploy the application.
Our web service will depend on annotations from the javax.jws library, which is provided by Weblogic. We can install this JAR, as above.


  mvn install:install-file -DgroupId=weblogic -DartifactId=javax.jws -Dversion=2.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\javax.jws_2.0.jar



We will configure this project dependency in our pom.xml.

<project ....>
    ....
    <dependencies>
        ....
        <dependency>
            <groupId>weblogic</groupId>
            <artifactId>javax.jws</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    ....
</project>



We can write an echo service with interface /src/main/java/timezra/blog/maven/weblogic/ws/IEcho.java, that simply resonates the client's input.

package timezra.blog.maven.weblogic.ws;

public interface IEcho {
    String echo(final String holla);
}



We will implement the web service as described in a previous post in /src/main/java/timezra/blog/maven/weblogic/ws/Echo.java.

package timezra.blog.maven.weblogic.ws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.WebParam.Mode;
import javax.jws.soap.SOAPBinding;

@WebService(name = "Echo", targetNamespace = Echo.NAMESPACE, serviceName = "Echo")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class Echo implements IEcho {
    static final String NAMESPACE = "http://timezra.blog.maven.weblogic";
    private static final String ELLIPSIS = " ... ";

    @WebMethod(operationName = "echo")
    @WebResult(name = "holla_back", targetNamespace = NAMESPACE)
    public String echo(@WebParam(name = "holla", targetNamespace = NAMESPACE, mode = Mode.IN) final String holla) {
        if (holla == null) {
            return null;
        }
        final String[] what = holla.split("\\s");
        final StringBuilder echo = new StringBuilder(what[0]);
        for (int i = 1; i < what.length; i++) {
            echo.append(' ');
            echo.append(what[i]);
        }
        final String theLastWord = what[what.length - 1];
        echo.append(ELLIPSIS);
        echo.append(theLastWord);
        echo.append(ELLIPSIS);
        echo.append(theLastWord);
        return echo.toString();
    }
}



The weblogic:jwsc goal has dependencies on more Weblogic modules.


  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.annogen -Dversion=1.2.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.annogen_1.2.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor.j2ee.binding -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor.j2ee.binding_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.xml.staxb.runtime -Dversion=1.3.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.xml.staxb.runtime_1.3.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.xml.beaxmlbeans -Dversion=1.0.0.0_2-4-0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.xml.beaxmlbeans_1.0.0.0_2-4-0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor.j2ee -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor.j2ee_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=javax.ejb -Dversion=3.0.1 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\javax.ejb_3.0.1.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.xml.xmlbeans -Dversion=1.0.0.0_2-4-0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.xml.xmlbeans_1.0.0.0_2-4-0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.stax -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.stax_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=javax.xml.rpc -Dversion=1.2.1 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\javax.xml.rpc_1.2.1.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.xml.staxb.buildtime -Dversion=1.3.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.xml.staxb.buildtime_1.3.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=glassfish.jaxws.rt -Dversion=2.1.3 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\glassfish.jaxws.rt_2.1.3.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor.wl.binding -Dversion=1.1.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor.wl.binding_1.1.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.descriptor.settable.binding -Dversion=1.4.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.descriptor.settable.binding_1.4.0.0.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=com.bea.core.weblogic.saaj -Dversion=1.3.0.0 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\com.bea.core.weblogic.saaj_1.3.0.0.jar



We will add these plug-in dependencies to the pom.xml along with configuration parameters specific to weblogic:jwsc.

<project ....>
    ....
    <build>
        <plugins>
            ....
            <plugin>
                ....
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>weblogic-maven-plugin</artifactId>
                <version>2.9.2-SNAPSHOT</version>
                <configuration>
                    ....
                    <outputName>${project.artifactId}</outputName>
                    <contextPath>${project.artifactId}</contextPath>
                    <descriptor>${basedir}/src/main/webapp/WEB-INF/web.xml</descriptor>
                </configuration>
                <dependencies>
                    ....
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.annogen</artifactId>
                        <version>1.2.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor.j2ee.binding</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.xml.staxb.runtime</artifactId>
                        <version>1.3.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.xml.beaxmlbeans</artifactId>
                        <version>1.0.0.0_2-4-0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor.j2ee</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                          <groupId>com.sun</groupId>
                          <artifactId>tools</artifactId>
                          <version>1.6.0</version>
                          <scope>system</scope>
                          <systemPath>${java.home}/../lib/tools.jar</systemPath>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>javax.ejb</artifactId>
                        <version>3.0.1</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.xml.xmlbeans</artifactId>
                        <version>1.0.0.0_2-4-0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.stax</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>javax.xml.rpc</artifactId>
                        <version>1.2.1</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.xml.staxb.buildtime</artifactId>
                        <version>1.3.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>glassfish.jaxws.rt</artifactId>
                        <version>2.1.3</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>javax.mail</groupId>
                        <artifactId>mail</artifactId>
                        <version>1.4.1</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor.wl.binding</artifactId>
                        <version>1.1.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.descriptor.settable.binding</artifactId>
                        <version>1.4.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>com.bea.core.weblogic.saaj</artifactId>
                        <version>1.3.0.0</version>
                        <scope>provided</scope>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
        ....
    </build>
    ....
</project>



Now we can run a new Eclipse launcher that calls weblogic:jwsc in the same way that we setup launchers for weblogic:listapps. We see that the web service artifacts are correctly generated in the target directory, and the URL for the new web service is correctly merged into the target/timezra.blog.maven.weblogic/WEB-INF/web.xml. Unfortunately, if we package and deploy the application, this merging of information in the web.xml will be overwritten with the contents of src/main/webapp/WEB-INF/web.xml (Try it if you do not believe me, if you did not follow or if you would like to discover for yourself why this is the case!). Fortunately, there is a way to automate the generation of web service and the correct packaging of the artifacts and compiled sources with some extra configuration in the pom.xml.

<project ....>
    ....
    <build>
        <plugins>
            ....
            <plugin>
                <executions>
                    <execution>
                        <id>generate.web.services</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jwsc</goal>
                        </goals>
                    </execution>
                    ....
                </executions>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>weblogic-maven-plugin</artifactId>
                <version>2.9.2-SNAPSHOT</version>
                ....
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1-beta-1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>war</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        ....
    </build>
    ....
</project>



We can view the published WSDL in a browser at address http://localhost:7001/timezra.blog.maven.weblogic/Echo?WSDL and we can use our favorite web service client application (here, I use soapUI) to run a few tests.
Holla-ing to the Echo Web Service through soapUI.

Generate a Web Service Client


Now that we have a deployment infrastructure and a working web service, we can create a client for that service. The current version of the weblogic-maven-plugin (2.9.2-SNAPSHOT) supports two goals for generating a web service client, weblogic:clientgen and weblogic:clientgen9. Unfortunately, neither works with Weblogic 10. If we were to call weblogic:clientgen9, for example, we would see an error similar to this:


[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Internal error in the plugin manager executing goal 'org.codehaus.mojo:weblogic-maven-plugin:2.9.2-SNAPSHOT:clientgen9': Unable to find the mojo 'clientgen9' (or one of its required components) in the plugin 'org.codehaus.mojo:weblogic-maven-plugin'
(class: org/codehaus/mojo/weblogic/ClientGen9Mojo, method: execute signature: ()V) Incompatible object argument for function call
[INFO] ------------------------------------------------------------------------



The only remedy I have seen to this issue is to modify the weblogic-maven-plugin and to install it in a local repository. After importing the project into Eclipse, we can simply apply this patch to the project root.


Index: pom.xml
===================================================================
--- pom.xml (revision 10022)
+++ pom.xml (working copy)
@@ -11,7 +11,7 @@
<artifactId>weblogic-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<name>Weblogic Maven Plugin</name>
- <version>2.9.2-SNAPSHOT</version>
+ <version>2.9.3-SNAPSHOT</version>
<inceptionYear>2005</inceptionYear>
<description>
This plugin will support various tasks within the Weblogic 8.1
@@ -95,6 +95,12 @@
<artifactId>webservices</artifactId>
<version>[9.0,11.0)</version>
</dependency>
+ <dependency>
+ <groupId>weblogic</groupId>
+ <artifactId>com.bea.core.utils.full</artifactId>
+ <version>1.4.0.0</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-plugin-testing-harness</artifactId>
@@ -126,4 +132,11 @@
</plugin>
</plugins>
</reporting>
+ <distributionManagement>
+ <snapshotRepository>
+ <id>localRepository</id>
+ <name>Local Repository</name>
+ <url>file://${HOMEDRIVE}/Docume~1/${USERNAME}/.m2/repository</url>
+ </snapshotRepository>
+ </distributionManagement>
</project>
\ No newline at end of file
Index: .settings/org.eclipse.jdt.core.prefs
===================================================================
--- .settings/org.eclipse.jdt.core.prefs (revision 0)
+++ .settings/org.eclipse.jdt.core.prefs (revision 0)
@@ -0,0 +1,5 @@
+#Sat Jun 20 17:00:42 PDT 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.source=1.3
Index: .settings/org.maven.ide.eclipse.prefs
===================================================================
--- .settings/org.maven.ide.eclipse.prefs (revision 0)
+++ .settings/org.maven.ide.eclipse.prefs (revision 0)
@@ -0,0 +1,9 @@
+#Sat Jun 20 17:00:25 PDT 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
Index: src/main/java/org/codehaus/mojo/weblogic/ClientGen10Mojo.java
===================================================================
--- src/main/java/org/codehaus/mojo/weblogic/ClientGen10Mojo.java (revision 0)
+++ src/main/java/org/codehaus/mojo/weblogic/ClientGen10Mojo.java (revision 0)
@@ -0,0 +1,402 @@
+package org.codehaus.mojo.weblogic;
+
+/*
+ * Copyright 2008 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.Path;
+import org.codehaus.mojo.weblogic.util.WeblogicMojoUtilities;
+import weblogic.wsee.tools.anttasks.ClientGenTask;
+
+import java.io.File;
+
+/**
+ * Runs Client Gen on a given WSDL. This client gen uses the BEA refactored client gen tool
+ * first appearing in weblogic 9. This is the preferred client gen tool for Weblogic 9.0 and
+ * newer.
+ *
+ * @author <a href="mailto:josborn@belltracy.com">Jon Osborn</a>
+ * @version $Id: ClientGen10Mojo.java 8652 2009-01-13 01:13:28Z jonnio $
+ * @description This mojo will run client gen on a given WSDL. This client gen uses the BEA refactored client gen tool
+ * first appearing in weblogic 9. This is the preferred client gen tool for Weblogic 10.0 and newer.
+ * @goal clientgen10
+ * @requiresDependencyResolution compile
+ */
+public class ClientGen10Mojo
+ extends AbstractWeblogicMojo
+{
+
+ /**
+ * The filename of the war file to find the services. The file path is
+ * extracted from the artifact list.
+ *
+ * @parameter
+ */
+ private String warFileName;
+
+ /**
+ * The wsdl to client gen from. If warFileName is specified, this parameter
+ * is the root relative file to use when creating the URI for the wsdl.
+ *
+ * @parameter
+ */
+ private String inputWSDL;
+
+ /**
+ * The directory to output the generated code to.
+ *
+ * @parameter default-value="${basedir}/src/main/java"
+ */
+ private String outputDir;
+
+ /**
+ * The package name of the output code.
+ *
+ * @parameter default-value="com.test.webservice"
+ */
+ private String packageName;
+
+ /**
+ * The name of the service.
+ *
+ * @parameter
+ */
+ private String serviceName;
+
+ /**
+ * Output verbose messages
+ *
+ * @parameter default-value="false"
+ */
+ private boolean verbose;
+
+ /**
+ * Whether or not to use server types from the ear file in the client jar.
+ *
+ * @parameter default-value="false"
+ */
+ private boolean useServerTypes;
+
+ /**
+ * Sets whether or not to create the type conversions for a web service in
+ * the client.
+ *
+ * @parameter default-value="true"
+ */
+ private boolean autotype;
+
+ /**
+ * Sets whether or not to use the jaxRPCWrappedArrayStyle
+ *
+ * @parameter default-value="true"
+ */
+ private boolean jaxRPCWrappedArrayStyle;
+
+ /**
+ * This method will run client gen on the given WSDL.
+ *
+ * @throws MojoExecutionException Thrown if we fail to obtain the WSDL.
+ */
+ public void execute()
+ throws MojoExecutionException
+ {
+ super.execute();
+
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( "Weblogic client gen beginning " );
+ }
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( " Detailed client gen settings information " + this.toString() );
+ }
+
+ try
+ {
+ final ClientGenTask clientGen = new ClientGenTask();
+ // Set the classpath
+ final Project project = new Project();
+ project.setName( "clientgen" );
+ final Path path = new Path( project, WeblogicMojoUtilities
+ .getDependencies( this.getArtifacts(), this.getPluginArtifacts() ) );
+ clientGen.setProject( project );
+ clientGen.setClasspath( path );
+ clientGen.setVerbose( this.verbose );
+ clientGen.setDestDir( new File( this.outputDir ) );
+ clientGen.setPackageName( this.packageName );
+ clientGen.setIncludeGlobalTypes( this.useServerTypes );
+ clientGen.setJaxRPCWrappedArrayStyle( this.jaxRPCWrappedArrayStyle );
+ String wsdlUri;
+ if ( this.warFileName != null )
+ {
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info(
+ " calculating wsdl URI from warFileName " + this.warFileName + " with wsdl " + this.inputWSDL );
+ }
+ wsdlUri = "jar:file:" + WeblogicMojoUtilities.getWarFileName( this.getArtifacts(), this.warFileName ) +
+ "!" + this.inputWSDL;
+ new File( this.inputWSDL ).toURI().toString();
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( " using " + wsdlUri + " for clientgen." );
+ }
+ }
+ else if ( this.inputWSDL.startsWith( "http" ) )
+ {
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( " using " + this.inputWSDL + " for clientgen." );
+ }
+ wsdlUri = this.inputWSDL;
+ }
+ else
+ {
+ wsdlUri = new File( this.inputWSDL ).toURI().toString();
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( " using " + wsdlUri + " for clientgen." );
+ }
+ }
+ clientGen.setWsdl( wsdlUri );
+ // set the service name if it is specified
+ if ( this.serviceName != null )
+ {
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( " generating client for service '" + this.serviceName + "'." );
+ }
+ clientGen.setServiceName( this.serviceName );
+ }
+ clientGen.execute();
+ }
+ catch ( Exception ex )
+ {
+ getLog().error( "Exception encountered during client gen", ex );
+ throw new MojoExecutionException( "Exception encountered during listapps", ex );
+ }
+ finally
+ {
+ WeblogicMojoUtilities.unsetWeblogicProtocolHandler();
+ }
+
+ if ( getLog().isInfoEnabled() )
+ {
+ getLog().info( "Weblogic client gen successful " );
+ }
+ }
+
+ /**
+ * Getter for property input WSDL.
+ *
+ * @return The value of input WSDL.
+ */
+ public String getInputWSDL()
+ {
+ return this.inputWSDL;
+ }
+
+ /**
+ * Setter for the input WSDL.
+ *
+ * @param inInputWSDL The value of input WSDL.
+ */
+ public void setInputWSDL( final String inInputWSDL )
+ {
+ this.inputWSDL = inInputWSDL;
+ }
+
+ /**
+ * Getter for property output dir.
+ *
+ * @return The value of output dir.
+ */
+ public String getOutputDir()
+ {
+ return this.outputDir;
+ }
+
+ /**
+ * Setter for the output dir.
+ *
+ * @param inOutputDir The value of output dir.
+ */
+ public void setOutputDir( final String inOutputDir )
+ {
+ this.outputDir = inOutputDir;
+ }
+
+ /**
+ * Getter for property package name.
+ *
+ * @return The value of package name.
+ */
+ public String getPackageName()
+ {
+ return this.packageName;
+ }
+
+ /**
+ * Setter for the package name.
+ *
+ * @param inPackageName The value of package name.
+ */
+ public void setPackageName( String inPackageName )
+ {
+ this.packageName = inPackageName;
+ }
+
+ /**
+ * Getter for property service name.
+ *
+ * @return The value of service name.
+ */
+ public String getServiceName()
+ {
+ return this.serviceName;
+ }
+
+ /**
+ * Setter for the service name.
+ *
+ * @param inServiceName The value of service name.
+ */
+ public void setServiceName( final String inServiceName )
+ {
+ this.serviceName = inServiceName;
+ }
+
+ /**
+ * toString method: creates a String representation of the object
+ *
+ * @return the String representation
+ */
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append( "ClientGen10Mojo[" );
+ buffer.append( "inputWSDL = " ).append( inputWSDL );
+ buffer.append( ", outputDir = " ).append( outputDir );
+ buffer.append( ", packageName = " ).append( packageName );
+ buffer.append( ", serviceName = " ).append( serviceName );
+ buffer.append( ", useServerTypes = " ).append( useServerTypes );
+ buffer.append( ", autotype = " ).append( autotype );
+ buffer.append( "]" );
+ return buffer.toString();
+ }
+
+ /**
+ * Getter for server types
+ *
+ * @return true if the client gen should use server type information
+ */
+ public boolean isUseServerTypes()
+ {
+ return useServerTypes;
+ }
+
+ /**
+ * Setter for server types
+ *
+ * @param useServerTypes - true if the client gen should use server types
+ */
+ public void setUseServerTypes( boolean useServerTypes )
+ {
+ this.useServerTypes = useServerTypes;
+ }
+
+ /**
+ * Getter for verbose messages
+ *
+ * @return true if the client gen should use verbose output
+ */
+ public boolean isVerbose()
+ {
+ return this.verbose;
+ }
+
+ /**
+ * Setter for verbose messages
+ *
+ * @param verbose - true of the clientgen should use verbose output
+ */
+ public void setVerbose( boolean verbose )
+ {
+ this.verbose = verbose;
+ }
+
+
+ /**
+ * Getter for autoType
+ *
+ * @return true if clientgen shoud autotype from the wsdl
+ */
+ public boolean isAutotype()
+ {
+ return this.autotype;
+ }
+
+ /**
+ * Setter for autoType
+ *
+ * @param autotype - true if the client should autotype
+ */
+ public void setAutotype( boolean autotype )
+ {
+ this.autotype = autotype;
+ }
+
+ /**
+ * Getter for warFileName
+ *
+ * @return the warFileName
+ */
+ public String getWarFileName()
+ {
+ return this.warFileName;
+ }
+
+ /**
+ * Setter for warFileName
+ *
+ * @param warFileName - the warFileName to set
+ */
+ public void setWarFileName( String warFileName )
+ {
+ this.warFileName = warFileName;
+ }
+
+ /**
+ * Getter for jaxRPCWrappedArrayStyle
+ *
+ * @return the jaxRPCWrappedArrayStyle
+ */
+ public boolean isJaxRPCWrappedArrayStyle()
+ {
+ return jaxRPCWrappedArrayStyle;
+ }
+
+ /**
+ * Setter for jaxRPCWrappedArrayStyle
+ *
+ * @param jaxRPCWrappedArrayStyle the jaxRPCWrappedArrayStyle to set
+ */
+ public void setJaxRPCWrappedArrayStyle( boolean jaxRPCWrappedArrayStyle )
+ {
+ this.jaxRPCWrappedArrayStyle = jaxRPCWrappedArrayStyle;
+ }
+}
Index: src/main/java/org/codehaus/mojo/weblogic/util/WeblogicMojoUtilities.java
===================================================================
--- src/main/java/org/codehaus/mojo/weblogic/util/WeblogicMojoUtilities.java (revision 10022)
+++ src/main/java/org/codehaus/mojo/weblogic/util/WeblogicMojoUtilities.java (working copy)
@@ -48,7 +48,7 @@
{
if ( "weblogic.utils".equals(System.getProperty("java.protocol.handler.pkgs") ) )
{
- System.setProperty( "java.protocol.handler.pkgs", null );
+ System.clearProperty( "java.protocol.handler.pkgs" );
}
}

Index: src/test/java/org/codehaus/mojo/weblogic/SimpleGoalMojoTest.java
===================================================================
--- src/test/java/org/codehaus/mojo/weblogic/SimpleGoalMojoTest.java (revision 10022)
+++ src/test/java/org/codehaus/mojo/weblogic/SimpleGoalMojoTest.java (working copy)
@@ -128,6 +128,21 @@
}

/**
+ * Test that we can find the deploy goal
+ *
+ * @throws Exception when the lookup fails
+ * @see org.codehaus.mojo.weblogic.ClientGen10Mojo
+ */
+ public void testMojoClientGen10Goal()
+ throws Exception
+ {
+
+ final ClientGen10Mojo mojo = (ClientGen10Mojo) lookupMojo( "clientgen10", this.testPom );
+
+ assertNotNull( mojo );
+ }
+
+ /**
* Test an invalid goal to be sure the valid ones are 'real'.
*
* @throws Exception - throws exception when something fails.



NB: This patch has been submitted as a bug report. Please comment or vote on this bug to get it pushed through.
We will deploy this update to the plug-in (here, with version number 2.9.3-SNAPSHOT) by simply running the goal as Maven install from Eclipse.

Now that we have a working client generation goal in our updated and installed plug-in, again, we will need to register a few more Weblogic libraries for our project.


  mvn install:install-file -DgroupId=weblogic -DartifactId=javax.jms -Dversion=1.1.1 -Dpackaging=jar -Dfile=C:\webservers\bea\modules\javax.jms_1.1.1.jar
  mvn install:install-file -DgroupId=weblogic -DartifactId=wseeclient -Dversion=10.3 -Dpackaging=jar -Dfile=C:\webservers\bea\wlserver_10.3\server\lib\wseeclient.jar



We can add these new project and plug-in dependencies, along with necessary configuration parameters to the pom.xml. Generally, I do not automate client generation, as published WSDLs infrequently change. You may have different project requirements, however.

<project ....>
    ....
    <dependencies>
        ....
        <dependency>
            <groupId>weblogic</groupId>
            <artifactId>wseeclient</artifactId>
            <version>10.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>weblogic</groupId>
            <artifactId>javax.xml.rpc</artifactId>
            <version>1.2.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            ....
            <plugin>
                ....
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>weblogic-maven-plugin</artifactId>
                <version>2.9.3-SNAPSHOT</version>
                <configuration>
                    ....
                    <inputWSDL>http://localhost:7001/timezra.blog.maven.weblogic/Echo?WSDL</inputWSDL>
                    <packageName>timezra.blog.maven.weblogic.ws.client</packageName>
                    <serviceName>Echo</serviceName>
                </configuration>
                <dependencies>
                    ....
                    <dependency>
                        <groupId>weblogic</groupId>
                        <artifactId>javax.jms</artifactId>
                        <version>1.1.1</version>
                        <scope>provided</scope>
                    </dependency>
                </dependencies>
            </plugin>
            ....
        </plugins>
        ....
    </build>
    ....
</project>



Our business component, src/main/java/timezra/blog/maven/weblogic/service/EchoService.java invokes the generated client.

package timezra.blog.maven.weblogic.service;

import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import timezra.blog.maven.weblogic.ws.client.Echo_Service;
import timezra.blog.maven.weblogic.ws.client.Echo_Service_Impl;

public class EchoService {

    public String say(final String phrase) {
        try {
            final Echo_Service echoService = new Echo_Service_Impl();
            return echoService.getEchoSoapPort().echo(phrase);
        } catch (final RemoteException e) {
            return "Putter, ka-chunk, piff.";
        } catch (final ServiceException e) {
            return "Gurgle, burble, glug.";
        }
    }
}



We will need a way to mediate the client invokation and the display of the response, so we will create a src/main/java/timezra/blog/maven/weblogic/controller/IsAnybodyThere.java controller.

package timezra.blog.maven.weblogic.controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import timezra.blog.maven.weblogic.service.EchoService;

public class IsAnybodyThere extends HttpServlet {

    private static final long serialVersionUID = -8205256394812150098L;

    @Override
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
        final String echo = new EchoService().say("Is there anybody in there?");
        req.setAttribute("echo", echo);
        req.getRequestDispatcher("echo.jsp").forward(req, resp);
    }
}



Our view, src/main/webapp/echo.jsp, will simply show the result of calling the service.

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>${echo}</h1>
    </body>
</html>



Finally, we will register our view in the web descriptor, web.xml.

....
<web-app ....>
    ....
    <servlet>
        <servlet-name>IsAnybodyThere</servlet-name>
        <servlet-class>timezra.blog.maven.weblogic.controller.IsAnybodyThere</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>IsAnybodyThere</servlet-name>
        <url-pattern>/IsAnybodyThere</url-pattern>
    </servlet-mapping>
    ....
    </web-app>



Now, when we run full_deploy and open http://localhost:7001/timezra.blog.maven.weblogic/IsAnybodyThere in a browser, we will see the message "Is there anybody in there? ... there? ... there?".

Conclusion


While it certainly would have been possible to hook the Maven Ant plugin to the build script presented in a previous post for executing tasks that disguise the need to run the setDomainEnv script, this post provides a different approach through the existing weblogic-maven-plugin and thus eliminates the need for manipulating setDomainEnv entirely with a manageable amount of configuration in the pom.xml.

A Maven project that compiles a web service, generates a web service client, and deploys a WAR to Weblogic.

Tuesday, May 26, 2009

Evicting the Hibernate Query Cache With Spring

Goal


This tutorial builds on the example presented in a previous post. Here we will explore the difference between the Hibernate second-level cache and the standard query cache and how the two can be used in conjunction to reduce the number of database transactions in an application.

The Hibernate Second-level Cache


We will begin with a quick review of the behavior of the Hibernate second-level cache. The results displayed on the http://localhost:8080/timezra.blog.hibernate.cache/books.htm page should look familiar if you have coded along with the hibernate cache eviction example.

We can pick right up where we left off and update our sample book.

  psql -U hibernate_cache -d hibernate_cache
  Update book Set subtitle = 'A Problem-Solution Approach' Where isbn_13 = 9781590599792;


After we follow the link for Spring Recipes, we will see that the subtitle of the book has not been updated. As demonstrated in the previous post, the query is persisting the individual Books in the second-level cache, so this result should not surprise us.

If we reopen or refresh books.htm and follow the link for Spring Recipes again, the subtitle of the book reflects the current state of the database. This result should raise an eyebrow. It appears that the transaction to find all the books has been re-run in the database and Hibernate has updated the individual items from the query's result in the second-level cache. Even though Hibernate stores individual Books, it does not store the result of the query itself, so all the Books are reloaded and persisted again.

Cache the Query Results


Caching individual domain objects has eliminated some unneccessary database traffic, but in this situation, we can optimize even more. Our database is updated exactly once at night, so we should not need a new transaction every time a user views the list of books.
In the Hibernate properties of application-context-daos.xml, we will enable the query cache.

....
<property name="hibernateProperties">
    <props>
        ....
        <prop key="hibernate.cache.use_query_cache">true</prop>
    </props>
</property>
....



We must also configure Book's @NamedQuery "findAllBooks" to store its results in the standard query cache.


....
@NamedQueries( {
        @NamedQuery(name = "findAllBooks", query = "from Book", hints = { @javax.persistence.QueryHint(name = "org.hibernate.cacheable", value = "true") }),
        @NamedQuery(name = "findByIsbn13", query = "from Book book where book.isbn13 = :vIsbn13") })
....



NB: Rather than naming our queries with the javax.persistence annotations, we could just as well have used org.hibernate.annotations, in which case our declaration would not require @QueryHints.


....
@org.hibernate.annotations.NamedQueries( {
        @org.hibernate.annotations.NamedQuery(name = "findAllBooks", query = "from Book", cacheable = true),
        @org.hibernate.annotations.NamedQuery(name = "findByIsbn13", query = "from Book book where book.isbn13 = :vIsbn13") })
....



NB: Rather than configuring the @NamedQuery with @QueryHints, we could also enable caching in the BookDAO invocation of the query itself.


    ....
    public Collection<Book> findAll() {
        return sessionFactory.getCurrentSession() //
                .getNamedQuery("findAllBooks") //
                .setCacheable(true) //
                .list();
    }
    ....



With any of these configurations, we can again run our test in the browser.
  • view all the books
  • select a single book
  • update the book in the database
  • view all the books again
  • select the same individual book

The book now does not reflect the most current state of the database and clearly comes from the cache. This is the behavior that we are seeking.

Flush the Query Cache


From the previous tutorial, our Hibernate second-level cache is cleared every minute on the zeroth second. If we run the tests described above and wait long enough, we will eventually see an update to the book's subtitle. Suppose, however, we delete a book from the database.

  Delete From book Where isbn_13 = 9781590599792;


If we allow the second-level cache to expire and refresh the list of all books, we will see an error.


org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [timezra.blog.hibernate.cache.domain.Book#9781590599792]
    at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:409)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:171)
    at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:223)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:126)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:905)
    at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:873)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:590)
    at org.hibernate.type.ManyToOneType.assemble(ManyToOneType.java:219)
    at org.hibernate.cache.StandardQueryCache.get(StandardQueryCache.java:155)
    at org.hibernate.loader.Loader.getResultFromQueryCache(Loader.java:2184)
    at org.hibernate.loader.Loader.listUsingQueryCache(Loader.java:2147)
    at org.hibernate.loader.Loader.list(Loader.java:2117)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:401)
    at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:361)
    at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1148)
    at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
    at timezra.blog.hibernate.cache.dao.BookDAO.findAll(BookDAO.java:25)
    at sun.reflect.GeneratedMethodAccessor23.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy31.findAll(Unknown Source)
    at timezra.blog.hibernate.cache.controller.Books.showAllBooks(Books.java:24)
....(more)


The persisted query results now reference the primary key for an invalid row that cannot be refreshed. Of course, since the query cache expires every 120 seconds per the default configuration in ehcache.xml, if we wait another minute, the error will no longer appear and we will simply see an empty table of books.

In order to avoid these types of stale or non-existent results, we can modify EvictTheSecondLevelCache.java to flush the standard query cache.

....
    @Override
    protected void executeInternal(final JobExecutionContext context) throws JobExecutionException {
        sessionFactory.evictQueries();
        sessionFactory.evict(Book.class);
    }
    ....


NB: We have chosen to evict the query cache before the second-level cache. If the two lines were reversed, we would still run the risk, however small, of encountering the org.hibernate.ObjectNotFoundException described above.

Here we are evicting all queries in the default org.hibernate.cache.StandardQueryCache. If we want to be selective about the cached queries that should be flushed, SessionFactory#sessionFactory.evictQueries(...) also takes the name of a cache region, which we can declare as a @QueryHint in Book.java just as we have configured cacheability (or we could set another attribute on the @org.hibernate.annotations.NamedQuery, or we could set the cache region when the query is called directly in the BookDAO).

....
@NamedQueries( {
        @NamedQuery(name = "findAllBooks", query = "from Book", hints = {
                @javax.persistence.QueryHint(name = "org.hibernate.cacheable", value = "true"),
                @javax.persistence.QueryHint(name = "org.hibernate.cacheRegion", value = "findAllBooks") }),
        @NamedQuery(name = "findByIsbn13", query = "from Book book where book.isbn13 = :vIsbn13"
        ) })
....



Cache the Query But Not the Domain


We now have three configurations for the two caches:
  • neither cache is enabled
  • only the second-level cache is enabled
  • both caches are enabled

What is the result if we enable the standard query cache without storing domain objects?
We can explore this scenario by simply removing the query hint from the @NamedQuery to disable the domain store. We will then insert a record into the database.

  Insert Into book (author, title, isbn_13) Values ('Craig Walls', 'Spring in Action', 9781933988139);


After we view all the books in a browser, we will update the record.

  Update book Set title = 'Sprig in Acton' Where isbn_13 = 9781933988139;


The new title appears on the refreshed books.htm page. Even though Hibernate has stored the query results, because it has not saved the domain objects themselves, they have been refreshed by their primary keys from the database. Our particular set of books is rather small, but suppose we have a larger data set. The first time the query to find all books runs, there is exactly one transaction. For subsequent requests, until the query cache is cleared, Hibernate refreshes each Book individually by its ISBN. Clearly there is no performance benefit for us to cache only query results without domain objects in this scenario. In fact, this misconfiguration could cause a significant performance loss far worse than having no cache at all.

Conclusion


By expanding the infrastructure introduced in the previous example, we have further optimized our application's database transactions by caching not only individual objects, but also the result sets of queries. We can now also clear this store of query results at a fixed regular time coordinated with the eviction of the second-level cache.

Friday, May 22, 2009

Evicting the Hibernate Domain Cache With Spring

Goal


This tutorial presents an exploration of the Hibernate second-level cache. By the end, we will have the tools to integrate Ehcache into a web application and to expire its entries at fixed, regular intervals using the Quartz scheduler to run a Spring batch process.

Setup a Database


For this example, we can setup a PostgreSQL database, as in the previous example. Here, our database, user and password can all be set to hibernate_cache.


  psql -U postgres -c "CREATE DATABASE hibernate_cache" -d template1
  psql -U postgres -c "CREATE USER hibernate_cache WITH PASSWORD 'hibernate_cache'" -d hibernate_cache
  psql -U postgres -c "grant all privileges on database hibernate_cache to hibernate_cache" -d hibernate_cache



A Maven2 Web Application


We can create our timezra.blog.hibernate.cache project with the m2eclipse plugin. Instead of setting up a simple project as in a previous post, we will use the maven-archetype-webapp from the Nexus Indexer catalog. We will also add src/main/java, src/test/resources and src/test/java explicitly to our build path since we naturally want to test our project as we add features incrementally. For web applications, Jetty provides a Maven plugin that scans our target path and automatically re-deploys any changes. The Jetty plugin can be configured in the <build> section of our pom.xml.

<project....>
    ....
    <build>
        ....
        <plugins>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <configuration>
                    <scanIntervalSeconds>5</scanIntervalSeconds>
                    <reload>automatic</reload>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>



We can start Jetty from a command prompt.


  mvn jetty:run


The page http://localhost:8080/timezra.blog.hibernate.cache/ will be available through a browser and we should see a "Hello World!" message.

A Core Sample from the Database to the UI


We are now ready to add Hibernate, Ehcache, Spring, Quartz and test-related dependencies. Our fully-configured pom.xml should look familiar to anyone who has gone through both the batch processing and Hibernate tutorials, with a few additions specifically for web development and for emitting Java6-compliant code from our compiler during the build.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>timezra.blog.hibernate.cache</groupId>
    <artifactId>timezra.blog.hibernate.cache</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>timezra.blog.hibernate.cache Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.context</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.context.support</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.web.servlet</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.transaction</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.orm</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-annotations</artifactId>
            <version>3.4.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.4.GA</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>1.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.opensymphony.quartz</groupId>
            <artifactId>quartz</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.3-603.jdbc4</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <scope>test</scope>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
        </dependency>
        <dependency>
            <scope>test</scope>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.test</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>timezra.blog.hibernate.cache</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <configuration>
                    <scanIntervalSeconds>5</scanIntervalSeconds>
                    <reload>automatic</reload>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - External Bundle Milestones</id>
            <url>http://repository.springsource.com/maven/bundles/milestone</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/release</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - External Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/external</url>
        </repository>
    </repositories>
</project>



Since we will be using Spring MVC for our web framework, we must configure the src/main/webapp/WEB-INF/web.xml to route all page requests with an htm extension through the Spring Front Controller.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
>
    <display-name>Archetype Created Web Application</display-name>
    <servlet>
        <servlet-name>timezra.blog.hibernate.cache</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>timezra.blog.hibernate.cache</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
</web-app>



We must not necessarily declare the application contextConfigLocation in the web.xml since the Spring MVC convention of locating a file with the servlet name and a -servlet.xml suffix will suffice. Our src/main/webapp/WEB-INF/timezra.blog.hibernate.cache-servlet.xml can simply indicate where the view pages will reside.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd"
>
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>



Suppose we have the following user story:

As a customer,
I want to browse through a list of titles,
So that I can find out who wrote a particular book.


We can interact with our domain through a data access component, an IBookDAO that has two functions: one for retrieving all the books, and another for retrieving a single book based on its key.

package timezra.blog.hibernate.cache.dao;

import java.util.Collection;
import timezra.blog.hibernate.cache.domain.Book;

public interface IBookDAO {
    Collection<Book> findAll();
    Book findByIsbn13(final long isbn13);
}



Our domain needs just one object, a Book, and it will have attributes for the title, author and a unique value for the primary key, which here can be an isbn. We can also specify the HQL queries for accessing this object.

package timezra.blog.hibernate.cache.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "BOOK", schema = "")
@NamedQueries( { @NamedQuery(name = "findAllBooks", query = "from Book"),
        @NamedQuery(name = "findByIsbn13", query = "from Book book where book.isbn13 = :vIsbn13") })
public class Book implements java.io.Serializable {

    private static final long serialVersionUID = 7081749995516354985L;
    private long isbn13;
    private String title;
    private String subtitle;
    private String author;

    @Id
    @Column(name = "ISBN_13", nullable = false)
    public long getIsbn13() {
        return isbn13;
    }

    public void setIsbn13(final long isbn13) {
        this.isbn13 = isbn13;
    }

    @Column(name = "TITLE", nullable = false, length = 50)
    public String getTitle() {
        return title;
    }

    public void setTitle(final String title) {
        this.title = title;
    }

    @Column(name = "SUBTITLE", nullable = true, length = 150)
    public String getSubtitle() {
        return subtitle;
    }

    public void setSubtitle(final String subtitle) {
        this.subtitle = subtitle;
    }

    @Column(name = "AUTHOR", nullable = false, length = 50)
    public String getAuthor() {
        return author;
    }

    public void setAuthor(final String author) {
        this.author = author;
    }
}



In the src/main/resources/hibernate.cfg.xml, we will register the Book.

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <mapping class="timezra.blog.hibernate.cache.domain.Book" />
    </session-factory>
</hibernate-configuration>




Our BookDAO can simply use the @NamedQueries declared on Book to implement its functions.

package timezra.blog.hibernate.cache.dao;

import java.util.Collection;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import timezra.blog.hibernate.cache.domain.Book;

@Repository
public class BookDAO implements IBookDAO {

    private final SessionFactory sessionFactory;

    @Autowired
    public BookDAO(final SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public Collection<Book> findAll() {
        return sessionFactory.getCurrentSession() //
                .getNamedQuery("findAllBooks") //
                .list();
    }

    @Transactional
    public Book findByIsbn13(final long isbn13) {
        return (Book) sessionFactory.getCurrentSession() //
                .getNamedQuery("findByIsbn13") //
                .setParameter("vIsbn13", isbn13) //
                .uniqueResult();
    }
}


NB: We have annotated the DAO as a @Resource. Instead of declaring this Spring bean explicitly, we merely need to configure Spring to scan a set of packages to discover the components to use as beans.

Because the web application context is outside the classpath, we can delegate Spring registration of our DAOs to a classpath resource. We will import the application-context-daos.xml in the timezra.blog.hibernate.cache-servlet.xml.

....
<beans....>
    <import resource="classpath:application-context-daos.xml"/>
    ....
</beans>



The src/main/resources/application-context-daos.xml file contains the database connection configuration.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd"
>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="username">
            <value>hibernate_cache</value>
        </property>
        <property name="password">
            <value>hibernate_cache</value>
        </property>
        <property name="url">
            <value>jdbc:postgresql://localhost:5432/hibernate_cache</value>
        </property>
    </bean>
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.generate_statistics">true</prop>
                <prop key="hibernate.use_sql_comments">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
                <prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>
            </props>
        </property>
    </bean>
    <context:component-scan base-package="timezra.blog.hibernate.cache.dao"/>
    <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    <tx:annotation-driven transaction-manager="txManager" />
</beans>


NB: We have not declared our DAO explicitly because we have configured Spring to scan for components in the package that contains our @Resource.

Now that we have a domain and a way to access the domain, we can write some tests in the BookDAOTest suite to ensure that our functions work.

package timezra.blog.hibernate.cache.dao;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import timezra.blog.hibernate.cache.domain.Book;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/application-context-daos.xml" })
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
@Transactional
public class BookDAOTest {

    @Autowired private SessionFactory sessionFactory;
    @Autowired private IBookDAO bookDAO;
    private Book hibernateMadeEasy;
    private Book harnessingHibernate;
    private Book javaPersistenceWithHibernate;

    @Before
    public void setUp() {
        final Session session = sessionFactory.getCurrentSession();
        session.save(getOrCreateHibernateMadeEasy());
        session.save(getOrCreateJavaPersistenceWithHibernate());
        session.save(getOrCreateHarnessingHibernate());
        session.flush();
    }

    @Test
    public void findAllReturnsAllTheBooks() throws Exception {
        final Collection<Book> books = bookDAO.findAll();

        assertTrue(books.contains(getOrCreateHibernateMadeEasy()));
        assertTrue(books.contains(getOrCreateHarnessingHibernate()));
        assertTrue(books.contains(getOrCreateJavaPersistenceWithHibernate()));
    }

    @Test
    public void findByIsbn13ReturnsTheBook() throws Exception {
        final Book actual = bookDAO.findByIsbn13(9780596517724L);
        assertSame(getOrCreateHarnessingHibernate(), actual);
    }

    private Book getOrCreateHibernateMadeEasy() {
        if (hibernateMadeEasy != null) {
            return hibernateMadeEasy;
        }
        hibernateMadeEasy = new Book();
        hibernateMadeEasy.setAuthor("Cameron McKenzie");
        hibernateMadeEasy.setIsbn13(9780615201955L);
        hibernateMadeEasy.setTitle("Hibernate Made Easy");
        hibernateMadeEasy
                .setSubtitle("Simplified Data Persistence with Hibernate and JPA (Java Persistence API) Annotations");
        return hibernateMadeEasy;
    }

    private Book getOrCreateHarnessingHibernate() {
        if (harnessingHibernate != null) {
            return harnessingHibernate;
        }
        harnessingHibernate = new Book();
        harnessingHibernate.setAuthor("James Elliott");
        harnessingHibernate.setIsbn13(9780596517724L);
        harnessingHibernate.setTitle("Harnessing Hibernate");
        return harnessingHibernate;
    }

    private Book getOrCreateJavaPersistenceWithHibernate() {
        if (javaPersistenceWithHibernate != null) {
            return javaPersistenceWithHibernate;
        }
        javaPersistenceWithHibernate = new Book();
        javaPersistenceWithHibernate.setAuthor("Christian Bauer");
        javaPersistenceWithHibernate.setIsbn13(9781932394887L);
        javaPersistenceWithHibernate.setTitle("Java Persistence with Hibernate");
        return javaPersistenceWithHibernate;
    }
}


NB: Because the application-context-daos.xml is on the classpath, Spring will locate the context for the tests and inject the dependencies.

Finally, we can create a view into the database for each of the functions of the user story. One controller called Books can handle both requests.

package timezra.blog.hibernate.cache.controller;

import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import timezra.blog.hibernate.cache.dao.IBookDAO;

@Controller
public class Books {

    private final IBookDAO bookDAO;

    @Autowired
    public Books(final IBookDAO bookDAO) {
        this.bookDAO = bookDAO;
    }

    @RequestMapping("/books.htm")
    public ModelAndView showAllBooks() throws Exception {
        return new ModelAndView("books", "books", bookDAO.findAll());
    }

    @RequestMapping("/book.htm")
    public ModelAndView showABook(final HttpServletRequest request) throws Exception {
        return new ModelAndView("book", "book", bookDAO.findByIsbn13(Long.valueOf(request.getParameter("isbn13"))));
    }
}



Note how Books is registered as a @Controller, just as BookDAO is a @Resource. We will simply register the controller's package in our timezra.blog.hibernate.cache-servlet.xml and Spring will locate the component.

....
<beans....>
    ....
    <context:component-scan base-package="timezra.blog.hibernate.cache.controller"/>
</beans>



The first request, which takes no parameters, provides a view of all the titles in the database. We can call this view src/main/webapp/WEB-INF/jsp/books.jsp.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>All The Books</title>
</head>
<body>
  <h1>All The Books</h1>
  <table>
    <tr><th>ISBN 13</th><th>Title</th></tr>
    <c:forEach items="${books}" var="book">
      <tr>
        <td><a href="<c:url value='/book.htm'/>?isbn13=<c:out value='${book.isbn13}' />"><c:out value="${book.isbn13}" /></a></td>
        <td><c:out value="${book.title}" /></td>
      </tr>
    </c:forEach>
  </table>
</body>
</html>



The second request provides a view of the details of a single book, which is located by its isbn-13. This view is src/main/webapp/WEB-INF/jsp/book.jsp.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>A Book</title>
</head>
<body>
  <h1>${book.title}<c:if test="${not empty book.subtitle}">:&nbsp;<c:out value="${book.subtitle}" /></c:if></h1><br/>
  Author: <c:out value="${book.author}" /><br/>
  ISBN 13: <c:out value="${book.isbn13}" /><br/>
</body>
</html>



The page http://localhost:8080/timezra.blog.hibernate.cache/books.htm will be available through a browser and should display an empty table of information. If we were to begin adding books to our database, we would see this table fill and we could navigate to individual book detail pages.


  psql -U hibernate_cache -d hibernate_cache
  Insert Into book (author, subtitle, title, isbn_13)
        values ('Gary Mak', 'A Problem-Solution Approach (Books for Professionals by Professionals)', 'Spring Recipes', 9781590599792);



Cache For Books


Suppose an external process updates our database of books once nightly, but we expect that customers will browse for books frequently during the day. To reduce database overhead, we can cache the book information on the application server. We will expire the cache only after the database has been updated.

The Hibernate primary cache is used automatically for transaction-level optimization. Therefore, a domain object should only be retrieved once for each full database transaction, even if it is referenced more than once.
Hibernate second-level persistence, by contrast, is SessionFactory-wide, so the information about the object is available across multiple transactions. This second-level store is not automatic and must be explicitly configured. We can register the Book as a candidate for such a cache with a class-level annotation.

....
@org.hibernate.annotations.Cache(usage = org.hibernate.annotations.CacheConcurrencyStrategy.READ_ONLY)
....



For this example, we will use Ehcache, which requires an external configuration, here called src/main/resources/ehcache.xml. We will declare default settings as well as properties for individual domain objects. We can store the Books forever because we will expire the cache periodically through a batch process.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd">
    <diskStore path="java.io.tmpdir" />
    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true"
        diskPersistent="false" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU" />
    <cache 
        name="timezra.blog.hibernate.cache.domain.Book"
        maxElementsInMemory="100" 
        eternal="true" 
        overflowToDisk="false" />
</ehcache>



Finally, we can update the Hibernate properties in the application-context-daos.xml.

....
<property name="hibernateProperties">
    <props>
        ....
        <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
        <prop key="hibernate.cache.use_second_level_cache">true</prop>
        <prop key="hibernate.cache.provider_configuration">classpath:ehcache.xml</prop>
    </props>
</property>
....



Now, we will view our book through the browser. None of the information will have changed, as expected.

Suppose we update the book through psql.


  Update book Set author = 'Mak, Gary' Where isbn_13 = 9781590599792;



Now that the second-level cache is available and our domain object has been registered, I would expect that the information on the page will not have changed. When we refresh the browser, however, the author's name is updated. This indicates that, even though the @NamedQuery uses HQL and the query does a lookup by the Book's primary key, the result is not taken from the second-level store, and a database transaction has occurred. The cache has provided no benefit.

Suppose we change BookDAO#findByIsbn13(...) to query by criteria.

    ....
    @Transactional
    public Book findByIsbn13(final long isbn13) {
        return (Book) sessionFactory.getCurrentSession() //
                .createCriteria(Book.class) //
                .add(org.hibernate.criterion.Restrictions.eq("isbn13", isbn13)) //
                .uniqueResult();
    }
    ....



Again, we will view the book through the browser, and again we will modify the data.


  Update book Set author = 'Gary Mak' Where isbn_13 = 9781590599792;



When we refresh the browser, the data will have changed to reflect the current state in the database. This behavior indicates that, despite very simple criteria, i.e., primary key equality, Hibernate is bypassing the store and directly querying the database.

Suppose we change BookDAO#findByIsbn13(...) to get the Book directly from the current session.

    @Transactional
    public Book findByIsbn13(final long isbn13) {
        return (Book) sessionFactory.getCurrentSession().get(Book.class, isbn13);
    }



Finally, when we view the data in the browser, update the author through psql, and refresh the browser again, we will see the expected caching behavior.
NB: We also could have used SessionFactory#load(...), but this method returns a proxy to the actual Book instance. We would need to resolve this within a transactional context, e.g., by calling an accessor method for one of the non-primary-key fields in the DAO method. Also, even if the Book does not exist in the cache, #load(...) will return a proxy, and we will see an error when Hibernate tries to resolve it. For our needs, SessionFactory#get(...) is sufficient.

Evict the Cache


Now that our caching behavior works as anticipated, we will use a Spring batch process with a Quartz timer to evict the cache at a specific time. In this type of scenario, the job might run during a period of low traffic, perhaps in the middle of the night. For our example, we will expire the cache every minute for fast feedback.

For anyone who has worked through the previous tutorial on Spring batch processing, there should be no surprises in this code.
We will first create a job implementation, EvictTheSecondLevelCache, that removes all the Books from the SessionFactory's store.

package timezra.blog.hibernate.cache.batch;

import org.hibernate.SessionFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.StatefulJob;
import org.springframework.scheduling.quartz.QuartzJobBean;
import timezra.blog.hibernate.cache.domain.Book;

public class EvictTheSecondLevelCache extends QuartzJobBean implements StatefulJob {

    private SessionFactory sessionFactory;

    @Override
    protected void executeInternal(final JobExecutionContext context) throws JobExecutionException {
        sessionFactory.evict(Book.class);
    }

    public void setSessionFactory(final SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}



We can register and schedule the job with a new Spring context file, src/main/resources/application-context-batch.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
>
    <import resource="classpath:application-context-daos.xml"/>
    <bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass" value="timezra.blog.hibernate.cache.batch.EvictTheSecondLevelCache" />
        <property name="jobDataAsMap">
            <map>
                <entry key="sessionFactory">
                    <ref bean="sessionFactory" />
                </entry>
            </map>
        </property>
    </bean>
    <bean id="jobDetailTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="jobDetail" />
        <property name="cronExpression" value="0 * * * * ?" />
    </bean>
    <bean id="schedulerFactoryBean"
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="jobDetailTrigger" />
            </list>
        </property>
    </bean>
</beans>


NB: To inject the sessionFactory declared in application-context-daos.xml, we must import that resource, as well.

Finally, we will register the new configuration with the general web application context, timezra.blog.hibernate.cache-servlet.xml, so that the job begins automatically when the web application starts.

....
<beans....>
    <import resource="classpath:application-context-batch.xml"/>
    ....
</beans>



Now, we can load our book details in the browser, update the author through psql, reload the page, and the data should not have changed. If we wait until the cache is invalidated (here, one or two minutes) and refresh the page, the data will reflect the current state of the table.

Conclusion


With a few kilobytes of custom code, we have a project infrastructure for unit tests, a domain, a data access layer, views into our data, a batch process and a cache that will make expanding the application with more tests, a larger domain, more sophisticated queries, caching and page flows straightforward, at least from a technical perspective. Gathering user stories and prioritizing them by potential ROI is another topic entirely and one that must be handled as a conversation between you and your product owner.

The Maven2 standard webapp project structure.

Coming Soon: The Hibernate query cache. . . .

Wednesday, May 13, 2009

Mapping Hibernate Entities to Views

Goal


By the end of this tutorial, we will have the tools to map a Hibernate Entity that does not correspond directly to a database table onto structures such as views or query results.

Setup a PostgreSQL Database


This tutorial breaks from the pattern of previous posts in this series by using PostgreSQL instead of Oracle. The PostgreSQL default one-click installation is sufficient for the example. On Windows, I found it necessary to remove whitespace from the default installation path, and adding the </path/to/postgresql>/bin to the system Path variable has been helpful.

For this example, we can create a new database called examples:

  psql -U postgres -c "CREATE DATABASE examples" -d template1


We can also create a new database user and assign full privileges:

  psql -U postgres -c "CREATE USER hibernate_query WITH PASSWORD 'hibernate_query'" -d examples
  psql -U postgres -c "grant all privileges on database examples to hibernate_query" -d examples


Create an M2Eclipse Project


Creating a simple M2Eclipse project without an archetype should be familiar if you have followed previous posts in this series. Note that, for our example, we will configure our pom.xml for Spring 3.0.0.M3 and for the postgresql 8.3-603.jdbc4 driver. Unlike in prior examples, no extra Maven2 setup of the Oracle JDBC driver is necessary since the PostgreSQL driver is distributed directly from the Maven2 repository.
Our pom.xml will look similar to this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>examples.hibernate.spring.query</groupId>
    <artifactId>examples.hibernate.spring.query</artifactId>
    <name>examples.hibernate.spring.query</name>
    <version>0.0.1-SNAPSHOT</version>
    <description>Hibernate Query Example</description>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.transaction</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.orm</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-annotations</artifactId>
            <version>3.4.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.4.GA</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.3-603.jdbc4</version>
        </dependency>
        <dependency>
            <scope>test</scope>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
        </dependency>
        <dependency>
            <scope>test</scope>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.test</artifactId>
            <version>3.0.0.M3</version>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - External Bundle Milestones</id>
            <url>http://repository.springsource.com/maven/bundles/milestone</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/release</url>
        </repository>
        <repository>
            <id>SpringSource Enterprise Bundle Repository - External Bundle Releases</id>
            <url>http://repository.springsource.com/maven/bundles/external</url>
        </repository>
    </repositories>
</project>



Similarly, our Spring src/main/resources/applicationContext.xml must also be configured for a PostgreSQL, rather than Oracle, connection:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd"
>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="username">
            <value>hibernate_query</value>
        </property>
        <property name="password">
            <value>hibernate_query</value>
        </property>
        <property name="url">
            <value>jdbc:postgresql://localhost:5432/examples</value>
        </property>
    </bean>
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/hibernate.cfg.xml" />
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.show_sql">true</prop>