فهرست منبع

Initial commit.

Ben Alex 20 سال پیش
والد
کامیت
cf241fb9ae
35فایلهای تغییر یافته به همراه2924 افزوده شده و 1 حذف شده
  1. 7 0
      .classpath
  2. 39 0
      domain/maven.xml
  3. 53 0
      domain/project.xml
  4. 51 0
      domain/src/main/java/org/acegisecurity/domain/DomainException.java
  5. 56 0
      domain/src/main/java/org/acegisecurity/domain/PersistableEntity.java
  6. 172 0
      domain/src/main/java/org/acegisecurity/domain/dao/Dao.java
  7. 52 0
      domain/src/main/java/org/acegisecurity/domain/dao/EvictionCapable.java
  8. 105 0
      domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java
  9. 477 0
      domain/src/main/java/org/acegisecurity/domain/dao/PaginatedList.java
  10. 92 0
      domain/src/main/java/org/acegisecurity/domain/dao/PaginatedListIterator.java
  11. 6 0
      domain/src/main/java/org/acegisecurity/domain/dao/package.html
  12. 269 0
      domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java
  13. 103 0
      domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java
  14. 6 0
      domain/src/main/java/org/acegisecurity/domain/hibernate/package.html
  15. 77 0
      domain/src/main/java/org/acegisecurity/domain/impl/AbstractPersistableEntity.java
  16. 81 0
      domain/src/main/java/org/acegisecurity/domain/impl/BusinessObject.java
  17. 78 0
      domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityInteger.java
  18. 78 0
      domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityLong.java
  19. 38 0
      domain/src/main/java/org/acegisecurity/domain/impl/PersistableValue.java
  20. 7 0
      domain/src/main/java/org/acegisecurity/domain/impl/package.html
  21. 6 0
      domain/src/main/java/org/acegisecurity/domain/package.html
  22. 51 0
      domain/src/main/java/org/acegisecurity/domain/util/CollectionIgnoringReflectionToStringBuilder.java
  23. 162 0
      domain/src/main/java/org/acegisecurity/domain/util/CollectionUtils.java
  24. 67 0
      domain/src/main/java/org/acegisecurity/domain/util/ReflectionToStringBuilder.java
  25. 6 0
      domain/src/main/java/org/acegisecurity/domain/util/package.html
  26. 57 0
      domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidation.java
  27. 50 0
      domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidationUtils.java
  28. 55 0
      domain/src/main/java/org/acegisecurity/domain/validation/IntrospectionManager.java
  29. 115 0
      domain/src/main/java/org/acegisecurity/domain/validation/ValidationAdvisor.java
  30. 107 0
      domain/src/main/java/org/acegisecurity/domain/validation/ValidationInterceptor.java
  31. 49 0
      domain/src/main/java/org/acegisecurity/domain/validation/ValidationManager.java
  32. 254 0
      domain/src/main/java/org/acegisecurity/domain/validation/ValidationManagerImpl.java
  33. 51 0
      domain/src/main/java/org/acegisecurity/domain/validation/ValidatorNotFoundException.java
  34. 46 0
      domain/src/main/java/org/acegisecurity/domain/validation/package.html
  35. 1 1
      project.properties

+ 7 - 0
.classpath

@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="samples/attributes/src/main/java"/>
+	<classpathentry kind="src" path="domain/src/main/java"/>
+	<classpathentry kind="src" path="domain/src/main/resource"/>
+	<classpathentry kind="src" path="domain/src/test/java"/>
+	<classpathentry kind="src" path="domain/src/test/resources"/>
 	<classpathentry kind="src" path="adapters/catalina/src/main/resources"/>
 	<classpathentry kind="src" path="samples/attributes/src/main/resources"/>
 	<classpathentry kind="src" path="samples/attributes/src/test/java"/>
@@ -53,5 +57,8 @@
 	<classpathentry kind="var" path="MAVEN_REPO/junit/jars/junit-3.8.1.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/ehcache/jars/ehcache-1.1.jar"/>
 	<classpathentry kind="var" path="MAVEN_REPO/jspapi/jars/jsp-api-2.0.jar"/>
+	<classpathentry kind="var" path="MAVEN_REPO/hibernate/jars/hibernate-2.1.8.jar"/>
+	<classpathentry kind="var" path="MAVEN_REPO/commons-lang/jars/commons-lang-2.0.jar"/>
+	<classpathentry sourcepath="DIST_BASE/commons-beanutils-1.6.1-src/src/java" kind="var" path="MAVEN_REPO/commons-beanutils/jars/commons-beanutils-1.6.1.jar"/>
 	<classpathentry kind="output" path="target/eclipseclasses"/>
 </classpath>

+ 39 - 0
domain/maven.xml

@@ -0,0 +1,39 @@
+<!--
+ * ========================================================================
+ * 
+ * Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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.
+ * 
+ * ========================================================================
+-->
+
+<project
+   xmlns:j="jelly:core"
+   xmlns:ant="jelly:ant"
+   xmlns:util="jelly:util"
+   xmlns:maven="jelly:maven"
+  >
+
+    <postGoal name="jar:jar">
+        <j:if test="${context.getVariable('signature.alias') != null}">
+        	<echo>signature.alias defined; signing JAR(s)...</echo>
+			<ant:signjar lazy="true" alias="${signature.alias}" storepass="${signature.storepass}" keystore="${signature.keystore}">
+				<fileset dir="${maven.build.dir}">
+					<include name="*.jar"/>
+				</fileset>
+			</ant:signjar>
+        </j:if>
+    </postGoal>
+
+</project>

+ 53 - 0
domain/project.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+  <extend>${basedir}/../project.xml</extend>
+  <pomVersion>3</pomVersion>
+  <artifactId>acegi-security-domain</artifactId>
+  <name>Acegi Security System for Spring - Domain Object Support</name>
+  <groupId>acegisecurity</groupId>
+  <siteDirectory>/home/groups/a/ac/acegisecurity/htdocs/multiproject/acegi-security-domain</siteDirectory>
+  <repository>
+    <connection>scm:cvs:pserver:anonymous@cvs.sourceforge.net:/cvsroot/acegisecurity:acegisecurity</connection>
+    <developerConnection>scm:cvs:ext:${maven.username}@cvs.sourceforge.net:/cvsroot/acegisecurity:acegisecurity</developerConnection>
+    <url>http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/acegisecurity/acegisecurity/domain/</url>
+  </repository>
+  <dependencies>
+    <dependency>
+      <groupId>hibernate</groupId>
+      <artifactId>hibernate</artifactId>
+      <version>2.1.8</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.0</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>commons-beanutils</groupId>
+      <artifactId>commons-beanutils</artifactId>
+      <version>1.6.1</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>acegisecurity</groupId>
+      <artifactId>acegi-security</artifactId>
+      <version>0.8.2-SNAPSHOT</version>
+      <type>jar</type>
+    </dependency>
+  </dependencies>
+  <build>
+    <resources>
+      <resource>
+        <directory>${basedir}/../</directory>
+        <targetPath>META-INF</targetPath>
+        <includes>
+          <include>notice.txt</include>
+        </includes>
+        <filtering>false</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>
+

+ 51 - 0
domain/src/main/java/org/acegisecurity/domain/DomainException.java

@@ -0,0 +1,51 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain;
+
+import net.sf.acegisecurity.AcegiSecurityException;
+
+
+/**
+ * Abstract superclass for all exceptions related to domain object support
+ * subproject.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class DomainException extends AcegiSecurityException {
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructs a <code>DomainException</code> with the specified message and
+     * root cause.
+     *
+     * @param msg the detail message
+     * @param t the root cause
+     */
+    public DomainException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+    /**
+     * Constructs a <code>DomainException</code> with the specified message and
+     * no root cause.
+     *
+     * @param msg the detail message
+     */
+    public DomainException(String msg) {
+        super(msg);
+    }
+}

+ 56 - 0
domain/src/main/java/org/acegisecurity/domain/PersistableEntity.java

