diff --git a/.classpath b/.classpath index 682ed83aaf..0c72902f7f 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,10 @@ + + + + @@ -53,5 +57,8 @@ + + + diff --git a/domain/maven.xml b/domain/maven.xml new file mode 100644 index 0000000000..89017035e2 --- /dev/null +++ b/domain/maven.xml @@ -0,0 +1,39 @@ + + + + + + + signature.alias defined; signing JAR(s)... + + + + + + + + + diff --git a/domain/project.xml b/domain/project.xml new file mode 100644 index 0000000000..7545053026 --- /dev/null +++ b/domain/project.xml @@ -0,0 +1,53 @@ + + + ${basedir}/../project.xml + 3 + acegi-security-domain + Acegi Security System for Spring - Domain Object Support + acegisecurity + /home/groups/a/ac/acegisecurity/htdocs/multiproject/acegi-security-domain + + scm:cvs:pserver:anonymous@cvs.sourceforge.net:/cvsroot/acegisecurity:acegisecurity + scm:cvs:ext:${maven.username}@cvs.sourceforge.net:/cvsroot/acegisecurity:acegisecurity + http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/acegisecurity/acegisecurity/domain/ + + + + hibernate + hibernate + 2.1.8 + jar + + + commons-lang + commons-lang + 2.0 + jar + + + commons-beanutils + commons-beanutils + 1.6.1 + jar + + + acegisecurity + acegi-security + 0.8.2-SNAPSHOT + jar + + + + + + ${basedir}/../ + META-INF + + notice.txt + + false + + + + + diff --git a/domain/src/main/java/org/acegisecurity/domain/DomainException.java b/domain/src/main/java/org/acegisecurity/domain/DomainException.java new file mode 100644 index 0000000000..83bfbeb893 --- /dev/null +++ b/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 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. + *

+ * + * @return the persistence identity of this instance + */ + abstract Serializable getInternalId(); +} diff --git a/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java b/domain/src/main/java/org/acegisecurity/domain/dao/Dao.java new file mode 100644 index 0000000000..920d823eea --- /dev/null +++ b/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}. + * + *

+ * 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. + *

+ * + * @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-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 PersistableEntitys, 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 + * Strings, and any Collections are ignored in + * the query by example evaluation. + *

+ * + * @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 + * 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. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public interface EvictionCapable { + //~ Methods ================================================================ + + /** + * Removes the indicated persistent instance from the DAO's internal + * map/session. + * + *

+ * 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 of PersistableEntitys + * 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. + *

+ * + * @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 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 a HibernateCallback 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 PersistableEntitys will commence with the + * version number defined by {@link #STARTING_VERSION}. + *

+ * + * @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; + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/BusinessObject.java b/domain/src/main/java/org/acegisecurity/domain/impl/BusinessObject.java new file mode 100644 index 0000000000..132ea1e2db --- /dev/null +++ b/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. + * + *

+ * 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. + *

+ * + * @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. + * + *

+ * 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 an Integer 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 a Long 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. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public abstract class PersistableValue extends BusinessObject {} diff --git a/domain/src/main/java/org/acegisecurity/domain/impl/package.html b/domain/src/main/java/org/acegisecurity/domain/impl/package.html new file mode 100644 index 0000000000..1619d5eaf4 --- /dev/null +++ b/domain/src/main/java/org/acegisecurity/domain/impl/package.html @@ -0,0 +1,7 @@ + + +

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; + + +/** + * A toString() 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 Collections. + * + * @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. + *

+ * + * @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 not assume the object is + * in a valid state. + * + *

+ * Implementations should modify the object as required so that the + * Validator will succeed if user-controllable properties are + * correct. + *

+ * + * @throws BindException if there are problems that the method wish to + * advise (note that the 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 be null) + * @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}. + *

+ * + * @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; + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/ValidationInterceptor.java b/domain/src/main/java/org/acegisecurity/domain/validation/ValidationInterceptor.java new file mode 100644 index 0000000000..c0a018d131 --- /dev/null +++ b/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. + * + *

+ * 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. + *

+ * + * @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; + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/ValidationManager.java b/domain/src/main/java/org/acegisecurity/domain/validation/ValidationManager.java new file mode 100644 index 0000000000..b3b6d16ef2 --- /dev/null +++ b/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. + * + *

+ * Before performing validation, implementations must execute {@link + * BindBeforeValidation} for any domain objects requesting it. + *

+ * + * @param domainObject to validate (cannot be null) + * + * @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. + *

+ * + * @param strictValidation set to 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. + *

+ * + * @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); + } + } + } +} diff --git a/domain/src/main/java/org/acegisecurity/domain/validation/ValidatorNotFoundException.java b/domain/src/main/java/org/acegisecurity/domain/validation/ValidatorNotFoundException.java new file mode 100644 index 0000000000..2f0a7a4873 --- /dev/null +++ b/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 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 Validators 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 Validators 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 Validators subclass a common +abstract parent that provides an evict(Object) and +evict(Collection) method. That way your Validators +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. +

+ + + diff --git a/project.properties b/project.properties index f4bf7babd3..c536d210b3 100644 --- a/project.properties +++ b/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}