diff --git a/.classpath b/.classpath
index 682ed83aaf..0c72902f7f 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,10 @@
DomainException
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 DomainException
with the specified message and
+ * no root cause.
+ *
+ * @param msg the detail message
+ */
+ public DomainException(String msg) {
+ super(msg);
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/PersistableEntity.java b/domain/src/main/java/org/acegisecurity/domain/PersistableEntity.java
new file mode 100644
index 0000000000..b9402265f1
--- /dev/null
+++ b/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 persistable entity.
+ *
+ *
+ * A persistable entity is any object that is capable of being persisted, + * typically via a {@link net.sf.acegisecurity.domain.dao.Dao} implementation. + *
+ * + * @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. + * + *
+ * Typically a subclass will delegate to a public
+ * SomePrimitiveWrapper getId()
method. The necessity for the
+ * getInternalId()
abstract method is solely because the
+ * persistence layer needs a way of obtaining the identity irrespective of
+ * the actual identity implementation choice.
+ *
+ * Returning null
from this method will indicate the object
+ * has never been saved. This will likely be relied on by some
+ * Dao
implementations.
+ *
+ * This interface provides a portable approach to Data Access Object (DAO) + * functionality across various object relational persistance solutions. + *
+ * + *
+ * It is not envisioned that this interface will provide all 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 Dao
interface are encouraged.
+ *
+ * A Dao
implementation (or a subclass of Dao
) 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.
+ *
+ * Each Dao
implementation will support one
+ * PersistableEntity
classes only. The supported
+ * PersistableEntity
class must be indicated via the {@link
+ * #supports(Class)} method.
+ *
null
+ * 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 List
will be
+ * returned if no matches are found)
+ */
+ public List findAll();
+
+ /**
+ * Find a List
of PersistableEntity
s, searched by
+ * their identifiers.
+ *
+ * @param ids collection of identifiers to locate
+ *
+ * @return the values with those identifiers (an empty List
+ * 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 null
if not found
+ */
+ public PersistableEntity readId(Serializable id);
+
+ /**
+ * Find persistent instances with properties matching those of the passed
+ * PersistableEntity
.
+ *
+ *
+ * Persistent instances are matched on the basis of query by example.
+ * Properties whose value is null
, empty
+ * String
s, and any Collection
s are ignored in
+ * the query by example evaluation.
+ *
PersistableEntity
that should be used to order the
+ * results
+ *
+ * @return the requested page of the result list (a properly formed
+ * PaginatedList
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
+ * PersistableEntity
+ *
+ * @return true
or false
, 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 PersistableEntity
having a
+ * non-null
identifier
+ *
+ * @return the updated value
+ */
+ public PersistableEntity update(PersistableEntity value);
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/EvictionCapable.java b/domain/src/main/java/org/acegisecurity/domain/dao/EvictionCapable.java
new file mode 100644
index 0000000000..a430d693a4
--- /dev/null
+++ b/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.
+ *
+ *
+ * Structured as a separate interface (rather than a subclass of
+ * Dao
), as it is not required for all persistence strategies.
+ *
+ * If the passed object does not exist in the internal map/session, the + * invocation has no effect. + *
+ * + *+ * May throw an exception if the implementation so desires. + *
+ * + * @param entity to remove from the internal map/session + */ + public void evict(PersistableEntity entity); +} diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java b/domain/src/main/java/org/acegisecurity/domain/dao/EvictionUtils.java new file mode 100644 index 0000000000..277dd574d9 --- /dev/null +++ b/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 ofPersistableEntity
s
+ * from those objects that implement {@link EvictionCapable}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EvictionUtils {
+ //~ Methods ================================================================
+
+ /**
+ * Evicts the PersistableEntity
using the passed
+ * Object
(provided that the passed Object
+ * implements EvictionCapable
).
+ *
+ * @param daoOrServices the potential source for
+ * EvictionCapable
services (never null
)
+ * @param entity to evict (never null
)
+ */
+ 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 PersistableEntity
element of the passed
+ * Collection
using the passed Object
(provided
+ * that the passed Object
implements
+ * EvictionCapable
).
+ *
+ * @param daoOrServices the potential source for
+ * EvictionCapable
services (never null
)
+ * @param collection whose members to evict (never null
)
+ */
+ 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 EvictionCapable
from the passed argument, or
+ * null
.
+ *
+ * @param daoOrServices to check if provides eviction services
+ *
+ * @return the EvictionCapable
object or null
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;
+ }
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/PaginatedList.java b/domain/src/main/java/org/acegisecurity/domain/dao/PaginatedList.java
new file mode 100644
index 0000000000..7cf753b775
--- /dev/null
+++ b/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;
+
+
+/**
+ *
+ * Represents a paginated List
.
+ *
+ * Elements in the internal List
(see {@link #getList()} represent
+ * only part of a larger resultset.
+ *
+ * Note that firstElement starts at zero. Any attempt to access other than the + * current page will cause an error. + *
+ * + *
+ * This is a read only implementation and many of the List
+ * methods are not implemented.
+ *
PaginatedList
which contains only the
+ * given entity.
+ *
+ * @param entity the entity to include (can be null
, which
+ * indicates an empty PaginatedList
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);
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/PaginatedListIterator.java b/domain/src/main/java/org/acegisecurity/domain/dao/PaginatedListIterator.java
new file mode 100644
index 0000000000..4510599f1b
--- /dev/null
+++ b/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 PaginatedList
.
+ *
+ * @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();
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/package.html b/domain/src/main/java/org/acegisecurity/domain/dao/package.html
new file mode 100644
index 0000000000..3d973c529b
--- /dev/null
+++ b/domain/src/main/java/org/acegisecurity/domain/dao/package.html
@@ -0,0 +1,6 @@
+
+
+Provides the base of a data access object (DAO) persistence layer.
+ + + diff --git a/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java b/domain/src/main/java/org/acegisecurity/domain/hibernate/DaoHibernate.java new file mode 100644 index 0000000000..345c4d4522 --- /dev/null +++ b/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 aHibernateCallback
that will load a list of
+ * objects by a Collection
of identities.
+ *
+ * @param ids collection of identities to be loaded
+ *
+ * @return a List
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 HibernateCallback
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);
+ }
+ };
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java b/domain/src/main/java/org/acegisecurity/domain/hibernate/IntrospectionManagerHibernate.java
new file mode 100644
index 0000000000..83c763f6f3
--- /dev/null
+++ b/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.
+ *
+ *
+ * Add children objects are added to the List
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
+ * IntrospectionManager
if you prefer).
+ *
+ * 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()}). + *
+ * + * @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); + } + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/hibernate/package.html b/domain/src/main/java/org/acegisecurity/domain/hibernate/package.html new file mode 100644 index 0000000000..80b8392915 --- /dev/null +++ b/domain/src/main/java/org/acegisecurity/domain/hibernate/package.html @@ -0,0 +1,6 @@ + + +Hibernate-specific implementations of the domain subproject interfaces.
+ + + diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/AbstractPersistableEntity.java b/domain/src/main/java/org/acegisecurity/domain/impl/AbstractPersistableEntity.java new file mode 100644 index 0000000000..71bd33668f --- /dev/null +++ b/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 + *null
or a non-null
value.
+ *
+ * @return true
if the instance has not been persisted,
+ * false
otherwise
+ */
+ public final boolean isNew() {
+ return (getInternalId() == null);
+ }
+
+ /**
+ * Returns the version number, which should be managed by the persistence
+ * layer.
+ *
+ *
+ * Initially all PersistableEntity
s will commence with the
+ * version number defined by {@link #STARTING_VERSION}.
+ *
+ * Only minimal convenience methods are provided by
+ * BusinessObject
. 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.
+ *
+ * This method delegates to BeanUtils.cloneBean(). + *
+ * + * @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(); + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityInteger.java b/domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityInteger.java new file mode 100644 index 0000000000..9f9cf0e5dc --- /dev/null +++ b/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 anInteger
based identity.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class PersistableEntityInteger extends AbstractPersistableEntity {
+ //~ Instance fields ========================================================
+
+ private Integer id;
+
+ //~ Methods ================================================================
+
+ /**
+ * DO NOT USE DIRECTLY.
+ *
+ * + * Typically only used by the persistence layer, but provided with public + * visibility to not limit flexibility. + *
+ * + * @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. + * + *+ * 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. + *
+ * + *+ * Internally delegates to {@link #getId()}. + *
+ * + * @return the instance's identity + */ + public Serializable getInternalId() { + return this.getId(); + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityLong.java b/domain/src/main/java/org/acegisecurity/domain/impl/PersistableEntityLong.java new file mode 100644 index 0000000000..ef555dcc48 --- /dev/null +++ b/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 aLong
based identity.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public abstract class PersistableEntityLong extends AbstractPersistableEntity {
+ //~ Instance fields ========================================================
+
+ private Long id;
+
+ //~ Methods ================================================================
+
+ /**
+ * DO NOT USE DIRECTLY.
+ *
+ * + * Typically only used by the persistence layer, but provided with public + * visibility to not limit flexibility. + *
+ * + * @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. + * + *+ * 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. + *
+ * + *+ * Internally delegates to {@link #getId()}. + *
+ * + * @return the instance's identity + */ + public Serializable getInternalId() { + return this.getId(); + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/PersistableValue.java b/domain/src/main/java/org/acegisecurity/domain/impl/PersistableValue.java new file mode 100644 index 0000000000..51c3f4f789 --- /dev/null +++ b/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 value object, which means a persistable business object that does + * not have its own persistence identity. + * + *+ * 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. + *
+ * + *
+ * In addition, a value object cannot be referenced from more than one
+ * PersistableEntity
. Use a PersistableEntity
+ * instead of a PersistableValue
if this is a design constraint.
+ *
Convenient domain object abstract classes, although none are mandatory/required by +other packages in this project.
+ + + diff --git a/domain/src/main/java/org/acegisecurity/domain/package.html b/domain/src/main/java/org/acegisecurity/domain/package.html new file mode 100644 index 0000000000..2c7b0d3655 --- /dev/null +++ b/domain/src/main/java/org/acegisecurity/domain/package.html @@ -0,0 +1,6 @@ + + +Provides tools to assist develop rich domain object models.
+ + + diff --git a/domain/src/main/java/org/acegisecurity/domain/util/CollectionIgnoringReflectionToStringBuilder.java b/domain/src/main/java/org/acegisecurity/domain/util/CollectionIgnoringReflectionToStringBuilder.java new file mode 100644 index 0000000000..12d77fe3f7 --- /dev/null +++ b/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; + + +/** + * AtoString()
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);
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/util/CollectionUtils.java b/domain/src/main/java/org/acegisecurity/domain/util/CollectionUtils.java
new file mode 100644
index 0000000000..78b8083ae5
--- /dev/null
+++ b/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 Collection
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 Set
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 List
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
+ * null
return null
.
+ *
+ * @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 Map
copying all its elements to a new one. If the
+ * passed argument is null
, the method will return
+ * null
.
+ *
+ * @param map to copy
+ *
+ * @return a copy of the Map
passed as an argument
+ *
+ * @throws IllegalArgumentException if the Map
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 List
(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 Set
(actually a {@link HashSet}) with only that
+ * object.
+ *
+ * @param object
+ *
+ * @return
+ */
+ public static Set newSet(Object object) {
+ return add(new HashSet(), object);
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/util/ReflectionToStringBuilder.java b/domain/src/main/java/org/acegisecurity/domain/util/ReflectionToStringBuilder.java
new file mode 100644
index 0000000000..e6a8434335
--- /dev/null
+++ b/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 ReflectionToStringBuilder
.
+ *
+ * @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;
+ }
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/util/package.html b/domain/src/main/java/org/acegisecurity/domain/util/package.html
new file mode 100644
index 0000000000..e17e10a36b
--- /dev/null
+++ b/domain/src/main/java/org/acegisecurity/domain/util/package.html
@@ -0,0 +1,6 @@
+
+
+Utilities useful in the domain package.
+ + + diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidation.java b/domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidation.java new file mode 100644 index 0000000000..471f19d8b6 --- /dev/null +++ b/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 + *Validator
is called.
+ *
+ *
+ * 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
+ * BindBeforeValidation
interface provides an AOP-style approach
+ * that ensures missing hooks do not cause invalid object state.
+ *
+ * Implementations should modify the object as required so that the
+ * Validator
will succeed if user-controllable properties are
+ * correct.
+ *
Validator
should be allowed
+ * to determine errors in most cases, rather than this method
+ * doing so)
+ */
+ public void bindSupport() throws BindException;
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidationUtils.java b/domain/src/main/java/org/acegisecurity/domain/validation/BindBeforeValidationUtils.java
new file mode 100644
index 0000000000..ac2dded432
--- /dev/null
+++ b/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 null
)
+ *
+ * @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();
+ }
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/IntrospectionManager.java b/domain/src/main/java/org/acegisecurity/domain/validation/IntrospectionManager.java
new file mode 100644
index 0000000000..769d8beeda
--- /dev/null
+++ b/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.
+ *
+ * + * Implementations may use a choice of reflective introspection or querying a + * persistence metadata API to locate the internal children. + *
+ * + * @author Ben Alex + * @version $Id$ + */ +public interface IntrospectionManager { + //~ Methods ================================================================ + + /** + * Locates any direct children of a domain object. + * + *+ * Typically used with a {@link ValidationManager} to validate each of the + * located children. + *
+ * + *+ * Implementations should only add the immediate layer of children. + * Grandchildren, great-grandchildren etc should not be added. + *
+ * + * @param parentObject the immediate parent which all children should share + * (guaranteed to never benull
)
+ * @param allObjects the list to which this method should append each
+ * immediate child (guaranteed to never be null
)
+ */
+ public void obtainImmediateChildren(Object parentObject, List allObjects);
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/ValidationAdvisor.java b/domain/src/main/java/org/acegisecurity/domain/validation/ValidationAdvisor.java
new file mode 100644
index 0000000000..4c81a4c0f9
--- /dev/null
+++ b/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}.
+ *
+ *
+ * Intended to be used with Spring's
+ * DefaultAdvisorAutoProxyCreator
.
+ *
+ * Registers {@link ValidationInterceptor} for every Method
+ * against a class that directly or through its superclasses implements {@link
+ * #supportsClass} and has a signature match those defined by {@link
+ * #methods}.
+ *
+ * For each method invocation, any argument that is assignable from {@link
+ * #argumentClasses}and is non-null
will be passed to the
+ * {@link net.sf.acegisecurity.domain.validation.ValidationManager} for
+ * processing.
+ *
+ * Before performing validation, implementations must execute {@link + * BindBeforeValidation} for any domain objects requesting it. + *
+ * + * @param domainObject to validate (cannot benull
)
+ *
+ * @throws BindException if a validation problem occurs
+ * @throws ValidatorNotFoundException if no matching Validator
+ * 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;
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/ValidationManagerImpl.java b/domain/src/main/java/org/acegisecurity/domain/validation/ValidationManagerImpl.java
new file mode 100644
index 0000000000..6011c64a16
--- /dev/null
+++ b/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
+ * Validator
defined against the {@link #validators}.
+ *
+ *
+ * Defaults to true
. This is a reasonable default, as callers
+ * of ValidationManager
should expect the object to support
+ * validation.
+ *
false
if you wish to
+ * silently ignore any domain object that is missing a
+ * Validator
+ */
+ 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 null
)
+ *
+ * @throws BindException if a validation problem occurs
+ * @throws ValidatorNotFoundException if no matching Validator
+ * could be found for the object or its children (only ever thrown
+ * if the {@link #strictValidation}) was set to
+ * true
).
+ */
+ 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 parentObject
,
+ * adding each of those immediate children to the allObjects
+ * list and then calling this same method for each of those immediate
+ * children.
+ *
+ *
+ * Does not add the passed parentObject
to the
+ * allObjects
list. The caller of this method should ensure
+ * the parentObject
is added to the list instead.
+ *
Validator
could be found that supports a domain
+ * object presented for validation.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ValidatorNotFoundException extends DomainException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs a ValidatorNotFoundException
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 DomainException
with the specified message and
+ * no root cause.
+ *
+ * @param msg the detail message
+ */
+ public ValidatorNotFoundException(String msg) {
+ super(msg);
+ }
+}
diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/package.html b/domain/src/main/java/org/acegisecurity/domain/validation/package.html
new file mode 100644
index 0000000000..f961521c89
--- /dev/null
+++ b/domain/src/main/java/org/acegisecurity/domain/validation/package.html
@@ -0,0 +1,46 @@
+
+
+Validation services for complex domain objects.
+ +Generally you will write Validator
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
+ValidationManager
so that children of a domain object presented
+for validation can be identified and in turn also validated.
+
+
The {@link ValidationInterceptor} and {@link ValidationAdvisor} should be
+used against each of your data access object (DAO) mutator methods, such as
+SomeDao.create(Object)
and SomeDao.update(Object)
.
+The interceptor will cause the Object
to be presented to the
+ValidationManager
, thus ensuring the domain object instance is in
+a valid state before being persisted.
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 ValidationManager
collaborator, and fire its validate method.
+Such collaborator can be autowired during both instance retrieval and creation.
+It should generally also be marked as transient
, to avoid possible
+serialisation issues if used inside a HttpSession
or similar.
Sometimes domain objects need to internally update themselves before being
+validated. Any such domain objects should implement {@link BindBeforeValidation}.
+The ValidationManager
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
+BindBeforeValidation
.
Finally, sometimes Validator
s might need to perform queries
+against a persistence or services layer. For example, the Validator
+may be checking no other user has this username. If using an ORM tool such as
+Hibernate, it is recommended your Validator
s subclass a common
+abstract parent that provides an evict(Object)
and
+evict(Collection)
method. That way your Validator
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 Validator
design choice than something mandated by
+this package, we have found it a worthwhile pattern in real-world applications.
+