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'sstore 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;
+ }
+ }
+ }
+ }
+}