jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ang...@apache.org
Subject svn commit: r421270 [7/23] - in /jackrabbit/trunk/contrib/spi: ./ commons/ commons/src/ commons/src/main/ commons/src/main/java/ commons/src/main/java/org/ commons/src/main/java/org/apache/ commons/src/main/java/org/apache/jackrabbit/ commons/src/main/...
Date Wed, 12 Jul 2006 13:33:27 GMT
Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,959 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi;
+
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistryImpl;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeStorage;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.jcr2spi.name.NamespaceStorage;
+import org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryImpl;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
+import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.jcr2spi.state.NodeReferences;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateManager;
+import org.apache.jackrabbit.jcr2spi.state.CachingItemStateManager;
+import org.apache.jackrabbit.jcr2spi.operation.OperationVisitor;
+import org.apache.jackrabbit.jcr2spi.operation.AddNode;
+import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
+import org.apache.jackrabbit.jcr2spi.operation.Clone;
+import org.apache.jackrabbit.jcr2spi.operation.Copy;
+import org.apache.jackrabbit.jcr2spi.operation.Move;
+import org.apache.jackrabbit.jcr2spi.operation.Remove;
+import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
+import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.Checkout;
+import org.apache.jackrabbit.jcr2spi.operation.Checkin;
+import org.apache.jackrabbit.jcr2spi.operation.Update;
+import org.apache.jackrabbit.jcr2spi.operation.Restore;
+import org.apache.jackrabbit.jcr2spi.operation.ResolveMergeConflict;
+import org.apache.jackrabbit.jcr2spi.operation.Merge;
+import org.apache.jackrabbit.jcr2spi.operation.LockOperation;
+import org.apache.jackrabbit.jcr2spi.operation.LockRefresh;
+import org.apache.jackrabbit.jcr2spi.operation.LockRelease;
+import org.apache.jackrabbit.jcr2spi.operation.AddLabel;
+import org.apache.jackrabbit.jcr2spi.operation.RemoveLabel;
+import org.apache.jackrabbit.jcr2spi.security.AccessManager;
+import org.apache.jackrabbit.jcr2spi.observation.InternalEventListener;
+import org.apache.jackrabbit.util.IteratorHelper;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.EventListener;
+import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.spi.LockInfo;
+import org.apache.jackrabbit.spi.QueryInfo;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QNodeTypeDefinitionIterator;
+import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.NodeInfo;
+import org.apache.jackrabbit.spi.PropertyInfo;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.QNodeTypeDefinition;
+import org.apache.jackrabbit.spi.IdIterator;
+import org.apache.jackrabbit.value.QValue;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NamespaceException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Repository;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.MergeException;
+import javax.jcr.Session;
+import javax.jcr.version.VersionException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * <code>WorkspaceManager</code>...
+ */
+public class WorkspaceManager implements UpdatableItemStateManager, NamespaceStorage, NodeTypeStorage, AccessManager {
+
+    private static Logger log = LoggerFactory.getLogger(WorkspaceManager.class);
+
+    private final RepositoryService service;
+    private final SessionInfo sessionInfo;
+
+    // TODO: TO-BE-FIXED. Major refactoring of caching mechanism with change to SPI ids
+    private final CachingItemStateManager cache;
+
+    // TODO: TO-BE-FIXED. With SPI_ItemId rootId must not be stored separately
+    private final NodeId rootNodeId;
+
+    private final NamespaceRegistryImpl nsRegistry;
+    private final NodeTypeRegistry ntRegistry;
+
+    /**
+     * This is the event listener that listens on the repository service
+     * for external changes. If <code>null</code> then the underlying repository
+     * service does not support observation.
+     */
+    private final EventListener externalChangeListener;
+
+    /**
+     * List of event listener that are set on this WorkspaceManager to get
+     * notifications about local and external changes.
+     */
+    private Set listeners = new HashSet();
+
+    public WorkspaceManager(RepositoryService service, SessionInfo sessionInfo) throws RepositoryException {
+        this.service = service;
+        this.sessionInfo = sessionInfo;
+
+        cache = new CachingItemStateManager(new WorkspaceItemStateManager());
+        addEventListener(cache);
+
+        nsRegistry = createNamespaceRegistry();
+        ntRegistry = createNodeTypeRegistry(nsRegistry);
+        rootNodeId = createRootNodeId();
+        externalChangeListener = createChangeListener();
+    }
+
+    public NamespaceRegistryImpl getNamespaceRegistryImpl() {
+        return nsRegistry;
+    }
+
+    public NodeTypeRegistry getNodeTypeRegistry() {
+        return ntRegistry;
+    }
+
+    public NodeId getRootNodeId() {
+        return rootNodeId;
+    }
+
+    public String[] getWorkspaceNames() throws RepositoryException {
+        // TODO: review
+        return service.getWorkspaceNames(sessionInfo);
+    }
+
+    public IdFactory getIdFactory() {
+        return service.getIdFactory();
+    }
+
+    public LockInfo getLockInfo(NodeId nodeId) throws LockException, RepositoryException {
+        return service.getLockInfo(sessionInfo, nodeId);
+    }
+
+    public String[] getLockTokens() {
+        return sessionInfo.getLockTokens();
+    }
+
+    /**
+     * This method always succeeds.
+     * This is not compliant to the requirements for {@link Session#addLockToken(String)}
+     * as defined by JSR170, which defines that at most one single <code>Session</code>
+     * may contain the same lock token. However, with SPI it is not possible
+     * to determine, whether another session holds the lock, nor can the client
+     * determine, which lock this token belongs to. The latter would be
+     * necessary in order to build the 'Lock' object properly.
+     *
+     * TODO: check if throwing an exception would be more appropriate
+     *
+     * @param lt
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    public void addLockToken(String lt) throws LockException, RepositoryException {
+        sessionInfo.addLockToken(lt);
+        /*
+        // TODO: JSR170 defines that a token can be present with one session only.
+        //       however, we cannot find out about another session holding the lock.
+        //       and neither knows the server, which session is holding a lock token.
+        // TODO: check if throwing would be more appropriate
+        throw new UnsupportedRepositoryOperationException("Session.addLockToken is not possible on the client.");
+        */
+    }
+
+    /**
+     * Tries to remove the given token from the <code>SessionInfo</code>. If the
+     * SessionInfo does not contains the specified token, this method returns
+     * silently.<br>
+     * Note, that any restriction regarding removal of lock tokens must be asserted
+     * before this method is called.
+     *
+     * @param lt
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    public void removeLockToken(String lt) throws LockException, RepositoryException {
+        sessionInfo.removeLockToken(lt);
+    }
+
+    public String[] getSupportedQueryLanguages() throws RepositoryException {
+        // TODO: review
+        return service.getSupportedQueryLanguages(sessionInfo);
+    }
+
+    public QueryInfo executeQuery(String statement, String language)
+            throws RepositoryException {
+        return service.executeQuery(sessionInfo, statement, language);
+    }
+
+    /**
+     * Sets the <code>InternalEventListener</code> that gets notifications about
+     * local and external changes.
+     * @param listener the new listener.
+     */
+    public void addEventListener(InternalEventListener listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     *
+     * @param listener
+     */
+    public void removeEventListener(InternalEventListener listener) {
+        listeners.remove(listener);
+    }
+    //--------------------------------------------------------------------------
+
+
+    private NamespaceRegistryImpl createNamespaceRegistry() throws RepositoryException {
+        return new NamespaceRegistryImpl(this, service.getRegisteredNamespaces(sessionInfo));
+    }
+
+    private NodeTypeRegistry createNodeTypeRegistry(NamespaceRegistry nsRegistry) throws RepositoryException {
+        QNodeDefinition rootNodeDef = service.getNodeDefinition(sessionInfo, service.getRootId(sessionInfo));
+        QNodeTypeDefinitionIterator it = service.getNodeTypeDefinitions(sessionInfo);
+        List ntDefs = new ArrayList();
+        while (it.hasNext()) {
+            ntDefs.add(it.nextDefinition());
+        }
+        return NodeTypeRegistryImpl.create(ntDefs, this, rootNodeDef, nsRegistry);
+    }
+
+    /**
+     * Creates and registers an EventListener on the RepositoryService that
+     * listens for external changes.
+     *
+     * @return the listener or <code>null</code> if the underlying
+     *         <code>RepositoryService</code> does not support observation.
+     * @throws RepositoryException if an error occurs while registering the
+     *                             event listener.
+     */
+    private EventListener createChangeListener() throws RepositoryException {
+        Properties descriptors = service.getRepositoryDescriptors();
+        String desc = descriptors.getProperty(Repository.OPTION_OBSERVATION_SUPPORTED);
+        EventListener l = null;
+        if (Boolean.getBoolean(desc)) {
+            l = new EventListener() {
+                public void onEvent(EventIterator events) {
+                    onEventReceived(null, events, false); // external
+                }
+            };
+            int allTypes = Event.NODE_ADDED | Event.NODE_REMOVED |
+                    Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+            // register for all events
+            service.addEventListener(sessionInfo, service.getRootId(sessionInfo),
+                    l, allTypes, true, null, null);
+        }
+        return l;
+    }
+
+    private NodeId createRootNodeId() throws RepositoryException {
+        return service.getRootId(sessionInfo);
+    }
+
+    //---------------------------------------------------< ItemStateManager >---
+    /**
+     * @inheritDoc
+     */
+    public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException {
+        // retrieve through cache
+        synchronized (cache) {
+            return cache.getItemState(id);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public boolean hasItemState(ItemId id) {
+        synchronized (cache) {
+            return cache.hasItemState(id);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public NodeReferences getNodeReferences(NodeId id) throws NoSuchItemStateException, ItemStateException {
+        synchronized (cache) {
+            return cache.getNodeReferences(id);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public boolean hasNodeReferences(NodeId id) {
+        synchronized (cache) {
+            return cache.hasNodeReferences(id);
+        }
+    }
+
+    //------ updatable -:>> review ---------------------------------------------
+    /**
+     * Creates a new <code>Batch</code> from the single workspace operation and
+     * executes it.
+     *
+     * @see UpdatableItemStateManager#execute(Operation)
+     */
+    public void execute(Operation operation) throws RepositoryException {
+        new Batch(sessionInfo).execute(operation);
+    }
+
+    /**
+     * Creates a new <code>Batch</code> from the given <code>Batch</code> and
+     * executes it.
+     *
+     * @param changes
+     * @throws RepositoryException
+     */
+    public void execute(ChangeLog changes) throws RepositoryException {
+        new Batch(sessionInfo).execute(changes);
+    }
+
+    public void store(ItemState state) throws IllegalStateException {
+    }
+
+    public void destroy(ItemState state) throws IllegalStateException {
+    }
+
+    public void dispose() {
+        if (externalChangeListener != null) {
+            try {
+                service.removeEventListener(sessionInfo, rootNodeId, externalChangeListener);
+            } catch (RepositoryException e) {
+                log.warn("exception while disposing workspace manager: " + e);
+            }
+        }
+    }
+    //------------------------------------------------------< AccessManager >---
+
+    // TODO: method can be removed, if jcr2spi uses spi-ids as well
+    public boolean isGranted(NodeId parentId, Path relPath, String[] actions) throws ItemNotFoundException, RepositoryException {
+        // TODO: 'createNodeId' is basically wrong since isGranted is unspecific for any item.
+        ItemId id = getIdFactory().createNodeId(parentId, relPath);
+        return isGranted(id, actions);
+    }
+
+    public boolean isGranted(ItemId id, String[] actions) throws ItemNotFoundException, RepositoryException {
+        return service.isGranted(sessionInfo, id, actions);
+    }
+
+    public boolean canRead(ItemId id) throws ItemNotFoundException, RepositoryException {
+        return service.isGranted(sessionInfo, id, AccessManager.READ);
+    }
+
+    public boolean canRemove(ItemId id) throws ItemNotFoundException, RepositoryException {
+        return service.isGranted(sessionInfo, id, AccessManager.REMOVE);
+    }
+
+    public boolean canAccess(String workspaceName) throws NoSuchWorkspaceException, RepositoryException {
+        String[] wspNames = getWorkspaceNames();
+        for (int i = 0; i < wspNames.length; i++) {
+            if (wspNames[i].equals(wspNames)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //---------------------------------------------------------< XML import >---
+    public void importXml(NodeId parentId, InputStream xmlStream, int uuidBehaviour) throws RepositoryException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, ItemExistsException, VersionException {
+        service.importXml(sessionInfo, parentId, xmlStream, uuidBehaviour);
+    }
+
+    //---------------------------------------------------< NamespaceStorage >---
+    /**
+     * @inheritDoc
+     */
+    public void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+        service.registerNamespace(sessionInfo, prefix, uri);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void unregisterNamespace(String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+        service.unregisterNamespace(sessionInfo, uri);
+    }
+
+    //----------------------------------------------------< NodetypeStorage >---
+    /**
+     * @inheritDoc
+     */
+    public void registerNodeTypes(QNodeTypeDefinition[] nodeTypeDefs) throws NoSuchNodeTypeException, UnsupportedRepositoryOperationException, RepositoryException {
+        service.registerNodeTypes(sessionInfo, nodeTypeDefs);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void reregisterNodeTypes(QNodeTypeDefinition[] nodeTypeDefs) throws NoSuchNodeTypeException, UnsupportedRepositoryOperationException, RepositoryException {
+        service.reregisterNodeTypes(sessionInfo, nodeTypeDefs);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void unregisterNodeTypes(QName[] nodeTypeNames) throws NoSuchNodeTypeException, UnsupportedRepositoryOperationException, RepositoryException {
+        service.unregisterNodeTypes(sessionInfo, nodeTypeNames);
+    }
+
+    //--------------------------------------------------------------------------
+
+    /**
+     * Called when local or external events occured. This method is called after
+     * changes have been applied to the repository.
+     *
+     * @param changeLog
+     * @param events the events.
+     * @param isLocal <code>true</code> if changes were local.
+     */
+    private void onEventReceived(ChangeLog changeLog, EventIterator events, boolean isLocal) {
+        if (changeLog != null) {
+            // use current change log for notification
+            changeLog.persisted();
+        }
+
+        // notify listener
+        // need to copy events into a list because we notify multiple listeners
+        List eventList = new ArrayList();
+        while (events.hasNext()) {
+            Event e = events.nextEvent();
+            eventList.add(e);
+        }
+
+        InternalEventListener[] lstnrs = (InternalEventListener[]) listeners.toArray(new InternalEventListener[listeners.size()]);
+        for (int i = 0; i < lstnrs.length; i++) {
+           lstnrs[i].onEvent(new EventIteratorImpl(eventList), isLocal);
+        }
+    }
+
+    /**
+     * Build a new <code>NodeState</code> from the information retrieved
+     * from the <code>RepositoryService</code>.
+     *
+     * @param id node id
+     * @return node state
+     * @throws NoSuchItemStateException
+     * @throws ItemStateException
+     */
+    private NodeState getNodeState(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        try {
+            NodeInfo info = service.getNodeInfo(sessionInfo, id);
+            QName ntName = info.getNodetype();
+            NodeId parentId = (info.getParentId() != null) ? info.getParentId() : null;
+
+            // build the node state
+            // NOTE: unable to retrieve definitionId -> needs to be retrieved
+            // by the itemManager upon Node creation.
+            NodeState state = new NodeState(info.getId(), ntName, parentId, ItemState.STATUS_EXISTING, false, getIdFactory());
+            // set mixin nodetypes
+            state.setMixinTypeNames(info.getMixins());
+            // references to child items
+
+            for (IdIterator it = info.getNodeIds(); it.hasNext(); ) {
+                NodeInfo childInfo = service.getNodeInfo(sessionInfo, (org.apache.jackrabbit.spi.NodeId)it.nextId());
+                NodeId childId = childInfo.getId();
+                state.addChildNodeEntry(childInfo.getQName(), childId);
+            }
+
+            for (IdIterator it = info.getPropertyIds(); it.hasNext(); ) {
+                PropertyId pId = (PropertyId) it.nextId();
+                state.addPropertyName(pId.getQName());
+            }
+
+            // copied from local-state-mgr TODO... check
+            // register as listener
+            // TODO check if needed
+            //state.addListener(this);
+            return state;
+        } catch (PathNotFoundException e) {
+            throw new NoSuchItemStateException(e.getMessage(), e);
+        } catch (RepositoryException e) {
+            throw new ItemStateException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Build a new <code>PropertyState</code> from the information retrieved
+     * from the <code>RepositoryService</code>.
+     *
+     * @param id property id
+     * @return property state
+     * @throws NoSuchItemStateException
+     * @throws ItemStateException
+     */
+    private PropertyState getPropertyState(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        try {
+            PropertyInfo info = service.getPropertyInfo(sessionInfo, id);
+
+            // build the PropertyState
+            // NOTE: unable to retrieve definitionId -> needs to be retrieved
+            // by the itemManager upon Property creation.
+            PropertyState state = new PropertyState(info.getId(), ItemState.STATUS_EXISTING, false);
+            state.setMultiValued(info.isMultiValued());
+            state.setType(info.getType());
+            QValue[] qValues;
+            if (info.getType() == PropertyType.BINARY) {
+                InputStream[] ins = info.getValuesAsStream();
+                qValues = new QValue[ins.length];
+                for (int i = 0; i < ins.length; i++) {
+                    qValues[i] = QValue.create(ins[i]);
+                }
+            } else {
+                String[] str = info.getValues();
+                qValues = new QValue[str.length];
+                for (int i = 0; i < str.length; i++) {
+                    qValues[i] = QValue.create(str[i], info.getType());
+                }
+            }
+
+            state.setValues(qValues);
+
+            // register as listener
+            // TODO check if needed
+            // state.addListener(this);
+            return state;
+        } catch (PathNotFoundException e) {
+            throw new NoSuchItemStateException(e.getMessage(), e);
+        } catch (RepositoryException e) {
+            throw new ItemStateException(e.getMessage(), e);
+        } catch (IOException e) {
+            throw new ItemStateException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Executes a sequence of operations on the repository service within
+     * a given <code>SessionInfo</code>.
+     */
+    private final class Batch implements OperationVisitor {
+
+        /**
+         * The session info for all operations in this batch.
+         */
+        private final SessionInfo sessionInfo;
+
+        private org.apache.jackrabbit.spi.Batch batch;
+        private EventIterator events;
+
+        private Batch(SessionInfo sessionInfo) {
+            this.sessionInfo = sessionInfo;
+        }
+
+        /**
+         * Executes the operations on the repository service.
+         */
+        private void execute(ChangeLog changeLog) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
+            try {
+                batch = service.createBatch(sessionInfo);
+                Iterator it = changeLog.getOperations();
+                while (it.hasNext()) {
+                    Operation op = (Operation) it.next();
+                    log.info("executing: " + op);
+                    op.accept(this);
+                }
+            } finally {
+                if (batch != null) {
+                    EventIterator events = service.submit(batch);
+                    onEventReceived(changeLog, events, true);
+                    // reset batch field
+                    batch = null;
+                }
+            }
+        }
+
+        /**
+         * Executes the operations on the repository service.
+         */
+        private void execute(Operation workspaceOperation) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
+            boolean success = false;
+            try {
+                log.info("executing: " + workspaceOperation);
+                workspaceOperation.accept(this);
+                success = true;
+            } finally {
+                if (success && events != null) {
+                    onEventReceived(null, events, true);
+                }
+            }
+        }
+        //-----------------------< OperationVisitor >---------------------------
+
+        public void visit(AddNode operation) throws RepositoryException {
+            batch.addNode(operation.getParentId(), operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid());
+        }
+
+        public void visit(AddProperty operation) throws RepositoryException {
+            org.apache.jackrabbit.spi.NodeId parentId = operation.getParentId();
+            QName propertyName = operation.getPropertyName();
+            int type = operation.getPropertyType();
+            if (operation.isMultiValued()) {
+                QValue[] values = operation.getValues();
+                if (type == PropertyType.BINARY) {
+                    InputStream[] ins = new InputStream[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        ins[i] = values[i].getStream();
+                    }
+                    batch.addProperty(parentId, propertyName, ins, type);
+                } else {
+                    String[] strs = new String[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        strs[i] = values[i].getString();
+                    }
+                    batch.addProperty(parentId, propertyName, strs, type);
+                }
+            } else {
+                QValue value = operation.getValues()[0];
+                if (type == PropertyType.BINARY) {
+                    batch.addProperty(parentId, propertyName, value.getStream(), type);
+                } else {
+                    batch.addProperty(parentId, propertyName, value.getString(), type);
+                }
+            }
+        }
+
+        public void visit(Clone operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+            events = service.clone(sessionInfo, operation.getWorkspaceName(), operation.getNodeId(), operation.getDestinationParentId(), operation.getDestinationName(), operation.isRemoveExisting());
+        }
+
+        public void visit(Copy operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+            events = service.copy(sessionInfo, operation.getWorkspaceName(), operation.getNodeId(), operation.getDestinationParentId(), operation.getDestinationName());
+        }
+
+        public void visit(Move operation) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+            if (batch == null) {
+                events = service.move(sessionInfo, operation.getNodeId(), operation.getDestinationParentId(), operation.getDestinationName());
+            } else {
+                batch.move(operation.getNodeId(), operation.getDestinationParentId(), operation.getDestinationName());
+            }
+        }
+
+        public void visit(Update operation) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException {
+            events = service.update(sessionInfo, operation.getNodeId(), operation.getSourceWorkspaceName());
+        }
+
+        public void visit(Remove operation) throws RepositoryException {
+            batch.remove(operation.getRemoveId());
+        }
+
+        public void visit(SetMixin operation) throws RepositoryException {
+            batch.setMixins(operation.getNodeId(), operation.getMixinNames());
+        }
+
+        public void visit(SetPropertyValue operation) throws RepositoryException {
+            org.apache.jackrabbit.spi.PropertyId id = operation.getPropertyId();
+            int type = operation.getPropertyType();
+            if (operation.isMultiValued()) {
+                QValue[] values = operation.getValues();
+                if (type == PropertyType.BINARY) {
+                    InputStream[] ins = new InputStream[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        ins[i] = values[i].getStream();
+                    }
+                    batch.setValue(id, ins, type);
+                } else {
+                    String[] strs = new String[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        strs[i] = values[i].getString();
+                    }
+                    batch.setValue(id, strs, type);
+                }
+            } else {
+                QValue value = operation.getValues()[0];
+                if (operation.getPropertyType() == PropertyType.BINARY) {
+                    batch.setValue(id, value.getStream(), type);
+                } else {
+                    batch.setValue(id, value.getString(), type);
+                }
+            }
+        }
+
+        public void visit(ReorderNodes operation) throws RepositoryException {
+            batch.reorderNodes(operation.getParentId(), operation.getInsertNodeId(), operation.getBeforeNodeId());
+        }
+
+        public void visit(Checkout operation) throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
+            events = service.checkout(sessionInfo, operation.getNodeId());
+        }
+
+        public void visit(Checkin operation) throws UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+            events = service.checkin(sessionInfo, operation.getNodeId());
+        }
+
+        public void visit(Restore operation) throws VersionException, PathNotFoundException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+            NodeId nId = operation.getNodeId();
+            NodeId[] versionIds = operation.getVersionIds();
+            NodeId[] vIds = new NodeId[versionIds.length];
+            for (int i = 0; i < vIds.length; i++) {
+                vIds[i] = versionIds[i];
+            }
+
+            if (nId == null) {
+                events = service.restore(sessionInfo, vIds, operation.removeExisting());
+            } else {
+                if (vIds.length > 1) {
+                    throw new IllegalArgumentException("Restore from a single node must specify but one single Version.");
+                }
+                events = service.restore(sessionInfo, nId, vIds[0], operation.removeExisting());
+            }
+        }
+
+        public void visit(Merge operation) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
+            events = service.merge(sessionInfo, operation.getNodeId(), operation.getSourceWorkspaceName(), operation.bestEffort());
+            // todo: improve.... inform operation about modified items (build mergefailed iterator)
+            operation.getEventListener().onEvent(events, true);
+        }
+
+        public void visit(ResolveMergeConflict operation) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+            try {
+                NodeId nId = operation.getNodeId();
+                NodeId vId = operation.getVersionId();
+
+                PropertyState mergeFailedState = getPropertyState(getIdFactory().createPropertyId(nId, QName.JCR_MERGEFAILED));
+                QValue[] vs = mergeFailedState.getValues();
+
+                NodeId[] mergeFailedIds = new NodeId[vs.length - 1];
+                for (int i = 0, j = 0; i < vs.length; i++) {
+                    NodeId id = getIdFactory().createNodeId(vs[i].getString());
+                    if (!id.equals(vId)) {
+                        mergeFailedIds[j] = id;
+                        j++;
+                    }
+                    // else: the version id is being solved by this call and not
+                    // part of 'jcr:mergefailed' any more
+                }
+
+                PropertyState predecessorState = getPropertyState(getIdFactory().createPropertyId(nId, QName.JCR_PREDECESSORS));
+                vs = predecessorState.getValues();
+
+                boolean resolveDone = operation.resolveDone();
+                int noOfPredecessors = (resolveDone) ? vs.length + 1 : vs.length;
+                NodeId[] predecessorIds = new NodeId[noOfPredecessors];
+
+                int i = 0;
+                while (i < vs.length) {
+                    predecessorIds[i] = getIdFactory().createNodeId(vs[i].getString());
+                    i++;
+                }
+                if (resolveDone) {
+                    predecessorIds[i] = vId;
+                }
+                events = service.resolveMergeConflict(sessionInfo, nId, mergeFailedIds, predecessorIds);
+            } catch (ItemStateException e) {
+                // should not occur.
+                throw new RepositoryException(e);
+            }
+        }
+
+        public void visit(LockOperation operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+            events = service.lock(sessionInfo, operation.getNodeId(), operation.isDeep());
+        }
+
+        public void visit(LockRefresh operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+            events = service.refreshLock(sessionInfo, operation.getNodeId());
+        }
+
+        public void visit(LockRelease operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+            events = service.unlock(sessionInfo, operation.getNodeId());
+        }
+
+        public void visit(AddLabel operation) throws VersionException, RepositoryException {
+            events = service.addVersionLabel(sessionInfo, operation.getVersionHistoryId(), operation.getVersionId(), operation.getLabel(), operation.moveLabel());
+        }
+
+        public void visit(RemoveLabel operation) throws VersionException, RepositoryException {
+            events = service.removeVersionLabel(sessionInfo, operation.getVersionHistoryId(), operation.getVersionId(), operation.getLabel());
+        }
+    }
+
+    private static final class EventIteratorImpl extends IteratorHelper implements EventIterator {
+
+        public EventIteratorImpl(Collection c) {
+            super(c);
+        }
+
+        public Event nextEvent() {
+            return (Event) next();
+        }
+    }
+
+    /**
+     * <code>NodeReferences</code> represents the references (i.e. properties of
+     * type <code>REFERENCE</code>) to a particular node (denoted by its uuid).
+     */
+    public class NodeReferencesImpl implements NodeReferences {
+
+        /**
+         * identifier of this <code>NodeReferences</code> instance.
+         */
+        private NodeId id;
+
+        /**
+         * list of PropertyId's (i.e. the id's of the properties that refer to
+         * the target node denoted by <code>id.getTargetId()</code>).
+         * <p/>
+         * note that the list can contain duplicate entries because a specific
+         * REFERENCE property can contain multiple references (if it's multi-valued)
+         * to potentially the same target node.
+         */
+        private ArrayList references = new ArrayList();
+
+        /**
+         * Package private constructor
+         *
+         * @param id
+         */
+        private NodeReferencesImpl(NodeId id) {
+            this.id = id;
+        }
+
+        //-------------------------------------------------< NodeReferences >---
+        /**
+         * Returns the identifier of this node references object.
+         *
+         * @return the id of this node references object.
+         */
+        public NodeId getId() {
+            return id;
+        }
+
+        /**
+         * Returns a flag indicating whether this object holds any references
+         *
+         * @return <code>true</code> if this object holds references,
+         *         <code>false</code> otherwise
+         */
+        public boolean hasReferences() {
+            return !references.isEmpty();
+        }
+
+        /**
+         * @return the list of references
+         */
+        public List getReferences() {
+            return Collections.unmodifiableList(references);
+        }
+
+        //--------------------------------------------------------< private >---
+        /**
+         * @param refId
+         */
+        private void addReference(PropertyId refId) {
+            references.add(refId);
+        }
+
+        /**
+         * @param refId
+         * @return <code>true</code> if the reference was removed;
+         *        <code>false</code> otherwise.
+         */
+        private boolean removeReference(PropertyId refId) {
+            return references.remove(refId);
+        }
+    }
+
+    public class WorkspaceItemStateManager implements ItemStateManager {
+
+        public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException {
+            ItemState state;
+            if (id.denotesNode()) {
+                state = getNodeState((NodeId) id);
+            } else {
+                state = getPropertyState((PropertyId) id);
+            }
+            return state;
+        }
+
+        public boolean hasItemState(ItemId id) {
+            try {
+                return service.exists(sessionInfo, id);
+            } catch (RepositoryException e) {
+                log.error(e.getMessage());
+                return false;
+            }
+        }
+
+        public NodeReferences getNodeReferences(NodeId id)
+                throws NoSuchItemStateException, ItemStateException {
+            try {
+                NodeReferencesImpl nrefs = new NodeReferencesImpl(id);
+                NodeInfo info = service.getNodeInfo(sessionInfo, id);
+                PropertyId[] refs = info.getReferences();
+                for (int i = 0; i < refs.length; i++) {
+                    PropertyInfo pInfo = service.getPropertyInfo(sessionInfo, refs[i]);
+                    nrefs.addReference(pInfo.getId());
+                }
+                return nrefs;
+            } catch (PathNotFoundException e) {
+                log.error(e.getMessage());
+                throw new NoSuchItemStateException(e.getMessage(), e);
+            } catch (RepositoryException e) {
+                throw new ItemStateException(e.getMessage(), e);
+            }
+        }
+
+        public boolean hasNodeReferences(NodeId id) {
+            try {
+                NodeInfo info = service.getNodeInfo(sessionInfo, id);
+                return info.getReferences().length > 0;
+            } catch (PathNotFoundException e) {
+                log.error(e.getMessage());
+            } catch (RepositoryException e) {
+                log.error(e.getMessage());
+            }
+            return false;
+        }
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.Session;
+import javax.transaction.xa.XAResource;
+
+/**
+ * The <code>XASession</code> interface extends the capability of
+ * <code>Session</code> by adding access to a JCR repository's support for
+ * the Java Transaction API (JTA).
+ * <p>
+ * This support takes the form of a <code>javax.transaction.xa.XAResource</code>
+ * object. The functionality of this object closely resembles that defined by
+ * the standard X/Open XA Resource interface.
+ * <p>
+ * This interface is used by the transaction manager; an application does not
+ * use it directly.
+ */
+public interface XASession extends Session {
+
+    /**
+     * Retrieves an <code>XAResource</code> object that the transaction manager
+     * will use to manage this <code>XASession</code> object's participation in
+     * a distributed transaction.
+     *
+     * @return the <code>XAResource</code> object.
+     */
+    XAResource getXAResource();
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASession.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import org.apache.jackrabbit.spi.XASessionInfo;
+import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
+
+import javax.transaction.xa.XAResource;
+
+/**
+ * <code>XASessionImpl</code> extends the regular session implementation with
+ * access to the <code>XAResource</code>.
+ */
+public class XASessionImpl extends SessionImpl implements XASession {
+
+    /**
+     * The XASessionInfo of this <code>SessionImpl</code>.
+     */
+    private final XASessionInfo sessionInfo;
+
+    /**
+     * Creates a new <code>XASessionImpl</code>.
+     *
+     * @param repository the repository instance associated with this session.
+     * @param sessionInfo the session info.
+     * @param service the underlying repository service.
+     * @throws RepositoryException if an error occurs while creating a session.
+     */
+    XASessionImpl(XASessionInfo sessionInfo, Repository repository,
+                  RepositoryConfig config) throws RepositoryException {
+        super(sessionInfo, repository, config);
+        this.sessionInfo = sessionInfo;
+    }
+
+    //--------------------------------< XASession >-----------------------------
+
+    /**
+     * @inheritDoc
+     */
+    public XAResource getXAResource() {
+        return sessionInfo.getXAResource();
+    }
+
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/XASessionImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi;
+
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateManager;
+import org.apache.jackrabbit.jcr2spi.state.NoSuchItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.NodeId;
+
+import java.util.Iterator;
+
+/**
+ * <code>HierarchyManager</code> implementation that is also able to
+ * build/resolve paths of those items that have been moved or removed
+ * (i.e. moved to the attic).
+ */
+public class ZombieHierarchyManager extends HierarchyManagerImpl {
+
+    /**
+     * the attic
+     */
+    protected ItemStateManager attic;
+
+    public ZombieHierarchyManager(NodeId rootNodeId,
+                                  ItemStateManager provider,
+                                  ItemStateManager attic,
+                                  NamespaceResolver nsResolver) {
+        super(rootNodeId, provider, nsResolver);
+        this.attic = attic;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Delivers state from attic if such exists, otherwise calls base class.
+     */
+    protected ItemState getItemState(ItemId id)
+            throws NoSuchItemStateException, ItemStateException {
+        // always check attic first
+        if (attic.hasItemState(id)) {
+            return attic.getItemState(id);
+        }
+        // delegate to base class
+        return super.getItemState(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Returns <code>true</code>  if there's state on the attic for the
+     * requested item; otherwise delegates to base class.
+     */
+    protected boolean hasItemState(ItemId id) {
+        // always check attic first
+        if (attic.hasItemState(id)) {
+            return true;
+        }
+        // delegate to base class
+        return super.hasItemState(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Also allows for removed items.
+     */
+    protected NodeId getParentId(ItemState state) {
+        if (state.hasOverlayedState()) {
+            // use 'old' parent in case item has been removed
+            return state.getOverlayedState().getParentId();
+        }
+        // delegate to base class
+        return super.getParentId(state);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Also allows for removed/renamed child node entries.
+     */
+    protected NodeState.ChildNodeEntry getChildNodeEntry(NodeState parent,
+                                                         QName name,
+                                                         int index) {
+        // check removed child node entries first
+        Iterator iter = parent.getRemovedChildNodeEntries().iterator();
+        while (iter.hasNext()) {
+            NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) iter.next();
+            if (entry.getName().equals(name) && entry.getIndex() == index) {
+                return entry;
+            }
+        }
+        // no matching removed child node entry found in parent,
+        // delegate to base class
+        return super.getChildNodeEntry(parent, name, index);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Also allows for removed child node entries.
+     */
+    protected NodeState.ChildNodeEntry getChildNodeEntry(NodeState parent,
+                                                         NodeId id) {
+        // check removed child node entries first
+        Iterator iter = parent.getRemovedChildNodeEntries().iterator();
+        while (iter.hasNext()) {
+            NodeState.ChildNodeEntry entry =
+                    (NodeState.ChildNodeEntry) iter.next();
+            if (entry.getId().equals(id)) {
+                return entry;
+            }
+        }
+        // no matching removed child node entry found in parent,
+        // delegate to base class
+        return super.getChildNodeEntry(parent, id);
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ZombieHierarchyManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi.config;
+
+import org.apache.jackrabbit.spi.RepositoryService;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
+
+/**
+ * <code>RepositoryConfig</code>...
+ */
+// TODO: needs to be done properly
+public interface RepositoryConfig {
+
+    public RepositoryService getRepositoryService() throws RepositoryException;
+
+    public ValueFactory getValueFactory() throws RepositoryException;
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi.lock;
+
+import org.apache.jackrabbit.spi.NodeId;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+/**
+ * <code>DefaultLockManager</code>...
+ */
+public class DefaultLockManager implements LockManager {
+
+    private static Logger log = LoggerFactory.getLogger(DefaultLockManager.class);
+
+    public Lock lock(NodeId nodeId, boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Locking ist not supported by this repository.");
+    }
+
+    public Lock getLock(NodeId nodeId) throws LockException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Locking ist not supported by this repository.");
+    }
+
+    public void unlock(NodeId nodeId) throws LockException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Locking ist not supported by this repository.");
+    }
+
+    public boolean isLocked(NodeId nodeId) throws RepositoryException {
+        return false;
+    }
+
+    public void checkLock(NodeId nodeId) throws LockException, RepositoryException {
+        // nothing to do since locking is not supported
+    }
+
+    public String[] getLockTokens() {
+        // return an empty string array
+        return new String[0];
+    }
+
+    public void addLockToken(String lt) {
+        // nothing to do since locking is not supported
+    }
+
+    public void removeLockToken(String lt) {
+        // nothing to do since locking is not supported
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/DefaultLockManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi.lock;
+
+import javax.jcr.RepositoryException;
+import org.apache.jackrabbit.spi.NodeId;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+
+/**
+ * Defines the functionality needed for locking and unlocking nodes.
+ */
+public interface LockManager {
+    
+    /**
+     * Lock a node. Checks whether the node is not locked and then
+     * returns a lock object for this node.
+     *
+     * @param nodeId node id.
+     * @param isDeep whether the lock applies to this node only
+     * @param isSessionScoped whether the lock is session scoped
+     * @return lock object
+     * @throws LockException if this node already is locked, or some descendant
+     *         node is locked and <code>isDeep</code> is <code>true</code>
+     * @see javax.jcr.Node#lock
+     */
+    Lock lock(NodeId nodeId, boolean isDeep, boolean isSessionScoped)
+        throws LockException, RepositoryException;
+
+    /**
+     * Returns the Lock object that applies to a node. This may be either a lock
+     * on this node itself or a deep lock on a node above this node.
+     *
+     * @param nodeId
+     * @return lock object
+     * @throws LockException if this node is not locked
+     * @see javax.jcr.Node#getLock
+     */
+    Lock getLock(NodeId nodeId) throws LockException, RepositoryException;
+
+    /**
+     * Removes the lock on a node.
+     *
+     * @param nodeId
+     * @throws LockException if this node is not locked or the session does not
+     * have the correct lock token
+     * @see javax.jcr.Node#unlock
+     */
+    void unlock(NodeId nodeId) throws LockException, RepositoryException;
+
+    /**
+     * Returns <code>true</code> if this node is locked either as a result
+     * of a lock held by this node or by a deep lock on a node above this
+     * node; otherwise returns <code>false</code>.
+     *
+     * @param nodeId
+     * @return <code>true</code> if this node is locked either as a result
+     * of a lock held by this node or by a deep lock on a node above this
+     * node; otherwise returns <code>false</code>
+     * @see javax.jcr.Node#isLocked
+     */
+    boolean isLocked(NodeId nodeId) throws RepositoryException;
+
+    /**
+     * Check whether the node given is locked by somebody else than the
+     * current session. Access is allowed if the node is not locked or
+     * if the session itself holds the lock to this node, i.e. the session
+     * contains the lock token for the lock. If the node is not locked at
+     * all this method returns silently.
+     *
+     * @param nodeId to check
+     * @throws LockException if write access to the specified node is not allowed
+     * @throws RepositoryException if some other error occurs
+     */
+    void checkLock(NodeId nodeId) throws LockException, RepositoryException;
+
+    /**
+     *
+     * @return
+     */
+    public String[] getLockTokens();
+
+    /**
+     * Invoked by a session to inform that a lock token has been added.
+     *
+     * @param lt added lock token
+     */
+    void addLockToken(String lt) throws LockException, RepositoryException;
+
+    /**
+     * Invoked by a session to inform that a lock token has been removed.
+     *
+     * @param lt removed lock token
+     */
+    void removeLockToken(String lt) throws LockException, RepositoryException;
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,562 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jackrabbit.jcr2spi.lock;
+
+import org.apache.jackrabbit.jcr2spi.ItemManager;
+import org.apache.jackrabbit.jcr2spi.SessionListener;
+import org.apache.jackrabbit.jcr2spi.WorkspaceManager;
+import org.apache.jackrabbit.jcr2spi.IdKeyMap;
+import org.apache.jackrabbit.jcr2spi.DefaultIdKeyMap;
+import org.apache.jackrabbit.jcr2spi.observation.InternalEventListener;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.LockOperation;
+import org.apache.jackrabbit.jcr2spi.operation.LockRelease;
+import org.apache.jackrabbit.jcr2spi.operation.LockRefresh;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.EventIterator;
+import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.spi.LockInfo;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.ItemId;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Item;
+import javax.jcr.Session;
+
+import java.util.Iterator;
+
+/**
+ * <code>LockManagerImpl</code>...
+ */
+public class LockManagerImpl implements LockManager, SessionListener {
+
+    private static Logger log = LoggerFactory.getLogger(LockManagerImpl.class);
+
+    private final WorkspaceManager wspManager;
+    private final ItemManager itemManager;
+
+    /**
+     * Internal map holding all locks that where created by {@link #lock(NodeId, boolean, boolean)}
+     * or accessed by {@link #getLock(NodeId)} until they end their life by
+     * an unlock (be it by the current Session or external reported by means
+     * of events).
+     */
+    // TODO: TO-BE-FIXED. With SPI_ItemId simple map cannot be used any more
+    private final IdKeyMap lockMap = new DefaultIdKeyMap();
+
+    public LockManagerImpl(WorkspaceManager wspManager, ItemManager itemManager) {
+        this.wspManager = wspManager;
+        this.itemManager = itemManager;
+    }
+
+    /**
+     * @see LockManager#lock(NodeId, boolean, boolean)
+     */
+    public Lock lock(NodeId nodeId, boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
+        // make sure the node is accessible before trying to create a lock.
+        Node node = (Node) itemManager.getItem(nodeId);
+        // execute the operation
+        Operation op = LockOperation.create(nodeId, isDeep, isSessionScoped);
+        wspManager.execute(op);
+
+        Lock lock = new LockImpl(nodeId, node, isSessionScoped);
+        return lock;
+    }
+
+    /**
+     * If the session created a lock on the node with the given id, we already
+     * know the lock. Otherwise, we look in the node state and the states of
+     * the ancestor nodes for properties indicating a lock.<br>
+     * Note, that the flag indicating session-scoped lock cannot be retrieved
+     * and the lock will always report 'false'.
+     *
+     * @see LockManager#getLock(NodeId)
+     */
+    public Lock getLock(NodeId nodeId) throws LockException, RepositoryException {
+        // shortcut: check if node holds a lock and lock has been accessed before
+        if (lockMap.containsKey(nodeId)) {
+            return (Lock) lockMap.get(nodeId);
+        }
+
+        // try to retrieve parent state that holds a lock.
+        NodeState lockHoldingState = getLockHoldingState(nodeId);
+        if (lockHoldingState == null) {
+            throw new LockException("Node with id '" + nodeId + "' is not locked.");
+        } else {
+            NodeId lhNodeId = lockHoldingState.getNodeId();
+            // check again lockMap with id of lockholding node
+            if (lockMap.containsKey(lhNodeId)) {
+                return (Lock) lockMap.get(lhNodeId);
+            }
+
+            // retrieve lock holding node. not that this may fail if the session
+            // does not have permission to see this node.
+            Item lockHoldingNode = itemManager.getItem(lhNodeId);
+            // TODO: we don;t know if lock is session scoped -> set flag to false
+            // TODO: ev. add 'isSessionScoped' to RepositoryService lock-call.
+            Lock l = new LockImpl(lhNodeId, (Node)lockHoldingNode, false);
+            return l;
+        }
+    }
+
+    /**
+     * @see LockManager#unlock(NodeId)
+     */
+    public void unlock(NodeId nodeId) throws LockException, RepositoryException {
+        // execute the operation. Note, that its possible that the session is
+        // lock holder and still the lock was never accessed. thus the lockMap
+        // does not provide sufficient information.
+        Operation op = LockRelease.create(nodeId);
+        wspManager.execute(op);
+
+        // if unlock was successfull: clean up lock map and lock life cycle
+        // in case the corresponding Lock object exists (and thus has been
+        // added to the map.
+        if (lockMap.containsKey(nodeId)) {
+            LockImpl l = (LockImpl) lockMap.remove(nodeId);
+            l.unlocked();
+        }
+    }
+
+    /**
+     * @see LockManager#isLocked(NodeId)
+     */
+    public boolean isLocked(NodeId nodeId) throws RepositoryException {
+        // shortcut: check if a given node holds a lock and lock has been
+        // accessed before (thus is known to the manager).
+        if (lockMap.containsKey(nodeId)) {
+            return true;
+        } else {
+            // check if any lock is present (checking lock-specific properties)
+            LockInfo lInfo = getLockInfo(nodeId);
+            return lInfo != null;
+        }
+    }
+
+    /**
+     * @see LockManager#checkLock(NodeId)
+     */
+    public void checkLock(NodeId nodeId) throws LockException, RepositoryException {
+        LockInfo lInfo;
+        // shortcut: check if a given node holds a lock and lock has been
+        // accessed before (thus is known to the manager).
+        if (lockMap.containsKey(nodeId)) {
+            lInfo = ((LockImpl)lockMap.get(nodeId)).lockInfo;
+        } else {
+            // check if any lock is present (checking lock-specific properties)
+            lInfo = getLockInfo(nodeId);
+        }
+
+        if (lInfo != null && lInfo.getLockToken() == null) {
+            // lock is present and token is null -> session is not lock-holder.
+            throw new LockException("Node with id '" + nodeId + "' is locked.");
+        }
+    }
+
+    /**
+     * Returns the lock tokens present on the <code>SessionInfo</code> this
+     * manager has been created with.
+     *
+     * @see LockManager#getLockTokens()
+     */
+    public String[] getLockTokens() {
+        return wspManager.getLockTokens();
+    }
+
+    /**
+     * Delegates this call to {@link WorkspaceManager#addLockToken(String)}.
+     * If this succeeds this method will inform all locks stored in the local
+     * map in order to give them the chance to update their lock information.
+     *
+     * @see LockManager#addLockToken(String)
+     */
+    public void addLockToken(String lt) throws LockException, RepositoryException {
+        wspManager.addLockToken(lt);
+        notifyTokenAdded(lt);
+    }
+
+    /**
+     * If the lock addressed by the token is session-scoped, this method will
+     * throw a LockException, such as defined by JSR170 v.1.0.1 for
+     * {@link Session#removeLockToken(String)}.<br>Otherwise the call is
+     * delegated to {@link WorkspaceManager#removeLockToken(String)} and
+     * all locks stored in the local lock map are notified by the removed
+     * token in order to give them the chance to update their lock information.
+     *
+     * @see LockManager#removeLockToken(String)
+     */
+    public void removeLockToken(String lt) throws LockException, RepositoryException {
+        // JSR170 v. 1.0.1 defines that the token of a session-scoped lock may
+        // not be moved over to another session. thus removal ist not possible
+        // and the lock is always present in the lock map.
+        NodeId key = null;
+        Iterator it = lockMap.values().iterator();
+        while (it.hasNext() && key == null) {
+            LockImpl l = (LockImpl) it.next();
+            // break as soon as the lock associated with the given token is found.
+            if (lt.equals(l.getLockToken())) {
+                if (l.isSessionScoped()) {
+                    throw new LockException("Cannot remove lock token associated with a session scoped lock.");
+                }
+                key = l.nodeId;
+            }
+        }
+
+        // remove lock token from sessionInfo
+        wspManager.removeLockToken(lt);
+        // inform about this lt being removed from this session
+        notifyTokenRemoved(lt);
+    }
+
+    //----------------------------------------------------< SessionListener >---
+    /**
+     *
+     * @param session
+     * @see SessionListener#loggingOut(Session)
+     */
+    public void loggingOut(Session session) {
+        // remove any session scoped locks:
+        ItemId[] ids = (ItemId[]) lockMap.keySet().toArray(new ItemId[lockMap.size()]);
+        for (int i = 0; i < ids.length; i++) {
+            NodeId nId = (NodeId) ids[i];
+            LockImpl l = (LockImpl) lockMap.get(nId);
+            if (l.isSessionScoped()) {
+                try {
+                    unlock(nId);
+                } catch (RepositoryException e) {
+                    log.error("Error while unlocking session scoped lock. Cleaning up local lock status.");
+                    // at least clean up local lock map and the locks life cycle
+                    l.unlocked();
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     * @param session
+     * @see SessionListener#loggedOut(Session)
+     */
+    public void loggedOut(Session session) {
+        // release all remaining locks without modifying their lock status
+        LockImpl[] locks = (LockImpl[]) lockMap.values().toArray(new LockImpl[lockMap.size()]);
+        for (int i = 0; i < locks.length; i++) {
+            locks[i].release();
+        }
+    }
+
+    //------------------------------------------------------------< private >---
+    /**
+     *
+     * @param nodeId
+     * @return
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    private LockInfo getLockInfo(NodeId nodeId) throws RepositoryException {
+        try {
+            return wspManager.getLockInfo(nodeId);
+        } catch (LockException e) {
+            log.debug("No lock present on node with id '" + nodeId + "'", e);
+            return null;
+        }
+    }
+
+    /**
+     *
+     * @param nodeId
+     * @return
+     * @throws RepositoryException
+     */
+    private NodeState getLockHoldingState(NodeId nodeId) throws RepositoryException {
+        try {
+            NodeState nodeState = (NodeState) wspManager.getItemState(nodeId);
+            // search nearest ancestor that is locked
+            /**
+             * FIXME should not only rely on existence of jcr:lockOwner property
+             * but also verify that node.isNodeType("mix:lockable")==true;
+             * this would have a negative impact on performance though...
+             */
+            while (!nodeState.hasPropertyName(QName.JCR_LOCKOWNER)) {
+                if (nodeState.getParentId() == null) {
+                    // reached root state without finding a locked node
+                    return null;
+                }
+                nodeState = (NodeState) wspManager.getItemState(nodeState.getParentId());
+            }
+            return nodeState;
+        } catch (ItemStateException e) {
+            // should not occur
+            throw new RepositoryException(e);
+        }
+    }
+
+    //----------------------------< Notification about modified lock-tokens >---
+    /**
+     * Notify all <code>Lock</code>s that have been accessed so far about the
+     * new lock token present on the session and allow them to reload their
+     * lock info.
+     *
+     * @param lt
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    private void notifyTokenAdded(String lt) throws LockException, RepositoryException {
+        LockTokenListener[] listeners = (LockTokenListener[]) lockMap.values().toArray(new LockTokenListener[lockMap.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].lockTokenAdded(lt);
+        }
+    }
+
+    /**
+     * Notify all <code>Lock</code>s that have been accessed so far about the
+     * removed lock token and allow them to reload their lock info, if necessary.
+     *
+     * @param lt
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    private void notifyTokenRemoved(String lt) throws LockException, RepositoryException {
+        LockTokenListener[] listeners = (LockTokenListener[]) lockMap.values().toArray(new LockTokenListener[lockMap.size()]);
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].lockTokenRemoved(lt);
+        }
+    }
+
+    //---------------------------------------------------------------< Lock >---
+    /**
+     * Inner class implementing the {@link Lock} interface.
+     */
+    private class LockImpl implements Lock, InternalEventListener, LockTokenListener {
+
+        private final NodeId nodeId;
+        private final Node node;
+        private final boolean isSessionScoped;
+
+        private LockInfo lockInfo;
+        private boolean isLive = true;
+
+        /**
+         *
+         * @param lockHoldingId The Id of the lock holding <code>Node</code>.
+         * @param lockHoldingNode the lock holding <code>Node</code> itself.
+         * @param lockHoldingNode
+         */
+        public LockImpl(NodeId lockHoldingId, Node lockHoldingNode, boolean isSessionScoped) throws LockException, RepositoryException {
+            this.nodeId = lockHoldingId;
+            this.node = lockHoldingNode;
+            this.isSessionScoped = isSessionScoped;
+
+            // retrieve lock info from wsp-manager, in order to get the complete
+            // lockInfo including lock-token, which is not available from the
+            // child properties nor from the original lock request.
+            this.lockInfo = wspManager.getLockInfo(nodeId);
+
+            // register as internal listener to the wsp manager in order to get
+            // informed if this lock ends his life.
+            wspManager.addEventListener(this);
+            // store lock in the map
+            lockMap.put(nodeId, this);
+        }
+
+        /**
+         * @see Lock#getLockOwner()
+         */
+        public String getLockOwner() {
+            return lockInfo.getOwner();
+        }
+
+        /**
+         * @see Lock#isDeep()
+         */
+        public boolean isDeep() {
+            return lockInfo.isDeep();
+        }
+
+        /**
+         * @see Lock#getNode()
+         */
+        public Node getNode() {
+            return node;
+        }
+
+        /**
+         * @see Lock#getLockToken()
+         */
+        public String getLockToken() {
+            return lockInfo.getLockToken();
+        }
+
+        /**
+         * @see Lock#isLive()
+         */
+        public boolean isLive() throws RepositoryException {
+            return isLive;
+        }
+
+        /**
+         * @see Lock#isSessionScoped()
+         */
+        public boolean isSessionScoped() {
+            return isSessionScoped;
+        }
+
+        /**
+         * @see Lock#refresh()
+         */
+        public void refresh() throws LockException, RepositoryException {
+            if (!isLive()) {
+                throw new LockException("Lock is not alive any more.");
+            }
+
+            if (getLockToken() == null) {
+                // shortcut, since lock is always updated if the session became
+                // lock-holder of a foreign lock.
+                throw new LockException("Session does not hold lock.");
+            } else {
+                // lock is still alive -> send refresh-lock operation.
+                Operation op = LockRefresh.create(nodeId);
+                wspManager.execute(op);
+            }
+        }
+
+        //----------------------------------------------< InternalEventListener >---
+        /**
+         *
+         * @param events
+         * @param isLocal
+         */
+        public void onEvent(EventIterator events, boolean isLocal) {
+            if (!isLive) {
+                // since only we only monitor the removal of the lock (by means
+                // of deletion of the jcr:lockOwner property, we are not interested
+                // if the lock is not active any more.
+                return;
+            }
+
+            while (events.hasNext()) {
+                Event ev = events.nextEvent();
+                // if the jcr:lockOwner property related to this Lock got removed,
+                // we assume that the lock has been released.
+                if (ev.getType() == Event.PROPERTY_REMOVED &&
+                    nodeId.equals(ev.getParentId()) &&
+                    QName.JCR_LOCKOWNER.equals(ev.getQPath().getNameElement().getName())) {
+                    // TODO: check if removal jcr:lockIsDeep must be checked as well.
+
+                    // this lock has been release by someone else (and not by
+                    // a call to LockManager#unlock -> clean up and set isLive
+                    // flag to false.
+                    unlocked();
+                    break;
+                }
+            }
+        }
+
+        /**
+         *
+         * @param lockToken
+         * @throws LockException
+         * @throws RepositoryException
+         */
+        public void lockTokenAdded(String lockToken) throws LockException, RepositoryException {
+            if (getLockToken() == null) {
+                // could be that this affects this lock and session became
+                // lock holder -> releoad info
+                reloadLockInfo();
+            }
+        }
+
+        /**
+         *
+         * @param lockToken
+         * @throws LockException
+         * @throws RepositoryException
+         */
+        public void lockTokenRemoved(String lockToken) throws LockException, RepositoryException {
+            // reload lock info, if session gave away its lock-holder state
+            // for this lock
+            if (lockToken.equals(getLockToken())) {
+                reloadLockInfo();
+            }
+        }
+
+        //--------------------------------------------------------< private >---
+        /**
+         *
+         * @throws LockException
+         * @throws RepositoryException
+         */
+        private void reloadLockInfo() throws LockException, RepositoryException {
+            // re-check with server, since Session.addLockToken will not
+            // automatically result in an update of the lock-map.
+            lockInfo = wspManager.getLockInfo(nodeId);
+        }
+
+        /**
+         * Release this lock by removing from the lock map and unregistering
+         * it from event listening
+         */
+        private void release() {
+            if (lockMap.containsKey(nodeId)) {
+                lockMap.remove(nodeId);
+            }
+            wspManager.removeEventListener(this);
+        }
+
+        /**
+         * This lock has been removed by the current Session or by an external
+         * unlock request. Since a lock will never come back to life after
+         * unlocking, it is released an its status is reset accordingly.
+         */
+        private void unlocked() {
+            if (isLive) {
+                isLive = false;
+                release();
+            }
+        }
+    }
+
+    //--------------------------------------------------< LockTokenListener >---
+    /**
+     *
+     */
+    private interface LockTokenListener {
+
+        /**
+         *
+         * @param lockToken
+         * @throws LockException
+         * @throws RepositoryException
+         */
+        void lockTokenAdded(String lockToken) throws LockException, RepositoryException;
+
+        /**
+         *
+         * @param lockToken
+         * @throws LockException
+         * @throws RepositoryException
+         */
+        void lockTokenRemoved(String lockToken) throws LockException, RepositoryException;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url



Mime
View raw message