package org.etk.orm.plugins.jcr; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.etk.orm.plugins.common.collection.AbstractFilterIterator; import org.etk.orm.plugins.common.collection.CompoundIterator; /** * <p>The reference manager takes care of managing references between nodes. The main reason is that * JCR reference management is a bit weird about the usage of <tt>Node#getReferences()</tt>. The goal * of this class is to manage one to many relationships between nodes and their consistency.</p> * * <p>The life time of this object is valid from the beginning of the session until the session * or a portion of the session is saved. When a session is saved, the clear operation will reset * the state of the reference manager.</p> * */ public abstract class AbstractLinkManager { /** . */ private Map<String, Entry> entries = new HashMap<String, Entry>(); /** . */ protected final Session session; public AbstractLinkManager(Session session) { this.session = session; } public Iterator<Node> getReferents(Node referenced, String propertyName) throws RepositoryException { Entry entry = getEntry(referenced); return entry.iterator(propertyName); } protected abstract Node _getReferenced(Property property) throws RepositoryException; protected abstract void _setReferenced(Node referent, String propertyName, Node referenced) throws RepositoryException; protected abstract Iterator<Node> _getReferents(Node referenced, String propertyName) throws RepositoryException; public Node getReferenced(Node referent, String propertyName) throws RepositoryException { if (referent.hasProperty(propertyName)) { Property property = referent.getProperty(propertyName); return _getReferenced(property); } else { return null; } } public Node setReferenced(Node referent, String propertyName, Node referenced) throws RepositoryException { Node oldReferenced = null; if (referent.hasProperty(propertyName)) { Property property = referent.getProperty(propertyName); oldReferenced = _getReferenced(property); if (oldReferenced != null) { Entry entry = getEntry(oldReferenced); boolean scheduleForAddition = true; NodeSet propertyScheduledForAddition = entry.propertiesScheduledForAddition.get(propertyName); if (propertyScheduledForAddition != null) { if (propertyScheduledForAddition.contains(referent)) { propertyScheduledForAddition.remove(referent); scheduleForAddition = false; } } // if (scheduleForAddition) { NodeSet propertyScheduledForRemoval = entry.propertiesScheduledForRemoval.get(propertyName); if (propertyScheduledForRemoval == null) { propertyScheduledForRemoval = new NodeSet(); entry.propertiesScheduledForRemoval.put(propertyName, propertyScheduledForRemoval); } propertyScheduledForRemoval.add(referent); entry.version++; } } } // _setReferenced(referent, propertyName, referenced); // if (referenced != null) { Entry entry = getEntry(referenced); NodeSet srcs = entry.propertiesScheduledForAddition.get(propertyName); if (srcs == null) { srcs = new NodeSet(); entry.propertiesScheduledForAddition.put(propertyName, srcs); } srcs.add(referent); entry.version++; } else { // } // return oldReferenced; } public void clear() { entries.clear(); } private Entry getEntry(Node referenced) throws RepositoryException { Entry entry = entries.get(referenced.getUUID()); if (entry == null) { entry = new Entry(referenced); entries.put(referenced.getUUID(), entry); } return entry; } private class Entry { /** . */ private int version; /** . */ private final Node referenced; /** . */ private final Map<String, NodeSet> propertiesScheduledForAddition; /** . */ private final Map<String, NodeSet> propertiesScheduledForRemoval; private Entry(Node referenced) { this.version = 0; this.referenced = referenced; this.propertiesScheduledForAddition = new HashMap<String, NodeSet>(); this.propertiesScheduledForRemoval = new HashMap<String, NodeSet>(); } public Iterator<Node> iterator(final String propertyName) throws RepositoryException { // Julien : that looks like a query that would be executed each time // does it make sense to cache it ? // Set<Node> blah = propertiesScheduledForRemoval.get(propertyName); if (blah == null) { blah = Collections.emptySet(); } final Set<Node> tutu = blah; Iterator<Node> aaa = _getReferents(referenced, propertyName); AbstractFilterIterator<Node, Node> i1 = new AbstractFilterIterator<Node, Node>(aaa) { protected Node adapt(Node node) { if (!tutu.contains(node)) { return node; } else { return null; } } }; // NodeSet srcs = propertiesScheduledForAddition.get(propertyName); if (srcs == null) { srcs = new NodeSet(); propertiesScheduledForAddition.put(propertyName, srcs); } final Iterator<Node> i2 = srcs.iterator(); // return new CompoundIterator<Node>(i1, i2) { int version = Entry.this.version; private void check() { if (version != Entry.this.version) { throw new ConcurrentModificationException(); } } @Override public boolean hasNext() { check(); return super.hasNext(); } @Override public Node next() { check(); return super.next(); } @Override public void remove() { check(); super.remove(); version = Entry.this.version++; } }; } } }