@@ -0,0 +1,56 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain;
+
+import java.io.Serializable;
+
+
+/**
+ * An interface that indicates an object is a <i>persistable entity</i>.
+ * 
+ * <p>
+ * A persistable entity is any object that is capable of being persisted,
+ * typically via a {@link net.sf.acegisecurity.domain.dao.Dao} implementation.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface PersistableEntity {
+    //~ Methods ================================================================
+
+    /**
+     * Provides a common getter for the persistence layer to obtain an
+     * identity, irrespective of the actual type of identity used.
+     * 
+     * <p>
+     * Typically a subclass will delegate to a <code>public
+     * SomePrimitiveWrapper getId()</code> method. The necessity for the
+     * <code>getInternalId()</code> abstract method is solely  because the
+     * persistence layer needs a way of obtaining the identity irrespective of
+     * the actual identity implementation choice.
+     * </p>
+     * 
+     * <p>
+     * Returning <code>null</code> from this method will indicate the object
+     * has never been saved. This will likely be relied on by some
+     * <code>Dao</code> implementations.
+     * </p>
+     *
+     * @return the persistence identity of this instance
+     */
+    abstract Serializable getInternalId();
+}

+ 172 - 0
domain/src/main/java/org/acegisecurity/domain/dao/Dao.java

@@ -0,0 +1,172 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.dao;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+
+import java.io.Serializable;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Provides fundamental DAO capabilities for a single concrete {@link
+ * PersistableEntity}.
+ * 
+ * <P>
+ * This interface provides a portable approach to Data Access Object (DAO)
+ * functionality across various object relational persistance solutions.
+ * </p>
+ * 
+ * <p>
+ * It is not envisioned that this interface will provide <b>all</b> data access
+ * requirements for applications, however it should provide all of the
+ * standard create, read, update, delete (CRUD) and finder functions that are
+ * routinely needed. Specialized subclasses (that provide finer-grained
+ * functionality) of the <code>Dao</code> interface are encouraged.
+ * </p>
+ * 
+ * <P>
+ * A <code>Dao</code> implementation (or a subclass of <code>Dao</code>) should
+ * be the sole entry point into the persistance layer of an application. The
+ * persistence layer should only respond to requests from the services layer.
+ * The services layer is where all transaction demarcation, security
+ * authorization, casting to and from concrete {@link
+ * net.sf.acegisecurity.domain.PersistableEntity}s, workflow and business
+ * logic should take place.
+ * </p>
+ * 
+ * <p>
+ * Each <code>Dao</code> implementation will support one
+ * <code>PersistableEntity</code> classes only. The supported
+ * <code>PersistableEntity</code> class must be indicated via the {@link
+ * #supports(Class)} method.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface Dao {
+    //~ Methods ================================================================
+
+    /**
+     * Create a new object, with the current {@link
+     * PersistableEntity#getInternalId()} value being ignored.
+     *
+     * @param value (without the identity property initialized)
+     *
+     * @return the value created (with the identity property initialised)
+     */
+    public PersistableEntity create(PersistableEntity value);
+
+    /**
+     * Saves an existing object to the persistence layer, or creates a new
+     * object in the persistence layer. Implementations typically rely on
+     * {@link PersistableEntity#getInternalId()} being non-<code>null</code>
+     * to differentiate between persistence instances previous saved and those
+     * requiring initial creation.
+     *
+     * @param value to save or update
+     *
+     * @return the saved or updated (as appropriate) value
+     */
+    public PersistableEntity createOrUpdate(PersistableEntity value);
+
+    /**
+     * Delete an object.
+     *
+     * @param value the value to delete
+     */
+    public void delete(PersistableEntity value);
+
+    /**
+     * Return all persistent instances.
+     *
+     * @return all persistence instances (an empty <code>List</code> will be
+     *         returned if no matches are found)
+     */
+    public List findAll();
+
+    /**
+     * Find a <code>List</code> of <code>PersistableEntity</code>s, searched by
+     * their identifiers.
+     *
+     * @param ids collection of identifiers to locate
+     *
+     * @return the values with those identifiers (an empty <code>List</code>
+     *         will be returned if no matches are found)
+     */
+    public List findId(Collection ids);
+
+    /**
+     * Load a persistent instance by its identifier.
+     *
+     * @param id the identifier of the persistent instance desired to be
+     *        retrieved
+     *
+     * @return the request item, or <code>null</code> if not found
+     */
+    public PersistableEntity readId(Serializable id);
+
+    /**
+     * Find persistent instances with properties matching those of the passed
+     * <code>PersistableEntity</code>.
+     * 
+     * <P>
+     * Persistent instances are matched on the basis of query by example.
+     * Properties whose value is <code>null</code>, empty
+     * <code>String</code>s, and any <code>Collection</code>s are ignored in
+     * the query by example evaluation.
+     * </p>
+     *
+     * @param value parameters to filter on
+     * @param firstElement the first result (start at zero to obtain all
+     *        results)
+     * @param maxElements the maximum number of results desired for this page
+     *        of the result set
+     * @param orderByAsc the property name of the
+     *        <code>PersistableEntity</code> that should be used to order the
+     *        results
+     *
+     * @return the requested page of the result list (a properly formed
+     *         <code>PaginatedList</code> is returned if no results match)
+     */
+    public PaginatedList scroll(PersistableEntity value, int firstElement,
+        int maxElements, String orderByAsc);
+
+    /**
+     * Indicates whether the DAO instance provides persistence services for the
+     * specified class.
+     *
+     * @param clazz to test, which should be an implementation of
+     *        <code>PersistableEntity</code>
+     *
+     * @return <code>true</code> or <code>false</code>, indicating whether or
+     *         not the passed class is supported by this DAO instance
+     */
+    public boolean supports(Class clazz);
+
+    /**
+     * Update an object.
+     *
+     * @param value to update, with the <code>PersistableEntity</code> having a
+     *        non-<code>null</code> identifier
+     *
+     * @return the updated value
+     */
+    public PersistableEntity update(PersistableEntity value);
+}

+ 52 - 0
domain/src/main/java/org/acegisecurity/domain/dao/EvictionCapable.java

@@ -0,0 +1,52 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.dao;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+
+
+/**
+ * Indicates an implementation capable of evicting {@link
+ * net.sf.acegisecurity.domain.PersistableEntity}s.
+ * 
+ * <p>
+ * Structured as a separate interface (rather than a subclass of
+ * <code>Dao</code>), as it is not required for all persistence strategies.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface EvictionCapable {
+    //~ Methods ================================================================
+
+    /**
+     * Removes the indicated persistent instance from the DAO's internal
+     * map/session.
+     * 
+     * <p>
+     * If the passed object does not exist in the internal map/session, the
+     * invocation has no effect.
+     * </p>
+     * 
+     * <p>
+     * May throw an exception if the implementation so desires.
+     * </p>
+     *
+     * @param entity to remove from the internal map/session
+     */
+    public void evict(PersistableEntity entity);
+}

+ 105 - 0
domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java

@@ -0,0 +1,105 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.dao;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+
+import org.springframework.util.Assert;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * Convenience methods that support eviction of <code>PersistableEntity</code>s
+ * from  those objects that implement {@link EvictionCapable}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EvictionUtils {
+    //~ Methods ================================================================
+
+    /**
+     * Evicts the <code>PersistableEntity</code> using the passed
+     * <code>Object</code> (provided that the passed <code>Object</code>
+     * implements <code>EvictionCapable</code>).
+     *
+     * @param daoOrServices the potential source for
+     *        <code>EvictionCapable</code> services (never <code>null</code>)
+     * @param entity to evict (never <code>null</code>)
+     */
+    public static void evictIfRequired(Object daoOrServices,
+        PersistableEntity entity) {
+        Assert.notNull(entity, "Cannot evict an empty PersistableEntity object!");
+
+        EvictionCapable evictor = getEvictionCapable(daoOrServices);
+
+        if (evictor != null) {
+            evictor.evict(entity);
+        }
+    }
+
+    /**
+     * Evicts each <code>PersistableEntity</code> element of the passed
+     * <code>Collection</code> using the passed <code>Object</code> (provided
+     * that the passed <code>Object</code> implements
+     * <code>EvictionCapable</code>).
+     *
+     * @param daoOrServices the potential source for
+     *        <code>EvictionCapable</code> services (never <code>null</code>)
+     * @param collection whose members to evict (never <code>null</code>)
+     */
+    public static void evictIfRequired(Object daoOrServices,
+        Collection collection) {
+        Assert.notNull(collection, "Cannot evict a null Collection");
+
+        if (getEvictionCapable(daoOrServices) == null) {
+            // save expense of iterating collection
+            return;
+        }
+
+        Iterator iter = collection.iterator();
+
+        while (iter.hasNext()) {
+            Object obj = iter.next();
+
+            if (obj instanceof PersistableEntity) {
+                evictIfRequired(daoOrServices, (PersistableEntity) obj);
+            }
+        }
+    }
+
+    /**
+     * Obtain the <code>EvictionCapable</code> from the passed argument, or
+     * <code>null</code>.
+     *
+     * @param daoOrServices to check if provides eviction services
+     *
+     * @return the <code>EvictionCapable</code> object or <code>null</code> if
+     *         the object does not provide eviction services
+     */
+    private static EvictionCapable getEvictionCapable(Object daoOrServices) {
+        Assert.notNull(daoOrServices,
+            "Cannot evict if the object that may provide EvictionCapable is null");
+
+        if (daoOrServices instanceof EvictionCapable) {
+            return (EvictionCapable) daoOrServices;
+        } else {
+            return null;
+        }
+    }
+}

