package ch.x42.terye; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.ItemExistsException; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.x42.terye.observation.EventCollection; import ch.x42.terye.observation.ObservationManagerImpl; import ch.x42.terye.path.Path; import ch.x42.terye.persistence.ChangeLog; import ch.x42.terye.persistence.ItemState; import ch.x42.terye.persistence.NodeState; import ch.x42.terye.persistence.PersistenceManager; import ch.x42.terye.persistence.PropertyState; import ch.x42.terye.persistence.id.ItemId; import ch.x42.terye.persistence.id.NodeId; import ch.x42.terye.persistence.id.PropertyId; import ch.x42.terye.value.ValueImpl; public class ItemManager { private final Logger logger = LoggerFactory.getLogger(getClass()); private SessionImpl session; private PersistenceManager persistenceManager; private ObservationManagerImpl observationManager; private ChangeLog log; private Map<String, ItemImpl> cache; // stores paths of items that have been removed in this session private Set<String> removed = new HashSet<String>(); protected ItemManager(SessionImpl session, ObservationManagerImpl observationManager) { this.session = session; this.persistenceManager = ((WorkspaceImpl) session.getWorkspace()) .getPersistenceManager(); this.observationManager = observationManager; this.log = new ChangeLog(); this.cache = new HashMap<String, ItemImpl>(); } private void putToCache(ItemImpl item) { cache.put(item.getId().toString(), item); } /** * Cache lookup for an item corresponding to the specified id (i.e. item * type is considered). */ private ItemImpl getFromCache(ItemId id) { ItemImpl item = cache.get(id.toString()); if (item != null) { // check if found item type corresponds with id type if ((item.isNode() && id.denotesNode()) || (!item.isNode() && !id.denotesNode())) { return item; } } return null; } private void removeFromCache(ItemId id) { cache.remove(id.toString()); } private void markRemoved(ItemId id) { removed.add(id.toString()); } /** * Checks if the specified item or one of its ancestors has been removed in * this session. */ private boolean isMarkedRemoved(ItemId id) { return removed.contains(id.toString()); } private void unmarkRemoved(ItemId id) { removed.remove(id.toString()); } private ItemImpl createNewInstance(ItemState state) throws RepositoryException { if (state.isNode()) { return new NodeImpl(session, (NodeState) state); } else { return new PropertyImpl(session, (PropertyState) state); } } public ItemImpl getItem(ItemId id) throws PathNotFoundException, RepositoryException { logger.debug("getItem({})", id); // check if item has been removed in this session if (isMarkedRemoved(id)) { throw new PathNotFoundException(id.toString()); } // check if the item is cached ItemImpl item = getFromCache(id); if (item != null) { return item; } // load item state from persistent storage ItemState state = persistenceManager.loadItem(id); if (state == null) { throw new PathNotFoundException(id.toString()); } // instantiate new in-memory copy and cache it item = createNewInstance(state); putToCache(item); return item; } public ItemImpl getItem(Path path) throws PathNotFoundException, RepositoryException { // we don't know what item type to expect, thus... try { // ...try getting a node at that path return getNode(path); } catch (PathNotFoundException e) { // ...there was no node so try getting a property return getProperty(path); } } public NodeImpl getNode(Path path) throws PathNotFoundException, RepositoryException { NodeId id = new NodeId(path.getCanonicalPath().toString()); return (NodeImpl) getItem(id); } public PropertyImpl getProperty(Path path) throws RepositoryException { PropertyId id = new PropertyId(path.getCanonicalPath().toString()); return (PropertyImpl) getItem(id); } public boolean itemExists(Path path) throws RepositoryException { try { getItem(path); } catch (PathNotFoundException e) { return false; } return true; } public boolean nodeExists(Path path) throws RepositoryException { try { getNode(path); } catch (PathNotFoundException e) { return false; } return true; } public boolean propertyExists(Path path) throws RepositoryException { try { getProperty(path); } catch (PathNotFoundException e) { return false; } return true; } public NodeImpl createNode(Path path, String primaryNodeTypeName) throws ItemExistsException, PathNotFoundException, RepositoryException { logger.debug("createNode({})", path); // check if path already exists if (itemExists(path)) { throw new ItemExistsException("An item already exists at: " + path); } // create new node NodeId id = new NodeId(path.getCanonicalPath().toString()); NodeState state = new NodeState(id, primaryNodeTypeName); NodeImpl node = (NodeImpl) createNewInstance(state); putToCache(node); log.added(node); unmarkRemoved(id); // add to parent Path parentPath = path.getParent(); NodeImpl parent = null; if (parentPath == null) { // only the case for the root node return node; } parent = getNode(parentPath); parent.addChild(node); log.modified(parent); return node; } public PropertyImpl createProperty(Path path, ValueImpl value) throws ItemExistsException, PathNotFoundException, RepositoryException { logger.debug("createProperty({})", path); // disallow nodes and properties having the same path if (nodeExists(path)) { throw new ItemExistsException("A node already exists at: " + path); } // create new property PropertyId id = new PropertyId(path.getCanonicalPath().toString()); PropertyState state = new PropertyState(id, value); PropertyImpl property = new PropertyImpl(session, state, value); putToCache(property); log.added(property); unmarkRemoved(id); // add to parent NodeImpl parent = getNode(path.getParent()); parent.addChild(property); log.modified(parent); return property; } public PropertyImpl updateProperty(Path path, ValueImpl value) throws RepositoryException { PropertyImpl property = getProperty(path); property.setValue(value); log.modified(property); return property; } public void removeItem(Path path) throws RepositoryException { removeItem(getItem(path)); } public void removeItem(ItemImpl item) throws RepositoryException { logger.debug("removeItem({})", item.getId()); // remove child nodes and properties recursively if (item.isNode()) { PropertyIterator pIterator = ((NodeImpl) item).getProperties(); while (pIterator.hasNext()) { pIterator.nextProperty().remove(); } NodeIterator nIterator = ((NodeImpl) item).getNodes(); while (nIterator.hasNext()) { nIterator.nextNode().remove(); } } // remove reference in parent NodeImpl parent = (NodeImpl) item.getParent(); parent.removeChild(item); log.modified(parent); // remove item from cache removeFromCache(item.getId()); log.removed(item); // add to paths removed in this session markRemoved(item.getId()); // mark item removed item.markRemoved(); } /** * Saves all changes that are at path 'root' or below. */ public void persistChanges(Path root) throws RepositoryException { ChangeLog changes = log; if (!root.isRoot()) { changes = log.split(root); } persistenceManager.persist(changes); observationManager .dispatchEvents(new EventCollection(changes, session)); log.purge(); } /** * Refreshes all items that are at path 'root' or below. */ public void refresh(Path root) throws RepositoryException { // list of items that have been removed by another session Set<ItemImpl> removed = new HashSet<ItemImpl>(); // loop through all items of this session for (ItemImpl item : cache.values()) { Path path = item.getPathInternal(); // check that the item is a descendant of the specified root and // that it hasn't been removed in this session if ((path.isEquivalentTo(root) || path.isDescendantOf(root)) && !isMarkedRemoved(item.getId())) { // load current persistent state ItemState state = persistenceManager.loadItem(item.getId()); if (state == null) { removed.add(item); } else { // set loaded state item.setState(state); } } } // mark removed items for (ItemImpl item : removed) { // remove item from cache removeFromCache(item.getId()); // mark item removed item.markRemoved(); // check if parent node has been loaded in this session NodeId parentId = new NodeId(item.getParentPath().toString()); NodeImpl parent = (NodeImpl) getFromCache(parentId); if (parent != null) { // if the parent has not been refreshed during this call if (!(parent.getPathInternal().isEquivalentTo(root) || parent .getPathInternal().isDescendantOf(root))) { // remove the stale reference from its children parent.removeChild(item); } // else the parent's state has been refreshed above and we don't // need to do anything } } } public boolean hasPendingChanges() { return !log.isEmpty(); } public void move(ItemImpl item, Path destination) throws RepositoryException { doMove(item, destination, true); } private void doMove(ItemImpl item, Path destination, boolean isRoot) throws RepositoryException { // move child nodes and properties recursively if (item.isNode()) { PropertyIterator pIterator = ((NodeImpl) item).getProperties(); while (pIterator.hasNext()) { PropertyImpl property = (PropertyImpl) pIterator.nextProperty(); doMove(property, destination.resolve(property.getName()), false); } NodeIterator nIterator = ((NodeImpl) item).getNodes(); while (nIterator.hasNext()) { NodeImpl node = (NodeImpl) nIterator.nextNode(); doMove(node, destination.resolve(node.getName()), false); } } // if this is the root of the move, then remove it from parent's // children if (isRoot) { NodeImpl oldParent = (NodeImpl) item.getParent(); oldParent.removeChild(item); log.modified(oldParent); } // remove the item removeFromCache(item.getId()); log.removed(item); // add to paths removed in this session markRemoved(item.getId()); // create new id ItemId id; if (item.isNode()) { id = new NodeId(destination.toString()); } else { id = new PropertyId(destination.toString()); } // clone item state ItemState old = item.getState(); ItemState clone = old.clone(id); // set cloned state item.setState(clone); // add item putToCache(item); log.added(item); // if this is the root of the move, then add it to the new parent's // children if (isRoot) { NodeImpl newParent = (NodeImpl) item.getParent(); newParent.addChild(item); log.modified(newParent); } // log item as moved log.moved(old, clone); } }