/*
* 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.version;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ACTIVITY;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ROOTVERSION;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_VERSIONHISTORY;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE;
import java.util.Calendar;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.version.VersionException;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.id.NodeIdFactory;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.state.ChildNodeEntry;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.LocalItemStateManager;
import org.apache.jackrabbit.core.state.NodeReferences;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of the {@link InternalVersionManager} interface.
* <p>
* All read operations must acquire the read lock before reading, all write
* operations must acquire the write lock.
*/
abstract class InternalVersionManagerBase implements InternalVersionManager {
/**
* Logger instance.
*/
private static Logger log = LoggerFactory.getLogger(InternalVersionManagerBase.class);
/**
* State manager for the version storage.
*/
protected LocalItemStateManager stateMgr;
/**
* Node type registry.
*/
protected final NodeTypeRegistry ntReg;
protected final NodeId historiesId;
protected final NodeId activitiesId;
/**
* the lock on this version manager
*/
private final VersioningLock rwLock = new VersioningLock();
private final NodeIdFactory nodeIdFactory;
protected InternalVersionManagerBase(NodeTypeRegistry ntReg,
NodeId historiesId,
NodeId activitiesId,
NodeIdFactory nodeIdFactory) {
this.ntReg = ntReg;
this.historiesId = historiesId;
this.activitiesId = activitiesId;
this.nodeIdFactory = nodeIdFactory;
}
//-------------------------------------------------------< InternalVersionManager >
/**
* {@inheritDoc}
*/
public InternalVersion getVersion(NodeId id) throws RepositoryException {
// lock handling via getItem()
InternalVersion v = (InternalVersion) getItem(id);
if (v == null) {
log.warn("Versioning item not found: " + id);
}
return v;
}
/**
* {@inheritDoc}
*/
public InternalBaseline getBaseline(NodeId id) throws RepositoryException {
// lock handling via getItem()
InternalVersionItem item = getItem(id);
if (item == null) {
log.warn("Versioning item not found: " + id);
} else if (!(item instanceof InternalBaseline)) {
log.warn("Versioning item is not a baseline: " + id);
item = null;
}
return (InternalBaseline) item;
}
/**
* {@inheritDoc}
*/
public InternalActivity getActivity(NodeId id) throws RepositoryException {
// lock handling via getItem()
InternalActivity v = (InternalActivity) getItem(id);
if (v == null) {
log.warn("Versioning item not found: " + id);
}
return v;
}
/**
* {@inheritDoc}
*/
public InternalVersionHistory getVersionHistory(NodeId id)
throws RepositoryException {
// lock handling via getItem()
return (InternalVersionHistory) getItem(id);
}
/**
* {@inheritDoc}
*/
public InternalVersionHistory getVersionHistoryOfNode(NodeId id)
throws RepositoryException {
VersioningLock.ReadLock lock = acquireReadLock();
try {
String uuid = id.toString();
Name name = getName(uuid);
NodeStateEx parent = getParentNode(getHistoryRoot(), uuid, null);
if (parent != null && parent.hasNode(name)) {
NodeStateEx history = parent.getNode(name, 1);
if (history == null) {
throw new InconsistentVersioningState("Unexpected failure to get child node " + name + " on parent node" + parent.getNodeId());
}
return getVersionHistory(history.getNodeId());
} else {
throw new ItemNotFoundException("Version history of node " + id + " not found.");
}
} finally {
lock.release();
}
}
/**
* {@inheritDoc}
*
* Assumes that all versions are stored chronologically below the version
* history and just returns the last one. i.e. currently only works for
* simple versioning.
*/
public InternalVersion getHeadVersionOfNode(NodeId id) throws RepositoryException {
InternalVersionHistory vh = getVersionHistoryOfNode(id);
Name[] names = vh.getVersionNames();
InternalVersion last = vh.getVersion(names[names.length - 1]);
return getVersion(last.getId());
}
//-------------------------------------------------------< implementation >
/**
* Acquires the write lock on this version manager.
* @return returns the write lock
*/
protected VersioningLock.WriteLock acquireWriteLock() {
while (true) {
try {
return rwLock.acquireWriteLock();
} catch (InterruptedException e) {
// ignore
}
}
}
/**
* acquires the read lock on this version manager.
* @return returns the read lock
*/
public VersioningLock.ReadLock acquireReadLock() {
while (true) {
try {
return rwLock.acquireReadLock();
} catch (InterruptedException e) {
// ignore
}
}
}
/**
* returns the id of the version history root node
*
* @return the id of the version history root node
*/
abstract protected NodeStateEx getHistoryRoot();
/**
* returns the id of the activities root node
*
* @return the id of the activities root node
*/
abstract protected NodeStateEx getActivitiesRoot();
/**
* Helper for managing write operations.
*/
private class WriteOperation {
/**
* Flag for successful completion of the write operation.
*/
private boolean success = false;
private final VersioningLock.WriteLock lock;
public WriteOperation(VersioningLock.WriteLock lock) {
this.lock = lock;
}
/**
* Saves the pending operations in the {@link LocalItemStateManager}.
*
* @throws ItemStateException if the pending state is invalid
* @throws RepositoryException if the pending state could not be persisted
*/
public void save() throws ItemStateException, RepositoryException {
stateMgr.update();
success = true;
}
/**
* Closes the write operation. The pending operations are canceled
* if they could not be properly saved. Finally the write lock is
* released.
*/
public void close() {
try {
if (!success) {
// update operation failed, cancel all modifications
stateMgr.cancel();
}
} finally {
lock.release();
}
}
}
/**
* Starts a write operation by acquiring the write lock and setting the
* item state manager to the "edit" state. If something goes wrong, the
* write lock is released and an exception is thrown.
* <p>
* The pattern for using this method and the returned helper instance is:
* <pre>
* WriteOperation operation = startWriteOperation();
* try {
* ...
* operation.save(); // if everything is OK
* ...
* } catch (...) {
* ...
* } finally {
* operation.close();
* }
* </pre>
*
* @return write operation helper
* @throws RepositoryException if the write operation could not be started
*/
private WriteOperation startWriteOperation() throws RepositoryException {
boolean success = false;
VersioningLock.WriteLock lock = acquireWriteLock();
try {
stateMgr.edit();
success = true;
return new WriteOperation(lock);
} catch (IllegalStateException e) {
throw new RepositoryException("Unable to start edit operation.", e);
} finally {
if (!success) {
lock.release();
}
}
}
/**
* Returns information about the version history of the specified node
* or <code>null</code> when unavailable.
*
* @param node node whose version history should be returned
* @return identifiers of the version history and root version nodes
* @throws RepositoryException if an error occurs
*/
public VersionHistoryInfo getVersionHistoryInfoForNode(NodeState node) throws RepositoryException {
VersionHistoryInfo info = null;
VersioningLock.ReadLock lock = acquireReadLock();
try {
String uuid = node.getNodeId().toString();
Name name = getName(uuid);
NodeStateEx parent = getParentNode(getHistoryRoot(), uuid, null);
if (parent != null && parent.hasNode(name)) {
NodeStateEx history = parent.getNode(name, 1);
if (history == null) {
throw new InconsistentVersioningState("Unexpected failure to get child node " + name + " on parent node " + parent.getNodeId());
}
ChildNodeEntry rootv = history.getState().getChildNodeEntry(JCR_ROOTVERSION, 1);
if (rootv == null) {
throw new InconsistentVersioningState("missing child node entry for " + JCR_ROOTVERSION + " on version history node " + history.getNodeId(),
history.getNodeId(), null);
}
info = new VersionHistoryInfo(history.getNodeId(),
rootv.getId());
}
} finally {
lock.release();
}
return info;
}
/**
* {@inheritDoc}
*/
public VersionHistoryInfo getVersionHistory(Session session, NodeState node,
NodeId copiedFrom)
throws RepositoryException {
VersionHistoryInfo info = getVersionHistoryInfoForNode(node);
if (info == null) {
info = createVersionHistory(session, node, copiedFrom);
}
return info;
}
/**
* Creates a new version history. This action is needed either when creating
* a new 'mix:versionable' node or when adding the 'mix:versionable' mixin
* to a node.
*
* @param session repository session
* @param node versionable node state
* @param copiedFrom node id for the jcr:copiedFrom property
* @return identifier of the new version history node
* @throws RepositoryException if an error occurs
* @see #getVersionHistory(Session, NodeState, NodeId)
*/
protected abstract VersionHistoryInfo createVersionHistory(Session session,
NodeState node,
NodeId copiedFrom)
throws RepositoryException;
/**
* Returns the item with the given persistent id. Subclass responsibility.
* <p>
* Please note, that the overridden method must acquire the readlock before
* reading the state manager.
*
* @param id the id of the item
* @return version item
* @throws RepositoryException if an error occurs
*/
protected abstract InternalVersionItem getItem(NodeId id)
throws RepositoryException;
/**
* Return a flag indicating if the item specified exists.
* Subclass responsibility.
* @param id the id of the item
* @return <code>true</code> if the item exists;
* <code>false</code> otherwise
*/
protected abstract boolean hasItem(NodeId id);
/**
* Checks if there are item references (from outside the version storage)
* that reference the given node. Subclass responsibility.
* <p>
* Please note, that the overridden method must acquire the readlock before
* reading the state manager.
*
* @param id the id of the node
* @return <code>true</code> if there are item references from outside the
* version storage; <code>false</code> otherwise.
* @throws RepositoryException if an error occurs while reading from the
* repository.
*/
protected abstract boolean hasItemReferences(NodeId id)
throws RepositoryException;
/**
* Returns the node with the given persistent id. Subclass responsibility.
* <p>
* Please note, that the overridden method must acquire the readlock before
* reading the state manager.
*
* @param id the id of the node
* @throws RepositoryException if an error occurs while reading from the
* repository.
* @return the nodestate for the given id.
*/
protected abstract NodeStateEx getNodeStateEx(NodeId id)
throws RepositoryException;
/**
* Creates a new Version History.
*
* @param node the node for which the version history is to be initialized
* @param copiedFrom node id for the jcr:copiedFrom parameter
* @return the identifiers of the newly created version history and root version
* @throws RepositoryException if an error occurs
*/
NodeStateEx internalCreateVersionHistory(NodeState node, NodeId copiedFrom)
throws RepositoryException {
WriteOperation operation = startWriteOperation();
try {
// create deep path
String uuid = node.getNodeId().toString();
NodeStateEx parent = getParentNode(getHistoryRoot(), uuid, NameConstants.REP_VERSIONSTORAGE);
Name name = getName(uuid);
if (parent.hasNode(name)) {
// already exists
return null;
}
// create new history node in the persistent state
NodeStateEx history =
InternalVersionHistoryImpl.create(this, parent, name, node, copiedFrom);
// end update
operation.save();
log.debug(
"Created new version history " + history.getNodeId()
+ " for " + node + ".");
return history;
} catch (ItemStateException e) {
throw new RepositoryException(e);
} finally {
operation.close();
}
}
/**
* Creates a new activity.
*
* @param title title of the new activity
* @return the id of the newly created activity
* @throws RepositoryException if an error occurs
*/
NodeStateEx internalCreateActivity(String title)
throws RepositoryException {
WriteOperation operation = startWriteOperation();
try {
// create deep path
NodeId activityId = nodeIdFactory.newNodeId();
NodeStateEx parent = getParentNode(getActivitiesRoot(), activityId.toString(), NameConstants.REP_ACTIVITIES);
Name name = getName(activityId.toString());
// create new activity node in the persistent state
NodeStateEx pNode = InternalActivityImpl.create(parent, name, activityId, title);
// end update
operation.save();
log.debug("Created new activity " + activityId
+ " with title " + title + ".");
return pNode;
} catch (ItemStateException e) {
throw new RepositoryException(e);
} finally {
operation.close();
}
}
/**
* Removes the specified activity
*
* @param activity the activity to remove
* @throws javax.jcr.RepositoryException if any other error occurs.
*/
protected void internalRemoveActivity(InternalActivityImpl activity)
throws RepositoryException {
WriteOperation operation = startWriteOperation();
try {
// check if the activity has any references in the workspaces
NodeId nodeId = activity.getId();
if (stateMgr.hasNodeReferences(nodeId)) {
NodeReferences refs = stateMgr.getNodeReferences(nodeId);
if (refs.hasReferences()) {
throw new ReferentialIntegrityException("Unable to delete activity. still referenced.");
}
}
// TODO:
// check if the activity is used in anywhere in the version storage
// and reject removal
// remove activity and possible empty parent directories
NodeStateEx act = getNodeStateEx(nodeId);
NodeId parentId = act.getParentId();
Name name = act.getName();
while (parentId != null) {
NodeStateEx parent = getNodeStateEx(parentId);
parent.removeNode(name);
parent.store();
if (parent.getChildNodes().length == 0 && !parentId.equals(activitiesId)) {
name = parent.getName();
parentId = parent.getParentId();
} else {
parentId = null;
}
}
operation.save();
} catch (ItemStateException e) {
log.error("Error while storing: " + e.toString());
} finally {
operation.close();
}
}
/**
* Utility method that returns the given string as a name in the default
* namespace.
*
* @param name string name
* @return A <code>Name</code> object.
*/
protected static Name getName(String name) {
return NameFactoryImpl.getInstance().create(Name.NS_DEFAULT_URI, name);
}
/**
* Utility method that returns the parent node under which the version
* history of the identified versionable node is or will be stored. If
* the <code>interNT</code> is not <code>null</code> then the returned
* parent node and any ancestor nodes are automatically created if they do
* not already exist. Otherwise
* <code>null</code> is returned if the parent node does not exist.
*
* @param parent the parent node
* @param uuid UUID of a versionable node
* @param interNT intermediate nodetype.
* @return parent node of the version history, or <code>null</code>
* @throws RepositoryException if an error occurs
*/
protected static NodeStateEx getParentNode(NodeStateEx parent, String uuid, Name interNT)
throws RepositoryException {
NodeStateEx n = parent;
for (int i = 0; i < 3; i++) {
Name name = getName(uuid.substring(i * 2, i * 2 + 2));
NodeStateEx childn = n.getNode(name, 1);
if (childn != null) {
n = childn;
} else if (interNT != null) {
childn = n.addNode(name, interNT, null, false);
n.store(false);
childn.store(true);
n = childn;
} else {
return null;
}
}
return n;
}
/**
* Creates a new version of the given node using the given version
* creation time.
*
* @param node the node to be checked in
* @param created version creation time
* @return the new version
* @throws RepositoryException if an error occurs
*/
protected InternalVersion checkin(NodeStateEx node, Calendar created)
throws RepositoryException {
WriteOperation operation = startWriteOperation();
try {
boolean simple =
!node.getEffectiveNodeType().includesNodeType(MIX_VERSIONABLE);
InternalVersionHistoryImpl vh;
if (simple) {
// in simple versioning the history id needs to be calculated
vh = (InternalVersionHistoryImpl) getVersionHistoryOfNode(
node.getNodeId());
} else {
// in full versioning, the history id can be retrieved via
// the property
vh = (InternalVersionHistoryImpl) getVersionHistory(
node.getPropertyValue(JCR_VERSIONHISTORY).getNodeId());
}
InternalVersion version =
internalCheckin(vh, node, simple, created);
operation.save();
return version;
} catch (ItemStateException e) {
throw new RepositoryException(e);
} finally {
operation.close();
}
}
/**
* Checks in a node
*
* @param history the version history
* @param node node to checkin
* @param simple flag indicates simple versioning
* @param created optional created date.
* @return internal version
* @throws javax.jcr.RepositoryException if an error occurs
* @see javax.jcr.Node#checkin()
*/
protected InternalVersion internalCheckin(
InternalVersionHistoryImpl history,
NodeStateEx node, boolean simple, Calendar created)
throws RepositoryException {
String versionName = calculateCheckinVersionName(history, node, simple);
InternalVersionImpl v = history.checkin(
NameFactoryImpl.getInstance().create("", versionName),
node, created);
// check for jcr:activity
if (node.hasProperty(JCR_ACTIVITY)) {
NodeId actId = node.getPropertyValue(JCR_ACTIVITY).getNodeId();
InternalActivityImpl act = (InternalActivityImpl) getItem(actId);
act.addVersion(v);
}
return v;
}
/**
* Calculates the name of the new version that will be created by a
* checkin call. The name is determined as follows:
* <ul>
* <li> first the predecessor version with the shortest name is searched.
* <li> if that predecessor version is the root version, the new version gets
* the name "{number of successors}+1" + ".0"
* <li> if that predecessor version has no successor, the last digit of it's
* version number is incremented.
* <li> if that predecessor version has successors but the incremented name
* does not exist, that name is used.
* <li> otherwise a ".0" is added to the name until a non conflicting name
* is found.
* </ul>
*
* Example Graph:
* <pre>
* jcr:rootVersion
* | |
* 1.0 2.0
* |
* 1.1
* |
* 1.2 ---\ ------\
* | \ \
* 1.3 1.2.0 1.2.0.0
* | |
* 1.4 1.2.1 ----\
* | | \
* 1.5 1.2.2 1.2.1.0
* | | |
* 1.6 | 1.2.1.1
* |-----/
* 1.7
* </pre>
*
* @param history the version history
* @param node the node to checkin
* @param simple if <code>true</code> indicates simple versioning
* @return the new version name
* @throws RepositoryException if an error occurs.
*/
protected String calculateCheckinVersionName(InternalVersionHistoryImpl history,
NodeStateEx node, boolean simple)
throws RepositoryException {
if (history == null) {
String message = "Node " + node.getNodeId() + " has no version history";
log.error(message);
throw new VersionException(message);
}
InternalVersion best = null;
if (simple) {
// 1. in simple versioning just take the 'head' version
Name[] names = history.getVersionNames();
best = history.getVersion(names[names.length - 1]);
} else {
// 1. search a predecessor, suitable for generating the new name
InternalValue[] values = node.getPropertyValues(NameConstants.JCR_PREDECESSORS);
if (values == null || values.length == 0) {
String message;
if (values == null) {
message = "Mandatory jcr:predecessors property missing on node " + node.getNodeId();
} else {
message = "Mandatory jcr:predecessors property is empty on node " + node.getNodeId();
}
log.error(message);
throw new VersionException(message);
}
for (InternalValue value: values) {
InternalVersion pred = history.getVersion(value.getNodeId());
if (pred == null) {
String message = "Could not instantiate InternalVersion for nodeId " + value.getNodeId() + " (VHR + " + history.getId() + ", node " + node.getNodeId() + ")";
log.error(message);
throw new VersionException(message);
}
if (best == null
|| pred.getName().getLocalName().length() < best.getName().getLocalName().length()) {
best = pred;
}
}
}
if (best == null) {
String message = "Could not find 'best' predecessor node for " + node.getNodeId();
log.error(message);
throw new VersionException(message);
}
// 2. generate version name (assume no namespaces in version names)
String versionName = best.getName().getLocalName();
int pos = versionName.lastIndexOf('.');
if (pos > 0) {
String newVersionName = versionName.substring(0, pos + 1)
+ (Integer.parseInt(versionName.substring(pos + 1)) + 1);
while (history.hasVersion(NameFactoryImpl.getInstance().create("", newVersionName))) {
versionName += ".0";
newVersionName = versionName;
}
return newVersionName;
} else {
// best is root version
return String.valueOf(best.getSuccessors().size() + 1) + ".0";
}
}
/**
* Removes the specified version from the history
*
* @param history the version history from where to remove the version.
* @param name the name of the version to remove.
* @throws javax.jcr.version.VersionException if the version <code>history</code> does
* not have a version with <code>name</code>.
* @throws javax.jcr.RepositoryException if any other error occurs.
*/
protected void internalRemoveVersion(InternalVersionHistoryImpl history, Name name)
throws VersionException, RepositoryException {
WriteOperation operation = startWriteOperation();
try {
history.removeVersion(name);
operation.save();
} catch (ItemStateException e) {
log.error("Error while storing: " + e.toString());
} finally {
operation.close();
}
}
/**
* Removes the specified history from the storage
*
* @param history the version history to remove
* @throws VersionException
* @throws RepositoryException
*/
public void internalRemoveVersionHistory(InternalVersionHistoryImpl history)
throws VersionException, RepositoryException {
String versionableUuid = history.getVersionableId().toString();
WriteOperation operation = startWriteOperation();
try {
NodeStateEx parent = getParentNode(getHistoryRoot(), versionableUuid, null);
parent.removeNode(history.node.getName());
parent.store();
operation.save();
} catch (ItemStateException e) {
log.error("Error while storing: " + e.toString());
} finally {
operation.close();
}
}
/**
* Set version label on the specified version.
*
* @param history version history
* @param version version name
* @param label version label
* @param move <code>true</code> to move from existing version;
* <code>false</code> otherwise.
* @return The internal version.
* @throws RepositoryException if an error occurs
*/
protected InternalVersion setVersionLabel(InternalVersionHistoryImpl history,
Name version, Name label,
boolean move)
throws RepositoryException {
WriteOperation operation = startWriteOperation();
try {
InternalVersion v = history.setVersionLabel(version, label, move);
operation.save();
return v;
} catch (ItemStateException e) {
log.error("Error while storing: " + e.toString());
return null;
} finally {
operation.close();
}
}
/**
* Invoked when a new internal item has been created.
* @param version internal version item
*/
protected void versionCreated(InternalVersion version) {
}
/**
* Invoked when a new internal item has been destroyed.
* @param version internal version item
*/
protected void versionDestroyed(InternalVersion version) {
}
/**
* Invoked by the internal version item itself, when it's underlying
* persistence state was discarded.
*
* @param item item that was discarded
*/
protected void itemDiscarded(InternalVersionItem item) {
}
/**
* Creates an {@link InternalVersionItem} based on the {@link NodeState}
* identified by <code>id</code>.
*
* @param id the node id of the version item.
* @return the version item or <code>null</code> if there is no node state
* with the given <code>id</code>.
* @throws RepositoryException if an error occurs while reading from the
* version storage.
*/
protected InternalVersionItem createInternalVersionItem(NodeId id)
throws RepositoryException {
try {
if (stateMgr.hasItemState(id)) {
NodeState state = (NodeState) stateMgr.getItemState(id);
NodeStateEx pNode = new NodeStateEx(stateMgr, ntReg, state, null);
NodeId parentId = pNode.getParentId();
InternalVersionItem parent = getItem(parentId);
Name ntName = state.getNodeTypeName();
if (ntName.equals(NameConstants.NT_FROZENNODE)) {
return new InternalFrozenNodeImpl(this, pNode, parent);
} else if (ntName.equals(NameConstants.NT_VERSIONEDCHILD)) {
return new InternalFrozenVHImpl(this, pNode, parent);
} else if (ntName.equals(NameConstants.NT_VERSION)) {
return ((InternalVersionHistory) parent).getVersion(id);
} else if (ntName.equals(NameConstants.NT_VERSIONHISTORY)) {
return new InternalVersionHistoryImpl(this, pNode);
} else if (ntName.equals(NameConstants.NT_ACTIVITY)) {
return new InternalActivityImpl(this, pNode);
} else {
return null;
}
} else {
return null;
}
} catch (ItemStateException e) {
throw new RepositoryException(e);
}
}
public NodeIdFactory getNodeIdFactory() {
return nodeIdFactory;
}
}