diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml
index 0c3902f42c..84015637eb 100644
--- a/config/alfresco/ehcache-default.xml
+++ b/config/alfresco/ehcache-default.xml
@@ -151,14 +151,14 @@
-
+
@@ -167,9 +167,15 @@
maxElementsInMemory="25000"
eternal="true"
overflowToDisk="false"/>
+
+
diff --git a/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java b/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java
index 9983a57bc8..2df59c8b59 100644
--- a/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java
+++ b/source/java/org/alfresco/repo/domain/DbAccessControlEntry.java
@@ -72,4 +72,10 @@ public interface DbAccessControlEntry
* @param allowed
*/
public void setAllowed(boolean allowed);
+
+ /**
+ * Helper method to delete the instance and make sure that all
+ * inverse associations are properly maintained.
+ */
+ public void delete();
}
diff --git a/source/java/org/alfresco/repo/domain/DbAccessControlList.java b/source/java/org/alfresco/repo/domain/DbAccessControlList.java
index 891c98b77f..dad02839a7 100644
--- a/source/java/org/alfresco/repo/domain/DbAccessControlList.java
+++ b/source/java/org/alfresco/repo/domain/DbAccessControlList.java
@@ -16,6 +16,10 @@
*/
package org.alfresco.repo.domain;
+import java.util.Set;
+
+import org.alfresco.repo.domain.hibernate.DbAccessControlEntryImpl;
+
/**
* The interface to support persistence of node access control entries in hibernate
@@ -30,17 +34,29 @@ public interface DbAccessControlList
public void setNode(Node node);
+ /**
+ *
+ * @return Returns the access control entries for this access control list
+ */
+ public Set getEntries();
+
/**
* Get inheritance behaviour
- * @return
+ * @return Returns the inheritance status of this list
*/
public boolean getInherits();
/**
* Set inheritance behaviour
- * @param inherits
+ * @param inherits true to set the permissions to inherit
*/
public void setInherits(boolean inherits);
+
+ public int deleteEntriesForAuthority(String authorityKey);
+
+ public int deleteEntriesForPermission(DbPermissionKey permissionKey);
+
+ public int deleteEntry(String authorityKey, DbPermissionKey permissionKey);
/**
* Delete the entries related to this access control list
@@ -48,4 +64,18 @@ public interface DbAccessControlList
* @return Returns the number of entries deleted
*/
public int deleteEntries();
+
+ public DbAccessControlEntry getEntry(String authorityKey, DbPermissionKey permissionKey);
+
+ /**
+ * Factory method to create an entry and wire it up.
+ * Note that the returned value may still be transient. Saving it should be fine, but
+ * is not required.
+ *
+ * @param permission the mandatory permission association with this entry
+ * @param authority the mandatory authority. Must not be transient.
+ * @param allowed allowed or disallowed. Must not be transient.
+ * @return Returns the new entry
+ */
+ public DbAccessControlEntryImpl newEntry(DbPermission permission, DbAuthority authority, boolean allowed);
}
diff --git a/source/java/org/alfresco/repo/domain/DbPermission.java b/source/java/org/alfresco/repo/domain/DbPermission.java
index d1e741c2b5..5973c813ec 100644
--- a/source/java/org/alfresco/repo/domain/DbPermission.java
+++ b/source/java/org/alfresco/repo/domain/DbPermission.java
@@ -51,4 +51,10 @@ public interface DbPermission extends Serializable
* @param name the name of the permission
*/
public void setName(String name);
+
+ /**
+ * @return Returns a key combining the {@link #getTypeQname() type}
+ * and {@link #getName() name}
+ */
+ public DbPermissionKey getKey();
}
diff --git a/source/java/org/alfresco/repo/domain/DbPermissionKey.java b/source/java/org/alfresco/repo/domain/DbPermissionKey.java
new file mode 100644
index 0000000000..654b37d8cd
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/DbPermissionKey.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.domain;
+
+import java.io.Serializable;
+
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.EqualsHelper;
+
+/**
+ * Compound key for persistence of {@link org.alfresco.repo.domain.DbPermission}.
+ *
+ * @author Derek Hulley
+ */
+public class DbPermissionKey implements Serializable
+{
+ private static final long serialVersionUID = -1667797216480779296L;
+
+ private QName typeQname;
+ private String name;
+
+ public DbPermissionKey()
+ {
+ }
+
+ public DbPermissionKey(QName typeQname, String name)
+ {
+ this.typeQname = typeQname;
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ return ("DbPermissionKey" +
+ "[ type=" + typeQname +
+ ", name=" + name +
+ "]");
+ }
+
+ public int hashCode()
+ {
+ return this.name.hashCode();
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (!(obj instanceof DbPermissionKey))
+ {
+ return false;
+ }
+ DbPermissionKey that = (DbPermissionKey) obj;
+ return (EqualsHelper.nullSafeEquals(this.typeQname, that.typeQname)
+ && EqualsHelper.nullSafeEquals(this.name, that.name)
+ );
+ }
+
+ public QName getTypeQname()
+ {
+ return typeQname;
+ }
+
+ /**
+ * Tamper-proof method only to be used by introspectors
+ */
+ @SuppressWarnings("unused")
+ private void setTypeQname(QName typeQname)
+ {
+ this.typeQname = typeQname;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Tamper-proof method only to be used by introspectors
+ */
+ @SuppressWarnings("unused")
+ private void setName(String name)
+ {
+ this.name = name;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/Node.java b/source/java/org/alfresco/repo/domain/Node.java
index c85eb73e07..f988b5554d 100644
--- a/source/java/org/alfresco/repo/domain/Node.java
+++ b/source/java/org/alfresco/repo/domain/Node.java
@@ -80,5 +80,5 @@ public interface Node
public DbAccessControlList getAccessControlList();
-// public void setAccessControlList(DbAccessControlList accessControlList);
+ public void setAccessControlList(DbAccessControlList accessControlList);
}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java
index 88b2b76a0b..f9d67561dc 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlEntryImpl.java
@@ -27,7 +27,7 @@ import org.alfresco.util.EqualsHelper;
*
* @author andyh
*/
-public class DbAccessControlEntryImpl implements DbAccessControlEntry
+public class DbAccessControlEntryImpl extends LifecycleAdapter implements DbAccessControlEntry
{
/** The object id */
private long id;
@@ -56,7 +56,7 @@ public class DbAccessControlEntryImpl implements DbAccessControlEntry
sb.append("DbAccessControlEntryImpl")
.append("[ id=").append(id)
.append(", acl=").append(accessControlList.getId())
- .append(", permission=").append(permission.getId())
+ .append(", permission=").append(permission.getKey())
.append(", authority=").append(authority.getRecipient())
.append("]");
return sb.toString();
@@ -74,16 +74,14 @@ public class DbAccessControlEntryImpl implements DbAccessControlEntry
return false;
}
DbAccessControlEntry other = (DbAccessControlEntry) o;
- return (this.allowed == other.isAllowed())
- && EqualsHelper.nullSafeEquals(this.accessControlList, other.getAccessControlList())
- && EqualsHelper.nullSafeEquals(this.permission, other.getPermission())
- && EqualsHelper.nullSafeEquals(this.authority, other.getAuthority());
+ return (EqualsHelper.nullSafeEquals(this.permission, other.getPermission())
+ && EqualsHelper.nullSafeEquals(this.authority, other.getAuthority()));
}
@Override
public int hashCode()
{
- int hashCode = accessControlList.hashCode();
+ int hashCode = 0;
if (permission != null)
{
hashCode = hashCode * 37 + permission.hashCode();
@@ -92,33 +90,9 @@ public class DbAccessControlEntryImpl implements DbAccessControlEntry
{
hashCode = hashCode * 37 + authority.hashCode();
}
- hashCode = hashCode * 37 + (allowed ? 1 : 0);
return hashCode;
}
-
- /**
- * Factory method to create an entry and wire it in to the contained nodePermissionEntry
- *
- * @param accessControlList the list of entries that this one belongs to
- * @param permission the mandatory permission association with this entry
- * @param authority the mandatory authority
- * @param allowed allowed or disallowed
- * @return Returns an unpersisted entity
- */
- public static DbAccessControlEntryImpl create(
- DbAccessControlList accessControlList,
- DbPermission permission,
- DbAuthority authority,
- boolean allowed)
- {
- DbAccessControlEntryImpl accessControlEntry = new DbAccessControlEntryImpl();
- accessControlEntry.setAccessControlList(accessControlList);
- accessControlEntry.setPermission(permission);
- accessControlEntry.setAuthority(authority);
- accessControlEntry.setAllowed(allowed);
- return accessControlEntry;
- }
-
+
public long getId()
{
return id;
@@ -171,4 +145,13 @@ public class DbAccessControlEntryImpl implements DbAccessControlEntry
{
this.allowed = allowed;
}
+
+ public void delete()
+ {
+ // remove the instance from the access control list
+ @SuppressWarnings("unused")
+ boolean removed = getAccessControlList().getEntries().remove(this);
+ // delete the instance
+ getSession().delete(this);
+ }
}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java
index 06692722e7..4fa4f1725e 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/DbAccessControlListImpl.java
@@ -16,13 +16,20 @@
*/
package org.alfresco.repo.domain.hibernate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.repo.domain.DbAccessControlEntry;
import org.alfresco.repo.domain.DbAccessControlList;
+import org.alfresco.repo.domain.DbAuthority;
+import org.alfresco.repo.domain.DbPermission;
+import org.alfresco.repo.domain.DbPermissionKey;
import org.alfresco.repo.domain.Node;
import org.alfresco.util.EqualsHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.hibernate.CallbackException;
-import org.hibernate.Query;
import org.hibernate.Session;
/**
@@ -36,8 +43,14 @@ public class DbAccessControlListImpl extends LifecycleAdapter implements DbAcces
private long id;
private Node node;
+ private Set entries;
private boolean inherits;
+ public DbAccessControlListImpl()
+ {
+ entries = new HashSet(5);
+ }
+
@Override
public String toString()
{
@@ -45,6 +58,7 @@ public class DbAccessControlListImpl extends LifecycleAdapter implements DbAcces
sb.append("DbAccessControlListImpl")
.append("[ id=").append(id)
.append(", node=").append(node)
+ .append(", entries=").append(entries.size())
.append(", inherits=").append(inherits)
.append("]");
return sb.toString();
@@ -73,35 +87,6 @@ public class DbAccessControlListImpl extends LifecycleAdapter implements DbAcces
return (node == null ? 0 : node.hashCode());
}
- public int deleteEntries()
- {
- /*
- * This can use a delete direct to the database as well, but then care must be taken
- * to evict the instances from the session.
- */
-
- // bypass L2 cache and get all entries for this list
- Query query = getSession()
- .getNamedQuery(PermissionsDaoComponentImpl.QUERY_GET_AC_ENTRIES_FOR_AC_LIST)
- .setLong("accessControlListId", this.id);
- int count = HibernateHelper.deleteQueryResults(getSession(), query);
- // done
- if (logger.isDebugEnabled())
- {
- logger.debug("Deleted " + count + " access entries for access control list " + this.id);
- }
- return count;
- }
-
- /**
- * Ensures that all this access control list's entries have been deleted.
- */
- public boolean onDelete(Session session) throws CallbackException
- {
- deleteEntries();
- return super.onDelete(session);
- }
-
public long getId()
{
return id;
@@ -126,9 +111,18 @@ public class DbAccessControlListImpl extends LifecycleAdapter implements DbAcces
this.node = node;
}
- public DbAccessControlListImpl()
+ public Set getEntries()
{
- super();
+ return entries;
+ }
+
+ /**
+ * For Hibernate use
+ */
+ @SuppressWarnings("unused")
+ private void setEntries(Set entries)
+ {
+ this.entries = entries;
}
public boolean getInherits()
@@ -140,4 +134,112 @@ public class DbAccessControlListImpl extends LifecycleAdapter implements DbAcces
{
this.inherits = inherits;
}
+
+ /**
+ * @see #deleteEntry(String, DbPermissionKey)
+ */
+ public int deleteEntriesForAuthority(String authority)
+ {
+ return deleteEntry(authority, null);
+ }
+
+ /**
+ * @see #deleteEntry(String, DbPermissionKey)
+ */
+ public int deleteEntriesForPermission(DbPermissionKey permissionKey)
+ {
+ return deleteEntry(null, permissionKey);
+ }
+
+ public int deleteEntry(String authority, DbPermissionKey permissionKey)
+ {
+ List toDelete = new ArrayList(2);
+ for (DbAccessControlEntry entry : entries)
+ {
+ if (authority != null && !authority.equals(entry.getAuthority().getRecipient()))
+ {
+ // authority is not a match
+ continue;
+ }
+ else if (permissionKey != null && !permissionKey.equals(entry.getPermission().getKey()))
+ {
+ // permission is not a match
+ continue;
+ }
+ toDelete.add(entry);
+ }
+ // delete them
+ for (DbAccessControlEntry entry : toDelete)
+ {
+ // remove from the entry list
+ entry.delete();
+ }
+ // done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Deleted " + toDelete.size() + " access entries: \n" +
+ " access control list: " + id + "\n" +
+ " authority: " + authority + "\n" +
+ " permission: " + permissionKey);
+ }
+ return toDelete.size();
+ }
+
+ public int deleteEntries()
+ {
+ /*
+ * We don't do the full delete-remove-from-set thing here. Just delete each child entity
+ * and then clear the entry set.
+ */
+
+ Session session = getSession();
+ List toDelete = new ArrayList(entries);
+ // delete each entry
+ for (DbAccessControlEntry entry : toDelete)
+ {
+ session.delete(entry);
+ }
+ // clear the list
+ int count = entries.size();
+ entries.clear();
+ // done
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Deleted " + count + " access entries for access control list " + this.id);
+ }
+ return count;
+ }
+
+ public DbAccessControlEntry getEntry(String authority, DbPermissionKey permissionKey)
+ {
+ for (DbAccessControlEntry entry : entries)
+ {
+ DbAuthority authorityEntity = entry.getAuthority();
+ DbPermission permissionEntity = entry.getPermission();
+ // check for a match
+ if (authorityEntity.getRecipient().equals(authority)
+ && permissionEntity.getKey().equals(permissionKey))
+ {
+ // found it
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ public DbAccessControlEntryImpl newEntry(DbPermission permission, DbAuthority authority, boolean allowed)
+ {
+ DbAccessControlEntryImpl accessControlEntry = new DbAccessControlEntryImpl();
+ // fill
+ accessControlEntry.setAccessControlList(this);
+ accessControlEntry.setPermission(permission);
+ accessControlEntry.setAuthority(authority);
+ accessControlEntry.setAllowed(allowed);
+ // save it
+ getSession().save(accessControlEntry);
+ // maintain inverse set on the acl
+ getEntries().add(accessControlEntry);
+ // done
+ return accessControlEntry;
+ }
}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java
index 5194057763..9a793b80d0 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/DbAuthorityImpl.java
@@ -38,11 +38,11 @@ public class DbAuthorityImpl extends LifecycleAdapter implements DbAuthority
private static Log logger = LogFactory.getLog(DbAuthorityImpl.class);
private String recipient;
- private Set externalKeys = new HashSet();
+ private Set externalKeys;
public DbAuthorityImpl()
{
- super();
+ externalKeys = new HashSet();
}
@Override
@@ -76,8 +76,8 @@ public class DbAuthorityImpl extends LifecycleAdapter implements DbAuthority
// bypass L2 cache and get all entries for this list
Query query = getSession()
.getNamedQuery(PermissionsDaoComponentImpl.QUERY_GET_AC_ENTRIES_FOR_AUTHORITY)
- .setString("recipient", this.recipient);
- int count = HibernateHelper.deleteQueryResults(getSession(), query);
+ .setString("authorityRecipient", this.recipient);
+ int count = HibernateHelper.deleteDbAccessControlEntries(getSession(), query);
// done
if (logger.isDebugEnabled())
{
@@ -115,4 +115,16 @@ public class DbAuthorityImpl extends LifecycleAdapter implements DbAuthority
{
this.externalKeys = externalKeys;
}
+
+ /**
+ * Helper method to find an authority based on its natural key
+ *
+ * @param session the Hibernate session to use
+ * @param authority the authority name
+ * @return Returns an existing instance or null if not found
+ */
+ public static DbAuthority find(Session session, String authority)
+ {
+ return (DbAuthority) session.get(DbAuthorityImpl.class, authority);
+ }
}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java
index 1e270483db..501d7f419a 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/DbPermissionImpl.java
@@ -17,6 +17,7 @@
package org.alfresco.repo.domain.hibernate;
import org.alfresco.repo.domain.DbPermission;
+import org.alfresco.repo.domain.DbPermissionKey;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.apache.commons.logging.Log;
@@ -90,7 +91,7 @@ public class DbPermissionImpl extends LifecycleAdapter implements DbPermission
Query query = getSession()
.getNamedQuery(PermissionsDaoComponentImpl.QUERY_GET_AC_ENTRIES_FOR_PERMISSION)
.setSerializable("permissionId", this.id);
- int count = HibernateHelper.deleteQueryResults(getSession(), query);
+ int count = HibernateHelper.deleteDbAccessControlEntries(getSession(), query);
// done
if (logger.isDebugEnabled())
{
@@ -142,6 +143,11 @@ public class DbPermissionImpl extends LifecycleAdapter implements DbPermission
this.name = name;
}
+ public DbPermissionKey getKey()
+ {
+ return new DbPermissionKey(typeQname, name);
+ }
+
/**
* Helper method to find a permission based on its natural key
*
diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateHelper.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateHelper.java
index 86070d90a8..8da807a8fe 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/HibernateHelper.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateHelper.java
@@ -16,8 +16,7 @@
*/
package org.alfresco.repo.domain.hibernate;
-import org.hibernate.CacheMode;
-import org.hibernate.ObjectDeletedException;
+import org.alfresco.repo.domain.DbAccessControlEntry;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
@@ -32,36 +31,25 @@ public class HibernateHelper
{
/**
* Helper method to scroll through the results of a query and delete all the
- * results, performing batch flushes. This will handle large resultsets by
- * pulling the results directly in from the query. For certain circumstances, it
- * may be better to perform a bulk delete directly instead.
+ * resulting access control entries, performing batch flushes.
*
* @param session the session to use for the deletions
- * @param query the query with all parameters set
- * @return Returns the number of entities deleted, regardless of type
+ * @param query the query with all parameters set and that will return
+ * {@link org.alfresco.repo.domain.DbAccessControlEntry access control entry} instances
+ * @return Returns the number of entries deleted
*/
- public static int deleteQueryResults(Session session, Query query)
+ public static int deleteDbAccessControlEntries(Session session, Query query)
{
- ScrollableResults entities = query.setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY);
+ ScrollableResults entities = query.scroll(ScrollMode.FORWARD_ONLY);
int count = 0;
while (entities.next())
{
- Object[] entityResults = entities.get();
- for (Object object : entityResults)
+ DbAccessControlEntry entry = (DbAccessControlEntry) entities.get(0);
+ entry.delete();
+ if (++count % 50 == 0)
{
- try
- {
- session.delete(object);
- }
- catch (ObjectDeletedException e)
- {
- // ignore - it's what we wanted
- }
- if (++count % 50 == 0)
- {
- session.flush();
- session.clear();
- }
+ session.flush();
+ session.clear();
}
}
return count;
diff --git a/source/java/org/alfresco/repo/domain/hibernate/LifecycleAdapter.java b/source/java/org/alfresco/repo/domain/hibernate/LifecycleAdapter.java
index 54c202accc..7e9740809c 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/LifecycleAdapter.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/LifecycleAdapter.java
@@ -18,6 +18,7 @@ package org.alfresco.repo.domain.hibernate;
import java.io.Serializable;
+import org.alfresco.error.AlfrescoRuntimeException;
import org.hibernate.CallbackException;
import org.hibernate.Session;
import org.hibernate.classic.Lifecycle;
@@ -37,6 +38,10 @@ public abstract class LifecycleAdapter implements Lifecycle
*/
protected Session getSession()
{
+ if (session == null)
+ {
+ throw new AlfrescoRuntimeException("Hibernate entity is not part of a session: " + this);
+ }
return session;
}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
index 0701924b99..917f39484f 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
+++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
@@ -28,9 +28,9 @@
name="store"
class="org.alfresco.repo.domain.hibernate.StoreImpl"
not-null="true"
- lazy="no-proxy"
+ lazy="proxy"
optimistic-lock="true"
- fetch="select">
+ fetch="join">
@@ -43,8 +43,8 @@
name="accessControlList"
class="org.alfresco.repo.domain.hibernate.DbAccessControlListImpl"
property-ref="node"
- lazy="no-proxy"
- fetch="select"
+ lazy="false"
+ fetch="join"
cascade="delete" />