+ 477 - 0
domain/src/main/java/org/acegisecurity/domain/dao/PaginatedList.java

@@ -0,0 +1,477 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.dao;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Vector;
+
+
+/**
+ * <p>
+ * Represents a paginated <code>List</code>.
+ * </p>
+ * 
+ * <p>
+ * Elements in the internal <code>List</code> (see {@link #getList()} represent
+ * only part of a larger resultset.
+ * </p>
+ * 
+ * <p>
+ * Note that firstElement starts at zero. Any attempt to access other than the
+ * current page will cause an error.
+ * </p>
+ * 
+ * <p>
+ * This is a read only implementation and many of the  <code>List</code>
+ * methods are not implemented.
+ * </p>
+ *
+ * @author Carlos Sanchez
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class PaginatedList implements List {
+    //~ Instance fields ========================================================
+
+    protected final transient Log logger = LogFactory.getLog(getClass());
+    private List list;
+    private int firstElement;
+    private int maxElements;
+    private int size;
+
+    //~ Constructors ===========================================================
+
+    // TODO: Consider removing this constructor
+    public PaginatedList() {}
+
+    /**
+     * Used to construct a <code>PaginatedList</code> which contains only the
+     * given entity.
+     *
+     * @param entity the entity to include (can be <code>null</code>, which
+     *        indicates an empty <code>PaginatedList</code> should be created)
+     */
+    public PaginatedList(PersistableEntity entity) {
+        if (entity == null) {
+            this.list = new Vector();
+            this.firstElement = 0;
+            this.maxElements = Integer.MAX_VALUE;
+            this.size = 0;
+        } else {
+            List list = new Vector();
+            list.add(entity);
+            this.list = list;
+            this.firstElement = 0;
+            this.maxElements = Integer.MAX_VALUE;
+            this.size = 1;
+        }
+    }
+
+    public PaginatedList(List list, int firstElement, int maxElements, int size) {
+        this.list = list;
+        this.firstElement = firstElement;
+        this.maxElements = maxElements;
+        this.size = size;
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Unsupported operation
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#isEmpty()
+     */
+    public boolean isEmpty() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setFirstElement(int firstElement) {
+        this.firstElement = firstElement;
+    }
+
+    /**
+     * First element of this page, starting at zero.
+     *
+     * @return
+     */
+    public int getFirstElement() {
+        return firstElement;
+    }
+
+    /**
+     * Calculate the last page number, starting at 0
+     *
+     * @return
+     */
+    public int getLastPageNumber() {
+        return (size() - 1) / getMaxElements();
+    }
+
+    public void setList(List list) {
+        this.list = list;
+    }
+
+    /**
+     * Get list with the elements of this page.
+     *
+     * @return this page of the results
+     */
+    public List getList() {
+        return list;
+    }
+
+    public void setMaxElements(int maxElements) {
+        this.maxElements = maxElements;
+    }
+
+    /**
+     * Max number of elements in the page
+     *
+     * @return
+     */
+    public int getMaxElements() {
+        return maxElements;
+    }
+
+    /**
+     * Calculate the page number, starting at 0
+     *
+     * @return
+     */
+    public int getPageNumber() {
+        return getFirstElement() / getMaxElements();
+    }
+
+    /**
+     * Number of elements in this page
+     *
+     * @return
+     */
+    public int getPageSize() {
+        return list.size();
+    }
+
+    /**
+     * Set the number of elements in all the pages
+     *
+     * @param size DOCUMENT ME!
+     */
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     * @param arg1 DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#add(int, java.lang.Object)
+     */
+    public void add(int arg0, Object arg1) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#add(java.lang.Object)
+     */
+    public boolean add(Object arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#addAll(java.util.Collection)
+     */
+    public boolean addAll(Collection arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     * @param arg1 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#addAll(int, java.util.Collection)
+     */
+    public boolean addAll(int arg0, Collection arg1) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#clear()
+     */
+    public void clear() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#contains(java.lang.Object)
+     */
+    public boolean contains(Object arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#containsAll(java.util.Collection)
+     */
+    public boolean containsAll(Collection arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @see java.util.List#get(int)
+     */
+    public Object get(int arg0) {
+        return list.get(arg0);
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#indexOf(java.lang.Object)
+     */
+    public int indexOf(Object arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Iterator iterator() {
+        return new PaginatedListIterator(this);
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#lastIndexOf(java.lang.Object)
+     */
+    public int lastIndexOf(Object arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#listIterator()
+     */
+    public ListIterator listIterator() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#listIterator(int)
+     */
+    public ListIterator listIterator(int arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#remove(int)
+     */
+    public Object remove(int arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#remove(java.lang.Object)
+     */
+    public boolean remove(Object arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#removeAll(java.util.Collection)
+     */
+    public boolean removeAll(Collection arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Collection#retainAll(java.util.Collection)
+     */
+    public boolean retainAll(Collection arg0) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     * @param arg1 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#set(int, java.lang.Object)
+     */
+    public Object set(int arg0, Object arg1) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Number of elements in all the pages
+     *
+     * @see java.util.Collection#size()
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @param arg0 DOCUMENT ME!
+     * @param arg1 DOCUMENT ME!
+     *
+     * @return DOCUMENT ME!
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.List#subList(int, int)
+     */
+    public List subList(int arg0, int arg1) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object[] toArray() {
+        return list.toArray();
+    }
+
+    public Object[] toArray(Object[] arg0) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("List size when convert to array "
+                + list.toArray().length);
+        }
+
+        return list.toArray(arg0);
+    }
+}

+ 92 - 0
domain/src/main/java/org/acegisecurity/domain/dao/PaginatedListIterator.java

@@ -0,0 +1,92 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.dao;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+/**
+ * An iterator of the <code>PaginatedList</code>.
+ *
+ * @author Carlos Sanchez
+ * @version $Id$
+ */
+public class PaginatedListIterator implements Iterator {
+    //~ Instance fields ========================================================
+
+    private Iterator iterator;
+    private PaginatedList list;
+    private int i = 0;
+
+    //~ Constructors ===========================================================
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @param list
+     */
+    public PaginatedListIterator(PaginatedList list) {
+        this.list = list;
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * @see java.util.Iterator#hasNext()
+     */
+    public boolean hasNext() {
+        return i < list.size();
+    }
+
+    /**
+     * This method follows the rules of Iterator.next() except that it returns
+     * null when requesting an element that it's not in the current page.
+     *
+     * @see java.util.Iterator#next()
+     */
+    public Object next() {
+        if (i == list.getFirstElement()) {
+            iterator = list.getList().iterator();
+        }
+
+        if ((i >= list.getFirstElement())
+            && (i < (list.getFirstElement() + list.getMaxElements()))) {
+            i++;
+
+            return iterator.next();
+        }
+
+        if (hasNext()) {
+            i++;
+
+            return null;
+        } else {
+            throw new NoSuchElementException();
+        }
+    }
+
+    /**
+     * Unsupported operation
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @see java.util.Iterator#remove()
+     */
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}

+ 6 - 0
domain/src/main/java/org/acegisecurity/domain/dao/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+<p>Provides the base of a data access object (DAO) persistence layer.</p>
+</body>
+</html>
+

+ 269 - 0
domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java

@@ -0,0 +1,269 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.hibernate;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+import net.sf.acegisecurity.domain.dao.Dao;
+import net.sf.acegisecurity.domain.dao.EvictionCapable;
+import net.sf.acegisecurity.domain.dao.PaginatedList;
+
+import net.sf.hibernate.Criteria;
+import net.sf.hibernate.Hibernate;
+import net.sf.hibernate.HibernateException;
+import net.sf.hibernate.Session;
+import net.sf.hibernate.expression.Expression;
+import net.sf.hibernate.expression.MatchMode;
+import net.sf.hibernate.expression.Order;
+import net.sf.hibernate.metadata.ClassMetadata;
+import net.sf.hibernate.type.Type;
+
+import org.springframework.orm.hibernate.HibernateCallback;
+import org.springframework.orm.hibernate.HibernateObjectRetrievalFailureException;
+import org.springframework.orm.hibernate.support.HibernateDaoSupport;
+
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * {@link Dao} implementation that uses Hibernate for persistence.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class DaoHibernate extends HibernateDaoSupport implements Dao,
+    EvictionCapable {
+    //~ Instance fields ========================================================
+
+    /** The class that this instance provides services for */
+    private Class supportsClass;
+
+    //~ Methods ================================================================
+
+    public void setSupportsClass(Class supportClass) {
+        this.supportsClass = supportClass;
+    }
+
+    public Class getSupportsClass() {
+        return supportsClass;
+    }
+
+    public PersistableEntity create(PersistableEntity value) {
+        Assert.notNull(value);
+        getHibernateTemplate().save(value);
+
+        return readId(value.getInternalId());
+    }
+
+    public PersistableEntity createOrUpdate(PersistableEntity value) {
+        Assert.notNull(value);
+
+        if (value.getInternalId() == null) {
+            return create(value);
+        } else {
+            return update(value);
+        }
+    }
+
+    public void delete(PersistableEntity value) {
+        Assert.notNull(value);
+        getHibernateTemplate().delete(value);
+    }
+
+    public void evict(PersistableEntity entity) {
+        Assert.notNull(entity);
+        getHibernateTemplate().evict(entity);
+    }
+
+    public List findAll() {
+        return getHibernateTemplate().loadAll(supportsClass);
+    }
+
+    public List findId(Collection ids) {
+        Assert.notNull(ids, "Collection of IDs cannot be null");
+        Assert.notEmpty(ids, "There must be some values in the Collection list");
+
+        return (List) getHibernateTemplate().execute(getFindByIdCallback(ids));
+    }
+
+    public PersistableEntity readId(Serializable id) {
+        Assert.notNull(id);
+
+        try {
+            return (PersistableEntity) getHibernateTemplate().load(supportsClass,
+                id);
+        } catch (HibernateObjectRetrievalFailureException notFound) {
+            return null;
+        }
+    }
+
+    public PaginatedList scroll(PersistableEntity value, int firstElement,
+        int maxElements, String orderByAsc) {
+        Assert.notNull(value);
+        Assert.hasText(orderByAsc,
+            "An orderByAsc is required (why not use your identity property?)");
+
+        return (PaginatedList) getHibernateTemplate().execute(getFindByValueCallback(
+                value, firstElement, maxElements, Order.asc(orderByAsc)));
+    }
+
+    public boolean supports(Class clazz) {
+        Assert.notNull(clazz);
+
+        return this.supportsClass.equals(clazz);
+    }
+
+    public PersistableEntity update(PersistableEntity value) {
+        Assert.notNull(value);
+        getHibernateTemplate().update(value);
+
+        return readId(value.getInternalId());
+    }
+
+    /**
+     * Custom initialization behavior. Called by superclass.
+     *
+     * @throws Exception if initialization fails
+     */
+    protected final void initDao() throws Exception {
+        Assert.notNull(supportsClass, "supportClass is required");
+        Assert.isTrue(PersistableEntity.class.isAssignableFrom(supportsClass),
+            "supportClass is not an implementation of PersistableEntity");
+        initHibernateDao();
+    }
+
+    /**
+     * Allows subclasses to provide custom initialization behaviour. Called
+     * during {@link #initDao()}.
+     *
+     * @throws Exception
+     */
+    protected void initHibernateDao() throws Exception {}
+
+    /**
+     * Provides a <code>HibernateCallback</code> that will load a list of
+     * objects by a <code>Collection</code> of identities.
+     *
+     * @param ids collection of identities to be loaded
+     *
+     * @return a <code>List</code> containing the matching objects
+     */
+    private HibernateCallback getFindByIdCallback(final Collection ids) {
+        return new HibernateCallback() {
+                public Object doInHibernate(Session session)
+                    throws HibernateException {
+                    Criteria criteria = session.createCriteria(supportsClass);
+
+                    ClassMetadata classMetadata = getSessionFactory()
+                                                      .getClassMetadata(supportsClass);
+
+                    String idPropertyName = classMetadata
+                        .getIdentifierPropertyName();
+                    criteria.add(Expression.in(idPropertyName, ids));
+
+                    return criteria.list();
+                }
+            };
+    }
+
+    /**
+     * Get a new <code>HibernateCallback</code> for finding objects by a bean
+     * property values, paginating the results. Properties with null values
+     * and collections and empty Strings are ignored, as is any property with
+     * the "version" name. If the property is mapped as String find a partial
+     * match, otherwise find by exact match.
+     *
+     * @param bean bean with the values of the parameters
+     * @param firstElement the first result, numbered from 0
+     * @param count the maximum number of results
+     * @param order DOCUMENT ME!
+     *
+     * @return a PaginatedList containing the requested objects
+     */
+    private HibernateCallback getFindByValueCallback(final Object bean,
+        final int firstElement, final int count, final Order order) {
+        return new HibernateCallback() {
+                public Object doInHibernate(Session session)
+                    throws HibernateException {
+                    Criteria criteria = session.createCriteria(bean.getClass());
+
+                    criteria.addOrder(order);
+
+                    ClassMetadata classMetadata = getSessionFactory()
+                                                      .getClassMetadata(bean
+                            .getClass());
+
+                    /* get persistent properties */
+                    Type[] propertyTypes = classMetadata.getPropertyTypes();
+                    String[] propertyNames = classMetadata.getPropertyNames();
+
+                    /* for each persistent property of the bean */
+                    for (int i = 0; i < propertyNames.length; i++) {
+                        String name = propertyNames[i];
+                        Object value = classMetadata.getPropertyValue(bean, name);
+
+                        if (value == null) {
+                            continue;
+                        }
+
+                        // ignore empty Strings
+                        if (value instanceof String) {
+                            String string = (String) value;
+
+                            if ("".equals(string)) {
+                                continue;
+                            }
+                        }
+
+                        // ignore any collections
+                        if (propertyTypes[i].isPersistentCollectionType()) {
+                            continue;
+                        }
+
+                        Type type = classMetadata.getPropertyType(name);
+
+                        if (name.equals("version")) {
+                            continue;
+                        }
+
+                        if (type.equals(Hibernate.STRING)) {
+                            // if the property is mapped as String, find partial match
+                            criteria.add(Expression.ilike(name,
+                                    value.toString(), MatchMode.ANYWHERE));
+                        } else {
+                            // find exact match
+                            criteria.add(Expression.eq(name, value));
+                        }
+                    }
+
+                    /*
+                     * TODO Use Criteria.count() when available in next Hibernate
+                     * versions
+                     */
+                    int size = criteria.list().size();
+
+                    List list = criteria.setFirstResult(firstElement)
+                                        .setMaxResults(count).list();
+
+                    return new PaginatedList(list, firstElement, count, size);
+                }
+            };
+    }
+}

+ 103 - 0
domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java

@@ -0,0 +1,103 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.hibernate;
+
+import net.sf.acegisecurity.domain.validation.IntrospectionManager;
+
+import net.sf.hibernate.HibernateException;
+import net.sf.hibernate.SessionFactory;
+import net.sf.hibernate.metadata.ClassMetadata;
+import net.sf.hibernate.type.Type;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.orm.hibernate.HibernateSystemException;
+
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+
+/**
+ * {@link IntrospectionManager} that uses Hibernate metadata to locate
+ * children.
+ * 
+ * <p>
+ * Add children objects are added to the <code>List</code> of children objects
+ * to validate, irrespective of whether a save/update/delete operation will
+ * cascade to them. This is not a perfect solution, but addresses most
+ * real-world validation requirements (you can always implement your own
+ * <code>IntrospectionManager</code> if you prefer).
+ * </p>
+ * 
+ * <p>
+ * This implementation only adds properties of a parent object that have a
+ * Hibernate {@link net.sf.hibernate.type.Type} that indicates it is an object
+ * type (ie {@link net.sf.hibernate.type.Type#isObjectType()}).
+ * </p>
+ *
+ * @author Matthew Porter
+ * @author Ben Alex
+ */
+public class IntrospectionManagerHibernate implements IntrospectionManager,
+    InitializingBean {
+    //~ Instance fields ========================================================
+
+    private SessionFactory sessionFactory;
+
+    //~ Methods ================================================================
+
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public SessionFactory getSessionFactory() {
+        return this.sessionFactory;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(sessionFactory, "SessionFactory is required");
+    }
+
+    public void obtainImmediateChildren(Object parentObject, List allObjects) {
+        Assert.notNull(parentObject,
+            "Violation of interface contract: parentObject null");
+        Assert.notNull(allObjects,
+            "Violation of interface contract: allObjects null");
+
+        ClassMetadata classMetadata = null;
+
+        try {
+            classMetadata = sessionFactory.getClassMetadata(parentObject
+                    .getClass());
+
+            if (classMetadata != null) {
+                String[] propertyNames = classMetadata.getPropertyNames();
+
+                for (int i = 0; i < propertyNames.length; i++) {
+                    Type propertyType = classMetadata.getPropertyType(propertyNames[i]);
+
+                    if (propertyType.isObjectType()) {
+                        allObjects.add(classMetadata.getPropertyValue(
+                                parentObject, propertyNames[i]));
+                    }
+                }
+            }
+        } catch (HibernateException he) {
+            throw new HibernateSystemException(he);
+        }
+    }
+}

+ 6 - 0
domain/src/main/java/org/acegisecurity/domain/hibernate/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+<p>Hibernate-specific implementations of the domain subproject interfaces.</p>
+</body>
+</html>
+

+ 77 - 0
domain/src/main/java/org/acegisecurity/domain/impl/AbstractPersistableEntity.java

@@ -0,0 +1,77 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.impl;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+
+
+/**
+ * An abstract implementation of {@link
+ * net.sf.acegisecurity.domain.PersistableEntity}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class AbstractPersistableEntity extends BusinessObject
+    implements PersistableEntity {
+    //~ Static fields/initializers =============================================
+
+    public static final int STARTING_VERSION = 0;
+
+    //~ Instance fields ========================================================
+
+    private int version = STARTING_VERSION;
+
+    //~ Methods ================================================================
+
+    /**
+     * Indicates whether this persistable entity has been persisted yet.
+     * Determine based on whether the {@link #getInternalId()} returns
+     * <code>null</code> or a non-<code>null</code> value.
+     *
+     * @return <code>true</code> if the instance has not been persisted,
+     *         <code>false</code> otherwise
+     */
+    public final boolean isNew() {
+        return (getInternalId() == null);
+    }
+
+    /**
+     * Returns the version number, which should be managed by the persistence
+     * layer.
+     * 
+     * <p>
+     * Initially all <code>PersistableEntity</code>s will commence with the
+     * version number defined by {@link #STARTING_VERSION}.
+     * </p>
+     *
+     * @return the version
+     */
+    public final int getVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the version numbers. Should only be used by the persistence layer.
+     *
+     * @param version the new version number to use
+     *
+     * @hibernate.version type="integer"
+     */
+    protected final void setVersion(int version) {
+        this.version = version;
+    }
+}

+ 81 - 0
domain/src/main/java/org/acegisecurity/domain/impl/BusinessObject.java

@@ -0,0 +1,81 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.impl;
+
+import net.sf.acegisecurity.domain.util.CollectionIgnoringReflectionToStringBuilder;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.Serializable;
+
+
+/**
+ * A business domain object.
+ * 
+ * <p>
+ * Only minimal convenience methods are provided by
+ * <code>BusinessObject</code>. Whilst many other methods could easily be
+ * offered (and overridden on an as-required basis) it is felt the default
+ * behaviour of {@link java.lang.Object} is widely understood and an
+ * appropriate default.
+ * </p>
+ *
+ * @author Carlos Sanchez
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class BusinessObject implements Serializable, Cloneable {
+    //~ Instance fields ========================================================
+
+    protected final transient Log logger = LogFactory.getLog(getClass());
+
+    //~ Methods ================================================================
+
+    /**
+     * Swallow cloning.
+     * 
+     * <p>
+     * This method delegates to BeanUtils.cloneBean().
+     * </p>
+     *
+     * @return a clone of the current instance
+     *
+     * @throws IllegalStateException if there are any problems with swallow
+     *         cloning
+     *
+     * @see java.lang.Object#clone()
+     * @see BeanUtils#cloneBean(Object)
+     */
+    public Object clone() {
+        try {
+            return BeanUtils.cloneBean(this);
+        } catch (Exception e) {
+            logger.error(e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Delegates to {@link CollectionIgnoringReflectionToStringBuilder}.
+     *
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return new CollectionIgnoringReflectionToStringBuilder(this).toString();
+    }
+}

+ 78 - 0
domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityInteger.java

@@ -0,0 +1,78 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.impl;
+
+import java.io.Serializable;
+
+
+/**
+ * A persistable entity that uses an <code>Integer</code> based identity.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class PersistableEntityInteger extends AbstractPersistableEntity {
+    //~ Instance fields ========================================================
+
+    private Integer id;
+
+    //~ Methods ================================================================
+
+    /**
+     * DO NOT USE DIRECTLY.
+     * 
+     * <p>
+     * Typically only used by the persistence layer, but provided with public
+     * visibility to not limit flexibility.
+     * </p>
+     *
+     * @param id the new instance identity
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * Obtains the persistence identity of this instance.
+     *
+     * @return the instance's identity
+     *
+     * @hibernate.id generator-class="sequence"
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * DO NOT USE DIRECTLY.
+     * 
+     * <p>
+     * Use {@link #getId()} instead, as it provides the correct return type.
+     * This method is only provided for use by the persistence layer and to
+     * satisfy the {@link net.sf.acegisecurity.domain.PersistableEntity}
+     * interface contract.
+     * </p>
+     * 
+     * <p>
+     * Internally delegates to {@link #getId()}.
+     * </p>
+     *
+     * @return the instance's identity
+     */
+    public Serializable getInternalId() {
+        return this.getId();
+    }
+}

+ 78 - 0
domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityLong.java

@@ -0,0 +1,78 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.impl;
+
+import java.io.Serializable;
+
+
+/**
+ * A persistable entity that uses a <code>Long</code> based identity.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class PersistableEntityLong extends AbstractPersistableEntity {
+    //~ Instance fields ========================================================
+
+    private Long id;
+
+    //~ Methods ================================================================
+
+    /**
+     * DO NOT USE DIRECTLY.
+     * 
+     * <p>
+     * Typically only used by the persistence layer, but provided with public
+     * visibility to not limit flexibility.
+     * </p>
+     *
+     * @param id the new instance identity
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * Obtains the persistence identity of this instance.
+     *
+     * @return the instance's identity
+     *
+     * @hibernate.id generator-class="sequence"
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * DO NOT USE DIRECTLY.
+     * 
+     * <p>
+     * Use {@link #getId()} instead, as it provides the correct return type.
+     * This method is only provided for use by the persistence layer and to
+     * satisfy the {@link net.sf.acegisecurity.domain.PersistableEntity}
+     * interface contract.
+     * </p>
+     * 
+     * <p>
+     * Internally delegates to {@link #getId()}.
+     * </p>
+     *
+     * @return the instance's identity
+     */
+    public Serializable getInternalId() {
+        return this.getId();
+    }
+}

+ 38 - 0
domain/src/main/java/org/acegisecurity/domain/impl/PersistableValue.java

@@ -0,0 +1,38 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.impl;
+
+/**
+ * A <i>value object</i>, which means a persistable business object  that does
+ * not have its own persistence identity.
+ * 
+ * <p>
+ * Every value object belongs to a single {@link
+ * net.sf.acegisecurity.domain.impl.AbstractPersistableEntity}. This is
+ * necessary so that the value object has some sort of persistence
+ * relationship/ownership.
+ * </p>
+ * 
+ * <P>
+ * In addition, a value object cannot be referenced from more than one
+ * <code>PersistableEntity</code>. Use a <code>PersistableEntity</code>
+ * instead of a  <code>PersistableValue</code> if this is a design constraint.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class PersistableValue extends BusinessObject {}

+ 7 - 0
domain/src/main/java/org/acegisecurity/domain/impl/package.html

@@ -0,0 +1,7 @@
+<html>
+<body>
+<p>Convenient domain object abstract classes, although none are mandatory/required by
+other packages in this project.</p>
+</body>
+</html>
+

+ 6 - 0
domain/src/main/java/org/acegisecurity/domain/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+<p>Provides tools to assist develop rich domain object models.</p>
+</body>
+</html>
+

+ 51 - 0
domain/src/main/java/org/acegisecurity/domain/util/CollectionIgnoringReflectionToStringBuilder.java

@@ -0,0 +1,51 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.util;
+
+import java.lang.reflect.Field;
+
+
+/**
+ * A <code>toString()</code> builder that ignores collections.
+ *
+ * @author Carlos Sanchez
+ * @version $Id$
+ *
+ * @see org.apache.commons.lang.builder.ReflectionToStringBuilder
+ */
+public class CollectionIgnoringReflectionToStringBuilder
+    extends ReflectionToStringBuilder {
+    //~ Constructors ===========================================================
+
+    public CollectionIgnoringReflectionToStringBuilder(Object object) {
+        super(object);
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Check if the field is a collection and return false in that case.
+     *
+     * @see org.apache.commons.lang.builder.ReflectionToStringBuilder#accept(java.lang.reflect.Field)
+     */
+    protected boolean accept(Field field) {
+        if (CollectionUtils.isCollection(field.getType())) {
+            return false;
+        }
+
+        return super.accept(field);
+    }
+}

+ 162 - 0
domain/src/main/java/org/acegisecurity/domain/util/CollectionUtils.java

@@ -0,0 +1,162 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+
+/**
+ * Some utility methods to use <code>Collection</code>s.
+ *
+ * @author Carlos Sanchez
+ * @version $Id$
+ */
+public class CollectionUtils {
+    //~ Methods ================================================================
+
+    public static boolean isCollection(Class theClass) {
+        return Collection.class.isAssignableFrom(theClass);
+    }
+
+    public static boolean isMap(Class theClass) {
+        return Map.class.isAssignableFrom(theClass);
+    }
+
+    /**
+     * Add an object to a <code>Set</code> and return the result.
+     *
+     * @param set
+     * @param object
+     *
+     * @return
+     */
+    public static Set add(Set set, Object object) {
+        set.add(object);
+
+        return set;
+    }
+
+    /**
+     * Add an object to a <code>List</code> and return the result.
+     *
+     * @param list
+     * @param object
+     *
+     * @return
+     */
+    public static List add(List list, Object object) {
+        list.add(object);
+
+        return list;
+    }
+
+    /**
+     * Clone a Collection copying all its elements to a new one. If map is
+     * <code>null</code> return <code>null</code>.
+     *
+     * @param collection
+     *
+     * @return
+     *
+     * @throws IllegalArgumentException DOCUMENT ME!
+     */
+    public static Collection clone(Collection collection) {
+        if (collection == null) {
+            return null;
+        }
+
+        Class clazz = collection.getClass();
+        Collection clone = null;
+
+        if (List.class.isAssignableFrom(clazz)) {
+            clone = new ArrayList(collection);
+        } else if (SortedSet.class.isAssignableFrom(clazz)) {
+            clone = new TreeSet(collection);
+        } else if (Set.class.isAssignableFrom(clazz)) {
+            clone = new HashSet(collection);
+        } else {
+            throw new IllegalArgumentException("Unknown collection class: "
+                + clazz);
+        }
+
+        return clone;
+    }
+
+    /**
+     * Clone a <code>Map</code> copying all its elements to a new one. If the
+     * passed argument is <code>null</code>, the method will return
+     * <code>null</code>.
+     *
+     * @param map to copy
+     *
+     * @return a copy of the <code>Map</code> passed as an argument
+     *
+     * @throws IllegalArgumentException if the <code>Map</code> implementation
+     *         is not supported by this method
+     */
+    public static Map clone(Map map) {
+        if (map == null) {
+            return null;
+        }
+
+        Class clazz = map.getClass();
+        Map clone = null;
+
+        if (SortedMap.class.isAssignableFrom(clazz)) {
+            clone = new TreeMap(map);
+        } else if (Map.class.isAssignableFrom(clazz)) {
+            clone = new HashMap(map);
+        } else {
+            throw new IllegalArgumentException("Unknown map class: " + clazz);
+        }
+
+        return clone;
+    }
+
+    /**
+     * Return a <code>List</code> (actually an {@link ArrayList}) with only
+     * that object.
+     *
+     * @param object
+     *
+     * @return
+     */
+    public static List newList(Object object) {
+        return add(new ArrayList(1), object);
+    }
+
+    /**
+     * Return a <code>Set</code> (actually a {@link HashSet}) with only that
+     * object.
+     *
+     * @param object
+     *
+     * @return
+     */
+    public static Set newSet(Object object) {
+        return add(new HashSet(), object);
+    }
+}

+ 67 - 0
domain/src/main/java/org/acegisecurity/domain/util/ReflectionToStringBuilder.java

@@ -0,0 +1,67 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.util;
+
+import org.apache.commons.lang.builder.ToStringStyle;
+
+import java.lang.reflect.Field;
+
+import java.text.DateFormat;
+
+import java.util.Calendar;
+
+
+/**
+ * Customized Commons Lang <code>ReflectionToStringBuilder</code>.
+ *
+ * @author Carlos Sanchez
+ * @version $Revision$
+ *
+ * @see org.apache.commons.lang.builder.ReflectionToStringBuilder
+ */
+public class ReflectionToStringBuilder
+    extends org.apache.commons.lang.builder.ReflectionToStringBuilder {
+    //~ Static fields/initializers =============================================
+
+    private static DateFormat formatter = DateFormat.getDateTimeInstance();
+
+    //~ Constructors ===========================================================
+
+    public ReflectionToStringBuilder(Object object) {
+        super(object, ToStringStyle.MULTI_LINE_STYLE);
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Calendar fields are formatted with DateFormat.getDateTimeInstance()
+     * instead of using Calendar.toString().
+     *
+     * @see org.apache.commons.lang.builder.ReflectionToStringBuilder#getValue(java.lang.reflect.Field)
+     */
+    protected Object getValue(Field f)
+        throws IllegalArgumentException, IllegalAccessException {
+        Object value = super.getValue(f);
+
+        if (Calendar.class.isInstance(value)) {
+            Calendar c = (Calendar) value;
+
+            return formatter.format(c.getTime());
+        } else {
+            return value;
+        }
+    }
+}

+ 6 - 0
domain/src/main/java/org/acegisecurity/domain/util/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+<p>Utilities useful in the domain package.</p>
+</body>
+</html>
+

+ 57 - 0
domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidation.java

@@ -0,0 +1,57 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import org.springframework.validation.BindException;
+
+
+/**
+ * Indicates a domain object wishes to perform additional binding before the
+ * <code>Validator</code> is called.
+ * 
+ * <p>
+ * Typically this type of binding sets up private or protected properties that
+ * the end user is not responsible for modifying. Whilst generally this can be
+ * done by adding a hook to every property setter, the
+ * <code>BindBeforeValidation</code> interface provides an AOP-style approach
+ * that ensures missing hooks do not cause invalid object state.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface BindBeforeValidation {
+    //~ Methods ================================================================
+
+    /**
+     * This method will be called by infrastructure code before attempting to
+     * validate the object. Given this method is called prior to validation,
+     * implementations of this method should <b>not</b> assume the object is
+     * in a valid state.
+     * 
+     * <p>
+     * Implementations should modify the object as required so that the
+     * <code>Validator</code> will succeed if user-controllable properties are
+     * correct.
+     * </p>
+     *
+     * @throws BindException if there are problems that the method wish to
+     *         advise (note that the <code>Validator</code> should be allowed
+     *         to determine errors in most cases, rather than this method
+     *         doing so)
+     */
+    public void bindSupport() throws BindException;
+}

+ 50 - 0
domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidationUtils.java

@@ -0,0 +1,50 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import org.springframework.util.Assert;
+
+import org.springframework.validation.BindException;
+
+
+/**
+ * Convenience class that invokes the {@link BindBeforeValidation} interface if
+ * the passed domain object has requested it.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BindBeforeValidationUtils {
+    //~ Methods ================================================================
+
+    /**
+     * Call {@link BindBeforeValidation#bindSupport()} if the domain object
+     * requests it.
+     *
+     * @param domainObject to attempt to bind (never <code>null</code>)
+     *
+     * @throws BindException if the binding failed
+     */
+    public static void bindIfRequired(Object domainObject)
+        throws BindException {
+        Assert.notNull(domainObject);
+
+        if (BindBeforeValidation.class.isAssignableFrom(domainObject.getClass())) {
+            BindBeforeValidation bbv = (BindBeforeValidation) domainObject;
+            bbv.bindSupport();
+        }
+    }
+}

+ 55 - 0
domain/src/main/java/org/acegisecurity/domain/validation/IntrospectionManager.java

@@ -0,0 +1,55 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import java.util.List;
+
+
+/**
+ * Indicates a concrete class capable of introspecting a domain object for its
+ * immediate children.
+ * 
+ * <p>
+ * Implementations may use a choice of reflective introspection or querying a
+ * persistence metadata API to locate the internal children.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface IntrospectionManager {
+    //~ Methods ================================================================
+
+    /**
+     * Locates any direct children of a domain object.
+     * 
+     * <p>
+     * Typically used with a {@link ValidationManager} to validate each of the
+     * located children.
+     * </p>
+     * 
+     * <P>
+     * Implementations should only add the <b>immediate layer of children</b>.
+     * Grandchildren, great-grandchildren etc should not be added.
+     * </p>
+     *
+     * @param parentObject the immediate parent which all children should share
+     *        (guaranteed to never be <code>null</code>)
+     * @param allObjects the list to which this method should append each
+     *        immediate child (guaranteed to never be <code>null</code>)
+     */
+    public void obtainImmediateChildren(Object parentObject, List allObjects);
+}

+ 115 - 0
domain/src/main/java/org/acegisecurity/domain/validation/ValidationAdvisor.java

@@ -0,0 +1,115 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import org.springframework.aop.framework.AopConfigException;
+import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Advisor for the {@link ValidationInterceptor}.
+ * 
+ * <p>
+ * Intended to be used with Spring's
+ * <code>DefaultAdvisorAutoProxyCreator</code>.
+ * </p>
+ * 
+ * <p>
+ * Registers {@link ValidationInterceptor} for every <code>Method</code>
+ * against a class that directly or through its superclasses implements {@link
+ * #supportsClass} and has a signature match those defined by {@link
+ * #methods}.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ValidationAdvisor extends StaticMethodMatcherPointcutAdvisor
+    implements InitializingBean {
+    //~ Instance fields ========================================================
+
+    private Class supportsClass;
+    private String[] methods = {"create", "update"};
+
+    //~ Constructors ===========================================================
+
+    public ValidationAdvisor(ValidationInterceptor advice) {
+        super(advice);
+
+        if (advice == null) {
+            throw new AopConfigException(
+                "Cannot construct a BindAndValidateAdvisor using a "
+                + "null BindAndValidateInterceptor");
+        }
+    }
+
+    //~ Methods ================================================================
+
+    public void setMethods(String[] methods) {
+        this.methods = methods;
+    }
+
+    public String[] getMethods() {
+        return methods;
+    }
+
+    public void setSupportsClass(Class clazz) {
+        this.supportsClass = clazz;
+    }
+
+    public Class getSupportsClass() {
+        return supportsClass;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(supportsClass, "A supportsClass is required");
+        Assert.notNull(methods, "A list of valid methods is required");
+        Assert.notEmpty(methods, "A list of valid methods is required");
+    }
+
+    public boolean matches(Method m, Class targetClass) {
+        // Check there are actual arguments
+        if (m.getParameterTypes().length == 0) {
+            return false;
+        }
+
+        // Check the method name matches one we're interested in
+        boolean found = false;
+
+        for (int i = 0; i < methods.length; i++) {
+            if (m.getName().equals(methods[i])) {
+                found = true;
+            }
+        }
+
+        if (!found) {
+            return false;
+        }
+
+        // Check the target is of the type of class we wish to advise
+        if (supportsClass.isAssignableFrom(targetClass)) {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 107 - 0
domain/src/main/java/org/acegisecurity/domain/validation/ValidationInterceptor.java

@@ -0,0 +1,107 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import net.sf.acegisecurity.domain.PersistableEntity;
+import net.sf.acegisecurity.domain.impl.BusinessObject;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Calls {@link ValidationManager} for method invocations.
+ * 
+ * <p>
+ * For each method invocation, any argument that is assignable from {@link
+ * #argumentClasses}<b>and</b> is non-<code>null</code> will be passed to the
+ * {@link net.sf.acegisecurity.domain.validation.ValidationManager} for
+ * processing.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ValidationInterceptor implements MethodInterceptor,
+    InitializingBean {
+    //~ Instance fields ========================================================
+
+    protected final Log logger = LogFactory.getLog(getClass());
+    private ValidationManager validationManager;
+    private Class[] argumentClasses = {BusinessObject.class, PersistableEntity.class};
+
+    //~ Methods ================================================================
+
+    public void setArgumentClasses(Class[] argumentClasses) {
+        this.argumentClasses = argumentClasses;
+    }
+
+    public Class[] getArgumentClasses() {
+        return argumentClasses;
+    }
+
+    public void setValidationManager(ValidationManager validationManager) {
+        this.validationManager = validationManager;
+    }
+
+    public ValidationManager getValidationManager() {
+        return validationManager;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(validationManager, "A ValidationManager is required");
+        Assert.notEmpty(argumentClasses,
+            "A list of business object classes to validate is required");
+    }
+
+    public Object invoke(MethodInvocation mi) throws Throwable {
+        Object[] args = mi.getArguments();
+
+        for (int i = 0; i < args.length; i++) {
+            if (shouldValidate(args[i])) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("ValidationInterceptor calling for: '"
+                        + args[i] + "'");
+                }
+
+                validationManager.validate(args[i]);
+            }
+        }
+
+        return mi.proceed();
+    }
+
+    private boolean shouldValidate(Object argument) {
+        if (argument == null) {
+            return false;
+        }
+
+        for (int i = 0; i < argumentClasses.length; i++) {
+            if (argumentClasses[i].isAssignableFrom(argument.getClass())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 49 - 0
domain/src/main/java/org/acegisecurity/domain/validation/ValidationManager.java

@@ -0,0 +1,49 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import org.springframework.validation.BindException;
+
+
+/**
+ * Able to validate any passed domain object instance, including its children.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface ValidationManager {
+    //~ Methods ================================================================
+
+    /**
+     * Validates the passed domain object, along with any children,
+     * grandchildren, great-grandchildren etc.
+     * 
+     * <p>
+     * Before performing validation, implementations must execute {@link
+     * BindBeforeValidation} for any domain objects requesting it.
+     * </p>
+     *
+     * @param domainObject to validate (cannot be <code>null</code>)
+     *
+     * @throws BindException if a validation problem occurs
+     * @throws ValidatorNotFoundException if no matching <code>Validator</code>
+     *         could be found (and the implementation wishes to treat this as
+     *         an exception condition as opposed to logging it and
+     *         continuing).
+     */
+    public void validate(Object domainObject)
+        throws BindException, ValidatorNotFoundException;
+}

+ 254 - 0
domain/src/main/java/org/acegisecurity/domain/validation/ValidationManagerImpl.java

@@ -0,0 +1,254 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+import org.springframework.validation.BindException;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * Default implementation of {@link ValidationManager}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ValidationManagerImpl implements InitializingBean,
+    ValidationManager {
+    //~ Instance fields ========================================================
+
+    protected final Log logger = LogFactory.getLog(getClass());
+    private IntrospectionManager introspectionManager;
+    private List validators;
+    private boolean strictValidation = true;
+
+    //~ Methods ================================================================
+
+    public void setIntrospectionManager(
+        IntrospectionManager introspectionManager) {
+        this.introspectionManager = introspectionManager;
+    }
+
+    public IntrospectionManager getIntrospectionManager() {
+        return introspectionManager;
+    }
+
+    /**
+     * Indicates whether a {@link ValidatorNotFoundException} should be thrown
+     * if any domain object does not have a corresponding
+     * <code>Validator</code> defined against the {@link #validators}.
+     * 
+     * <p>
+     * Defaults to <code>true</code>. This is a reasonable default, as callers
+     * of <code>ValidationManager</code> should expect the object to support
+     * validation.
+     * </p>
+     *
+     * @param strictValidation set to <code>false</code> if you wish to
+     *        silently ignore any domain object that is missing a
+     *        <code>Validator</code>
+     */
+    public void setStrictValidation(boolean strictValidation) {
+        this.strictValidation = strictValidation;
+    }
+
+    public boolean isStrictValidation() {
+        return strictValidation;
+    }
+
+    /**
+     * Sets the {@link Validator} objects to be used.
+     *
+     * @param newList that should be used for validation.
+     */
+    public void setValidators(List newList) {
+        Assert.notNull(newList, "A list of Validators is required");
+        Assert.isTrue(newList.size() > 0,
+            "At least one Validator must be defined");
+
+        Iterator iter = newList.iterator();
+
+        while (iter.hasNext()) {
+            Object currentObject = null;
+            currentObject = iter.next();
+            Assert.isInstanceOf(Validator.class, currentObject,
+                "Validator '" + currentObject
+                + "' must be an instance of Validator");
+        }
+
+        this.validators = newList;
+    }
+
+    public List getValidators() {
+        return this.validators;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(validators, "A list of Validators is required");
+        Assert.isTrue(validators.size() > 0,
+            "At least one Validator must be defined");
+        Assert.notNull(introspectionManager,
+            "An IntrospectionManager is required");
+    }
+
+    /**
+     * Validates the passed domain object, along with any children,
+     * grandchildren, great-grandchildren etc.
+     *
+     * @param domainObject to validate (cannot be <code>null</code>)
+     *
+     * @throws BindException if a validation problem occurs
+     * @throws ValidatorNotFoundException if no matching <code>Validator</code>
+     *         could be found for the object or its children (only ever thrown
+     *         if the {@link #strictValidation}) was set to
+     *         <code>true</code>).
+     */
+    public void validate(Object domainObject)
+        throws BindException, ValidatorNotFoundException {
+        // Abort if null
+        Assert.notNull(domainObject,
+            "Cannot validate a null domain object, as unable to getClass()");
+
+        // Construct a list of objects to be validated and add self
+        List allObjects = new Vector();
+        allObjects.add(domainObject);
+
+        // Add all children (and grandchildren, great-grandchildren etc)
+        // of domain object to the list of objects to be validated
+        // (list never contains null)
+        obtainAllChildren(domainObject, allObjects);
+
+        Assert.notEmpty(allObjects,
+            "The list of objects to be validated was empty");
+
+        // Process list of objects to be validated by validating each
+        Iterator iter = allObjects.iterator();
+
+        while (iter.hasNext()) {
+            Object currentDomainObject = iter.next();
+            Class clazz = currentDomainObject.getClass();
+
+            try {
+                Errors errors = new BindException(currentDomainObject,
+                        clazz.getName());
+                Validator v = findValidator(clazz);
+
+                // Call bindSupport() if this class wishes
+                BindBeforeValidationUtils.bindIfRequired(currentDomainObject);
+
+                // Perform validation
+                v.validate(currentDomainObject, errors);
+
+                // Handle validation outcome
+                if (errors.getErrorCount() == 0) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Validated '" + clazz + "' successfully");
+                    }
+                } else {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Validated '" + clazz
+                            + "' but errors detected");
+                    }
+
+                    throw (BindException) errors;
+                }
+            } catch (ValidatorNotFoundException validatorNotFoundException) {
+                if (strictValidation) {
+                    if (logger.isErrorEnabled()) {
+                        logger.error(validatorNotFoundException);
+                    }
+
+                    throw validatorNotFoundException;
+                }
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Could not locate validator for class '"
+                        + clazz + "'; skipping without error");
+                }
+            }
+        }
+    }
+
+    private Validator findValidator(Class clazz)
+        throws ValidatorNotFoundException {
+        Assert.notNull(clazz, "Class cannot be null");
+
+        Iterator iter = validators.iterator();
+
+        while (iter.hasNext()) {
+            Validator validator = (Validator) iter.next();
+
+            if (validator.supports(clazz)) {
+                return validator;
+            }
+        }
+
+        throw new ValidatorNotFoundException("No Validator found for class '"
+            + clazz + "'");
+    }
+
+    /**
+     * Locates all immediate children of the passed <code>parentObject</code>,
+     * adding each of those immediate children to the <code>allObjects</code>
+     * list and then calling this same method for each of those immediate
+     * children.
+     * 
+     * <p>
+     * Does <b>not</b> add the passed <code>parentObject</code> to the
+     * <code>allObjects</code> list. The caller of this method should ensure
+     * the <code>parentObject</code> is added to the list instead.
+     * </p>
+     *
+     * @param parentObject the object we wish to locate all children for
+     * @param allObjects the list to add the located children to
+     */
+    private void obtainAllChildren(Object parentObject, List allObjects) {
+        Assert.notNull(parentObject, "Violation of parentObject method contract");
+        Assert.notNull(allObjects, "Violation of allObjects method contract");
+        Assert.isTrue(allObjects.contains(parentObject),
+            "List of objects missing the requested parentObject");
+
+        // Add immediate children of this domain object
+        List currentChildren = new Vector();
+        introspectionManager.obtainImmediateChildren(parentObject,
+            currentChildren);
+
+        // Add the children
+        allObjects.addAll(currentChildren);
+
+        // Now iterate the children, adding their children to the object list
+        Iterator childrenIter = currentChildren.iterator();
+
+        while (childrenIter.hasNext()) {
+            Object childObject = childrenIter.next();
+
+            if (childObject != null) {
+                obtainAllChildren(childObject, allObjects);
+            }
+        }
+    }
+}

+ 51 - 0
domain/src/main/java/org/acegisecurity/domain/validation/ValidatorNotFoundException.java

@@ -0,0 +1,51 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * 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 net.sf.acegisecurity.domain.validation;
+
+import net.sf.acegisecurity.domain.DomainException;
+
+
+/**
+ * Thrown if no <code>Validator</code> could be found that supports a domain
+ * object presented for validation.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ValidatorNotFoundException extends DomainException {
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructs a <code>ValidatorNotFoundException</code> with the specified
+     * message and root cause.
+     *
+     * @param msg the detail message
+     * @param t the root cause
+     */
+    public ValidatorNotFoundException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+    /**
+     * Constructs a <code>DomainException</code> with the specified message and
+     * no root cause.
+     *
+     * @param msg the detail message
+     */
+    public ValidatorNotFoundException(String msg) {
+        super(msg);
+    }
+}

+ 46 - 0
domain/src/main/java/org/acegisecurity/domain/validation/package.html

@@ -0,0 +1,46 @@
+<html>
+<body>
+<p>Validation services for complex domain objects.</p>
+
+<p>Generally you will write <code>Validator</code>s for each of your domain
+objects, and add a {@link ValidationManager} to your application context. You
+will need to wire a suitable {@link IntrospectionManager} against the
+<code>ValidationManager</code> so that children of a domain object presented
+for validation can be identified and in turn also validated.
+
+<p>The {@link ValidationInterceptor} and {@link ValidationAdvisor} should be
+used against each of your data access object (DAO) mutator methods, such as
+<code>SomeDao.create(Object)</code> and <code>SomeDao.update(Object)</code>.
+The interceptor will cause the <code>Object</code> to be presented to the
+<code>ValidationManager</code>, thus ensuring the domain object instance is in
+a valid state before being persisted.</p>
+
+<p>If you domain objects themselves wish to ensure they are in a valid state
+prior to internal business methods being invoked, it is suggested they provide
+a <code>ValidationManager</code> collaborator, and fire its validate method.
+Such collaborator can be autowired during both instance retrieval and creation.
+It should generally also be marked as <code>transient</code>, to avoid possible
+serialisation issues if used inside a <code>HttpSession</code> or similar.</p>
+
+<p>Sometimes domain objects need to internally update themselves before being
+validated. Any such domain objects should implement {@link BindBeforeValidation}.
+The <code>ValidationManager</code> will fire the related method just prior to
+validation, and you can do it manually using {@link BindBeforeValidationUtils}.
+Using the utility class is generally preferred over calling the method
+directly, as it ignores classes that do not implement
+<code>BindBeforeValidation</code>.</p>
+
+<p>Finally, sometimes <code>Validator</code>s might need to perform queries
+against a persistence or services layer. For example, the <code>Validator</code>
+may be checking no other user has this username. If using an ORM tool such as
+Hibernate, it is recommended your <code>Validator</code>s subclass a common
+abstract parent that provides an <code>evict(Object)</code> and 
+<code>evict(Collection)</code> method. That way your <code>Validator</code>s
+can utilise standard services layer or DAO methods as required to retrieve
+other domain objects, and flush them from the session cache afterwards. Whilst
+this is more a <code>Validator</code> design choice than something mandated by
+this package, we have found it a worthwhile pattern in real-world applications.
+</p>
+</body>
+</html>
+

+ 1 - 1
project.properties

@@ -63,7 +63,7 @@ maven.license.licenseFile=${rootdir}/LICENSE.txt
 
 # multiproject
 maven.multiproject.basedir=${rootdir}
-maven.multiproject.includes=core/project.xml,adapters/*/project.xml,samples/*/project.xml
+maven.multiproject.includes=core/project.xml,adapters/*/project.xml,samples/*/project.xml,domain/project.xml
 
 # multichanges
 maven.multichanges.basedir=${maven.multiproject.basedir}