package ch.x42.terye;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.x42.terye.store.ChangeLog;
import ch.x42.terye.store.ItemStore;
import ch.x42.terye.store.ItemType;
public class ItemManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
private SessionImpl session;
private ItemStore store;
private ChangeLog log;
private NavigableMap<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) {
this.session = session;
this.store = ItemStore.getInstance();
this.log = new ChangeLog();
this.cache = new TreeMap<String, ItemImpl>();
}
public ItemImpl getItem(Path path, ItemType type)
throws PathNotFoundException {
logger.debug("getItem({})", path);
// check if the item or one of its ancestors has
// been removed in this session
String pathStr = path.toString();
Iterator<String> iterator = removed.iterator();
while (iterator.hasNext()) {
String prefix = iterator.next();
if (pathStr.startsWith(prefix)) {
throw new PathNotFoundException(pathStr);
}
}
// check if the item is cached
ItemImpl item = cache.get(pathStr);
if (item != null) {
// if type matters, then the types must match
if (type == null || item.getItemType().equals(type)) {
return item;
}
throw new PathNotFoundException(pathStr);
}
// load item from store
item = store.load(pathStr, type);
if (item == null) {
throw new PathNotFoundException(pathStr);
}
// instantiate new in-memory copy and cache it
if (item.getItemType().equals(ItemType.NODE)) {
item = new NodeImpl(session, (NodeImpl) item);
} else {
item = new PropertyImpl(session, (PropertyImpl) item);
}
cache.put(pathStr, 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);
}
public boolean nodeExists(Path path) {
try {
getNode(path);
} catch (PathNotFoundException e) {
return false;
}
return true;
}
public boolean propertyExists(Path path) {
try {
getProperty(path);
} catch (PathNotFoundException e) {
return false;
}
return true;
}
public boolean itemExists(Path path) {
return nodeExists(path) || propertyExists(path);
}
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
NodeImpl node = new NodeImpl(session, path, primaryNodeTypeName);
cache.put(path.toString(), node);
log.itemAdded(node);
removed.remove(path.toString());
// add to parent
Path parentPath = path.getParent();
if (parentPath == null) {
// only the case for the root node
return node;
}
NodeImpl parent = getNode(parentPath);
parent.addChild(node);
log.itemModified(parent);
return node;
}
public PropertyImpl createProperty(Path path, Value value)
throws ItemExistsException, PathNotFoundException,
RepositoryException {
// disallow nodes and properties having the same path
if (nodeExists(path)) {
throw new ItemExistsException("A node already exists at: " + path);
}
// create new property
PropertyImpl property = new PropertyImpl(session, path, value);
cache.put(path.toString(), property);
log.itemAdded(property);
removed.remove(path.toString());
// add to parent
NodeImpl parent = getNode(path.getParent());
parent.addChild(property);
log.itemModified(parent);
return property;
}
public void removeItem(Path path) throws RepositoryException {
ItemImpl item = getItem(path);
cache.remove(path.toString());
// takes care of removing descendants from store
log.itemRemoved(item);
// add to paths removed in this session
removed.add(path.toString());
// remove reference in parent
NodeImpl parent = (NodeImpl) item.getParent();
parent.removeChild(item);
log.itemModified(parent);
// if it is a node...
if (!item.isNode()) {
return;
}
// ...remove its descendants from cache
Iterator<String> iterator = cache.tailMap(path.toString(), true)
.navigableKeySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (!key.startsWith(path.toString())) {
break;
}
iterator.remove();
}
}
public void persistChanges() throws RepositoryException {
store.persist(log);
log.purge();
}
public boolean hasPendingChanges() {
return !log.isEmpty();
}
}