package ch.x42.terye; import java.net.UnknownHostException; import java.util.HashSet; import java.util.Iterator; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import javax.jcr.ItemExistsException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Value; import ch.x42.terye.persistence.ChangeLog; import ch.x42.terye.persistence.ItemState; import ch.x42.terye.persistence.ItemType; import ch.x42.terye.persistence.NodeState; import ch.x42.terye.persistence.PersistenceManager; import ch.x42.terye.persistence.PropertyState; import com.mongodb.MongoException; public class ItemManager { private SessionImpl session; private Index index; private PersistenceManager persistenceManager; private ChangeLog log; private NavigableMap<String, ItemImpl> cache = new TreeMap<String, ItemImpl>(); // stores paths of items that have been removed private Set<String> removed = new HashSet<String>(); protected ItemManager(SessionImpl session, Index index) throws RepositoryException, UnknownHostException, MongoException { this.session = session; this.index = index; persistenceManager = new PersistenceManager(); log = new ChangeLog(); } /** * Returns an item by looking up the cache and if not present fetching the * item from the database. * * @param path canonical path * @param type item type wanted or null if it doesn't matter * @return the item * @throws PathNotFoundException when no item at that path exists or the * types don't match */ public ItemImpl getItem(Path path, ItemType type) throws PathNotFoundException { // check if the item or one of its ancestors has // been removed in this session Iterator<String> iterator = removed.iterator(); while (iterator.hasNext()) { String prefix = iterator.next(); if (path.toString().startsWith(prefix)) { throw new PathNotFoundException(path.toString()); } } // check if the item is cached ItemImpl item = cache.get(path.toString()); if (item != null) { // if type matters, then both types must match if (type == null || item.getState().getType().equals(type)) { return item; } throw new PathNotFoundException(path.toString()); } // load item state from db ItemState state = persistenceManager.load(path.toString(), type); if (state == null) { throw new PathNotFoundException(path.toString()); } // instantiate, cache and return item if (state.getType().equals(ItemType.NODE)) { item = new NodeImpl(session, (NodeState) state); } else { item = new PropertyImpl(session, (PropertyState) state); } cache.put(path.toString(), item); return item; } public ItemImpl getItem(Path path) throws PathNotFoundException { return getItem(path, null); } public NodeImpl getNode(Path path) throws PathNotFoundException { return (NodeImpl) getItem(path, ItemType.NODE); } public PropertyImpl getProperty(Path path) throws PathNotFoundException { return (PropertyImpl) getItem(path, ItemType.PROPERTY); } /** * @param path canonical path */ public NodeImpl createNode(Path path) throws PathNotFoundException, ItemExistsException { if (nodeExists(path)) { throw new ItemExistsException(path.toString()); } NodeState state = new NodeState(path.toString()); NodeImpl node = new NodeImpl(session, state); cache.put(path.toString(), node); removed.remove(path.toString()); log.itemAdded(node); Path parentPath = path.getParent(); if (parentPath == null) { // only the case for the root node return node; } NodeImpl parent = getNode(parentPath); parent.getState().getChildren().add(path.toString()); log.itemModified(parent); return node; } /** * @param path canonical path */ public PropertyImpl createProperty(Path path, Value value) throws RepositoryException { // disallow nodes and properties having the same path if (nodeExists(path)) { throw new ItemExistsException(); } PropertyState state = new PropertyState(path.toString(), value); PropertyImpl property = new PropertyImpl(session, state); cache.put(path.toString(), property); removed.remove(path.toString()); log.itemAdded(property); NodeImpl parent = getNode(path.getParent()); if (!parent.getState().getProperties().contains(path.toString())) { parent.getState().getProperties().add(path.toString()); } log.itemModified(parent); return property; } /** * @param path canonical path * @param value the new value */ public void updateProperty(Path path, Value value) throws RepositoryException { PropertyImpl property = getProperty(path); property.getState().setValue(value); log.itemModified(property); } /** * Removes an item from cache and the database (on persist). All descendants * are automatically being removed from the database. * * @param path canonical path */ public void removeItem(Path path) throws RepositoryException { ItemImpl item = getItem(path); cache.remove(path.toString()); removed.add(path.toString()); // takes care of removing descendants from db log.itemRemoved(item); // remove reference in parent NodeImpl parent = (NodeImpl) item.getParent(); if (item.isNode()) { parent.getState().getChildren().remove(path.toString()); } else { parent.getState().getProperties().remove(path.toString()); } log.itemModified(parent); // only for nodes: remove descendants from cache if (!item.isNode()) { return; } Iterator<String> iterator = cache.tailMap(path.toString(), true) .navigableKeySet().iterator(); boolean done = false; while (iterator.hasNext() && !done) { String key = iterator.next(); if (!key.startsWith(path.toString())) { done = true; } else { iterator.remove(); } } } /** * @param path canonical path */ public boolean nodeExists(Path path) { try { getNode(path); } catch (PathNotFoundException e) { return false; } return true; } /** * @param path canonical path */ public boolean propertyExists(Path path) { try { getProperty(path); } catch (PathNotFoundException e) { return false; } return true; } /** * @param path canonical path */ public boolean itemExists(Path path) { return nodeExists(path) || propertyExists(path); } public void save() throws RepositoryException { // atomically { persistenceManager.persist(log); index.update(log); log.purge(); // } } public boolean hasPendingChanges() { return !log.isEmpty(); } }