Index: /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/state/NodeVersionHistoriesUpdatableStateManager.java =================================================================== --- /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/state/NodeVersionHistoriesUpdatableStateManager.java (revision 0) +++ /home/ntoper/workspace/jackrabbit/java/org/apache/jackrabbit/core/state/NodeVersionHistoriesUpdatableStateManager.java (revision 0) @@ -0,0 +1,545 @@ +/* + * 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.core.state; + +import java.util.Iterator; + +import javax.jcr.ReferentialIntegrityException; + +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.name.QName; + +import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock; +import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock; + +/** + * Allow to update a version history. Not thread-safe. You need to restart the + * repository after altering its state. + * + */ +public class NodeVersionHistoriesUpdatableStateManager implements UpdatableItemStateManager { + + /** + * editMode activated or not. + */ + private boolean editMode; + + /** + * ChangeLog of the updates. + */ + private ChangeLog changeLog; + + /** + * The sharedItemStateManager. + */ + private SharedItemStateManager shMgr; + + /** + * This lock is acquired before any commit of + * updates. + */ + private final ReadWriteLock rwLock = + new ReentrantWriterPreferenceReadWriteLock() { + /** + * Allow reader when there is no active writer, or current + * thread owns the write lock (reentrant). + *

+ * the 'noLockHack' is only temporary (hopefully) + */ + protected boolean allowReader() { + return activeWriter_ == null + || activeWriter_ == Thread.currentThread(); + } + }; + + /** + * The PM where the changes are persisted. + */ + private PersistenceManager persistMgr; + + /** + * Is the lock in use? + */ + private boolean holdingWriteLock; + + /** + * Acquires the read lock on this item state manager. + * + * @throws ItemStateException if the read lock cannot be acquired. + */ + private void acquireReadLock() throws ItemStateException { + try { + rwLock.readLock().acquire(); + } catch (InterruptedException e) { + throw new ItemStateException("Interrupted while acquiring read lock"); + } + } + + /** + * Acquires the write lock on this item state manager. + * + * @throws ItemStateException if the write lock cannot be acquired. + */ + private void acquireWriteLock() throws ItemStateException { + try { + rwLock.writeLock().acquire(); + } catch (InterruptedException e) { + throw new ItemStateException("Interrupted while acquiring write lock"); + } + } + + /** + * Constructor of the class. + * @param shMgr SharedItemStateManager + * @param pMgr PersistenceManager + */ + public NodeVersionHistoriesUpdatableStateManager(SharedItemStateManager shMgr, PersistenceManager pMgr) { + this.shMgr = shMgr; + this.changeLog = new ChangeLog(); + this.persistMgr = pMgr; + } + + /** + * Activate the edit mode. + * + * @throws IllegalStateException in case of issue + */ + public void edit() throws IllegalStateException { + if (editMode) { + throw new IllegalStateException("Already in edit mode"); + } + changeLog.reset(); + editMode = true; + } + + /** + * Check whether we are or not in edit mode + * @return true if we are. Otherwise false. + */ + public boolean inEditMode() { + return editMode; + } + + /** + * Create a new NodeState + * @param id NodeId + * @param nodeTypeName QName + * @param parentId NodeId + * + * @throws IllegalStateException if any issue such as not in edit mode + * + * @return the newly created NodeState + */ + public NodeState createNew(NodeId id, QName nodeTypeName, NodeId parentId) throws IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + + NodeState state = new NodeState(id, nodeTypeName, parentId, + ItemState.STATUS_NEW, false); + changeLog.added(state); + return state; + } + + /** + * Create a new PropertyState + * + * @param propName the QName of the property + * @param parentId the NodeId of the Node + * @throws IllegalStateException if any issue such as not in edit mode + * @return the newly created PropertyState + */ + public PropertyState createNew(QName propName, NodeId parentId) throws IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + PropertyState state = new PropertyState( + new PropertyId(parentId, propName), ItemState.STATUS_NEW, false); + changeLog.added(state); + return state; + } + /** + * {@inheritDoc} + */ + public void store(ItemState state) throws IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + changeLog.modified(state); + } + + /** + * {@inheritDoc} + */ + public void destroy(ItemState state) throws IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + changeLog.deleted(state); + } + + /** + * {@inheritDoc} + */ + public void cancel() throws IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + changeLog.undo(this.shMgr); + editMode = false; + } + + /** + * {@inheritDoc} + */ + public void update() throws ReferentialIntegrityException, StaleItemStateException, ItemStateException, IllegalStateException { + if (!editMode) { + throw new IllegalStateException("Not in edit mode"); + } + + beginUpdate().end(); + changeLog.persisted(); + changeLog.reset(); + editMode = false; + } + /** + * Create an Update object. Used to persist updates. Launch the update. + * @throws ReferentialIntegrityException if the update breaks referential integrity. + * @throws StaleItemStateException if the update cannot take place. + * @throws ItemStateException if the update cannot take place. + * + * @return Update instantiated object. + */ + public Update beginUpdate() + throws ReferentialIntegrityException, StaleItemStateException, + ItemStateException { + + Update update = new Update(this.changeLog); + update.begin(); + return update; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + /** + * {@inheritDoc} + */ + public ItemState getItemState(ItemId id) throws NoSuchItemStateException, ItemStateException { + // check change log + ItemState state = changeLog.get(id); + if (state != null) { + return state; + } + + // regular behaviour + if (id.denotesNode()) { + state = getNodeState((NodeId) id); + } else { + state = getPropertyState((PropertyId) id); + } + return state; + } + + private NodeState getNodeState(NodeId id) throws NoSuchItemStateException, ItemStateException { + // load from parent manager and wrap + NodeState state = (NodeState) this.shMgr.getItemState(id); + state = new NodeState(state, state.getStatus(), false); + return state; + } + /** + * Load item state from persistent storage. + * + * @param id item id + * @return item state + */ + private ItemState loadItemState(ItemId id) + throws NoSuchItemStateException, ItemStateException { + + ItemState state; + if (id.denotesNode()) { + state = persistMgr.load((NodeId) id); + } else { + state = persistMgr.load((PropertyId) id); + } + return state; + } + + private PropertyState getPropertyState(PropertyId id) throws NoSuchItemStateException, ItemStateException { + // load from parent manager and wrap + PropertyState state = (PropertyState) this.shMgr.getItemState(id); + state = new PropertyState(state, state.getStatus(), false); + return state; + } + + /** + * {@inheritDoc} + */ + public boolean hasItemState(ItemId id) { + // check items in change log + try { + ItemState state = changeLog.get(id); + if (state != null) { + return true; + } + } catch (NoSuchItemStateException e) { + return false; + } + + // regular behaviour + return this.shMgr.hasItemState(id); + } + + /** + * {@inheritDoc} + */ + public NodeReferences getNodeReferences(NodeReferencesId id) throws NoSuchItemStateException, ItemStateException { + // check change log + NodeReferences refs = changeLog.get(id); + if (refs != null) { + return refs; + } else { + return this.shMgr.getNodeReferences(id); + } + } + + /** + * Create a new node state instance + * + * @param other other state associated with new instance + * @return new node state instance + */ + private ItemState createInstance(ItemState other) { + if (other.isNode()) { + NodeState ns = (NodeState) other; + return createInstance(ns.getNodeId(), ns.getNodeTypeName(), ns.getParentId()); + } else { + PropertyState ps = (PropertyState) other; + return createInstance(ps.getName(), ps.getParentId()); + } + } + + /** + * Create a new property state instance + * + * @param propName property name + * @param parentId parent Id + * @return new property state instance + */ + private PropertyState createInstance(QName propName, NodeId parentId) { + PropertyState state = persistMgr.createNew(new PropertyId(parentId, propName)); + state.setStatus(ItemState.STATUS_NEW); + return state; + } + + /** + * Create a new node state instance + * + * @param id uuid + * @param nodeTypeName node type name + * @param parentId parent UUID + * @return new node state instance + */ + private NodeState createInstance(NodeId id, QName nodeTypeName, + NodeId parentId) { + NodeState state = persistMgr.createNew(id); + state.setNodeTypeName(nodeTypeName); + state.setParentId(parentId); + state.setStatus(ItemState.STATUS_NEW); + + return state; + } + /** + * Check whether this NodeReferencesId id is referenced + * + * @param id NodeReferencesId + * + * @return true if it has; false otherwise. + */ + public boolean hasNodeReferences(NodeReferencesId id) { + // check change log + if (changeLog.get(id) != null) { + return true; + } + return this.shMgr.hasNodeReferences(id); + } + + /** + * Object representing a single update operation. + */ + class Update { + + /** + * Local change log. + */ + private final ChangeLog local; + + /** + * Shared change log. + */ + private ChangeLog shared; + + /** + * Flag indicating whether we are holding write lock. + */ + private boolean holdingWriteLock; + + /** + * Create a new instance of this class. + * + * @param local Changelog to apply + */ + public Update(ChangeLog local) { + this.local = local; + } + + /** + * Begin update operation. Prepares everything upto the point where + * the persistence manager's store method may be invoked. + * If this method succeeds, a write lock will have been acquired on the + * item state manager and either {@link #end()} or {@link #cancel()} has + * to be called in order to release it. + */ + public void begin() throws ItemStateException, ReferentialIntegrityException { + shared = new ChangeLog(); + acquireWriteLock(); + holdingWriteLock = true; + boolean succeeded = false; + + try { + /** + * Reconnect all items contained in the change log to their + * respective shared item and add the shared items to a + * new change log. + */ + for (Iterator iter = local.modifiedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + state.connect(getItemState(state.getId())); + if (state.isStale()) { + String msg = state.getId() + " has been modified externally"; + throw new StaleItemStateException(msg); + } + + // update modification count (will be persisted as well) + state.getOverlayedState().touch(); + shared.modified(state.getOverlayedState()); + } + for (Iterator iter = local.deletedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + state.connect(getItemState(state.getId())); + if (state.isStale()) { + String msg = state.getId() + " has been modified externally"; + throw new StaleItemStateException(msg); + } + shared.deleted(state.getOverlayedState()); + } + for (Iterator iter = local.addedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + state.connect(createInstance(state)); + shared.added(state.getOverlayedState()); + } + + /* Push all changes from the local items to the shared items */ + local.push(); + succeeded = true; + } finally { + if (!succeeded) { + cancel(); + } + } + } + + /** + * End update operation. This will store the changes to the associated + * PersistenceManager. At the end of this operation, an + * eventual read or write lock on the item state manager will have + * been released. + * @throws ItemStateException if some error occurs + */ + public void end() throws ItemStateException { + boolean succeeded = false; + + try { + /* Store items in the underlying persistence manager */ + persistMgr.store(shared); + succeeded = true; + } finally { + if (!succeeded) { + cancel(); + } + } + + try { + /* Let the shared item listeners know about the change */ + shared.persisted(); + // downgrade to read lock + acquireReadLock(); + rwLock.writeLock().release(); + holdingWriteLock = false; + } finally { + if (holdingWriteLock) { + // exception occured before downgrading lock + rwLock.writeLock().release(); + holdingWriteLock = false; + } else { + rwLock.readLock().release(); + } + } + } + + /** + * Cancel update operation. At the end of this operation, the write lock + * on the item state manager will have been released. + */ + public void cancel() { + try { + local.disconnect(); + + for (Iterator iter = shared.modifiedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + try { + state.copy(loadItemState(state.getId())); + } catch (ItemStateException e) { + state.discard(); + } + } + for (Iterator iter = shared.deletedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + try { + state.copy(loadItemState(state.getId())); + } catch (ItemStateException e) { + state.discard(); + } + } + for (Iterator iter = shared.addedStates(); iter.hasNext();) { + ItemState state = (ItemState) iter.next(); + state.discard(); + } + } finally { + if (holdingWriteLock) { + rwLock.writeLock().release(); + holdingWriteLock = false; + } + } + } + } +}