initial version of support project with build support.
authorErik Brakkee <erik@brakkee.org>
Thu, 9 Mar 2006 10:09:56 +0000 (10:09 +0000)
committerErik Brakkee <erik@brakkee.org>
Thu, 9 Mar 2006 10:09:56 +0000 (10:09 +0000)
54 files changed:
build.xml [new file with mode: 0644]
build/delegator.xml [new file with mode: 0644]
build/header.xml [new file with mode: 0644]
build/trailer.xml [new file with mode: 0644]
conf/properties/log4j.properties [new file with mode: 0644]
download-deps.xml [new file with mode: 0644]
forrest.properties [new file with mode: 0644]
lib/ant/ant-contrib-1.0b2/README.txt [new file with mode: 0644]
lib/ant/ant-contrib-1.0b2/ant-contrib.jar [new file with mode: 0644]
lib/ant/ant-dependencies.jar [new file with mode: 0644]
lib/special/hibernate/antlr-2.7.5H3.jar [new file with mode: 0644]
lib/special/hibernate/asm-attrs.jar [new file with mode: 0644]
lib/special/hibernate/asm.jar [new file with mode: 0644]
lib/special/test/jdbc2_0-stdext.jar [new file with mode: 0644]
lib/special/test/jdbc2_0-stdext.licence.txt [new file with mode: 0644]
lib/special/test/jta.jar [new file with mode: 0644]
lib/special/test/jta.licence.txt [new file with mode: 0644]
src/org/wamblee/Pair.java [new file with mode: 0644]
src/org/wamblee/cache/Cache.java [new file with mode: 0644]
src/org/wamblee/cache/CachedObject.java [new file with mode: 0644]
src/org/wamblee/cache/EhCache.java [new file with mode: 0644]
src/org/wamblee/cache/ForeverCache.java [new file with mode: 0644]
src/org/wamblee/cache/ZeroCache.java [new file with mode: 0644]
src/org/wamblee/concurrency/JvmLock.java [new file with mode: 0644]
src/org/wamblee/concurrency/Lock.java [new file with mode: 0644]
src/org/wamblee/concurrency/LockAdvice.java [new file with mode: 0644]
src/org/wamblee/concurrency/ReadWriteLock.java [new file with mode: 0644]
src/org/wamblee/general/BeanFactory.java [new file with mode: 0644]
src/org/wamblee/general/BeanFactoryException.java [new file with mode: 0644]
src/org/wamblee/general/BeanKernel.java [new file with mode: 0644]
src/org/wamblee/general/SpringBeanFactory.java [new file with mode: 0644]
src/org/wamblee/io/ClassPathResource.java [new file with mode: 0644]
src/org/wamblee/io/FileResource.java [new file with mode: 0644]
src/org/wamblee/io/InputResource.java [new file with mode: 0644]
src/org/wamblee/io/StreamResource.java [new file with mode: 0644]
src/org/wamblee/observer/DefaultObserverNotifier.java [new file with mode: 0644]
src/org/wamblee/observer/Observable.java [new file with mode: 0644]
src/org/wamblee/observer/Observer.java [new file with mode: 0644]
src/org/wamblee/observer/ObserverNotifier.java [new file with mode: 0644]
src/org/wamblee/persistence/AbstractPersistent.java [new file with mode: 0644]
src/org/wamblee/persistence/Persistent.java [new file with mode: 0644]
src/org/wamblee/persistence/hibernate/HibernateMappingFiles.java [new file with mode: 0644]
src/org/wamblee/persistence/hibernate/HibernateSupport.java [new file with mode: 0644]
test/org/wamblee/concurrency/ReadWriteLockTest.java [new file with mode: 0644]
test/org/wamblee/observer/ObservableTest.java [new file with mode: 0644]
test/org/wamblee/test/HibernateExporter.java [new file with mode: 0644]
test/org/wamblee/test/HibernateUpdater.java [new file with mode: 0644]
test/org/wamblee/test/HibernateUtils.java [new file with mode: 0644]
test/org/wamblee/test/SpringConfigFiles.java [new file with mode: 0644]
test/org/wamblee/test/SpringTestCase.java [new file with mode: 0644]
test/org/wamblee/test/TestSpringBeanFactory.java [new file with mode: 0644]
test/org/wamblee/test/TestSupport.java [new file with mode: 0644]
test/org/wamblee/test/TestTransactionCallback.java [new file with mode: 0644]
test/org/wamblee/test/TestTransactionCallbackWithoutResult.java [new file with mode: 0644]

diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..d2fdcf3
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+    <!ENTITY header SYSTEM "file:build/header.xml">
+    <!ENTITY trailer SYSTEM "file:build/trailer.xml">
+]>
+
+<project name="support" default="jar" basedir=".">
+
+
+       <!-- =============================================================================== -->
+       <!-- Include the build header defining general properties                            -->
+       <!-- =============================================================================== -->
+    <property name="project.home" value="."/>
+    <property name="module.name" value="support" />
+
+   &header;
+       
+       <target name="module.build.deps" 
+         depends="logging.d,dom4j.d,xerces.d,spring.d">
+       </target>
+       
+       <!-- Set libraries to use in addition for test, a library which 
+                            is already mentioned in module.build.path should not be 
+                            mentioned below again --> 
+       <target name="module.test.deps" depends="hibernate.standalone.d">
+       </target>
+                       
+  &trailer; 
+  
+  
+</project>
diff --git a/build/delegator.xml b/build/delegator.xml
new file mode 100644 (file)
index 0000000..b14dc90
--- /dev/null
@@ -0,0 +1,193 @@
+
+
+    <!--  =============================================================
+      This build file provides delegation to a number of other
+      projects. The projects must be defined in a projects 
+      property which contains a comma-separated list of directories
+      to which we must delegate. 
+      This build file should be included from another build file 
+      which needs to delegate to other targets. 
+      ================================================================= -->
+    
+       <!--  ================================================================
+      Delegates the build to a number of other projects. 
+      Two properties must be set for this to work: 
+      - projects: a comma separated list of directories to execute the
+        build in. 
+      - targets: List of targets (separated by commas) to execute.
+      ================================================================ -->
+    <target name="delegator" >
+           <for list="${targets}" param="target">
+               <sequential> 
+                   <for list="${projects}" param="proj">
+                       <sequential>
+                           <echo>
+=====================================================================
+Executing target '@{target}' for @{proj}
+=====================================================================
+                           </echo>
+                           <ant dir="@{proj}" 
+                             inheritAll="false" 
+                             target="@{target}"/>
+                    </sequential>
+                </for>
+            </sequential>
+        </for>
+       </target>
+
+       
+       
+       <target name="info">
+           <echo>Delegator build file for delegating a task
+                 to several other build files. </echo>
+       </target>
+       
+       <target name="init_delegator" depends="init_directory_properties,import_header">
+       </target>
+       
+       <target name="clean" depends="init_delegator" description="Clean all targets">
+               <antcall target="delegator">
+                       <param name="targets" value="clean"/>
+               </antcall>
+       </target>
+       
+       <target name="compile" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="compile,dist-lite-product"/>
+               </antcall>
+       </target>
+
+    <target name="schemaexport" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="schemaexport"/>
+               </antcall>
+       </target>
+       
+       <target name="jar" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="jar"/>
+               </antcall>
+       </target>
+       
+       <target name="javadoc" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="javadoc"/>
+               </antcall>
+       </target>
+       
+       <target name="testjavadoc" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="testjavadoc"/>
+               </antcall>
+       </target>
+       
+       <target name="pdfdoc" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="pdfdoc"/>
+               </antcall>
+       </target>
+       
+       <target name="doccheck" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="doccheck"/>
+               </antcall>
+       </target>
+       
+       <target name="clean-dist" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="clean-dist"/>
+               </antcall>
+       </target>
+       
+       <target name="dist-lite" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-lite"/>
+               </antcall>
+       </target>
+       
+       <target name="dist-javadoc" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-javadoc"/>
+               </antcall>
+       </target>
+               
+       <target name="dist-lite-product" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-lite-product"/>
+               </antcall>
+       </target>
+       
+       <target name="dist-lite-test" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-lite-test"/>
+               </antcall>
+       </target>
+       
+       
+       <target name="dist" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist"/>
+               </antcall>
+       </target>
+
+       <target name="checkstyle" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="checkstyle"/>
+               </antcall>
+       </target>
+       
+       <target name="format" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="format"/>
+               </antcall>
+       </target>
+       
+       <target name="simian" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="simian"/>
+               </antcall>
+       </target>
+       
+       <target name="macker" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="macker"/>
+               </antcall>
+       </target>
+
+    <target name="testclasses" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-lite-product,dist-lite-test"/>
+               </antcall>
+       </target>
+       
+       <target name="test" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="dist-lite-product,dist-lite-test,test"/>
+               </antcall>
+       </target>
+       
+       <target name="junit-reports" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="junit-reports,dist-lite-product,dist-lite-test"/>
+               </antcall>
+       </target>
+       
+       <target name="jcov-reports" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="jcov-reports,dist-lite-product,dist-lite-test"/>
+               </antcall>
+       </target>
+       
+       <target name="reports" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="reports,dist-lite-product,dist-lite-test"/>
+               </antcall>
+       </target>
+       
+       <target name="schemaupdate" depends="init_delegator">
+               <antcall target="delegator">
+                       <param name="targets" value="schemaupdate"/>
+               </antcall>
+       </target>
+       
+       
diff --git a/build/header.xml b/build/header.xml
new file mode 100644 (file)
index 0000000..33a8cd0
--- /dev/null
@@ -0,0 +1,512 @@
+<!-- PROPERTIES -->
+
+<target name="echoUserPropertiesFileFound" if="user.properties.found">
+  <echo>Using properties file ${user.property.file}</echo>
+</target>
+    
+<target name="echoUserPropertiesFileNotFound" unless="user.properties.found">
+  <echo>Properties file ${user.property.file} not found, reverting to defaults</echo>
+</target>
+
+
+
+<target name="download.dep">
+  <if>
+    <isset property="artifact"/>
+    <then>
+      <echo>Getting dependence ${group}/${artifact}/${version}</echo>
+      <dependencies verbose="true" fileSetId="my.deps"> 
+       <dependency group="${group}" artifact="${artifact}" version="${version}"/>
+      </dependencies> 
+      <copy todir="${download.dir}" flatten="yes">
+       <fileset refid="my.deps"/>
+      </copy>
+    </then>
+    <else>
+      <echo>Getting dependence ${group}/${version}</echo>
+      <dependencies verbose="true" fileSetId="my.deps"> 
+       <dependency group="${group}" version="${version}"/>
+      </dependencies> 
+      <copy todir="${download.dir}" flatten="yes">
+       <fileset refid="my.deps"/>
+      </copy>
+    </else>
+  </if>
+</target>
+
+<!-- LOG4j -->    
+<target name="log4j.d">
+  <antcall target="download.dep">
+    <param name="group" value="log4j"/>
+    <param name="version" value="1.2.9"/>
+  </antcall>
+</target>
+
+<target name="commons-logging.d">
+  <antcall target="download.dep">
+    <param name="group" value="commons-logging"/>
+    <param name="artifact" value="commons-logging"/>
+    <param name="version" value="1.0.2"/>
+  </antcall>
+</target>
+
+<target name="logging.d" depends="log4j.d,commons-logging.d">
+</target>
+
+<target name="dom4j.d">
+  <antcall target="download.dep">
+    <param name="group" value="dom4j"/>
+    <param name="version" value="1.6"/>
+  </antcall>
+</target>
+
+<target name="xerces.d">
+  <antcall target="download.dep">
+    <param name="group" value="ehcache"/>
+    <param name="version" value="1.1"/>
+  </antcall>
+</target>
+
+<target name="ehcache.d">
+  <antcall target="download.dep">
+    <param name="group" value="xerces"/>
+    <param name="version" value="2.4.0"/>
+  </antcall>
+</target>
+
+<target name="oro.d">
+  <antcall target="download.dep">
+    <param name="group" value="oro"/>
+    <param name="version" value="2.0.6"/>
+  </antcall>
+</target>
+
+<target name="cglib.d">
+  <antcall target="download.dep">
+    <param name="group" value="cglib"/>
+    <param name="version" value="2.1"/>
+  </antcall>
+</target>
+
+<target name="hibernate.d" depends="cglib.d,oro.d">
+  <antcall target="download.dep">
+    <param name="group" value="hibernate"/>
+    <param name="version" value="3.0.5"/>
+  </antcall>
+</target>
+
+<target name="hibernate.standalone.d" depends="hibernate.d">
+  <copy todir="${download.dir}">
+    <fileset dir="${special.lib.dir}/hibernate">
+       <include name="*.jar"/>
+    </fileset>
+  </copy>
+</target>
+
+<target name="spring.d">
+  <antcall target="download.dep">
+    <param name="group" value="springframework"/>
+    <param name="artifact" value="spring"/>
+    <param name="version" value="1.2.5"/>
+  </antcall>
+</target>
+
+<target name="junit.d">
+  <antcall target="download.dep">
+    <param name="group" value="junit"/>
+    <param name="version" value="3.8.1"/>
+  </antcall>
+  <copy todir="${download.dir}">
+    <fileset dir="${special.lib.dir}/test">
+       <include name="*.jar"/>
+    </fileset>
+  </copy>
+</target>
+
+<target name="emma.d">
+  <antcall target="download.dep">
+    <param name="group" value="emma"/>
+    <param name="artifact" value="emma"/>
+    <param name="version" value="2.0.5312"/>
+  </antcall>
+  <antcall target="download.dep">
+    <param name="group" value="emma"/>
+    <param name="artifact" value="emma_ant"/>
+    <param name="version" value="2.0.5312"/>
+  </antcall>
+</target>
+
+<target name="jmock.d">
+  <antcall target="download.dep">
+    <param name="group" value="jmock"/>
+    <param name="artifact" value="jmock"/>
+    <param name="version" value="1.0.1"/>
+  </antcall>
+  <antcall target="download.dep">
+    <param name="group" value="jmock"/>
+    <param name="artifact" value="jmock-cglib"/>
+    <param name="version" value="1.0.1"/>
+  </antcall>
+</target>
+
+<target name="dbunit.d">
+  <antcall target="download.dep">
+    <param name="group" value="dbunit"/>
+    <param name="version" value="2.1"/>
+  </antcall>
+</target>
+
+<!-- common test dependencies for all test code --> 
+<target name="test.d" depends="junit.d,jmock.d,dbunit.d,emma.d">
+</target>
+
+
+
+ <target name="import_header" unless="build_header_included">
+    
+    <!-- ========================================================================================
+         Locate user properties to (optionally) override defaults
+         ======================================================================================== -->
+    <!--
+      Give user a chance to override without editing this file
+      (and without typing -D each time they run it).
+
+      The following properties must be defined in this file:
+      - jcoverage.home: root of the jcoverage installation. 
+      - jalopy.home: root of the jalopy installation. 
+    -->
+    <property name="user.property.file" value="${user.home}/wamblee.properties"/>
+    <available file="${user.property.file}" property="user.properties.found"/>
+    <property file="${user.property.file}"/>
+    
+    <antcall target="echoUserPropertiesFileFound"/>
+    <antcall target="echoUserPropertiesFileNotFound"/>
+
+    <!-- project properties defaults, can be overriden in the user's properties file -->
+    
+      <!--  The project.home property can also be overriden in a build.xml
+            in case the source is in a subdirectory and not necessarily
+            in the top-level directory --> 
+    <property name="project.home" value=".."/>
+    <property name="build.dir" value="${project.home}/build"/>
+    <property name="lib.dir" value="${project.home}/lib/wamblee"/>
+    <property name="ant.lib.dir" value="${project.home}/lib/ant"/>
+    <property name="external.lib.dir" value="lib/external"/>
+    <property name="test.lib.dir" value="lib/test"/>
+    <property name="special.lib.dir" value="${project.home}/lib/special"/>
+    <property name="forrest.xdocs.dir" value="${project.home}/site/xdocs"/>
+    <property name="forrest.build.dir" value="${project.home}/site/build"/>
+    <property name="forrest.build.site.dir" value="${forrest.build.dir}/site"/>
+    <property name="build.properties.dir" value="${project.home}" />
+    <property name="build.properties.name" value="build.properties" />
+    
+    <mkdir dir="${external.lib.dir}"/>
+    <mkdir dir="${test.lib.dir}"/>
+    
+    <property name="conf.dir" value="conf"/>
+    <property name="log4j.properties.dir" value="${conf.dir}/properties"/>
+    
+    <!-- Compilation properties -->
+    <property name="javac.debug" value="true"/>
+    <property name="javac.debug.level" value="lines,vars,source"/>
+    <property name="javac.source" value="1.5"/>
+
+    <!-- ========================================================================================
+         Standard path definitions to be used with all projects 
+         ======================================================================================= -->
+    <property name="xmlschemas.dir" value="${conf.dir}/schemas"/>
+    <path id="xmlschemas.path">
+      <pathelement location="${xmlschemas.dir}"/>
+    </path>
+
+    <!-- ========================================================================================
+         Path definitions for external libraries.
+         ======================================================================================== -->
+    <!-- Ignore system classpath! -->
+    <property name="build.sysclasspath" value="ignore" />
+
+    <!-- JUnit -->
+    <property name="junit.halt.on.failure" value="false"/>
+    <property name="unitreport" value="cl-unit.xml"/>
+        
+    <!-- Hibernate paths --> 
+    <!--  name of the file in the source directory containing
+          the names of hibernate files to look at in the correct order --> 
+    <property name="hibernate.home" value="${lib.dir}/hibernate-3.0"/>
+    <path id="hibernate.basic.path">
+      <fileset dir="${hibernate.home}">
+        <include name="*.jar"/>
+      </fileset>
+      <path refid="xerces.path"/>
+      <path refid="dom4j.path"/>
+    </path>
+    <path id="hibernate.appserver.path"> 
+      <path refid="hibernate.basic.path"/>
+      <fileset dir="${hibernate.home}/appserver">
+        <include name="*.jar"/>
+      </fileset>
+    </path>
+    <path id="hibernate.standalone.path"> 
+      <path refid="hibernate.basic.path"/>
+      <fileset dir="${hibernate.home}/standalone">
+        <include name="*.jar"/>
+      </fileset>
+    </path>
+
+    <!-- Commented the inclusion of a system-wide path in the user's environment into a 
+         classpath. System wide classpaths should not be used. -->
+    <!-- property name="classpath_id" value="build.path" / -->
+    <!-- property name="classpath" refid="${classpath_id}"/ -->
+    <property name="classpath" value=""/>
+    
+    
+    <!-- ========================================================================================
+         Database settings
+         ======================================================================================== -->
+    <property name="database" value="Derby"/>
+    
+    <!-- ========================================================================================
+         Ant task defs
+         ======================================================================================== -->
+    <!-- ant-contrib integration --> 
+    <property name="ant.contrib.home" value="${ant.lib.dir}/ant-contrib-1.0b2"/>
+    <taskdef resource="net/sf/antcontrib/antlib.xml">
+      <classpath>
+        <pathelement location="${ant.contrib.home}/ant-contrib.jar" />
+      </classpath>
+    </taskdef>
+    
+    <!-- taskdef for ant-dependencies task --> 
+    <taskdef name="dependencies" classpath="${ant.lib.dir}/ant-dependencies.jar" 
+      classname="org.apache.tools.ant.taskdefs.optional.dependencies.Dependencies"/>
+  
+    <!-- Emma integration --> 
+    <path id="emma.lib">
+      <fileset dir="${test.lib.dir}" includes="emma*.jar"/>
+    </path>
+    <taskdef resource="emma_ant.properties" classpathref="emma.lib" />
+    <property name="emma.enabled" value="false"/>
+    
+    <!-- checkstyle -->
+    <!-- TMP download this dependence as well 
+    <property name="checkstyle.home" value="${lib.dir}/checkstyle-3.5"/>
+    <taskdef resource="checkstyletask.properties"
+             classpath="${checkstyle.home}/checkstyle-all-3.5.jar"/>
+    <property name="checkstyle.rules" value="coding-rules.xml"/>
+    <property name="checkstyle.test.rules" value="coding-rules-test.xml"/>
+    --> 
+             
+    <!-- jalopy -->
+    <!-- TMP download this dependence as well 
+    <property name="jalopy.home" value="${lib.dir}/jalopy-ant-0.6.2"/>
+    <property name="jalopy.rules" value="${build.dir}/formatting-rules.xml"/>
+    <taskdef name="jalopy"
+         classname="de.hunsicker.jalopy.plugin.ant.AntPlugin">
+      <classpath>
+        <fileset dir="${jalopy.home}">
+          <include name="*.jar" />
+        </fileset>
+      </classpath>
+    </taskdef>
+    -->
+  
+    <!-- simian integration -->
+    <!-- TMP download this dependence as well 
+    <property name="simian.home" value="${lib.dir}/simian"/>
+    <taskdef resource="simiantask.properties" classpath="${simian.home}/simian.jar"/>
+    --> 
+    
+    <!-- macker integration -->
+    <!-- TMP download this dependence as well 
+    <property name="macker.home" value="${lib.dir}/macker-0.4.2"/>
+    <path id="macker.path">
+         <fileset dir="${macker.home}">
+           <include name="**/*.jar"/>
+         </fileset>
+    </path>
+    <taskdef name="macker"
+      classname="net.innig.macker.ant.MackerAntTask"
+      classpathref="macker.path"/>
+
+    --> 
+   
+    <!-- ========================================================================================
+         Information
+         ======================================================================================== -->
+    <echo level="debug" message="project.home=${project.home}"/>
+    <echo level="debug" message="project.libs=${project.libs}"/>
+    <echo level="debug" message="classpath=${classpath}"/>
+
+
+    <!-- ========================================================================================
+         Included marker
+         ======================================================================================== -->
+    <property name="build_header_included" value="true" />
+</target>
+      
+<!--  =================================================================
+     Sets up the directory definitions for the module
+     ================================================================= -->
+<target name="init_directory_properties" depends="import_header">
+       <!-- Set module properties for this build -->
+
+       <property name="module.home" value="." />
+       
+       <!--  Replace the dash by a / to create a relative directory 
+             from a module name --> 
+       <propertyregex property="module.reldir" 
+                      input="${module.name}"
+                      regexp="-"
+                      replace="/"
+                      global="true"
+                      defaultValue="${module.name}"/>
+       <property name="module.build.dir"
+               value="${build.dir}/${module.reldir}" />
+
+       <property name="module.api.forrest.dir" value="${forrest.build.site.dir}/api/${module.name}" />
+       <!-- property name="module.classes.dir"     value="${module.build.dir}/bin"/ -->
+       <!-- property name="module.testclasses.dir" value="${module.build.dir}/testbin"/ -->
+       <property name="module.classes.dir" value="bin" />
+       <property name="module.testclasses.dir" value="testbin" />
+       <!-- Directory where generated SQL will be put by the schema export-->
+       <property name="module.sql.dir" value="${module.build.dir}/sql" />
+
+
+       <property name="module.jcovclasses.dir"
+               value="${module.build.dir}/testjcov" />
+       <property name="module.jars.dir" value="${module.build.dir}/jars" />
+       <property name="module.jar.file"
+               value="${module.jars.dir}/${module.jar.name}" />
+       <property name="module.source.dir" value="${module.home}/src" />
+       <property name="module.forrest.src.dir" value="${module.home}/xdocs" />
+       <property name="module.ejbsource.dir" value="${module.home}/ejbsrc" />
+       <property name="module.test.dir" value="${module.home}/test" />
+       <property name="module.testjar.name"
+               value="${module.name}-test.jar" />
+        <property name="module.testresourcesjar.name"
+               value="${module.name}-test-resources.jar" />
+       <property name="module.resource.dir"
+               value="${module.home}/resources" />
+       <property name="module.testresource.dir"
+               value="${module.resource.dir}/test" />
+    <property name="module.testoutput.dir"
+        value="${module.resource.dir}/testoutput"/>
+       <property name="module.report.dir"
+               value="${module.build.dir}/testresults" />
+        <property name="module.emmareport.dir"
+               value="${module.report.dir}/html/emma"/>
+       <property name="module.docbase.dir"
+               value="${module.build.dir}/javadoc" />
+       <property name="module.javadoc.dir"
+               value="${module.docbase.dir}/api" />
+       <property name="module.javadocjar.name"
+               value="${module.name}-doc.jar" />
+       <property name="module.testjavadocjar.name"
+               value="${module.name}-test-doc.jar" />
+       <property name="module.testjavadoc.dir"
+               value="${module.docbase.dir}/testapi" />
+       <property name="module.pdfdoc.dir"
+               value="${module.docbase.dir}/pdf" />
+       <property name="module.doccheck.dir"
+               value="${module.docbase.dir}/doccheck" />
+       <property name="module.jar.name" value="${module.name}.jar" />
+       
+       <!--  Replace the dash by a / to create a relative directory 
+             from a module name --> 
+       <property name="module.dist.dir" value="${lib.dir}/${module.reldir}" />
+</target>
+
+<!-- ============================================================================
+  Initialize the environment for other tasks. Normally, every task which
+  does not depend on any other task should at least depend on init. 
+  ============================================================================ -->
+  
+  <target name="help" depends="import_header">
+    <antcall target="help-within-module"/>
+    <antcall target="help-outside-module"/>
+  </target>
+  
+  <target name="help-within-module" depends="init_directory_properties" if="module.name">
+    <antcall target="help-impl"/>
+  </target>
+  
+  <target name="help-outside-module" unless="module.name">
+    <antcall target="help-outside-module-2">
+      <param name="module.name" value="MODULE_NAME"/>
+    </antcall>
+  </target>
+  
+  <target name="help-outside-module-2" depends="init_directory_properties">
+    <antcall target="help-impl"/>
+  </target>
+  
+  
+  <target name="help-impl">
+    <echo>
+      General build targets: 
+      
+      clean:         Cleans up all build results (excluding the dist location).
+      compile:       Compiles all java clasess. Target dir ${module.classes.dir} 
+      jar:           Creates a jar file. Target: ${module.jar.file} 
+      dist:          Makes the build results available for other modules Target: ${module.dist.dir}. 
+      dist-lite:     Similar to dist but only creates the jars for code and test code. Useful
+                     during development because it takes much less time than the dist
+                    target
+      clean-dist:    Cleans up the dist directories. 
+      
+      Test: 
+      
+      test:          Compile and run tests. 
+      junit-reports: As test but generates a test report. Target: ${module.report.dir}(/html/unit) 
+      reports:       As junit-reports but also generates an emma test coverage
+                     report
+                    
+      Note: By specifying -Dtest=TestCaseName on the ant command line, 
+      only the specified testcase will be run. 
+      
+      Emma can also be executed manually:
+            
+        emma:          Overwrites the production classes by their instrumented versions.
+        emma-reports:  Generates emma code coverage reports after running the
+                       testcases with instrumented classes.
+      
+      Javadoc targets: 
+      
+      javadoc:       Generates javadoc. Target: ${module.javadoc.dir} 
+      doccheck:      Checks documentation. Target: ${module.doccheck.dir}
+      
+      Code analysis: 
+      
+      checkstyle:    Checks the style of the code. 
+      format:        Formats to the code in accordance with the checkstyle rules.
+      simian:        Analyse similarities in the code.
+      
+      Database targets: 
+      
+      For all targets below the database is configured using 
+      hibernate.properties (hibernate.dialect property) and 
+      the JDBC connection properties in database.properties. 
+      
+      The schemaexport and schemaupdate targets require the 
+      setting of a property named hibernate.filelist. The value
+      of the property must be the fully qualified class name of
+      a concrete subclass of ConfigFileList which has a default 
+      public constructor and defines the Hibernate mapping files that
+      can be used. 
+      
+      schemaexport:  Generate SQL code to create the required database structures.
+      
+      startdb:       Startup a lightweight database. The database
+      type to start is obtained from the currently
+      configured database. 
+      
+      schemaupdate:  Populate the database with a schema by running
+      Hibernate schemaupdate against the currently
+      configured database.  
+    </echo>
+  </target>
+  
+  
diff --git a/build/trailer.xml b/build/trailer.xml
new file mode 100644 (file)
index 0000000..b93f427
--- /dev/null
@@ -0,0 +1,505 @@
+<!-- PROPERTIES -->
+
+
+<!-- Default entry point for the build -->
+<target name="all" depends="clean, init, compile, jar" />
+
+<target name="startup" depends="init_directory_properties">
+  <property name="module.classpath_id" value="module.build.path" />
+  <property name="module.classpath" refid="${module.classpath_id}" />
+  
+  <path id="module.build.path">
+    <fileset dir="${external.lib.dir}">
+      <include name="**/*.jar"/>
+    </fileset>
+  </path>
+  
+  <path id="module.testbuild.path">
+    <fileset dir="${test.lib.dir}">
+      <include name="**/*.jar"/>
+    </fileset>
+  </path>
+
+</target>
+
+<target name="init" depends="import_header,startup">
+       <tstamp />
+</target>
+
+<!-- ============================================================================
+       Cleanup 
+       ============================================================================ -->
+
+<target name="ant.deps">
+</target>
+
+<target name="deps" depends="import_header">
+  <antcall target="module.build.deps">
+    <param name="download.dir" value="${external.lib.dir}"/>
+  </antcall>
+  <antcall target="test.d">
+    <param name="download.dir" value="${test.lib.dir}"/>
+  </antcall>
+  <antcall target="module.test.deps">
+    <param name="download.dir" value="${test.lib.dir}"/>
+  </antcall>
+</target>
+
+<target name="clean" depends="init_directory_properties,base-test-clean">
+       <delete dir="${module.classes.dir}" />
+       <delete dir="${module.testclasses.dir}" />
+       <delete dir="${module.testoutput.dir}" />
+       <delete dir="${module.jars.dir}" />
+       <delete dir="${module.docbase.dir}" />
+       <delete dir="${module.report.dir}" />
+       <delete dir="${module.sql.dir}" />
+</target>
+
+<target name="clean-deps" depends="init_directory_properties">
+  <delete dir="${external.lib.dir}"/>
+  <delete dir="${test.lib.dir}"/>
+</target>
+
+<!-- ============================================================================
+       Compilation of java code. 
+       ============================================================================ -->
+
+<!--  Compilation of java code, requires a srcdirs variable to be
+      set to a path of one of more directories separated by : or ;
+-->
+<target name="compile-impl" depends="init">
+       <mkdir dir="${module.classes.dir}" />
+       <echo level="info" message="compile ${module.name}, source dirs ${srcdirs}" />
+       <javac srcdir="${srcdirs}" source="${javac.source}"
+               destdir="${module.classes.dir}" classpath="${module.classpath}"
+               debug="${javac.debug}" debuglevel="${javac.debug.level}" />
+       <copy todir="${module.classes.dir}">
+               <fileset dir="${module.source.dir}" excludes="**/*.java" />
+       </copy>
+</target>
+
+<!--  Compilation of regular source code --> 
+<target name="compile-src" unless="ejbsource.available" depends="init">
+    <antcall target="compile-impl">
+        <param name="srcdirs" value="${module.source.dir}"/>
+    </antcall>
+</target>
+
+<!--  Compilation of regular source code and generated source code
+      for EJBs -->
+<target name="compile-src-ejb" if="ejbsource.available" depends="init">
+    <antcall target="compile-impl">
+        <param name="srcdirs" value="${module.source.dir}:${module.ejbsource.dir}"/>
+    </antcall>
+</target>
+
+<target name="compile" depends="init">
+    <available file="${module.ejbsource.dir}" type="dir" property="ejbsource.available"/>
+    <antcall target="compile-src"/>
+    <antcall target="compile-src-ejb"/>
+</target>
+
+<!-- ============================================================================
+       Generating a jar file. 
+       ============================================================================ -->
+
+<target name="jar" depends="compile">
+       <mkdir dir="${module.jars.dir}" />
+       <echo level="info" message="jar cdmvbase ${module.name}" />
+       <!-- Hack: how to remove the schemas directory from the jar when it does not exist -->
+       <mkdir dir="${xmlschemas.dir}" />
+       <jar jarfile="${module.jars.dir}/${module.jar.name}"
+               compress="${jarcompress}">
+               <fileset dir="${module.classes.dir}" />
+               <fileset dir="${xmlschemas.dir}" />
+       </jar>
+</target>
+
+
+<!-- ============================================================================
+       Generate javadoc.
+       ============================================================================ -->
+
+<target name="javadoc" depends="init">
+       <!-- create javadocs -->
+       <javadoc packagenames="*" destdir="${module.javadoc.dir}"
+               author="true" version="true" use="true" private="yes"
+               windowtitle="Dragon documentation" source="${javac.source}"
+               sourcepath="${module.source.dir}"
+               classpath="${module.classpath}">
+               <arg value="-docfilessubdirs"/>
+               <arg value="-overview"/>
+               <arg value="${module.source.dir}/overview.html"/>
+       </javadoc>
+</target>
+
+<target name="testjavadoc" depends="testclasses">
+       <!-- create javadocs -->
+       <javadoc packagenames="*" destdir="${module.testjavadoc.dir}"
+               author="true" version="true" use="true" private="yes"
+               windowtitle="Dragon test documentation" source="${javac.source}"
+               sourcepath="${module.test.dir}"
+               classpathref="test.classpath">
+       </javadoc>
+</target>
+
+<!-- ============================================================================
+       Generate javadoc in pdf format
+       ============================================================================ -->
+
+<target name="pdfdoc" depends="init">
+       <!-- create javadocs -->
+       <mkdir dir="${module.pdfdoc.dir}" />
+       <javadoc packagenames="*" author="true" version="true" private="yes"
+               source="${javac.source}" doclet="com.tarsec.javadoc.pdfdoclet.PDFDoclet"
+               docletpathref="pdfdoclet.path"
+               additionalparam="-pdf ${module.pdfdoc.dir}/${module.name}.pdf">
+               <fileset dir="${module.source.dir}">
+                       <include name="**/*.java" />
+               </fileset>
+       </javadoc>
+       <echo
+               message="Unfortuantely, this target will have generated some errors but the pdf will still look fine" />
+</target>
+
+<!-- ============================================================================
+       Check javadoc
+       ============================================================================ -->
+
+<target name="doccheck" depends="init">
+       <mkdir dir="${module.doccheck.dir}" />
+       <javadoc packagenames="*" destdir="${module.doccheck.dir}"
+               author="true" version="true"
+               doclet="com.sun.tools.doclets.doccheck.DocCheck"
+               docletpathref="doccheck.path" source="${javac.source}">
+               <fileset dir="${module.source.dir}">
+                       <include name="**/*.java" />
+               </fileset>
+       </javadoc>
+</target>
+
+<!-- ============================================================================
+       Copy build results to a standard location. 
+       ============================================================================ -->
+       
+<target name="clean-dist" depends="clean">
+  <delete>
+    <fileset dir="${module.dist.dir}" includes="**"/>
+  </delete> 
+</target>
+       
+<target name="dist-lite-product" depends="jar">
+    <mkdir dir="${module.dist.dir}"/>
+       <delete>
+               <fileset dir="${module.dist.dir}" excludes="**/CVS" />
+       </delete>
+       <copy todir="${module.dist.dir}" flatten="yes">
+               <fileset dir="${module.build.dir}">
+                       <include name="**/*.jar" />
+               </fileset>
+               <fileset dir="${module.build.dir}">
+                       <include name="**/*.pdf" />
+               </fileset>
+       </copy>
+</target>
+
+<target name="dist-lite-test" depends="testclasses">
+    <jar destfile="${module.dist.dir}/${module.testjar.name}"
+               basedir="${module.testclasses.dir}" 
+               includes="**/*.class"/>
+    <jar destfile="${module.dist.dir}/${module.testresourcesjar.name}"
+                basedir="${module.testresource.dir}" />
+</target>
+
+<target name="dist-lite" depends="clean,dist-lite-product,dist-lite-test">
+</target>
+
+<target name="dist-javadoc" depends="javadoc,pdfdoc,testjavadoc">
+    <jar destfile="${module.dist.dir}/${module.javadocjar.name}"
+               basedir="${module.javadoc.dir}" />
+       <jar destfile="${module.dist.dir}/${module.testjavadocjar.name}"
+               basedir="${module.testjavadoc.dir}" />          
+       <copy todir="${module.api.forrest.dir}">
+               <fileset dir="${module.javadoc.dir}" />
+       </copy>                         
+</target>
+
+<target name="dist"
+       depends="dist-lite,dist-javadoc"
+       description="copying compiled sources to dist location and copy documentation to forrest site">
+       <echo
+               message="Copying build results for ${module.name} to a location where other modules can find it." />
+</target>
+
+<!-- ============================================================================
+       Check style, 
+       ============================================================================ -->
+
+<target name="checkstyle" depends="testclasses">
+       <checkstyle config="${build.dir}/${checkstyle.rules}"
+               failOnViolation="false" classpathref="module.testbuild.path">
+               <fileset dir="${module.source.dir}" includes="**/*.java" />
+               <formatter type="plain" />
+       </checkstyle>
+       <checkstyle config="${build.dir}/${checkstyle.test.rules}"
+               failOnViolation="false" classpathref="module.testbuild.path">
+               <fileset dir="${module.test.dir}" includes="**/*.java" />
+               <formatter type="plain" />
+       </checkstyle>
+</target>
+
+<!-- ============================================================================
+       Formatting source code.  
+       ============================================================================ -->
+<target name="format" depends="init">
+       <jalopy loglevel="INFO" classpathref="module.testbuild.path"
+               convention="${jalopy.rules}">
+               <fileset dir="${module.source.dir}">
+                       <include name="**/*.java" />
+               </fileset>
+               <fileset dir="${module.test.dir}">
+                       <include name="**/*.java" />
+               </fileset>
+       </jalopy>
+</target>
+
+<!-- =========================================================================
+       Check redundancy in the code
+       ========================================================================= -->
+<target name="simian" depends="init">
+       <simian threshold="7">
+               <fileset dir="${module.source.dir}" includes="**/*.java" />
+       </simian>
+</target>
+
+
+<!-- =========================================================================
+       Check dependencies using macker.
+       ========================================================================= -->
+<property name="macker.file" value="macker.xml" />
+<target name="macker" depends="compile">
+       <available file="${macker.file}" property="macker.file.found" />
+       <fail message="Macker source file ${macker.file} was not found."
+               unless="macker.file.found" />
+       <macker>
+               <rules dir="." includes="macker.xml" />
+               <classes dir="${module.classes.dir}">
+                       <include name="**/*.class" />
+               </classes>
+               <classpath>
+                   <pathelement path="${module.classes.dir}" />
+                       <path refid="module.build.path" />
+               </classpath>
+       </macker>
+</target>
+
+
+<!-- ===========================================================================
+       JUnit tests.
+       =========================================================================== -->
+
+<target name="testclasses" depends="compile">
+       <mkdir dir="${module.test.dir}" />
+       <mkdir dir="${module.testresource.dir}" />
+       <mkdir dir="${module.testclasses.dir}" />
+               <path id="test.classpath">
+               <pathelement path="${module.classes.dir}" />
+               <pathelement path="${module.test.dir}" />
+               <path refid="module.testbuild.path" />
+               <path refid="module.build.path" />
+       </path>
+       <javac srcdir="${module.test.dir}"
+               destdir="${module.testclasses.dir}" 
+               debug="${javac.debug}" debuglevel="${javac.debug.level}" compiler="modern"
+               source="${javac.source}" failonerror="yes">
+               <classpath>
+                   <path refid="test.classpath"/>
+               </classpath>
+       </javac>
+       <copy todir="${module.testclasses.dir}">
+               <fileset dir="${module.test.dir}" excludes="**/*.java" />
+               <fileset dir="${module.testresource.dir}" />
+       </copy>
+</target>
+
+<!-- test-base is a target mean to be used by other targets. 
+       It expects one parameter names 'testedclasses' to indicate the directory
+       where the compiled test classes reside. 
+-->
+
+<target name="base-test-clean">
+  <delete file="coverage.ec"/>
+  <delete file="coverage.em"/>
+</target>
+
+
+<target name="base-test-impl" depends="testclasses">
+       <mkdir dir="${module.report.dir}" />
+       <antcall target="emma"/>
+       <junit haltonfailure="${junit.halt.on.failure}" printsummary="on" dir="." fork="yes" showoutput="yes">
+               <batchtest todir="${module.report.dir}">
+                       <fileset dir="${module.testclasses.dir}">
+                               <include name="${testclassnames}" />
+                               <!--  exclude inner classes --> 
+                               <exclude name="**/*$$*.class"/>
+                       </fileset>
+               </batchtest>
+               <formatter type="xml" />
+               <classpath>
+                       <path refid="xmlschemas.path" />
+                       <pathelement path="${module.testclasses.dir}" />
+                       <pathelement path="${testedclasses}" />
+                       <pathelement path="${testlibrary}" />
+                       <pathelement path="${module.classes.dir}" />
+                       <pathelement path="${log4j.properties.dir}" /><!--  for log4j.properties -->
+                       <!-- the test resource directory is added to the path as well to make it
+                               easy to access resources from tests -->
+                       <pathelement path="${module.testresource.dir}" />
+                       <path refid="module.testbuild.path" />
+                       <path refid="module.build.path" />
+               </classpath>
+       </junit>
+</target>
+
+<target name="base-test-allclasses" unless="test">
+       <echo>Testing all classes</echo>
+       <antcall target="base-test-impl">
+               <param name="testclassnames" value="**/*Test.class" />
+       </antcall>
+</target>
+
+<target name="base-test-singleclass" if="test">
+       <echo>Testing testcases that conform to the pattern *${test}*</echo>
+       <antcall target="base-test-impl">
+               <param name="testclassnames" value="**/*${test}*.class" />
+       </antcall>
+</target>
+
+<target name="base-test">
+       <antcall target="base-test-allclasses" />
+       <antcall target="base-test-singleclass" />
+</target>
+
+<!-- Run junit test -->
+
+<target name="test">
+       <antcall target="base-test">
+               <param name="testedclasses" value="${module.classes.dir}" />
+       </antcall>
+</target>
+
+<!-- reporting target used by other targets. This should not depend on 
+       the test target to avoid unnecessary duplicate runs of the tests -->
+
+<target name="base-reports" depends="init">
+       <mkdir dir="${module.report.dir}/html" />
+       <mkdir dir="${module.report.dir}/html/unit" />
+       <junitreport todir="${module.report.dir}" tofile="${unitreport}">
+               <fileset dir="${module.report.dir}">
+                       <include name="TEST-*.xml" />
+               </fileset>
+               <report todir="${module.report.dir}/html/unit" />
+       </junitreport>
+</target>
+
+<!-- Run junit tests with HTML reporting -->
+
+<target name="junit-reports" depends="test">
+       <antcall target="base-reports" />
+       <antcall target="emma-reports" />
+</target>
+
+<target name="reports">
+  <antcall target="junit-reports">
+    <param name="emma.enabled" value="true"/>
+  </antcall>
+</target>
+
+ <!-- Instrument classes using emma --> 
+  
+  <target name="emma" depends="init_directory_properties">
+    <emma enabled="${emma.enabled}">
+      <instr instrpath="${module.classes.dir}" mode="overwrite"/>
+    </emma>
+  </target>
+  
+  <target name="emma-reports" depends="init_directory_properties" if="emma.enabled">
+    <mkdir dir="${module.emmareport.dir}"/>
+    <emma enabled="${emma.enabled}" >
+      <report sourcepath="${module.source.dir}">
+        <!-- collect all EMMA data dumps (metadata and runtime): -->
+        <infileset dir="." includes="*.em, *.ec" />
+
+        <txt outfile="${module.emmareport.dir}/report.txt"/>
+        <html outfile="${module.emmareport.dir}/index.html"/>
+       <!-- sourcepath>
+         <dirset dir="${src}" >
+            <include name="." /> 
+          </dirset>
+        </sourcepath -->
+      </report>
+    </emma>
+  </target>
+
+
+<!--  ============================================================================
+      Database targets
+      ============================================================================ -->
+           
+<!--  Runs a main program found in the test source tree. 
+      Properties:
+         - my.test.program: class name of the program to run 
+         - my.args: arguments to be passed to the program --> 
+<target name="runTestProgram" depends="testclasses">
+       <java classname="${my.test.program}">
+           <arg line="${my.args}"/>
+               <classpath>
+                       <path refid="xmlschemas.path" />
+                       <path refid="junit.path" />
+                       <pathelement path="${module.testclasses.dir}" />
+                       <pathelement path="${testedclasses}" />
+                       <pathelement path="${testlibrary}" />
+                       <pathelement path="${module.classes.dir}" />
+                       <pathelement path="${log4j.properties.dir}" /><!--  for log4j.properties -->
+                       <!-- the test resource directory is added to the path as well to make it
+                               easy to access resources from tests -->
+                       <pathelement path="${module.testresource.dir}" />
+                       <path refid="module.testbuild.path" />
+                       <path refid="module.build.path" />
+               </classpath>
+       </java>
+</target>
+
+<property name="database.starter" 
+          value="org.wamblee.persistence.test.DatabaseStarter"/>
+<target name="startdb">
+    <echo>After the database has started, you must populate the database in some way.
+          For instance, using the schemaupdate ant target</echo>
+    <antcall target="runTestProgram">
+        <param name="my.test.program" value="${database.starter}"/>
+        <param name="my.args" value=""/>
+    </antcall>
+</target>
+
+<property name="schemaupdater" 
+          value="org.wamblee.test.HibernateUtils"/>
+<target name="schemaupdate">
+    <antcall target="runTestProgram">
+        <param name="my.test.program" value="${schemaupdater}"/>
+        <param name="my.args" value="${hibernate.filelist}"/>
+    </antcall>
+</target>
+
+<target name="schemaexport" depends="init">
+    <mkdir dir="${module.sql.dir}" />
+    <echo>Generating schema in output file ${module.sql.dir}/hibernate.sql</echo>
+    <antcall target="runTestProgram">
+        <param name="my.test.program" value="${schemaupdater}"/>
+        <param name="my.args" value="-export ${module.sql.dir}/hibernate.sql ${hibernate.filelist}"/>
+    </antcall>
+</target>
+
+<!-- INCLUDED MARKER -->
+<property name="build_trailer_included" value="true" />
+
+
diff --git a/conf/properties/log4j.properties b/conf/properties/log4j.properties
new file mode 100644 (file)
index 0000000..313cd7a
--- /dev/null
@@ -0,0 +1,56 @@
+
+############################################################################################
+# Default configuration file for log4j. 
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=WARN
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output. 
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.  
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries. 
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL:     %x - %m%n
+
+
+
diff --git a/download-deps.xml b/download-deps.xml
new file mode 100644 (file)
index 0000000..d39b33c
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version='1.0'?>
+<project name="support" default="download-deps" basedir="."
+    xmlns:artifact="urn:maven-artifact-ant">
+
+   <!--
+      Give user a chance to override without editing this file
+      (and without typing -D each time they run it).
+   -->
+  <property file="ant.properties" />
+  <property file="${user.home}/ant.properties" />
+
+  <property environment="env" />
+  
+  
+  <!-- set global properties for this build -->
+  
+  <!-- Directory where external jar files reside -->
+  <property name="external" value="lib/external"/>
+  
+  <!-- Directory where special libs reside that cannot be downloaded --> 
+  <property name="special.lib.dir" value="lib/special"/>
+  
+  <!-- Directory where basic j2ee apis reside -->
+  <property name="j2eelibs.dir" value="lib/j2ee"/>
+  
+  <!-- Directory where ant libs reside --> 
+  <property name="antlibs.dir" value="lib/ant"/>
+  
+  <!-- Directory where test libs reside --> 
+  <property name="testlibs.dir" value="lib/test"/>
+
+  <!-- taskdef for ant-dependencies task --> 
+  <taskdef name="dependencies" classpath="lib/ant/ant-dependencies.jar" 
+      classname="org.apache.tools.ant.taskdefs.optional.dependencies.Dependencies"/>
+  
+  <target name="clean">
+     <delete>
+        <fileset dir="${external}" includes="*.jar">
+       </fileset>
+       <fileset dir="${j2eelibs.dir}" includes="*.jar"/>
+       <fileset dir="${testlibs.dir}" includes="*.jar">
+         <include name="*.jar"/>
+         <exclude name="jta.jar"/>
+         <exclude name="jdbc*.jar"/>
+       </fileset>
+       <fileset dir="${antlibs.dir}">
+         <include name="*.jar"/>
+         <exclude name="ant-depend*.jar"/>
+       </fileset>
+     </delete>
+  </target>
+  
+  <target name="download-deps">
+    <dependencies verbose="true" fileSetId="war.deps">
+      <dependency group="commons-logging" artifact="commons-logging" version="1.0.2"/>
+      <dependency group="commons-validator" artifact="commons-validator" version="1.1.4"/>
+      <dependency group="xalan" artifact="xalan" version="2.7.0"/>
+      <dependency group="xerces" artifact="xerces" version="2.4.0"/>
+      <dependency group="springframework" artifact="spring" version="1.2.5"/>
+      
+      <dependency group="hibernate" version="3.0.5"/>
+      <dependency group="ehcache" version="1.1"/>
+      <dependency group="dom4j" version="1.6"/>
+      
+      <dependency group="cglib" version="2.1"/>
+    </dependencies> 
+    <copy todir="${external}" flatten="yes">
+      <fileset refid="war.deps"/>
+      <fileset dir="${special.lib.dir}" includes="*.jar"/>
+    </copy>
+    
+   
+    <dependencies verbose="true" fileSetId="j2ee.deps">
+      <dependency group="log4j" version="1.2.9"/>
+      <dependency group="javax.servlet" artifact="servlet-api" version="2.4"/>
+      <dependency group="javax.servlet" artifact="jsp-api" version="2.0"/>
+    </dependencies> 
+    <copy todir="${j2eelibs.dir}" flatten="yes">
+      <fileset refid="j2ee.deps"/>
+    </copy>
+    
+    <dependencies verbose="true" fileSetId="ant.deps">
+      <dependency group="emma" artifact="emma_ant" version="2.0.5312"/>
+      <dependency group="emma" artifact="emma" version="2.0.5312"/>
+    </dependencies> 
+    <copy todir="${antlibs.dir}" flatten="yes">
+      <fileset refid="ant.deps"/>
+    </copy>
+    
+    <dependencies verbose="true" fileSetId="test.deps">
+      <dependency group="junit" version="3.8.1"/>
+      <dependency group="dbunit" version="2.1"/>      
+      <dependency group="mysql" artifact="mysql-connector-java" version="3.0.10"/>
+      <dependency group="jmock" artifact="jmock" version="1.0.1"/>
+      <dependency group="jmock" artifact="jmock-cglib" version="1.0.1"/>
+    </dependencies> 
+    <copy todir="${testlibs.dir}" flatten="yes">
+      <fileset refid="test.deps"/>
+    </copy>
+    
+    
+  </target>
+  
+</project>
+
+
+
diff --git a/forrest.properties b/forrest.properties
new file mode 100644 (file)
index 0000000..84c4620
--- /dev/null
@@ -0,0 +1,131 @@
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+project.skin=tigris
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+project.classes-dir=${project.resources-dir}/properties
+#project.translations-dir=${project.content-dir}/translations
+
+
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working 
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
diff --git a/lib/ant/ant-contrib-1.0b2/README.txt b/lib/ant/ant-contrib-1.0b2/README.txt
new file mode 100644 (file)
index 0000000..2e97de8
--- /dev/null
@@ -0,0 +1,46 @@
+Ant-Contrib library
+
+This library is for contributed Ant tasks that have
+not been approved for inclusion into the ant core or
+optional library.
+
+Requirements
+-------------------------
+Runtime:
+       Requires APACHE Ant Version 1.5 or above.  Note, that output
+       handlers on the ForEach task will not properly report the
+       task which is outputting the message unless you are using
+       Ant version 1.5.2 or greater.
+
+       In addition, the <for> task will not work on versions prior
+       to Ant 1.6
+
+Compilation:
+       Requires Ant Version 1.6 or greater to compile and build the
+       package.
+
+
+Inclusion in your project
+-------------------------
+       The easiest way to use the tasks is to use
+
+<taskdef resource="net/sf/antcontrib/antlib.xml">
+  <classpath>
+    <pathelement location="your/path/to/ant-contrib-${version}.jar" />
+  </classpath>
+</taskdef>
+
+in your build file.  If the jar file is on your CLASSPATH or in
+ANT_HOME/lib you can even simplify this to read
+
+<taskdef resource="net/sf/antcontrib/antlib.xml" />
+
+For projects which will run under 1.5 versions, you would
+use the .properties file instead of the antlib.xml file:
+
+<taskdef resource="net/sf/antcontrib/antcontrib.properties">
+  <classpath>
+    <pathelement location="your/path/to/ant-contrib.jar" />
+  </classpath>
+</taskdef>
+
diff --git a/lib/ant/ant-contrib-1.0b2/ant-contrib.jar b/lib/ant/ant-contrib-1.0b2/ant-contrib.jar
new file mode 100644 (file)
index 0000000..ea817cd
Binary files /dev/null and b/lib/ant/ant-contrib-1.0b2/ant-contrib.jar differ
diff --git a/lib/ant/ant-dependencies.jar b/lib/ant/ant-dependencies.jar
new file mode 100644 (file)
index 0000000..83c7abf
Binary files /dev/null and b/lib/ant/ant-dependencies.jar differ
diff --git a/lib/special/hibernate/antlr-2.7.5H3.jar b/lib/special/hibernate/antlr-2.7.5H3.jar
new file mode 100644 (file)
index 0000000..d02328e
Binary files /dev/null and b/lib/special/hibernate/antlr-2.7.5H3.jar differ
diff --git a/lib/special/hibernate/asm-attrs.jar b/lib/special/hibernate/asm-attrs.jar
new file mode 100644 (file)
index 0000000..f07bcb2
Binary files /dev/null and b/lib/special/hibernate/asm-attrs.jar differ
diff --git a/lib/special/hibernate/asm.jar b/lib/special/hibernate/asm.jar
new file mode 100644 (file)
index 0000000..ee0c7cc
Binary files /dev/null and b/lib/special/hibernate/asm.jar differ
diff --git a/lib/special/test/jdbc2_0-stdext.jar b/lib/special/test/jdbc2_0-stdext.jar
new file mode 100644 (file)
index 0000000..ddafa13
Binary files /dev/null and b/lib/special/test/jdbc2_0-stdext.jar differ
diff --git a/lib/special/test/jdbc2_0-stdext.licence.txt b/lib/special/test/jdbc2_0-stdext.licence.txt
new file mode 100644 (file)
index 0000000..b4fc38a
--- /dev/null
@@ -0,0 +1,199 @@
+                Sun Microsystems, Inc.
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED
+SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY
+"AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE
+MEDIA PACKAGE.  BY OPENING THE SOFTWARE MEDIA
+PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT.
+IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY,
+INDICATE YOUR ACCEPTANCE OF THESE TERMS BY
+SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS
+AGREEMENT.  IF YOU DO NOT AGREE TO ALL THESE
+TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR
+PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE
+IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE"
+BUTTON AT THE END OF THIS AGREEMENT.
+
+1.  LICENSE TO USE.  Sun grants you a
+non-exclusive and non-transferable license for the
+internal use only of the accompanying software and
+documentation and any error corrections provided
+by Sun (collectively "Software"), by the number of
+users and the class of computer hardware for which
+the corresponding fee has been paid.
+
+2.  RESTRICTIONS Software is confidential and
+copyrighted. Title to Software and all associated
+intellectual property rights is retained by Sun
+and/or its licensors.  Except as specifically
+authorized in any Supplemental License Terms, you
+may not make copies of Software, other than a
+single copy of Software for archival purposes.
+Unless enforcement is prohibited by applicable
+law, you may not modify, decompile, or reverse
+engineer Software.  Software is not designed or
+licensed for use in on-line control of aircraft,
+air traffic, aircraft navigation or aircraft
+communications; or in the design, construction,
+operation or maintenance of any nuclear facility.
+No right, title or interest in or to any
+trademark, service mark, logo or trade name of Sun
+or its licensors is granted under this Agreement.
+
+3. LIMITED WARRANTY.  Sun warrants to you that for
+a period of ninety (90) days from the date of
+purchase, as evidenced by a copy of the receipt,
+the media on which Software is furnished (if any)
+will be free of defects in materials and
+workmanship under normal use.  Except for the
+foregoing, Software is provided "AS IS".  Your
+exclusive remedy and Sun's entire liability under
+this limited warranty will be at Sun's option to
+replace Software media or refund the fee paid for
+Software.
+
+4.  DISCLAIMER OF WARRANTY.  UNLESS SPECIFIED IN
+THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS,
+REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE
+DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE
+DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
+
+5.  LIMITATION OF LIABILITY.  TO THE EXTENT NOT
+PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS
+LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL,
+INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT
+OF OR RELATED TO THE USE OF OR INABILITY TO USE
+SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.  In no event will
+Sun's liability to you, whether in contract, tort
+(including negligence), or otherwise, exceed the
+amount paid by you for Software under this
+Agreement.  The foregoing limitations will apply
+even if the above stated warranty fails of its
+essential purpose.
+
+6.  Termination.  This Agreement is effective
+until terminated.  You may terminate this
+Agreement at any time by destroying all copies of
+Software.  This Agreement will terminate
+immediately without notice from Sun if you fail to
+comply with any provision of this Agreement.  Upon
+Termination, you must destroy all copies of
+Software.
+
+7.  Export Regulations.  All Software and
+technical data delivered under this Agreement are
+subject to US export control laws and may be
+subject to export or import regulations in other
+countries.  You agree to comply strictly with all
+such laws and regulations and acknowledge that you
+have the responsibility to obtain such licenses to
+export, re-export, or import as may be required
+after delivery to you.
+
+8.  U.S. Government Restricted Rights.  If
+Software is being acquired by or on behalf of the
+U.S. Government or by a U.S. Government prime
+contractor or subcontractor (at any tier), then
+the Government's rights in Software and
+accompanying documentation will be only as set
+forth in this Agreement; this is in accordance
+with 48 CFR 227.7201 through 227.7202-4 (for
+Department of Defense (DOD) acquisitions) and with
+48 CFR 2.101 and 12.212 (for non-DOD
+acquisitions).
+
+9.  Governing Law.  Any action related to this
+Agreement will be governed by California law and
+controlling U.S. federal law.  No choice of law
+rules of any jurisdiction will apply.
+
+10.  Severability. If any provision of this
+Agreement is held to be unenforceable, this
+Agreement will remain in effect with the provision
+omitted, unless omission would frustrate the
+intent of the parties, in which case this
+Agreement will immediately terminate.
+
+11.  Integration.  This Agreement is the entire
+agreement between you and Sun relating to its
+subject matter.  It supersedes all prior or
+contemporaneous oral or written communications,
+proposals, representations and warranties and
+prevails over any conflicting or additional terms
+of any quote, order, acknowledgment, or other
+communication between the parties relating to its
+subject matter during the term of this Agreement.
+No modification of this Agreement will be binding,
+unless in writing and signed by an authorized
+representative of each party.
+
+For inquiries please contact: Sun Microsystems,
+Inc.  901 San Antonio Road, Palo Alto, California
+94303
+
+
+SUPPLEMENTAL LICENSE TERMS
+JDBCTM 2.0 INTERFACE CLASSES
+
+These supplemental license terms ("Supplement")
+add to or modify the terms of the Binary Code
+License Agreement (collectively, the
+"Agreement"). Capitalized terms not defined in
+this Supplement shall have the same meanings
+ascribed to them in the Agreement. These
+Supplement terms shall supersede any inconsistent
+or conflicting terms in the Agreement, or in any
+license contained within the Software.
+
+1. License to Distribute. Sun grants you a
+non-exclusive, non-transferable, limited license
+to reproduce and distribute the binary and/or
+source code form of the Software to third party
+end users through multiple tiers of distribution,
+provided that you: (i) distribute the Software
+complete and unmodified in its original Java
+Archive file, and only bundled as a part of your
+program that incorporates the Software
+("Program"); (ii) do not distribute additional
+software intended to replace any component(s) of
+the Software; (iii) agree to incorporate the most
+current version of the Software that was available
+from Sun no later than 180 days prior to each
+production release of the Program; (iv) do not
+remove or alter any proprietary legends or notices
+contained in or on the Software; (v) only
+distribute the Program pursuant to a license
+agreement that protects Sun's interest consistent
+with the terms contained in the Agreement; (vi)
+may not create, or authorize your licensees to cr!
+eate additional classes, interfaces, or
+subpackages that are contained in the "java"
+"javax" or "sun" packages or similar as specified
+by Sun in any class file naming convention; and
+(vii) agree to defend and indemnify Sun and its
+licensors from and against any damages, costs,
+liabilities, settlement amounts and/or expenses
+(including attorneys' fees) incurred in connection
+with any claim, lawsuit or action by any third
+party that arises or results from the use or
+distribution of any and all Programs.
+
+2. Trademarks and Logos. You acknowledge as
+between you and Sun that Sun owns the Java
+trademark and all Java-related trademarks, logos
+and icons including the Coffee Cup and Duke ("Java
+Marks") and agree to comply with the Java
+Trademark Guidelines at
+http://java.sun.com/trademarks.html.
+
+
+
+
+
+                
\ No newline at end of file
diff --git a/lib/special/test/jta.jar b/lib/special/test/jta.jar
new file mode 100644 (file)
index 0000000..e0822a9
Binary files /dev/null and b/lib/special/test/jta.jar differ
diff --git a/lib/special/test/jta.licence.txt b/lib/special/test/jta.licence.txt
new file mode 100644 (file)
index 0000000..45328c1
--- /dev/null
@@ -0,0 +1,49 @@
+  
+Sun Microsystems, Inc. 
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE.  BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT.  IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS AGREEMENT.  IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" BUTTON AT THE END OF THIS AGREEMENT. 
+
+1.  LICENSE TO USE.  Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively "Software"), by the number of users and the class of computer hardware for which the corresponding fee has been paid. 
+
+2.  RESTRICTIONS.  Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors.  Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes.  Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software.  Licensee acknowledges that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun Microsystems, Inc. disclaims any express or implied warranty of fitness for such uses.   No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement. 
+
+3. LIMITED WARRANTY.  Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use.  Except for the foregoing, Software is provided "AS IS".  Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software. 
+
+4.  DISCLAIMER OF WARRANTY.  UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. 
+
+5.  LIMITATION OF LIABILITY.  TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  In no event will Sun's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement.  The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. 
+
+6.  Termination.  This Agreement is effective until terminated.  You may terminate this Agreement at any time by destroying all copies of Software.  This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement.  Upon Termination, you must destroy all copies of Software. 
+
+7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries.  You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you. 
+
+8.   U.S. Government Restricted Rights.  If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions). 
+
+9.  Governing Law.  Any action related to this Agreement will be governed by California law and controlling U.S. federal law.  No choice of law rules of any jurisdiction will apply. 
+
+10.  Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate. 
+
+11.  Integration.  This Agreement is the entire agreement between you and Sun relating to its subject matter.  It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement.  No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party. 
+
+JAVATM INTERFACE CLASSES 
+JAVA TRANSACTION API (JTA), VERSION 1.0.1B, MAINTENANCE RELEASE
+SUPPLEMENTAL LICENSE TERMS
+
+These supplemental license terms ("Supplemental Terms") add to or modify the terms of the Binary Code License Agreement (collectively, the "Agreement"). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 
+
+1. Software Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to Section 3 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce internally and use internally the binary form of the Software, complete and unmodified, for the sole purpose of designing, developing and testing your Java applets and applications ("Programs"). 
+
+2. License to Distribute Software.  In addition to the license granted in Section 1 (Software Internal Use and Development License Grant) of these Supplemental Terms, subject to the terms and conditions of this Agreement, including but not limited to Section 3 (Java Technology Restrictions), Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute the Software in binary form only, provided that you (i) distribute the Software complete and unmodified and only bundled as part of your Programs, (ii) do not distribute additional software intended to replace any component(s) of the Software, (iii) do not remove or alter any proprietary legends or notices contained in the Software, (iv) only distribute the Software subject to a license agreement that protects Sun's interests consistent with the terms contained in this Agreement, and (v) agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 
+
+3. Java Technology Restrictions. You may not modify the Java Platform Interface ("JPI", identified as classes contained within the "java" package or any subpackages of the "java" package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI.  In the event that you create an additional class and associated API(s) which (i) extends the functionality of the Java Platform, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Sun in any naming convention designation. 
+
+4. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET-related trademarks, service marks, logos and other brand designations ("Sun Marks"), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Sun Marks inures to Sun's benefit. 
+
+5. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement.  Source code may not be redistributed unless expressly provided for in this Agreement. 
+
+6. Termination for Infringement.  Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right. 
+
+For inquiries please contact: Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, California 95054. 
+
diff --git a/src/org/wamblee/Pair.java b/src/org/wamblee/Pair.java
new file mode 100644 (file)
index 0000000..e06e8e1
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.utils;
+
+/**
+ * Represents a pair of objects. This is inspired on the C++ Standard Template Library
+ * pair template. 
+ * 
+ * @param <T> Type of the first object. 
+ * @param <U> Type of the second object. 
+ */
+public class Pair<T,U> {
+       
+       private T _t; 
+       private U _u; 
+       
+       public Pair(T t, U u ) {
+           _t = t; 
+           _u = u; 
+       }
+       
+       public Pair(Pair<T,U> p) {
+               _t = p._t; 
+               _u = p._u; 
+       }
+       
+       public T getFirst() {
+               return _t; 
+       }
+       
+       public U getSecond() {
+               return _u; 
+       }
+
+}
diff --git a/src/org/wamblee/cache/Cache.java b/src/org/wamblee/cache/Cache.java
new file mode 100644 (file)
index 0000000..15f2c50
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * The <code>Cache</code> interface represents... a cache. 
+ * In some circumstances it is more optimal to implement caching directly in 
+ * the code instead of relying on Hibernate caching methods. This interface abstracts
+ * from the used cache implementation. 
+ * Cache implementations must be thread-safe.  
+ */
+public interface Cache<KeyType extends Serializable, ValueType extends Serializable> {
+
+    /**
+     * Adds a key-value pair to the cache. 
+     * @param aKey Key. 
+     * @param aValue Value. 
+     */
+    void put(KeyType aKey, ValueType aValue); 
+    
+    /**
+     * Retrieves a value from the cache. 
+     * @param aKey Key to retrieve. 
+     * @return Key. 
+     */
+    ValueType get(KeyType aKey);
+    
+    /**
+     * Removes an entry from the cache. 
+     * @param aKey Key to remove the entry for. 
+     */
+    void remove(KeyType aKey); 
+    
+    /**
+     * Removes all entries from the cache. 
+     *
+     */
+    void clear(); 
+}
diff --git a/src/org/wamblee/cache/CachedObject.java b/src/org/wamblee/cache/CachedObject.java
new file mode 100644 (file)
index 0000000..8a4772f
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.cache;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a cached object. The object is either retrieved from the cache if the cache
+ * has it, or a call back is invoked to get the object (and put it in the cache). 
+ */
+public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
+    
+    private static final Logger LOGGER = Logger.getLogger(CachedObject.class);
+    
+    /**
+     * Callback invoked to compute an object if it was not found in the cache. 
+     * @param <T> Type of the object 
+     */
+    public static interface Computation<Key extends Serializable, Value extends Serializable> {
+        /**
+         * Gets the object. Called when the object is not in the cache. 
+         * @param aObjectKey Id of the object in the cache. 
+         * @return Object, must be non-null. 
+         */
+        Value getObject(Key aObjectKey); 
+    }
+    
+    /**
+     * Cache to use. 
+     */
+    private Cache<KeyType, ValueType> _cache;
+    
+    /**
+     * Key of the object in the cache. 
+     */
+    private KeyType _objectKey; 
+    
+    /**
+     * Computation used to obtain the object if it is not found in the cache. 
+     */
+    private Computation<KeyType, ValueType> _computation; 
+    
+    /**
+     * Constructs the cached object. 
+     * @param aCache Cache to use. 
+     * @param aObjectKey Key of the object in the cache. 
+     * @param aComputation Computation to get the object in case the object is not in the cache.
+     */
+    public CachedObject(Cache<KeyType,ValueType> aCache, KeyType aObjectKey,  Computation<KeyType,ValueType> aComputation) {
+        _cache = aCache; 
+        _objectKey = aObjectKey; 
+        _computation = aComputation; 
+    }
+    
+    /**
+     * Gets the object. Since the object is cached, different calls to this method may return 
+     * different objects. 
+     * @return Object. 
+     */
+    public ValueType get() { 
+        ValueType object = (ValueType)_cache.get(_objectKey); // the used cache is thread safe. 
+        if ( object == null ) {
+            // synchronize the computation to make sure that the object is only computed
+            // once when multiple concurrent threads detect that the entry must be
+            // recomputed. 
+            synchronized (this) { 
+               object = (ValueType)_cache.get(_objectKey); 
+               if ( object == null ) { 
+                   // No other thread did a recomputation so we must do this now.
+                   LOGGER.debug("Refreshing cache for '" + _objectKey + "'"); 
+                   object = _computation.getObject(_objectKey);
+                   _cache.put(_objectKey, object); 
+               }
+            }
+        }
+        return object;     
+    }
+    
+    /**
+     * Invalidates the cache for the object so that it is recomputed the next 
+     * time it is requested. 
+     *
+     */
+    public void invalidate() { 
+        _cache.remove(_objectKey);
+    }
+    
+    /**
+     * Gets the cache. 
+     * @return Cache. 
+     */
+    public Cache getCache() { 
+        return _cache; 
+    }
+}
diff --git a/src/org/wamblee/cache/EhCache.java b/src/org/wamblee/cache/EhCache.java
new file mode 100644 (file)
index 0000000..4f56cd3
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */
+
+package org.wamblee.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+import org.wamblee.io.InputResource;
+
+/**
+ * Cache implemented on top of EhCache.
+ */
+public class EhCache<KeyType extends Serializable, ValueType extends Serializable>
+        implements org.wamblee.cache.Cache<KeyType, ValueType> {
+
+    private static final Logger LOGGER = Logger.getLogger(EhCache.class);
+    
+    /**
+     * EH Cache manager. 
+     */
+    private CacheManager _manager;
+
+    /**
+     * EH cache. 
+     */
+    private Cache _cache;
+
+    /**
+     * Constructs a cache based on EHCache. 
+     * @param aResource Resource containing the configuration file for 
+     *    EHCache. 
+     * @param aCacheName Name of the cache to use. If a cache with this name does 
+     *    not exist, one is created based on default settings. 
+     * @throws IOException
+     * @throws CacheException
+     */
+    public EhCache(InputResource aResource, String aCacheName)
+            throws IOException, CacheException {
+        InputStream is = aResource.getInputStream();
+        try {
+            _manager = CacheManager.create();
+            _cache = _manager.getCache(aCacheName);
+            if (_cache == null) {
+                LOGGER.warn("Creating cache '" + aCacheName + "' because it is not configured");;
+                _manager.addCache(aCacheName);
+                _cache = _manager.getCache(aCacheName);
+            }
+            assert _cache != null; 
+        } finally {
+            is.close();
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+     */
+    public void put(KeyType aKey, ValueType aValue) {
+        _cache.put(new Element(aKey, aValue));
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.cache.Cache#get(KeyType)
+     */
+    public ValueType get(KeyType aKey) {
+        try {
+            Element element =  _cache.get(aKey);
+            if ( element == null ) { 
+                return null; 
+            }
+            return (ValueType)element.getValue();
+        } catch (CacheException e) {
+            throw new RuntimeException("Cache problem key = '" + aKey + "'", e);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.cache.Cache#remove(KeyType)
+     */
+    public void remove(KeyType aKey) {
+        _cache.remove(aKey);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.cache.Cache#clear()
+     */
+    public void clear() {
+        try {
+            _cache.removeAll();
+        } catch (IOException e) {
+            throw new RuntimeException("Problem removing items from cache", e);
+        }
+    }
+}
diff --git a/src/org/wamblee/cache/ForeverCache.java b/src/org/wamblee/cache/ForeverCache.java
new file mode 100644 (file)
index 0000000..58b34dc
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * A very simple cache based on a HashMap,
+ * It never expires any entries, and has no bounds on its size. 
+ */
+public class ForeverCache<KeyType extends Serializable,ValueType extends Serializable> 
+    implements Cache<KeyType,ValueType> {
+
+    /**
+     * Cached entries. 
+     */
+    private HashMap<KeyType, ValueType> _map; 
+    
+    /**
+     * Constructs the cache. 
+     *
+     */
+    public ForeverCache() { 
+        _map = new HashMap<KeyType,ValueType>(); 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+     */
+    public synchronized void put(KeyType aKey, ValueType aValue) {
+        _map.put(aKey, aValue);    
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#get(KeyType)
+     */
+    public synchronized ValueType get(KeyType aKey) {
+        return _map.get(aKey);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#remove(KeyType)
+     */
+    public synchronized void remove(KeyType aKey) {
+        _map.remove(aKey);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#clear()
+     */
+    public synchronized void clear() {
+        _map.clear();
+    }
+}
diff --git a/src/org/wamblee/cache/ZeroCache.java b/src/org/wamblee/cache/ZeroCache.java
new file mode 100644 (file)
index 0000000..4c5e1b1
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache that does not cache. This implementation is useful for disabling caching. 
+ * Because of this implementation, application code does not need to distinguish 
+ * between the situation where it a cache is used and where it isn't. 
+ */
+public class ZeroCache<KeyType extends Serializable, ValueType extends Serializable> 
+   implements Cache<KeyType, ValueType> {
+
+    public ZeroCache() { 
+        // Empty. 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+     */
+    public void put(KeyType aKey, ValueType aValue) {
+        // Empty.    
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#get(KeyType)
+     */
+    public ValueType get(KeyType aKey) {
+        return null;
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#remove(KeyType)
+     */
+    public void remove(KeyType aKey) {
+        // Empty   
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.cache.Cache#clear()
+     */
+    public void clear() {
+        // Empty   
+    }
+}
diff --git a/src/org/wamblee/concurrency/JvmLock.java b/src/org/wamblee/concurrency/JvmLock.java
new file mode 100644 (file)
index 0000000..6de043d
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.concurrency;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * In memory JVM lock. 
+ */ 
+public class JvmLock implements Lock {
+    
+    /**
+     * Reentrant lock to use. 
+     */
+    private ReentrantLock _lock; 
+    
+    /**
+     * In-memory lock. 
+     */
+    public JvmLock() { 
+        _lock = new ReentrantLock(true);     
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.concurrency.Lock#acquire()
+     */
+    public void acquire() {
+        _lock.lock();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.concurrency.Lock#release()
+     */
+    public void release() {
+        _lock.unlock();       
+    }
+}
diff --git a/src/org/wamblee/concurrency/Lock.java b/src/org/wamblee/concurrency/Lock.java
new file mode 100644 (file)
index 0000000..a30c45e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.concurrency;
+
+/**
+ * Represents a re-entrant lock. 
+ * Implementations can provide inmemory JVM locking or full cluster safe locking 
+ * mechanisms.  
+ */
+public interface Lock {
+    /**
+     * Acquires the lock. 
+     */
+    void acquire(); 
+    
+    /**
+     * Releases the lock. 
+     */
+    void release(); 
+}
\ No newline at end of file
diff --git a/src/org/wamblee/concurrency/LockAdvice.java b/src/org/wamblee/concurrency/LockAdvice.java
new file mode 100644 (file)
index 0000000..7137b52
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.concurrency;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * Locking advice. This automatically synchronized an object using a given lock.  
+ */
+public class LockAdvice implements MethodInterceptor {
+    
+    /**
+     * Lock to use. 
+     */
+    private Lock _lock; 
+    
+    /**
+     * Constructs lock advice.  
+     * @param aLock Lock to use. 
+     */
+    public LockAdvice(Lock aLock) { 
+        _lock = aLock; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+     */
+    public Object invoke(MethodInvocation aInvocation) throws Throwable {
+        _lock.acquire();
+        try {  
+            return aInvocation.proceed(); 
+        } finally { 
+            _lock.release();
+        }
+    }
+
+}
diff --git a/src/org/wamblee/concurrency/ReadWriteLock.java b/src/org/wamblee/concurrency/ReadWriteLock.java
new file mode 100644 (file)
index 0000000..e765816
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.concurrency;
+
+import java.util.HashSet;
+
+
+/**
+ * Read-write lock for allowing multiple concurrent readers or at most one
+ * writer. This implementation does not aim for high performance but for
+ * robustness and simplicity. Users of this class should not synchronize on
+ * objects of this class.
+ */
+public class ReadWriteLock {
+    /**
+     * Sets containing the references to the threads that are currently
+     * reading. This administration is useful to check that the lock has
+     * already been  acquired before it is release. This check adds robustness
+     * to the application.
+     */
+    private HashSet<Thread> _readers;
+
+    /**
+     * The thread that has acquired the lock for writing or null if no such
+     * thread exists currently.
+     */
+    private Thread _writer;
+
+    /**
+     * Constructs read-write lock.
+     */
+    public ReadWriteLock() {
+        _readers     = new HashSet<Thread>();
+        _writer      = null;
+    }
+
+    /**
+     * Acquires the lock for reading. This call will block until the lock can
+     * be acquired.
+     *
+     * @throws IllegalStateException Thrown if the read or  write lock is
+     *         already acquired.
+     */
+    public synchronized void acquireRead() {
+        if (_readers.contains(Thread.currentThread())) {
+            throw new IllegalStateException(
+                "Read lock already acquired by current thread: "
+                + Thread.currentThread());
+        }
+
+        if (_writer == Thread.currentThread()) {
+            throw new IllegalStateException(
+                "Trying to acquire the read lock while already holding a write lock: "
+                + Thread.currentThread());
+        }
+
+        while (_writer != null) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                notifyAll();
+            }
+        }
+
+        _readers.add(Thread.currentThread());
+    }
+
+    /**
+     * Releases the lock for reading. Note: This implementation assumes that
+     * the lock has already been acquired for reading previously.
+     *
+     * @throws IllegalStateException Thrown when the lock was not acquired by
+     *         this thread.
+     */
+    public synchronized void releaseRead() {
+        if (!_readers.remove(Thread.currentThread())) {
+            throw new IllegalStateException(
+                "Cannot release read lock because current thread has not acquired it.");
+        }
+
+        if (_readers.size() == 0) {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Acquires the lock for writing. This call will block until the lock has
+     * been acquired.
+     *
+     * @throws IllegalStateException Thrown if the read or write lock is
+     *         already acquired.
+     */
+    public synchronized void acquireWrite() {
+        if (_writer == Thread.currentThread()) {
+            throw new IllegalStateException(
+                "Trying to acquire a write lock while already holding the write lock: "
+                + Thread.currentThread());
+        }
+
+        if (_readers.contains(Thread.currentThread())) {
+            throw new IllegalStateException(
+                "Trying to acquire a write lock while already holding the read lock: "
+                + Thread.currentThread());
+        }
+
+        // wait until there are no more writers and no more
+        // readers 
+        while ((_writer != null) || (_readers.size() > 0)) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                notifyAll();
+            }
+        }
+
+        _writer = Thread.currentThread();
+
+        // notification not necessary since all writers and
+        // readers are now blocked by this thread.
+    }
+
+    /**
+     * Releases the lock for writing.
+     *
+     * @throws IllegalStateException Thrown when the lock was not acquired.
+     */
+    public synchronized void releaseWrite() {
+        if (_writer != Thread.currentThread()) {
+            throw new IllegalStateException(
+                "Cannot release write lock because it was not acquired. ");
+        }
+
+        _writer = null;
+        notifyAll();
+    }
+}
diff --git a/src/org/wamblee/general/BeanFactory.java b/src/org/wamblee/general/BeanFactory.java
new file mode 100644 (file)
index 0000000..53f006f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.general;
+
+/**
+ * Bean factory used to obtain objects in a transparent way. 
+ */
+public interface BeanFactory {
+
+    /**
+     * Finds a bean based on id. 
+     * @param aId Id of the bean. 
+     * @return Object  (always non-null). 
+     * @throws BeanFactoryException In case the object could not be found. 
+     */
+    Object find(String aId);  
+    
+    /**
+     * Finds a bean of the given class and which can be cast to the 
+     * specified class. This is typically used by specifying the interface
+     * class for retrieving an implementation of that class. This 
+     * means that the bean implementing the class is configured in the bean factory
+     * with id equal to the class name of the interface.  
+     * @param aClass
+     * @return Object (always non-null).
+     * @throws BeanFactoryException In case the object could not be found. 
+     */
+     <T> T find(Class<T> aClass);
+     
+     /**
+      * Finds a bean with the given id which can be cast to the specified
+      * class.  
+      * @param <T> Type of the object to get. 
+      * @param aId Id of the object to lookup. 
+      * @param aClass Class that the object must extends. 
+      * @return Object, always non-null. 
+      * @throws BeanFactoryException In case the object could not be found. 
+      */
+     <T> T find(String aId, Class<T> aClass); 
+}
diff --git a/src/org/wamblee/general/BeanFactoryException.java b/src/org/wamblee/general/BeanFactoryException.java
new file mode 100644 (file)
index 0000000..5eb7b48
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.general;
+
+/**
+ * Exception thrown by the BeanFactory if an object could not be found. 
+ */
+public class BeanFactoryException extends RuntimeException {
+    static final long serialVersionUID = -1215992188624874902L;
+
+    public BeanFactoryException(String aMsg) { 
+        super(aMsg); 
+    }
+    
+    public BeanFactoryException(String aMsg, Throwable aThrowable) {
+        super(aMsg, aThrowable);
+    }
+    
+    public static <T extends Object> T bla(Class<T> ddk) {
+        return null; 
+    }
+}
diff --git a/src/org/wamblee/general/BeanKernel.java b/src/org/wamblee/general/BeanKernel.java
new file mode 100644 (file)
index 0000000..fa48fb9
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */
+package org.wamblee.general;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * The standard means to obtain the bean factory.
+ */
+public class BeanKernel {
+
+    private static final Log LOG = LogFactory.getLog(BeanKernel.class);
+
+    /**
+     * Bean factory kernel properties file.
+     */
+    private static final String BEAN_KERNEL_PROP_FILE = "org.wamblee.beanfactory.properties";
+
+    /**
+     * Name of the property to define the name of the bean factory class to use.
+     * THis class must have a public default constructor.
+     */
+    private static final String BEAN_FACTORY_CLASS = "org.wamblee.beanfactory.class";
+
+    /**
+     * Cached bean factory.
+     */
+    private static BeanFactory BEAN_FACTORY;
+
+    /**
+     * Overrides the default mechanism for looking up the bean factory by
+     * specifying it yourself.
+     * 
+     * @param aOverride
+     *            Override bean factory.
+     */
+    public static void overrideBeanFactory(BeanFactory aOverride) {
+        BEAN_FACTORY = aOverride;
+    }
+
+    /**
+     * Gets the bean factory. 
+     * @return Bean factory. 
+     */
+    public static BeanFactory getBeanFactory() {
+        synchronized (BeanFactory.class) {
+            if (BEAN_FACTORY == null) {
+                BEAN_FACTORY = lookupBeanFactory();
+            }
+        }
+        return BEAN_FACTORY;
+    }
+
+    /**
+     * Lookup the bean factory based on the properties file.
+     * 
+     * @return Bean factory.
+     */
+    private static BeanFactory lookupBeanFactory() {
+        InputResource resource = new ClassPathResource(BEAN_KERNEL_PROP_FILE);
+        InputStream is;
+        try {
+            is = resource.getInputStream();
+        } catch (IOException e) {
+            throw new BeanFactoryException("Cannot open resource " + resource,
+                    e);
+        }
+        try {
+            Properties props = new Properties();
+            props.load(resource.getInputStream());
+            String className = props.getProperty(BEAN_FACTORY_CLASS);
+            Class beanFactory = Class.forName(className);
+            return (BeanFactory) beanFactory.newInstance();
+        } catch (Exception e) {
+            throw new BeanFactoryException("Cannot read from resource "
+                    + resource, e);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                // last resort cannot do much now.
+                LOG.error("Error closing resource " + resource);
+            }
+        }
+    }
+}
diff --git a/src/org/wamblee/general/SpringBeanFactory.java b/src/org/wamblee/general/SpringBeanFactory.java
new file mode 100644 (file)
index 0000000..05a421e
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.general;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.access.BeanFactoryReference;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+
+/**
+ * Bean factory which uses Spring. 
+ */
+public class SpringBeanFactory implements BeanFactory {
+    
+    private String _factoryName; 
+    
+    public SpringBeanFactory(String aFactoryName) {
+        _factoryName = aFactoryName; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+     */
+    public Object find(String aId) {
+       return find(aId, Object.class);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+     */
+    public <T> T find(Class<T> aClass) {
+        return find(aClass.getName(), aClass);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+     */
+    public <T> T find(String aId, Class<T> aClass) {
+        BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(); 
+        BeanFactoryReference beanFactory = locator.useBeanFactory(_factoryName);
+       
+        try {
+            Object obj = beanFactory.getFactory().getBean(aId, aClass);
+            assert obj != null; 
+            return aClass.cast(obj); 
+        } catch (BeansException e) { 
+            throw new BeanFactoryException(e.getMessage(), e);
+        }
+    }
+
+}
diff --git a/src/org/wamblee/io/ClassPathResource.java b/src/org/wamblee/io/ClassPathResource.java
new file mode 100644 (file)
index 0000000..23c8941
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents an input resource in the classpath.
+ */
+public class ClassPathResource implements InputResource {
+    /**
+     * Resource name.
+     */
+    private String _resource;
+
+    /**
+     * Construct the class path resource.
+     * @param aResource Resource
+     */
+    public ClassPathResource( String aResource ) {
+        _resource = aResource;
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.io.InputResource#getInputStream()
+     */
+    public InputStream getInputStream(  ) throws IOException {
+        InputStream stream = Thread.currentThread(  ).getContextClassLoader(  )
+                     .getResourceAsStream( _resource );
+        if ( stream == null ) {
+            throw new IOException("Class path resource '" + _resource + "' not found.");
+        }
+        return stream; 
+    }
+    
+    /** (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString( ) {
+        return "ClassPathResource(" + _resource + ")";
+    }
+}
diff --git a/src/org/wamblee/io/FileResource.java b/src/org/wamblee/io/FileResource.java
new file mode 100644 (file)
index 0000000..63ac27e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.io;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+
+/**
+ * Resource implemention for reading from a file.
+ */
+public class FileResource implements InputResource {
+    /**
+     * File to read.
+     */
+    private File _file;
+
+    /**
+     * Constructs the resource.
+     * @param aFile File to read. 
+     */
+    public FileResource( File aFile ) {
+        _file = aFile;
+    }
+
+    /** (non-Javadoc)
+     * @see org.wamblee.io.InputResource#getInputStream()
+     */
+    public InputStream getInputStream(  ) throws IOException {
+        return new BufferedInputStream( new FileInputStream( _file ) );
+    }
+}
diff --git a/src/org/wamblee/io/InputResource.java b/src/org/wamblee/io/InputResource.java
new file mode 100644 (file)
index 0000000..aa4fb91
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Represents a resource from which information can be read.
+ */
+public interface InputResource {
+    /**
+     * Gets the input stream to the resource. The obtained input stream 
+     * must be closed once reading has finished. 
+     * 
+     * @return Input stream to the resource, never null.
+     * @throws IOException in case the resource cannot be found.
+     */
+    InputStream getInputStream(  ) throws IOException;
+}
diff --git a/src/org/wamblee/io/StreamResource.java b/src/org/wamblee/io/StreamResource.java
new file mode 100644 (file)
index 0000000..e2feb53
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Input resource based on an input stream.
+ */
+public class StreamResource implements InputResource {
+    /**
+     * Input stream to read.
+     */
+    private InputStream _stream;
+
+    /**
+     * Constructs a resource.
+     * @param aStream Input stream to read.
+     */
+    public StreamResource( InputStream aStream ) {
+        _stream = aStream;
+    }
+
+    /** (non-Javadoc)
+     * @see InputResource#getInputStream()
+     */
+    public InputStream getInputStream(  ) throws IOException {
+        return _stream;
+    }
+}
diff --git a/src/org/wamblee/observer/DefaultObserverNotifier.java b/src/org/wamblee/observer/DefaultObserverNotifier.java
new file mode 100644 (file)
index 0000000..fbc3970
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.observer;
+
+/**
+ * Default observer notifier which calls {@link org.wamblee.observer.Observer#send(ObservableType, Event)}
+ * immediately. 
+ */
+public class DefaultObserverNotifier<ObservableType,Event> implements ObserverNotifier<ObservableType,Event>{
+
+    /**
+     * Constructs the notifier. 
+     *
+     */
+    public DefaultObserverNotifier() { 
+        // Empty
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.observer.ObserverNotifier#update(org.wamblee.observer.Observer, ObservableType, Event)
+     */
+    public void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent) {
+        aObserver.send(aObservable, aEvent);   
+    }
+}
diff --git a/src/org/wamblee/observer/Observable.java b/src/org/wamblee/observer/Observable.java
new file mode 100644 (file)
index 0000000..7af7ea7
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.observer;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Implements subscription and notification logic for an observer pattern.
+ * This class is thread safe. 
+ */
+public class Observable<ObservableType, Event> {
+    
+    private static final Logger LOGGER = Logger.getLogger(Observable.class);
+    
+    /**
+     * Observable. 
+     */
+    private ObservableType _observable;
+    
+    /**
+     * Used to notify observers. 
+     */
+    private ObserverNotifier<ObservableType,Event> _notifier;
+    
+    /**
+     * Map of subscription to observer. 
+     */
+    private Map<Long, Observer<ObservableType,Event>> _observers;
+    
+    /**
+     * Counter for subscriptions. Holds the next subscription. 
+     */
+    private long _counter; 
+    
+    /**
+     * Constructs the observable. 
+     * @param aObservable Observable this instance is used for. 
+     * @param aNotifier Object used for implementing notification of listeners. 
+     */
+    public Observable(ObservableType aObservable, ObserverNotifier<ObservableType,Event> aNotifier) { 
+        _observable = aObservable;
+        _notifier = aNotifier; 
+        _observers = new TreeMap<Long, Observer<ObservableType, Event>>();
+        _counter = 0; 
+    }
+    
+    /**
+     * Subscribe an obvers. 
+     * @param aObserver Observer to subscribe. 
+     * @return Event Event to send. 
+     */
+    public synchronized long subscribe(Observer<ObservableType, Event> aObserver) {
+        long subscription = _counter++; // integer rage is so large it will never roll over. 
+        _observers.put(subscription, aObserver);
+        return subscription; 
+    }
+    
+    /**
+     * Unsubscribe an observer. 
+     * @param aSubscription Subscription which is used
+     * @throws IllegalArgumentException In case the subscription is not known. 
+     */
+    public synchronized void unsubscribe(long aSubscription) {
+        Object obj = _observers.remove(aSubscription);
+        if ( obj == null ) { 
+            throw new IllegalArgumentException("Subscription '" + aSubscription + "'");
+        }
+    }
+    
+    /**
+     * Gets the number of subscribed observers. 
+     */
+    public int getObserverCount() { 
+        return _observers.size(); 
+    }
+    
+    /**
+     * Notifies all subscribed observers. 
+     * @param aEvent Event to send. 
+     */
+    public void send(Event aEvent) {
+        // Make sure we do the notification while not holding the lock to avoid potential deadlock
+        // situations. 
+        List<Observer<ObservableType,Event>> observers = new ArrayList<Observer<ObservableType, Event>>();
+        synchronized (this) { 
+            observers.addAll(_observers.values());
+        }
+        for (Observer<ObservableType,Event> observer: observers) { 
+            _notifier.update(observer, _observable, aEvent); 
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#finalize()
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        if ( _observers.size() > 0 ) { 
+            LOGGER.error("Still observers registered at finalization of observer!"); 
+            for (Observer observer: _observers.values()) { 
+                LOGGER.error("  observer: " + observer);
+            }
+        }
+        
+        super.finalize();
+    }
+
+}
diff --git a/src/org/wamblee/observer/Observer.java b/src/org/wamblee/observer/Observer.java
new file mode 100644 (file)
index 0000000..9150ddb
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.observer;
+
+/**
+ * This is a type-safe version of {@link java.util.Observable}. 
+ */
+public interface Observer<ObservableType,Event> {
+
+    /**
+     * Called when an event has occurred on the observable.
+     * @param aObservabdle Observable. 
+     * @param aEvent Event. 
+     */
+    void send(ObservableType aObservable, Event aEvent); 
+}
diff --git a/src/org/wamblee/observer/ObserverNotifier.java b/src/org/wamblee/observer/ObserverNotifier.java
new file mode 100644 (file)
index 0000000..26cbd58
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.observer;
+
+/**
+ * Implementation of notification of subscribers. 
+ */
+public interface ObserverNotifier<ObservableType, Event> {
+    
+    /**
+     * Notifies an observer. 
+     *
+     * @param aObserver Observer to notify
+     * @param aObservable Observable at which the event occured. 
+     * @param aEvent Event that occured. 
+     */
+    void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent);
+
+}
diff --git a/src/org/wamblee/persistence/AbstractPersistent.java b/src/org/wamblee/persistence/AbstractPersistent.java
new file mode 100644 (file)
index 0000000..6514256
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of Persistent. 
+ */
+public abstract class AbstractPersistent implements Persistent {
+    
+    /**
+     * Primary key. 
+     */
+    private Serializable _primaryKey; 
+    
+    /**
+     * Version. 
+     */
+    private int _version; 
+    
+    /**
+     * Constructs the object. 
+     *
+     */
+    protected AbstractPersistent() { 
+        _primaryKey = null; 
+        _version = -1; 
+    }
+    
+    /**
+     * Copy constructor. 
+     * @param aPersistent Object to copy. 
+     */
+    protected AbstractPersistent(AbstractPersistent aPersistent) {
+        _primaryKey = aPersistent._primaryKey; 
+        _version = aPersistent._version; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.persistence.Persistent#getPrimaryKey()
+     */
+    public Serializable getPrimaryKey() {
+        return _primaryKey; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.persistence.Persistent#setPrimaryKey(java.io.Serializable)
+     */
+    public void setPrimaryKey(Serializable aKey) {
+        _primaryKey = aKey; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.persistence.Persistent#getPersistedVersion()
+     */
+    public int getPersistedVersion() {
+        return _version; 
+    }
+    
+    /* (non-Javadoc)
+     * @see org.wamblee.persistence.Persistent#setPersistedVersion(int)
+     */
+    public void setPersistedVersion(int aVersion) {
+        _version = aVersion;
+    }
+}
diff --git a/src/org/wamblee/persistence/Persistent.java b/src/org/wamblee/persistence/Persistent.java
new file mode 100644 (file)
index 0000000..cd56ca4
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Interface for persistent objects. This defines required functionality for all objects
+ * that are persisted. 
+ * 
+ * Objects that implement this interface and which implement 
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * should exclude the primary key and version from determining equality. 
+ */
+public interface Persistent {
+
+    /**
+     * Gets the primary key. 
+     * @return Primary key. 
+     */
+    Serializable getPrimaryKey(); 
+    
+    /**
+     * Sets the primary key. 
+     * @param aKey Primary key. 
+     */
+    void setPrimaryKey(Serializable aKey); 
+    
+    /**
+     * Gets the version. 
+     * @return Version. 
+     */
+    int getPersistedVersion(); 
+    
+    /**
+     * Sets the version. 
+     * @param aVersion Version. 
+     */
+    void setPersistedVersion(int aVersion); 
+}
diff --git a/src/org/wamblee/persistence/hibernate/HibernateMappingFiles.java b/src/org/wamblee/persistence/hibernate/HibernateMappingFiles.java
new file mode 100644 (file)
index 0000000..b051455
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.persistence.hibernate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Hibernate mapping files to use. 
+ */
+public class HibernateMappingFiles extends ArrayList<String> {
+    
+    /**
+     * Constructs an empty list of hibernate mapping files. 
+     *
+     */
+    public HibernateMappingFiles() { 
+        super(); 
+    }
+    
+    /**
+     * Constructs the list of Spring config files. 
+     * @param aFiles Files. 
+     */
+    public HibernateMappingFiles(String[] aFiles) { 
+        super(); 
+        addAll(Arrays.asList(aFiles));
+    }
+    
+}
diff --git a/src/org/wamblee/persistence/hibernate/HibernateSupport.java b/src/org/wamblee/persistence/hibernate/HibernateSupport.java
new file mode 100644 (file)
index 0000000..29debe7
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */
+
+package org.wamblee.persistence.hibernate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Extension of
+ * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
+ */
+public class HibernateSupport extends HibernateDaoSupport {
+    
+    private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
+    
+    /**
+     * This class provided an equality operation based on the object reference of the
+     * wrapped object. This is required because we cannto assume that the equals operation
+     * has any meaning for different types of persistent objects. This allows us to use
+     * the standard collection classes for detecting cyclic dependences and avoiding 
+     * recursion. 
+     *
+     */
+    private static final class ObjectElem {
+        private Object _object;
+        public ObjectElem(Object aObject) {
+            _object = aObject; 
+        }
+        public boolean equals(Object obj) {
+            return ((ObjectElem)obj)._object == _object; 
+        }
+        public int hashCode() {
+            return _object.hashCode(); 
+        }
+    }
+
+    /**
+     * Constructs the object.
+     * 
+     */
+    public HibernateSupport() {
+        // Empty
+    }
+
+    /**
+     * Performes a hibernate <code>Session.merge()</code> and updates the
+     * object with the correct primary key and version. This is an extension to
+     * the hibernate merge operation because hibernate itself leaves the object
+     * passed to merge untouched.
+     * 
+     * Use this method with extreme caution since it will recursively load all 
+     * objects that the current object has relations with and for which cascade="merge"
+     * was specified in the Hibernate mapping file. 
+     * 
+     * @param aPersistent Object to merge. 
+     */
+    public void merge(Persistent aPersistent) {
+        merge(getHibernateTemplate(), aPersistent);
+    }
+    
+    /**
+     * As {@link #merge(Persistent)} but with a given template. 
+     * This method can be accessed in a static way. 
+     * @param aTemplate Hibernate template 
+     * @param aPersistent Object to merge. 
+     */
+    public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
+        Persistent merged = (Persistent) aTemplate.merge(
+                aPersistent);
+        processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
+    }
+    
+    
+
+    /**
+     * Copies primary keys and version from the result of the merged to the 
+     * object that was passed to the merge operation. It does this by traversing
+     * the properties of the object. It copies the primary key and version for 
+     * objects that implement {@link Persistent} and applies the same rules to
+     * objects in maps and sets as well (i.e. recursively). 
+     * @param aPersistent Object whose primary key and version are to be set. 
+     * @param aMerged Object that was the result of the merge.
+     * @param aProcessed List of already processed Persistent objects of the persistent part.   
+     */
+    public static void processPersistent(Persistent aPersistent,
+            Persistent aMerged, List<ObjectElem> aProcessed) {
+        if ( aPersistent == null && aMerged == null ) { 
+            return; 
+        }
+        if ( aPersistent == null || aMerged == null ) { 
+            throw new RuntimeException("persistent or merged object is null '" + aPersistent + "'" + 
+                    "  '" + aMerged + "'"); 
+        }
+        ObjectElem elem = new ObjectElem(aPersistent); 
+        if ( aProcessed.contains(elem)) { 
+            return; // already processed. 
+        }
+        aProcessed.add(elem);
+        
+        LOG.info("Setting pk/version on " + aPersistent + " from " + aMerged);
+        
+        if ( aPersistent.getPrimaryKey() != null && 
+                !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey()) ) {
+            LOG.error("Mismatch between primary key values: " + aPersistent + " " + aMerged); 
+        } else {
+            aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
+            aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
+        }
+
+        Method[] methods = aPersistent.getClass().getMethods();
+        for (Method getter : methods) {
+            if (getter.getName().startsWith("get")) {
+                Class returnType = getter.getReturnType();
+
+                try {
+                    if (Set.class.isAssignableFrom(returnType)) {
+                        Set merged = (Set) getter.invoke(aMerged);
+                        Set persistent = (Set) getter.invoke(aPersistent);
+                        processSet(persistent, merged, aProcessed);
+                    } else if ( List.class.isAssignableFrom(returnType)) {
+                        List merged = (List) getter.invoke(aMerged);
+                        List persistent = (List) getter.invoke(aPersistent);
+                        processList(persistent, merged, aProcessed);
+                    } else if ( Map.class.isAssignableFrom(returnType)) {
+                        Map merged = (Map) getter.invoke(aMerged);
+                        Map persistent = (Map) getter.invoke(aPersistent);
+                        processMap(persistent, merged, aProcessed);
+                    } else if ( Persistent.class.isAssignableFrom(returnType)) {
+                        Persistent merged = (Persistent) getter.invoke(aMerged); 
+                        Persistent persistent = (Persistent) getter.invoke(aPersistent); 
+                        processPersistent(persistent, merged, aProcessed);
+                    } else if ( returnType.isArray() && 
+                            Persistent.class.isAssignableFrom(returnType.getComponentType())) {
+                        Persistent[] merged = (Persistent[]) getter.invoke(aMerged);
+                        Persistent[] persistent = (Persistent[]) getter.invoke(aPersistent);
+                        for (int i = 0; i < persistent.length; i++) { 
+                            processPersistent(persistent[i], merged[i], aProcessed);
+                        }
+                    }
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Process the persistent objects in the collections. 
+     * @param aPersistent Collection in the original object. 
+     * @param aMerged Collection as a result of the merge. 
+     * @param aProcessed List of processed persistent objects. 
+     */
+    public static void processList(List aPersistent, List aMerged, List<ObjectElem> aProcessed) {
+        Object[] merged = aMerged.toArray();
+        Object[] persistent = aPersistent.toArray();
+        if (merged.length != persistent.length) {
+            throw new RuntimeException("Array sizes differ " + merged.length
+                    + " " + persistent.length);
+        }
+        for (int i = 0; i < merged.length; i++) {
+            assert merged[i].equals(persistent[i]);
+            if (merged[i] instanceof Persistent) {
+                processPersistent((Persistent)persistent[i], (Persistent)merged[i], aProcessed);
+            }
+        }
+    }
+    
+    /**
+     * Process the persistent objects in sets. 
+     * @param aPersistent Collection in the original object. 
+     * @param aMerged Collection as a result of the merge. 
+     * @param aProcessed List of processed persistent objects. 
+     */
+    public static void processSet(Set aPersistent, Set aMerged, List<ObjectElem> aProcessed) {
+        if (aMerged.size() != aPersistent.size()) {
+            throw new RuntimeException("Array sizes differ " + aMerged.size()
+                    + " " + aPersistent.size());
+        }
+        for (Object merged: aMerged) { 
+            // Find the object that equals the merged[i]
+            for (Object persistent: aPersistent) { 
+                if ( persistent.equals(merged)) {
+                    processPersistent((Persistent)persistent, (Persistent)merged, aProcessed);
+                    break;
+                }
+            }
+        }
+    }
+    
+    /**
+     * Process the Map objects in the collections. 
+     * @param aPersistent Collection in the original object. 
+     * @param aMerged Collection as a result of the merge. 
+     * @param aProcessed List of processed persistent objects. 
+     */
+    public static void processMap(Map aPersistent, Map aMerged, List<ObjectElem> aProcessed) { 
+        if ( aMerged.size() != aPersistent.size() ) { 
+            throw new RuntimeException("Sizes differ " + aMerged.size() + " " + aPersistent.size()); 
+        }
+        Set keys = aMerged.keySet();
+        for (Object key: keys) { 
+            if ( !aPersistent.containsKey(key) ) {
+                throw new RuntimeException("Key '" + key + "' not found"); 
+            }
+            Object mergedValue = aMerged.get(key); 
+            Object persistentValue = aPersistent.get(key); 
+            if ( mergedValue instanceof Persistent ) {
+                if ( persistentValue instanceof Persistent ) {
+                    processPersistent((Persistent)persistentValue, (Persistent)mergedValue, aProcessed);
+                } else {
+                    throw new RuntimeException("Value in original object is null, whereas merged object contains a value");
+                }
+            }
+        }
+    }
+
+}
diff --git a/test/org/wamblee/concurrency/ReadWriteLockTest.java b/test/org/wamblee/concurrency/ReadWriteLockTest.java
new file mode 100644 (file)
index 0000000..84afd1c
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+    private ReadWriteLock _lock;
+    int                   _nReaders;
+    int                   _nWriters;
+
+    /**
+     * Constructor for ReadWriteLockTest.
+     *
+     * @param aName
+     */
+    public ReadWriteLockTest(String aName) {
+        super(aName);
+    }
+
+    private synchronized int getReaderCount() {
+        return _nReaders;
+    }
+
+    private synchronized int getWriterCount() {
+        return _nWriters;
+    }
+
+    synchronized void incrementReaderCount() {
+        _nReaders++;
+    }
+
+    synchronized void incrementWriterCount() {
+        _nWriters++;
+    }
+
+    synchronized void decrementReaderCount() {
+        _nReaders--;
+    }
+
+    synchronized void decrementWriterCount() {
+        _nWriters--;
+    }
+
+    /*
+     * @see TestCase#setUp()
+     */
+    protected void setUp() throws Exception {
+        super.setUp();
+        _lock = new ReadWriteLock();
+    }
+
+    /*
+     * @see TestCase#tearDown()
+     */
+    protected void tearDown() throws Exception {
+        _lock = null;
+        super.tearDown();
+    }
+
+    /**
+     * Acquire and release a read lock.
+     */
+    public void testRead() {
+        _lock.acquireRead();
+        _lock.releaseRead();
+    }
+
+    /**
+     * Acquire and release a write lock.
+     */
+    public void testWrite() {
+        _lock.acquireWrite();
+        _lock.releaseWrite();
+    }
+
+    /**
+     * Verify concurrent access by multiple readers is possible.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testMultipleReaders() throws InterruptedException {
+        Runnable runnable = new ReadLocker(_lock, this, 2000);
+
+        Thread   t1 = new Thread(runnable);
+        t1.start();
+
+        Thread t2 = new Thread(runnable);
+        t2.start();
+        Thread.sleep(1000);
+        assertTrue("Not enough readers!", getReaderCount() == 2);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that only one writer at a time can acquire the write lock.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testSingleWriter() throws InterruptedException {
+        WriteLocker writer = new WriteLocker(_lock, this, 1000);
+        Thread      t1 = new Thread(writer);
+        Thread      t2 = new Thread(writer);
+
+        t1.start();
+        t2.start();
+        Thread.sleep(500);
+        assertTrue("Wrong writer count: " + getWriterCount(),
+            getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that multiple writers cannot acquire the write lock concurrently.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testMultipleWriters() throws InterruptedException {
+        WriteLocker writer1 = new WriteLocker(_lock, this, 1500);
+        WriteLocker writer2 = new WriteLocker(_lock, this, 1000);
+        Thread      t1      = new Thread(writer1);
+        Thread      t2      = new Thread(writer2);
+
+        t1.start();
+        Thread.sleep(500);
+        assertTrue(getWriterCount() == 1);
+        t2.start();
+        Thread.sleep(500);
+        assertTrue(getWriterCount() == 1); // first writer still
+
+        // has the lock.
+        Thread.sleep(1000);
+
+        // at t = 2, the second writer still must have
+        // a lock. 
+        assertTrue(getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that after the first reader acquires a lock,  a subsequent writer
+     * can only acquire the lock after the reader has released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite1() throws InterruptedException {
+        ReadLocker  readLocker  = new ReadLocker(_lock, this, 2000);
+        Thread      t1          = new Thread(readLocker);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, 2000);
+        Thread      t2          = new Thread(writeLocker);
+
+        t1.start(); // acquire read lock
+        Thread.sleep(500);
+        assertTrue(getReaderCount() == 1);
+        t2.start();
+        Thread.sleep(500);
+
+        // 1 second underway, reader still holding the
+        //   lock so write lock cannot be acquired.
+        assertTrue(getReaderCount() == 1);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(1500);
+
+        // 2.5 seconds underway, read lock released and 
+        // write lock must be acquired. 
+        assertTrue("Wrong no. of readers: " + getReaderCount(),
+            getReaderCount() == 0);
+        assertTrue(getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that when multiple readers have acquired a read lock, the writer
+     * can only acquire the lock after all readers have released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite2() throws InterruptedException {
+        ReadLocker  readLocker1 = new ReadLocker(_lock, this, 2500);
+        ReadLocker  readLocker2 = new ReadLocker(_lock, this, 2500);
+        Thread      t1          = new Thread(readLocker1);
+        Thread      t2          = new Thread(readLocker2);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, 2000);
+        Thread      t3          = new Thread(writeLocker);
+
+        t1.start(); // acquire read lock
+        Thread.sleep(1000);
+        assertTrue(getReaderCount() == 1);
+        t2.start();
+        Thread.sleep(500);
+        assertTrue(getReaderCount() == 2);
+        t3.start();
+        Thread.sleep(500);
+
+        // 2 seconds, 
+        assertTrue(getReaderCount() == 2);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(1000);
+
+        // 3 seconds underway, first read lock must
+        // have been released.
+        assertTrue(getReaderCount() == 1);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(500);
+
+        // 4 seconds underway, write lock must have 
+        // been acquired. 
+        assertTrue(getReaderCount() == 0);
+        assertTrue(getWriterCount() == 1);
+
+        t1.join();
+        t2.join();
+        t3.join();
+    }
+
+    /**
+     * Verify that after a writer acquires a lock,  a subsequent reader can
+     * only acquire the lock after the writer has released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite3() throws InterruptedException {
+        ReadLocker  readLocker  = new ReadLocker(_lock, this, 2000);
+        Thread      t1          = new Thread(readLocker);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, 2000);
+        Thread      t2          = new Thread(writeLocker);
+
+        t2.start(); // acquire write lock
+        Thread.sleep(500);
+        assertTrue(getWriterCount() == 1);
+        t1.start();
+        Thread.sleep(500);
+
+        // 1 second underway, writer still holding the
+        //   lock so read lock cannot be acquired.
+        assertTrue(getWriterCount() == 1);
+        assertTrue(getReaderCount() == 0);
+        Thread.sleep(1500);
+
+        // 2.5 seconds underway, write lock released and 
+        // read lock must be acquired. 
+        assertTrue("Wrong no. of writers: " + getReaderCount(),
+            getWriterCount() == 0);
+        assertTrue(getReaderCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /*
+     * The following test cases are for testing whether or not
+     * the read write lock checks the locking correctly.
+     * Strictly speaking, these checks wouldn't be necessary
+     * because it involves the contract of the ReadWriteLock which
+     * must be obeyed by users of the ReadWriteLock. Nevertheless,
+     * this is tested anyway to be absolutely sure.
+     */
+
+    /**
+     * Acquire a read lock from one thread, release it from another. Verify
+     * that a RuntimeException is thrown.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReleaseReadFromWrongThread() throws InterruptedException {
+        Thread t1 = null;
+
+        try {
+            t1 = new Thread(new Runnable() {
+                        public void run() {
+                            ReadWriteLockTest.this._lock.acquireRead();
+                        }
+                    });
+            t1.start();
+            Thread.sleep(1000); // wait until thread is started
+            _lock.releaseRead(); // release lock from wrong thread.
+        } catch (RuntimeException e) {
+            return; // ok
+        } finally {
+            t1.join();
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a write lock from one thread, release it from another. Verify
+     * that a RuntimeException is thrown.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReleaseWriteFromWrongThread() throws InterruptedException {
+        Thread t1 = null;
+
+        try {
+            t1 = new Thread(new Runnable() {
+                        public void run() {
+                            ReadWriteLockTest.this._lock.acquireWrite();
+                        }
+                    });
+            t1.start();
+            Thread.sleep(1000); // wait until thread is started
+            _lock.releaseWrite(); // release lock from wrong thread.
+        } catch (RuntimeException e) {
+            return; // ok
+        } finally {
+            t1.join();
+        }
+
+        fail();
+    }
+
+    /**
+     * Try to acquire a read lock multiple times. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireReadTwice() {
+        try {
+            _lock.acquireRead();
+            _lock.acquireRead();
+        } catch (RuntimeException e) {
+            // ok
+            return;
+        }
+
+        fail();
+    }
+
+    /**
+     * Try to acquire a write lock multiple times. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireWriteTwice() {
+        try {
+            _lock.acquireWrite();
+            _lock.acquireWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire the lock for reading and directly afterwards acquire it for
+     * writing. Verify that a RuntimeException is thrown.
+     */
+    public void testAcquireReadFollowedByWrite() {
+        try {
+            _lock.acquireRead();
+            _lock.acquireWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire the lock for writing and directly afterwards acquire it for
+     * reading. Verify that a RuntimeException is thrown.
+     */
+    public void testAcquireWriteFollowedByRead() {
+        try {
+            _lock.acquireWrite();
+            _lock.acquireRead();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a read lock and release it as a write lock. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireReadFollowedByReleaseaWrite() {
+        try {
+            _lock.acquireRead();
+            _lock.releaseWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a write lock and release it as a read lock. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireWriteFollowedByReleaseRead() {
+        try {
+            _lock.acquireWrite();
+            _lock.releaseRead();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when  the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+    ReadWriteLock     _lock;
+    ReadWriteLockTest _lockTest;
+    int               _sleepTime;
+
+    public ReadLocker(ReadWriteLock lock, ReadWriteLockTest lockTest,
+        int sleepTime) {
+        _lock          = lock;
+        _lockTest      = lockTest;
+        _sleepTime     = sleepTime;
+    }
+
+    public void run() {
+        _lock.acquireRead();
+        _lockTest.incrementReaderCount();
+
+        try {
+            Thread.sleep(_sleepTime);
+        } catch (InterruptedException e) {
+            Assert.fail("ReadLocker thread was interrupted."
+                + Thread.currentThread());
+        }
+
+        _lock.releaseRead();
+        _lockTest.decrementReaderCount();
+    }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when  the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+    ReadWriteLock     _lock;
+    ReadWriteLockTest _lockTest;
+    int               _sleepTime;
+
+    public WriteLocker(ReadWriteLock lock, ReadWriteLockTest lockTest,
+        int sleepTime) {
+        _lock          = lock;
+        _lockTest      = lockTest;
+        _sleepTime     = sleepTime;
+    }
+
+    public void run() {
+        _lock.acquireWrite();
+        _lockTest.incrementWriterCount();
+
+        try {
+            Thread.sleep(_sleepTime);
+        } catch (InterruptedException e) {
+            Assert.fail("WriteLocker thread was interrupted: "
+                + Thread.currentThread());
+        }
+
+        _lock.releaseWrite();
+        _lockTest.decrementWriterCount();
+    }
+}
diff --git a/test/org/wamblee/observer/ObservableTest.java b/test/org/wamblee/observer/ObservableTest.java
new file mode 100644 (file)
index 0000000..b2b7bdd
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.observer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+
+/**
+ * Test of the observer pattern implementation. 
+ */
+public class ObservableTest extends MockObjectTestCase {
+    
+    private static final String UPDATE = "send";
+    
+
+    private Observable<ObservableTest, String> _observable; 
+  
+    /* (non-Javadoc)
+     * @see junit.framework.TestCase#setUp()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        _observable = new Observable<ObservableTest, String>(this, new DefaultObserverNotifier());
+    }
+    
+    /**
+     * Tests subscription and notification of one subscriber.
+     */
+    public void testOneObserver() { 
+        Mock mockObserver = mock(Observer.class);
+        Observer<ObservableTest,String> observer = (Observer<ObservableTest,String>)mockObserver.proxy();        
+        long subscription = _observable.subscribe(observer);
+        
+        assertEquals(1, _observable.getObserverCount()); 
+        
+        String message = "hallo";
+        mockObserver.expects(once()).method(UPDATE).with(same(this), eq(message)); 
+       
+        _observable.send(message);
+        _observable.unsubscribe(subscription); 
+        assertEquals(0, _observable.getObserverCount()); 
+        
+        _observable.send(message);
+       
+    }
+   
+    
+    /**
+     * Subscribes many susbcribers and sends notifications to subscribers. 
+     * Verifies that unique subscription number are returned. 
+     * Also verifies that the correct subscribers are notfied. 
+     */
+    public void testManySubscribers() { 
+        int nsubscribers = 100; 
+        Mock[] mocks = new Mock[nsubscribers];
+        List<Long> subscriptions = new ArrayList<Long>();
+        for (int i = 0; i < nsubscribers; i++) { 
+            Mock mockObserver = mock(Observer.class);
+            Observer<ObservableTest,String> observer = (Observer<ObservableTest,String>)mockObserver.proxy();        
+            long subscription = _observable.subscribe(observer);
+            
+            mocks[i] = mockObserver;
+            assertTrue( subscriptions.add(subscription));  
+        }
+        
+        assertEquals(nsubscribers, _observable.getObserverCount());
+       
+        String message = "hallo";
+        for (int i = 0; i < nsubscribers; i++) { 
+           mocks[i].expects(once()).method(UPDATE).with(same(this), eq(message));
+        }
+        
+        _observable.send(message);
+       
+        for (int i = nsubscribers/2; i < nsubscribers; i++) { 
+            _observable.unsubscribe(subscriptions.get(i));    
+        }
+        assertEquals(nsubscribers - ( nsubscribers - nsubscribers/2), _observable.getObserverCount());
+     
+        message = "blabla"; 
+        for (int i = 0; i < nsubscribers/2; i++) { 
+            mocks[i].expects(once()).method(UPDATE).with(same(this), eq(message));
+        }
+        _observable.send(message);
+    }
+
+}
diff --git a/test/org/wamblee/test/HibernateExporter.java b/test/org/wamblee/test/HibernateExporter.java
new file mode 100644 (file)
index 0000000..29bd257
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+
+/**
+ * Exporting the hibernate mapping. 
+ */
+public class HibernateExporter {
+    
+    public static void main(String[] args) throws IOException {
+        String file = args[0];
+        File dir = new File(args[1]); 
+      
+        Configuration conf = HibernateUtils.getConfiguration(dir);
+        SchemaExport export = new SchemaExport(conf);
+        export.setDelimiter(";");
+        export.setOutputFile(file); 
+        export.create(true, false);
+    }
+
+}
diff --git a/test/org/wamblee/test/HibernateUpdater.java b/test/org/wamblee/test/HibernateUpdater.java
new file mode 100644 (file)
index 0000000..833764c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaUpdate;
+
+/**
+ * Exporting the hibernate mapping. 
+ */
+public class HibernateUpdater {
+    
+    public static void main(String[] args) throws IOException {  
+        String file = args[0]; 
+        File dir = new File(args[0]); 
+      
+        Configuration conf = HibernateUtils.getConfiguration(dir);
+        SchemaUpdate lSchemaUpdate = new SchemaUpdate( conf );
+        lSchemaUpdate.execute( true, true );
+    }
+
+}
diff --git a/test/org/wamblee/test/HibernateUtils.java b/test/org/wamblee/test/HibernateUtils.java
new file mode 100644 (file)
index 0000000..4630eb8
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.test;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.hibernate.cfg.Configuration;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * Hibernate utilities. 
+ */
+public class HibernateUtils {
+    
+    private static final String DATABASE_PROPS = "test.database.properties";
+
+    /**
+     * @param dir
+     * @return
+     */
+    public static Configuration getConfiguration(File dir) throws IOException {
+        Configuration conf = new Configuration();
+        File[] files = dir.listFiles((FileFilter)(new AwkFilenameFilter(".*\\.hbm\\.xml")));
+        for (File f: files) { 
+            System.out.println("Mapping file: " + f);
+            conf.addFile(f);
+        }
+      
+        Map<String,String> dbProps = getHibernateProperties(); 
+        
+        for (Map.Entry<String,String> entry : dbProps.entrySet()) {
+            System.out.println("Property: " + entry.getKey() + "=" + entry.getValue());
+            conf.setProperty(entry.getKey(), entry.getValue());
+        }
+    
+        return conf;
+    }
+    
+    private static Map<String,String> getHibernateProperties() throws IOException {
+        
+        System.out.println( "Reading properties file: " +  DATABASE_PROPS);
+        InputResource lPropFile = new ClassPathResource( DATABASE_PROPS );
+        Properties props = new Properties(); 
+        props.load( lPropFile.getInputStream( ) );
+        
+        Map<String,String> result = new TreeMap<String,String>(); 
+        result.put( "hibernate.connection.driver_class", props.getProperty( "database.driver" ) );
+        result.put( "hibernate.connection.url", props.getProperty( "database.url" ) );
+        result.put( "hibernate.connection.username", props.getProperty( "database.username" ) );
+        result.put( "hibernate.connection.password", props.getProperty( "database.password" ) );
+        
+        return result;     
+    }
+
+}
diff --git a/test/org/wamblee/test/SpringConfigFiles.java b/test/org/wamblee/test/SpringConfigFiles.java
new file mode 100644 (file)
index 0000000..5d43a31
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+
+package org.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Spring configuration files to use. 
+ */
+public class SpringConfigFiles extends ArrayList<String> {
+
+    /**
+     * Constructs an empty list of Spring config files. 
+     *
+     */
+    public SpringConfigFiles() { 
+        super(); 
+    }
+    
+    /**
+     * Constructs the list of Spring config files. 
+     * @param aFiles Files. 
+     */
+    public SpringConfigFiles(String[] aFiles) { 
+        super(); 
+        addAll(Arrays.asList(aFiles));
+    }
+    
+}
diff --git a/test/org/wamblee/test/SpringTestCase.java b/test/org/wamblee/test/SpringTestCase.java
new file mode 100644 (file)
index 0000000..8b43621
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */
+
+package org.wamblee.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.operation.DatabaseOperation;
+import org.hibernate.SessionFactory;
+import org.jmock.cglib.MockObjectTestCase;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Test case support class for spring tests. 
+ */
+public class SpringTestCase extends MockObjectTestCase {
+
+    private Log LOG = LogFactory.getLog(SpringTestCase.class);
+
+    /**
+     * Session factory bean name.
+     */
+    private static final String SESSION_FACTORY = "sessionFactory";
+
+    /**
+     * Data source bean name.
+     */
+    private static final String DATA_SOURCE = "dataSource";
+
+    /**
+     * Transaction manager bean name.
+     */
+    private static final String TRANSACTION_MANAGER = "transactionManager";
+
+    /**
+     * Name of the ConfigFileList bean that describes the Hibernate mapping
+     * files to use.
+     */
+    private static final String HIBERNATE_CONFIG_FILES = "hibernateMappingFiles";
+
+    /**
+     * Schema pattern.
+     */
+    private static final String SCHEMA_PATTERN = "%";
+   
+    /**
+     * List of (String) configuration file locations for spring.
+     */
+    private String[] _configLocations;
+    
+    /**
+     * Application context for storing bean definitions that vary on a test by
+     * test basis and cannot be hardcoded in the spring configuration files.
+     */
+    private GenericApplicationContext _parentContext;
+    
+    /**
+     * Cached spring application context. 
+     */
+    private ApplicationContext _context; 
+
+    public SpringTestCase(Class<? extends SpringConfigFiles> aSpringFiles,
+            Class<? extends HibernateMappingFiles> aMappingFiles) {
+        try {
+            SpringConfigFiles springFiles = aSpringFiles.newInstance();
+            _configLocations = springFiles.toArray(new String[0]);
+        } catch (Exception e) {
+            fail("Could not construct spring config files class '" + aSpringFiles.getName() + "'"); 
+        }
+   
+        // Register the Hibernate mapping files as a bean.
+        _parentContext = new GenericApplicationContext();
+        BeanDefinition lDefinition = new RootBeanDefinition(aMappingFiles);
+        _parentContext.registerBeanDefinition(HIBERNATE_CONFIG_FILES,
+                lDefinition);
+        _parentContext.refresh();
+
+    }
+
+    /**
+     * Gets the spring context.
+     * 
+     * @return Spring context.
+     */
+    protected synchronized ApplicationContext getSpringContext() {
+        if ( _context == null ) { 
+            _context = new ClassPathXmlApplicationContext(
+                (String[]) _configLocations, _parentContext);
+            assertNotNull(_context);
+        }
+        return _context; 
+    }
+
+    /**
+     * @return Hibernate session factory.
+     */
+    protected SessionFactory getSessionFactory() {
+        SessionFactory factory = (SessionFactory) getSpringContext().getBean(SESSION_FACTORY);
+        assertNotNull(factory); 
+        return factory; 
+    }
+
+    protected void setUp() throws Exception {
+        LOG.info("Performing setUp()");
+
+        super.setUp();
+        
+        _context = null;  // make sure we get a new application context for every
+                          // new test. 
+
+        BeanKernel
+                .overrideBeanFactory(new TestSpringBeanFactory(getSpringContext()));
+  
+        cleanDatabase(); 
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            super.tearDown();
+        } finally {
+            LOG.info("tearDown() complete");
+        }
+    }
+
+    /**
+     * @return Transaction manager
+     */
+    protected PlatformTransactionManager getTransactionManager() {
+        PlatformTransactionManager manager = (PlatformTransactionManager) getSpringContext()
+        .getBean(TRANSACTION_MANAGER);
+        assertNotNull(manager); 
+        return manager; 
+    }
+
+    /**
+     * @return Starts a new transaction.
+     */
+    protected TransactionStatus getTransaction() {
+        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+        return getTransactionManager().getTransaction(def);
+    }
+
+    /**
+     * Returns the hibernate template for executing hibernate-specific
+     * functionality.
+     * 
+     * @return Hibernate template.
+     */
+    protected HibernateTemplate getTemplate() {
+        HibernateTemplate template = (HibernateTemplate) getSpringContext().getBean(HibernateTemplate.class.getName());
+        assertNotNull(template); 
+        return template; 
+    }
+
+    /**
+     * Flushes the session. Should be called after some Hibernate work and
+     * before JDBC is used to check results.
+     * 
+     */
+    protected void flush() {
+        getTemplate().flush();
+    }
+
+    /**
+     * Flushes the session first and then removes all objects from the Session
+     * cache. Should be called after some Hibernate work and before JDBC is used
+     * to check results.
+     * 
+     */
+    protected void clear() {
+        flush();
+        getTemplate().clear();
+    }
+
+    /**
+     * Evicts the object from the session. This is essential for the
+     * implementation of unit tests where first an object is saved and is
+     * retrieved later. By removing the object from the session, Hibernate must
+     * retrieve the object again from the database.
+     * 
+     * @param aObject
+     */
+    protected void evict(Object aObject) {
+        getTemplate().evict(aObject);
+    }
+
+    /**
+     * Gets the connection.
+     * 
+     * @return Connection.
+     */
+    public Connection getConnection() {
+        return DataSourceUtils.getConnection(getDataSource());
+    }
+
+    public void cleanDatabase() throws SQLException {
+        
+        if (! isDatabaseConfigured() ) {
+            return; 
+        }
+        
+        String[] tables = getTableNames();
+
+        try {
+            IDatabaseConnection connection = new DatabaseConnection(
+                    getConnection());
+            ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
+            IDataSet dataset = new FilteredDataSet(filter, connection
+                    .createDataSet(tables));
+
+            DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+        } catch (DatabaseUnitException e) {
+            SQLException exc = new SQLException(e.getMessage());
+            exc.initCause(e);
+            throw exc;
+        } 
+    }
+
+    /**
+     * @throws SQLException
+     */
+    public String[] getTableNames() throws SQLException {
+
+        List result = new ArrayList();
+        LOG.debug("Getting database table names to clean (schema: '"
+                + SCHEMA_PATTERN + "'");
+
+        ResultSet tables = getConnection().getMetaData().getTables(null,
+                SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+        while (tables.next()) {
+            String table = tables.getString("TABLE_NAME");
+            // Make sure we do not touch hibernate's specific
+            // infrastructure tables.
+            if (!table.toLowerCase().startsWith("hibernate")) {
+                result.add(table);
+                LOG.debug("Adding " + table
+                        + " to list of tables to be cleaned.");
+            }
+        }
+        return (String[]) result.toArray(new String[0]);
+    }
+
+    /**
+     * @return
+     * @throws SQLException
+     */
+    public void emptyTables(List aTableList) throws SQLException {
+        Iterator liTable = aTableList.iterator();
+        while (liTable.hasNext()) {
+            emptyTable((String) liTable.next());
+        }
+    }
+
+    /**
+     * @return
+     * @throws SQLException
+     */
+    public void emptyTable(String aTable) throws SQLException {
+        executeSql("delete from " + aTable);
+    }
+
+    /**
+     * @return
+     * @throws SQLException
+     */
+    public void dropTable(String aTable) throws SQLException {
+        executeQuery("drop table " + aTable);
+    }
+
+    /**
+     * Executes an SQL statement within a transaction.
+     * 
+     * @param aSql
+     *            SQL statement.
+     * @return Return code of the corresponding JDBC call.
+     */
+    public int executeSql(final String aSql) {
+        return executeSql(aSql, new Object[0]);
+    }
+
+    /**
+     * Executes an SQL statement within a transaction. See
+     * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+     * supported argument types.
+     * 
+     * @param aSql
+     *            SQL statement.
+     * @param aArg
+     *            Argument of the sql statement.
+     * @return Return code of the corresponding JDBC call.
+     */
+    public int executeSql(final String aSql, final Object aArg) {
+        return executeSql(aSql, new Object[] { aArg });
+    }
+
+    /**
+     * Executes an sql statement. See
+     * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+     * supported argument types.
+     * 
+     * @param aSql
+     *            SQL query to execute.
+     * @param aArgs
+     *            Arguments.
+     * @return Number of rows updated.
+     */
+    public int executeSql(final String aSql, final Object[] aArgs) {
+        Map results = executeTransaction(new TestTransactionCallback() {
+            public Map execute() throws Exception {
+                JdbcTemplate template = new JdbcTemplate(getDataSource());
+                int result = template.update(aSql, aArgs);
+
+                Map map = new TreeMap();
+                map.put("result", new Integer(result));
+
+                return map;
+            }
+        });
+
+        return ((Integer) results.get("result")).intValue();
+    }
+
+    /**
+     * Executes a transaction with a result.
+     * 
+     * @param aCallback
+     *            Callback to do your transactional work.
+     * @return Result.
+     */
+    public Object executeTransaction(TransactionCallback aCallback) {
+        TransactionTemplate lTemplate = new TransactionTemplate(
+                getTransactionManager());
+        return lTemplate.execute(aCallback);
+    }
+
+    /**
+     * Executes a transaction without a result.
+     * 
+     * @param aCallback
+     *            Callback to do your transactional work. .
+     */
+    protected void executeTransaction(TransactionCallbackWithoutResult aCallback) {
+        TransactionTemplate template = new TransactionTemplate(
+                getTransactionManager());
+        template.execute(aCallback);
+    }
+
+    /**
+     * Executes a transaction with a result, causing the testcase to fail if any
+     * type of exception is thrown.
+     * 
+     * @param aCallback
+     *            Code to be executed within the transaction.
+     * @return Result.
+     */
+    public Map executeTransaction(final TestTransactionCallback aCallback) {
+        return (Map) executeTransaction(new TransactionCallback() {
+            public Object doInTransaction(TransactionStatus aArg) {
+                try {
+                    return aCallback.execute();
+                } catch (Exception e) {
+                    // test case must fail.
+                    e.printStackTrace();
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Executes a transaction with a result, causing the testcase to fail if any
+     * type of exception is thrown.
+     * 
+     * @param aCallback
+     *            Code to be executed within the transaction.
+     */
+    public void executeTransaction(
+            final TestTransactionCallbackWithoutResult aCallback) {
+        executeTransaction(new TransactionCallbackWithoutResult() {
+            public void doInTransactionWithoutResult(TransactionStatus aArg) {
+                try {
+                    aCallback.execute();
+                } catch (Exception e) {
+                    // test case must fail.
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Executes an SQL query within a transaction.
+     * 
+     * @param aSql
+     *            Query to execute.
+     * @return Result set.
+     */
+    public ResultSet executeQuery(String aSql) {
+        return executeQuery(aSql, new Object[0]);
+    }
+
+    /**
+     * Executes a query with a single argument. See
+     * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+     * supported argument types.
+     * 
+     * @param aSql
+     *            Query.
+     * @param aArg
+     *            Argument.
+     * @return Result set.
+     */
+    public ResultSet executeQuery(String aSql, Object aArg) {
+        return executeQuery(aSql, new Object[] { aArg });
+    }
+
+    /**
+     * Executes a query within a transaction. See
+     * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+     * supported argument types.
+     * 
+     * @param aSql
+     *            Sql query.
+     * @param aArgs
+     *            Arguments to the query.
+     * @return Result set.
+     */
+    public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
+        Map results = executeTransaction(new TestTransactionCallback() {
+            public Map execute() throws Exception {
+                Connection connection = getConnection();
+
+                PreparedStatement statement = connection.prepareStatement(aSql);
+                setPreparedParams(aArgs, statement);
+
+                ResultSet resultSet = statement.executeQuery();
+                TreeMap results = new TreeMap();
+                results.put("resultSet", resultSet);
+
+                return results;
+            }
+        });
+
+        return (ResultSet) results.get("resultSet");
+    }
+
+    /**
+     * Sets the values of a prepared statement. See
+     * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+     * supported argument types.
+     * 
+     * @param aArgs
+     *            Arguments to the prepared statement.
+     * @param aStatement
+     *            Prepared statement
+     * @throws SQLException
+     */
+    private void setPreparedParams(final Object[] aArgs,
+            PreparedStatement aStatement) throws SQLException {
+        for (int i = 1; i <= aArgs.length; i++) {
+            setPreparedParam(i, aStatement, aArgs[i - 1]);
+        }
+    }
+
+    /**
+     * Sets a prepared statement parameter.
+     * 
+     * @param aIndex
+     *            Index of the parameter.
+     * @param aStatement
+     *            Prepared statement.
+     * @param aObject
+     *            Value Must be of type Integer, Long, or String. TODO extend
+     *            with more types of values.
+     * @throws SQLException
+     */
+    private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+            Object aObject) throws SQLException {
+        if (aObject instanceof Integer) {
+            aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+        } else if (aObject instanceof Long) {
+            aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+        } else if (aObject instanceof String) {
+            aStatement.setString(aIndex, (String) aObject);
+        } else {
+            TestCase.fail("Unsupported object type for prepared statement: "
+                    + aObject.getClass() + " value: " + aObject
+                    + " statement: " + aStatement);
+        }
+    }
+    
+    private boolean isDatabaseConfigured() { 
+        try { 
+            getDataSource(); 
+        } catch (NoSuchBeanDefinitionException e ) { 
+            return false; 
+        }
+        return true; 
+    }
+
+    /**
+     * @return Returns the dataSource.
+     */
+    public DataSource getDataSource() {
+        DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(DATA_SOURCE);
+        assertNotNull(ds); 
+        return ds; 
+    }
+
+    /**
+     * @return
+     * @throws SQLException
+     */
+    protected int getTableSize(String aTable) throws SQLException {
+        ResultSet resultSet = executeQuery("select * from " + aTable);
+        int count = 0;
+
+        while (resultSet.next()) {
+            count++;
+        }
+
+        return count;
+    }
+    
+    protected int countResultSet(ResultSet aResultSet) throws SQLException { 
+        int count = 0;
+
+        while (aResultSet.next()) {
+            count++;
+        }
+
+        return count;
+    }
+
+}
diff --git a/test/org/wamblee/test/TestSpringBeanFactory.java b/test/org/wamblee/test/TestSpringBeanFactory.java
new file mode 100644 (file)
index 0000000..24f90a8
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.test;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+
+/**
+ * Bean factory which uses Spring. 
+ */
+public class TestSpringBeanFactory implements BeanFactory {
+    
+    private ApplicationContext _context; 
+    
+    public TestSpringBeanFactory(ApplicationContext aContext) {
+        _context = aContext; 
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+     */
+    public Object find(String aId) {
+       return find(aId, Object.class);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+     */
+    public <T> T find(Class<T> aClass) {
+        return find(aClass.getName(), aClass);
+    }
+
+    /* (non-Javadoc)
+     * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+     */
+    public <T> T find(String aId, Class<T> aClass) {
+        try {
+            Object obj = _context.getBean(aId, aClass);
+            assert obj != null; 
+            return aClass.cast(obj); 
+        } catch (BeansException e) { 
+            throw new BeanFactoryException(e.getMessage(), e);
+        }
+    }
+
+}
diff --git a/test/org/wamblee/test/TestSupport.java b/test/org/wamblee/test/TestSupport.java
new file mode 100644 (file)
index 0000000..4cb23e3
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.
+ */ 
+package org.wamblee.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import junit.framework.Assert;
+
+/**
+ * @author Erik Test support utility.
+ */
+public class TestSupport {
+
+       /**
+        * Obtain root directory of JUnit tests.
+        * 
+        * @return Directory name.
+        */
+       public static File getTestRootDir() {
+               return new File("testdata");
+       }
+
+       /**
+        * Returns a temporary directory.
+        * 
+        * @return Temporary directory.
+        */
+       public static File getTmpDir() {
+               return new File(getTestRootDir(), "tmpdir");
+       }
+
+       /**
+        * Recursively remove a directory.
+        * 
+        * @param aSrc
+        *            Directoryto remove.
+        */
+       public static void removeDir(File aSrc) {
+               if (!aSrc.exists()) {
+                       return;
+               }
+               Assert.assertTrue(aSrc.getPath(), aSrc.isDirectory());
+               File[] files = aSrc.listFiles();
+               for (int i = 0; i < files.length; i++) {
+                       File file = files[i];
+                       if (file.isDirectory()) {
+                               removeDir(file);
+                       } else {
+                               Assert.assertTrue(file.getPath(), file.delete());
+                       }
+               }
+               Assert.assertTrue(aSrc.getPath(), aSrc.delete());
+       }
+
+       /**
+        * Recursively copy a directory.
+        * 
+        * @param aSrc
+        *            Source directory
+        * @param aTarget
+        *            Target directory.
+        */
+       public static void copyDir(File aSrc, File aTarget) {
+               Assert.assertTrue(aSrc.isDirectory());
+               Assert.assertTrue(!aTarget.exists());
+
+               aTarget.mkdirs();
+
+               File[] files = aSrc.listFiles();
+               for (int i = 0; i < files.length; i++) {
+                       File file = files[i];
+                       if (file.isDirectory()) {
+                               if (!file.getName().equals(".svn")) {
+                                       copyDir(new File(aSrc, file.getName()), new File(aTarget,
+                                                       file.getName()));
+                               }
+                       } else {
+                               copyFile(file, new File(aTarget, file.getName()));
+                       }
+               }
+       }
+
+       /**
+        * Copy a file. If copying fails then the testcase will fail.
+        * 
+        * @param aSrc
+        *            Source file.
+        * @param aTarget
+        *            Target file.
+        */
+       public static void copyFile(File aSrc, File aTarget) {
+
+               try {
+                       FileInputStream fis = new FileInputStream(aSrc);
+                       FileOutputStream fos = new FileOutputStream(aTarget);
+                       FileChannel fcin = fis.getChannel();
+                       FileChannel fcout = fos.getChannel();
+
+                       // map input file
+
+                       MappedByteBuffer mbb = fcin.map(FileChannel.MapMode.READ_ONLY, 0,
+                                       fcin.size());
+
+                       // do the file copy
+                       fcout.write(mbb);
+
+                       // finish up
+
+                       fcin.close();
+                       fcout.close();
+                       fis.close();
+                       fos.close();
+               } catch (IOException e) {
+                       Assert.assertTrue("Copying file " + aSrc.getPath() + " to "
+                                       + aTarget.getPath() + " failed.", false);
+               }
+       }
+
+       /**
+        * Remove a file or directory. The test case will fail if this does not
+        * succeed.
+        * 
+        * @param aFile
+        *            entry to remove.
+        */
+       public static void delete(File aFile) {
+               Assert.assertTrue("Could not delete " + aFile.getPath(), aFile.delete());
+       }
+
+       /**
+        * Remove all files within a given directory including the directory itself.
+        * This only attempts to remove regular files and not directories within the
+        * directory. If the directory contains a nested directory, the deletion
+        * will fail. The test case will fail if this fails.
+        * 
+        * @param aDir
+        *            Directory to remove.
+        */
+       public static void deleteDir(File aDir) {
+               cleanDir(aDir);
+               delete(aDir);
+       }
+
+       /**
+        * Remove all regular files within a given directory.
+        * 
+        * @param outputDirName
+        */
+       public static void cleanDir(File aDir) {
+               if (!aDir.exists()) {
+                       return; // nothing to do.
+               }
+               File[] entries = aDir.listFiles();
+               for (int i = 0; i < entries.length; i++) {
+                       File file = entries[i];
+                       if (file.isFile()) {
+                               Assert.assertTrue("Could not delete " + entries[i].getPath(),
+                                               entries[i].delete());
+                       }
+               }
+       }
+
+       /**
+        * Creates directory if it does not already exist. The test case will fail
+        * if the directory cannot be created.
+        * 
+        * @param aDir
+        *            Directory to create.
+        */
+       public static void createDir(File aDir) {
+               if (aDir.isDirectory()) {
+                       return; // nothing to do.
+               }
+               Assert.assertTrue("Could not create directory " + aDir.getPath(), aDir
+                               .mkdirs());
+       }
+}
diff --git a/test/org/wamblee/test/TestTransactionCallback.java b/test/org/wamblee/test/TestTransactionCallback.java
new file mode 100644 (file)
index 0000000..fa07b69
--- /dev/null
@@ -0,0 +1,21 @@
+
+package org.wamblee.test;
+
+import java.util.Map;
+
+
+/**
+ * Transaction callback for testing.
+ * The test will fail if any type of exception is thrown.
+ */
+public interface TestTransactionCallback {
+    /**
+     * Executes code within a transaction, causing the testcase to fail if any
+     * type of exception is thrown.
+     *
+     * @return A map containg the resuls of the execution. This is
+     *  a convenient method of returning multiple results from
+     *  a call.
+     */
+    Map execute(  ) throws Exception;
+}
diff --git a/test/org/wamblee/test/TestTransactionCallbackWithoutResult.java b/test/org/wamblee/test/TestTransactionCallbackWithoutResult.java
new file mode 100644 (file)
index 0000000..05a2357
--- /dev/null
@@ -0,0 +1,16 @@
+
+package org.wamblee.test;
+
+
+/**
+ * Transaction callback for testing.
+ * The test will fail if any type of exception is thrown.
+ */
+public interface TestTransactionCallbackWithoutResult {
+    /**
+     * Executes code within a transaction, causing the testcase to fail if any
+     * type of exception is thrown.
+     *
+     */
+    void execute(  ) throws Exception;
+}