Index: modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (revision 1692) +++ modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (working copy) @@ -808,6 +808,13 @@ public class GraphSession { } }); + root.onChangedNodes(new LoadAllChildrenVisitor() { + @Override + protected void finishParentAfterLoading( Node node ) { + nodeOperations.compute(operations, node); + } + }); + // Execute the batched operations ... try { operations.execute(); @@ -894,9 +901,17 @@ public class GraphSession { } } if (branchRequests.isEmpty()) return; - + // Now execute the branch ... - Graph.Batch branchBatch = store.batch(new BatchRequestBuilder(branchRequests)); + final Graph.Batch branchBatch = store.batch(new BatchRequestBuilder(branchRequests)); + + node.onChangedNodes(new LoadAllChildrenVisitor() { + @Override + protected void finishParentAfterLoading( Node node ) { + nodeOperations.compute(branchBatch, node); + } + }); + try { branchBatch.execute(); } catch (org.modeshape.graph.property.PathNotFoundException e) { @@ -1115,6 +1130,14 @@ public class GraphSession { * @throws ValidationException if there is a problem during validation */ void preSave( Node node ) throws ValidationException; + + /** + * Updated any computed fields based on the given node + * + * @param batch the workspace graph batch in which computed fields should be created + * @param node the node form which computed fields will be derived + */ + void compute( Graph.Batch batch, Node node ); } @ThreadSafe @@ -1211,6 +1234,15 @@ public class GraphSession { /** * {@inheritDoc} * + * @see GraphSession.Operations#compute(Graph.Batch, GraphSession.Node) + */ + public void compute( Graph.Batch batch, Node node ) { + // do nothing here + } + + /** + * {@inheritDoc} + * * @see org.modeshape.graph.session.GraphSession.Operations#preSetProperty(Node, Name, PropertyInfo) */ public void preSetProperty( Node node, Index: modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrItem.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrItem.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrItem.java (working copy) @@ -75,6 +75,10 @@ abstract class AbstractJcrItem implements Item { return context().getValueFactories().getPathFactory().createSegment(segment); } + final Path.Segment segmentFrom( Name segment ) { + return context().getValueFactories().getPathFactory().createSegment(segment); + } + final NamespaceRegistry namespaces() { return context().getNamespaceRegistry(); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (working copy) @@ -660,7 +660,7 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node * @throws IllegalArgumentException if relativePath is empty or null. * @see javax.jcr.Node#getNode(java.lang.String) */ - public final javax.jcr.Node getNode( String relativePath ) throws RepositoryException { + public final AbstractJcrNode getNode( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); if (relativePath.equals(".")) return this; if (relativePath.equals("..")) return this.getParent(); @@ -681,7 +681,7 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node if (path.size() > 1) { AbstractJcrItem item = cache.findJcrNode(nodeId, location.getPath(), path); if (item instanceof javax.jcr.Node) { - return (javax.jcr.Node)item; + return (AbstractJcrNode)item; } I18n msg = JcrI18n.nodeNotFoundAtPathRelativeToReferenceNode; throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName())); @@ -790,17 +790,6 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node CheckArg.isNotNull(mixinName, "mixinName"); CheckArg.isNotZeroLength(mixinName, "mixinName"); - /* - * Special workaround for SeralizationTest (and others) in JR TCK that incorrectly test whether a repository supports - * versioning by trying to add mix:versionable to a node. The 1.0.1 says in section 4.11 that: - * "A node is versionable if and only if it has been assigned the mixin type mix:versionable, - * otherwise it is nonversionable. Repositories that do not support versioning will simply not - * provide this mixin type, whereas repositories that do support versioning must provide it." - */ - if (JcrMixLexicon.VERSIONABLE.getString(namespaces()).equals(mixinName)) { - return false; - } - JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName); if (this.isLocked()) { @@ -1371,23 +1360,54 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node } /** + * Throw an {@link UnsupportedRepositoryOperationException} if this node is not versionable (i.e., + * isNodeType(JcrMixLexicon.VERSIONABLE) == false). + * @throws UnsupportedRepositoryOperationException if !isNodeType({@link JcrMixLexicon#VERSIONABLE}) + * @throws RepositoryException if an error occurs reading the node types for this node + */ + private void checkVersionable() throws UnsupportedRepositoryOperationException, RepositoryException { + if (!isNodeType(JcrMixLexicon.VERSIONABLE)) { + throw new UnsupportedRepositoryOperationException("TODO: Add message"); + } + } + + /** * {@inheritDoc} * - * @return false * @see javax.jcr.Node#isCheckedOut() */ - public final boolean isCheckedOut() { - return false; + public final boolean isCheckedOut() throws RepositoryException { + for (AbstractJcrNode node = this; !node.isRoot(); node = node.getParent()) { + if (node.isNodeType(JcrMixLexicon.VERSIONABLE)) { + return getProperty(JcrLexicon.IS_CHECKED_OUT).getBoolean(); + } + } + + return true; } /** * {@inheritDoc} * - * @throws UnsupportedRepositoryOperationException always * @see javax.jcr.Node#checkin() */ - public final Version checkin() throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); + public final Version checkin() throws UnsupportedRepositoryOperationException, RepositoryException { + checkVersionable(); + + if (isNew() || isModified()) { + throw new InvalidItemStateException(); + } + + Property isCheckedOut = getProperty(JcrLexicon.IS_CHECKED_OUT); + + if (!getProperty(JcrLexicon.IS_CHECKED_OUT).getBoolean()) { + return getBaseVersion(); + } + + getProperty(JcrLexicon.IS_CHECKED_OUT).setValue(false); + save(); + + return getBaseVersion(); } /** @@ -1396,13 +1416,112 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node * @throws UnsupportedRepositoryOperationException always * @see javax.jcr.Node#checkout() */ - public final void checkout() throws UnsupportedRepositoryOperationException { + public final void checkout() throws UnsupportedRepositoryOperationException, RepositoryException { + checkVersionable(); + + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always + * @see javax.jcr.Node#merge(java.lang.String, boolean) + */ + public final NodeIterator merge( String srcWorkspace, + boolean bestEffort ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always + * @see javax.jcr.Node#cancelMerge(javax.jcr.version.Version) + */ + public final void cancelMerge( Version version ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always + * @see javax.jcr.Node#doneMerge(javax.jcr.version.Version) + */ + public final void doneMerge( Version version ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.Node#getVersionHistory() + */ + public final VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException { + checkVersionable(); + + return new JcrVersionHistoryNode(session().getNodeByUUID(getProperty(JcrLexicon.VERSION_HISTORY).getString())); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.Node#getBaseVersion() + */ + public final Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException { + checkVersionable(); + + return new JcrVersionNode(session().getNodeByUUID(getProperty(JcrLexicon.BASE_VERSION).getString())); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedRepositoryOperationException always + * @see javax.jcr.Node#restore(java.lang.String, boolean) + */ + public final void restore( String versionName, + boolean removeExisting ) throws UnsupportedRepositoryOperationException { throw new UnsupportedRepositoryOperationException(); } /** * {@inheritDoc} * + * @throws UnsupportedRepositoryOperationException always + * @see javax.jcr.Node#restore(javax.jcr.version.Version, boolean) + */ + public final void restore( Version version, + boolean removeExisting ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedRepositoryOperationException always + * @see javax.jcr.Node#restore(javax.jcr.version.Version, java.lang.String, boolean) + */ + public final void restore( Version version, + String relPath, + boolean removeExisting ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedRepositoryOperationException always + * @see javax.jcr.Node#restoreByLabel(java.lang.String, boolean) + */ + public final void restoreByLabel( String versionLabel, + boolean removeExisting ) throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); + } + + /** + * {@inheritDoc} + * * @return false * @see javax.jcr.Node#holdsLock() */ @@ -1546,37 +1665,6 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node /** * {@inheritDoc} * - * @throws UnsupportedOperationException always - * @see javax.jcr.Node#merge(java.lang.String, boolean) - */ - public final NodeIterator merge( String srcWorkspace, - boolean bestEffort ) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException always - * @see javax.jcr.Node#cancelMerge(javax.jcr.version.Version) - */ - public final void cancelMerge( Version version ) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException always - * @see javax.jcr.Node#doneMerge(javax.jcr.version.Version) - */ - public final void doneMerge( Version version ) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * * @see javax.jcr.Node#getCorrespondingNodePath(java.lang.String) */ public final String getCorrespondingNodePath( String workspaceName ) @@ -1632,71 +1720,6 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node * {@inheritDoc} * * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#getVersionHistory() - */ - public final VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#getBaseVersion() - */ - public final Version getBaseVersion() throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#restore(java.lang.String, boolean) - */ - public final void restore( String versionName, - boolean removeExisting ) throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#restore(javax.jcr.version.Version, boolean) - */ - public final void restore( Version version, - boolean removeExisting ) throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#restore(javax.jcr.version.Version, java.lang.String, boolean) - */ - public final void restore( Version version, - String relPath, - boolean removeExisting ) throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always - * @see javax.jcr.Node#restoreByLabel(java.lang.String, boolean) - */ - public final void restoreByLabel( String versionLabel, - boolean removeExisting ) throws UnsupportedRepositoryOperationException { - throw new UnsupportedRepositoryOperationException(); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedRepositoryOperationException always * @see javax.jcr.Node#orderBefore(java.lang.String, java.lang.String) */ public final void orderBefore( String srcChildRelPath, Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (working copy) @@ -62,6 +62,7 @@ public class JcrLexicon extends org.modeshape.graph.JcrLexicon { public static final Name VERSIONABLE_UUID = new BasicName(Namespace.URI, "versionableUuid"); public static final Name VERSION_HISTORY = new BasicName(Namespace.URI, "versionHistory"); public static final Name VERSION_LABELS = new BasicName(Namespace.URI, "versionLabels"); + public static final Name VERSION_STORAGE = new BasicName(Namespace.URI, "versionStorage"); public static final Name XMLTEXT = new BasicName(Namespace.URI, "xmltext"); public static final Name XMLCHARACTERS = new BasicName(Namespace.URI, "xmlcharacters"); Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java (working copy) @@ -38,7 +38,7 @@ import org.modeshape.graph.session.GraphSession.NodeId; * @see JcrRootNode */ @NotThreadSafe -final class JcrNode extends AbstractJcrNode { +class JcrNode extends AbstractJcrNode { JcrNode( SessionCache cache, NodeId nodeId, @@ -52,7 +52,7 @@ final class JcrNode extends AbstractJcrNode { * @see org.modeshape.jcr.AbstractJcrNode#isRoot() */ @Override - boolean isRoot() { + final boolean isRoot() { return false; } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -452,7 +452,7 @@ public class JcrRepository implements Repository { modifiableDescriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, "true"); modifiableDescriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, "false"); // not JCR 1.0 SQL modifiableDescriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, "false"); - modifiableDescriptors.put(Repository.OPTION_VERSIONING_SUPPORTED, "false"); + modifiableDescriptors.put(Repository.OPTION_VERSIONING_SUPPORTED, "true"); modifiableDescriptors.put(Repository.QUERY_XPATH_DOC_ORDER, "false"); // see MODE-613 if (!modifiableDescriptors.containsKey(Repository.QUERY_XPATH_POS_INDEX)) { // don't override what was supplied ... @@ -469,7 +469,7 @@ public class JcrRepository implements Repository { modifiableDescriptors.put(Repository.REP_VENDOR_URL_DESC, "http://www.modeshape.org"); } if (!modifiableDescriptors.containsKey(Repository.REP_VERSION_DESC)) { - modifiableDescriptors.put(Repository.REP_VERSION_DESC, "0.4"); + modifiableDescriptors.put(Repository.REP_VERSION_DESC, "1.1"); } modifiableDescriptors.put(Repository.SPEC_NAME_DESC, JcrI18n.SPEC_NAME_DESC.text()); modifiableDescriptors.put(Repository.SPEC_VERSION_DESC, "1.0"); @@ -676,6 +676,11 @@ public class JcrRepository implements Repository { Property systemPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.SYSTEM); systemGraph.create(systemPath, systemPrimaryType).ifAbsent().and(); + // Make sure the required jcr:versionStorage node exists... + Path versionPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.VERSION_STORAGE); + Property versionPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.VERSION_STORAGE); + systemGraph.create(versionPath, versionPrimaryType).ifAbsent().and(); + // Right now, the other nodes will be created as needed } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (working copy) @@ -568,7 +568,7 @@ class JcrSession implements Session { * * @see javax.jcr.Session#getNodeByUUID(java.lang.String) */ - public Node getNodeByUUID( String uuid ) throws ItemNotFoundException, RepositoryException { + public AbstractJcrNode getNodeByUUID( String uuid ) throws ItemNotFoundException, RepositoryException { return cache.findJcrNode(Location.create(UUID.fromString(uuid))); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionHistoryNode.java new file mode 100644 =================================================================== --- /dev/null (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionHistoryNode.java (working copy) @@ -0,0 +1,275 @@ +package org.modeshape.jcr; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import javax.jcr.AccessDeniedException; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.ReferentialIntegrityException; +import javax.jcr.RepositoryException; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.version.Version; +import javax.jcr.version.VersionException; +import javax.jcr.version.VersionHistory; +import javax.jcr.version.VersionIterator; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path.Segment; + +/** + * Convenience wrapper around a version history {@link JcrNode node}. + * + */ +public class JcrVersionHistoryNode extends JcrNode implements VersionHistory { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public JcrVersionHistoryNode( AbstractJcrNode node ) { + super(node.cache, node.nodeId, node.location); + + assert !node.isRoot() : "Version histories should always be located in the /jcr:system/jcr:versionStorage subgraph"; + } + + /** + * @return a reference to the {@code jcr:versionLabels} child node of this history node. + * @throws RepositoryException if an error occurs accessing this node + */ + private AbstractJcrNode versionLabels() throws RepositoryException { + Segment segment = segmentFrom(JcrLexicon.VERSION_LABELS); + return nodeInfo().getChild(segment).getPayload().getJcrNode(); + } + + @Override + public VersionIterator getAllVersions() throws RepositoryException { + return new JcrVersionIterator(getNodes()); + } + + @Override + public Version getRootVersion() throws RepositoryException { + // Copied from AbstractJcrNode.getNode(String) to avoid double conversion. Needs to be refactored. + Segment segment = context().getValueFactories().getPathFactory().createSegment(JcrLexicon.ROOT_VERSION); + try { + return new JcrVersionNode(nodeInfo().getChild(segment).getPayload().getJcrNode()); + } catch (org.modeshape.graph.property.PathNotFoundException e) { + String msg = JcrI18n.childNotFoundUnderNode.text(segment, getPath(), cache.workspaceName()); + throw new PathNotFoundException(msg); + } catch (RepositorySourceException e) { + throw new RepositoryException(e.getLocalizedMessage(), e); + } + } + + @Override + public Version getVersion( String versionName ) throws VersionException, RepositoryException { + AbstractJcrNode version = getNode(versionName); + if (version == null) return null; + + return new JcrVersionNode(version); + } + + @Override + public Version getVersionByLabel( String label ) throws VersionException, RepositoryException { + Property prop = versionLabels().getProperty(label); + if (prop == null) throw new VersionException(); + + AbstractJcrNode version = session().getNodeByUUID(prop.getString()); + + assert version != null; + + return new JcrVersionNode(version); + } + + @Override + public String[] getVersionLabels() throws RepositoryException { + PropertyIterator iter = versionLabels().getProperties(); + + String[] labels = new String[(int) iter.getSize()]; + for ( int i = 0; iter.hasNext(); i++) { + labels[i] = iter.nextProperty().getName(); + } + + return labels; + } + + /** + * Returns the version labels that point to the given version + * @param version the version for which the labels should be retrieved + * @return the version labels for that version + * @throws RepositoryException if an error occurs accessing the repository + */ + private Collection versionLabelsFor(Version version) throws RepositoryException { + String versionUuid = version.getUUID(); + + PropertyIterator iter = versionLabels().getProperties(); + + List labels = new LinkedList(); + for ( int i = 0; iter.hasNext(); i++) { + Property prop = iter.nextProperty(); + + if (versionUuid.equals(prop.getString())) { + labels.add(prop.getName()); + } + } + + return labels; + } + + @Override + public String[] getVersionLabels( Version version ) throws RepositoryException { + return versionLabelsFor(version).toArray(EMPTY_STRING_ARRAY); + } + + @Override + public String getVersionableUUID() throws RepositoryException { + return getProperty(JcrLexicon.VERSIONABLE_UUID).getString(); + } + + @Override + public boolean hasVersionLabel( String label ) throws RepositoryException { + return versionLabels().hasProperty(label); + } + + @Override + public boolean hasVersionLabel( Version version, + String label ) throws RepositoryException { + Collection labels = versionLabelsFor(version); + + return labels.contains(label); + } + + @Override + public void removeVersion( String versionName ) + throws ReferentialIntegrityException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, + RepositoryException { + // TODO Auto-generated method stub + + } + + @Override + public void addVersionLabel( String versionName, + String label, + boolean moveLabel ) throws VersionException, RepositoryException { + AbstractJcrNode versionLabels = versionLabels(); + Property prop = versionLabels.getProperty(label); + Version version = getVersion(versionName); + + if (prop != null) { + if (!moveLabel) throw new VersionException(); + + prop.setValue(version); + } + else { + versionLabels.setProperty(label, version); + } + } + + @Override + public void removeVersionLabel( String label ) throws VersionException, RepositoryException { + Property prop = versionLabels().getProperty(label); + + if (prop == null) { + throw new VersionException(); + } + + prop.remove(); + } + + /** + * Iterator over the versions within a version history. + * This class wraps the {@link JcrChildNodeIterator node iterator} for all nodes of the {@link JcrVersionHistoryNode version history}, + * silently ignoring the {@code jcr:rootVersion} and {@code jcr:versionLabels} children. + * + */ + class JcrVersionIterator implements VersionIterator { + + private final NodeIterator nodeIterator; + private Version next; + private int position = 0; + + public JcrVersionIterator( NodeIterator nodeIterator ) { + super(); + this.nodeIterator = nodeIterator; + } + + @Override + public Version nextVersion() { + Version next = this.next; + + if (next != null) { + this.next = null; + return next; + } + + next = nextVersionIfPossible(); + + if (next == null) { + throw new NoSuchElementException(); + } + + position++; + return next; + } + + private JcrVersionNode nextVersionIfPossible() { + while (nodeIterator.hasNext()) { + AbstractJcrNode node = (AbstractJcrNode)nodeIterator.nextNode(); + + Name nodeName; + try { + nodeName = node.name(); + } catch (RepositoryException re) { + throw new IllegalStateException(re); + } + + if (!JcrLexicon.VERSION_LABELS.equals(nodeName)) { + return new JcrVersionNode(node); + } + } + + return null; + } + + @Override + public long getPosition() { + return position; + } + + @Override + public long getSize() { + // The number of version nodes is the number of child nodes of the version history - 1 + // (the jcr:versionLabels node) + return nodeIterator.getSize() - 1; + } + + @Override + public void skip( long count ) { + // Walk through the list to make sure that we don't accidentally count jcr:rootVersion or jcr:versionLabels as a + // skipped node + while (count-- > 0) { + nextVersion(); + } + } + + @Override + public boolean hasNext() { + if (this.next != null) return true; + + this.next = nextVersionIfPossible(); + + return this.next != null; + } + + @Override + public Object next() { + return nextVersion(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionNode.java new file mode 100644 =================================================================== --- /dev/null (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionNode.java (working copy) @@ -0,0 +1,69 @@ +package org.modeshape.jcr; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.version.Version; +import org.modeshape.graph.property.Name; + +public class JcrVersionNode extends JcrNode implements Version { + + private static final Version[] EMPTY_VERSION_ARRAY = new Version[0]; + + + public JcrVersionNode( AbstractJcrNode node ) { + super(node.cache, node.nodeId, node.location); + + assert !node.isRoot() : "Versions should always be located in the /jcr:system/jcr:versionStorage subgraph"; + } + + @Override + public JcrVersionHistoryNode getContainingHistory() throws RepositoryException { + return new JcrVersionHistoryNode(getParent()); + } + + @Override + public Calendar getCreated() throws RepositoryException { + return getProperty(JcrLexicon.CREATED).getDate(); + } + + @Override + public Version[] getPredecessors() throws RepositoryException { + return getNodesForProperty(JcrLexicon.PREDECESSORS); + } + + /** + * Returns the successor versions of this version. This corresponds to returning all the nt:version nodes referenced by the + * jcr:successors multi-value property in the nt:version node that represents this version. + */ + @Override + public Version[] getSuccessors() throws RepositoryException { + return getNodesForProperty(JcrLexicon.SUCCESSORS); + } + + private final Version[] getNodesForProperty(Name propertyName) throws RepositoryException { + assert JcrLexicon.SUCCESSORS.equals(propertyName) || JcrLexicon.PREDECESSORS.equals(propertyName); + + Property references = getProperty(propertyName); + + if (references == null) return EMPTY_VERSION_ARRAY; + + Value[] values = references.getValues(); + + List versions = new ArrayList(values.length); + + for (int i = 0; i < values.length; i++) { + String uuid = values[i].getString(); + + AbstractJcrNode node = session().getNodeByUUID(uuid); + versions.add(new JcrVersionNode(node)); + } + + return versions.toArray(EMPTY_VERSION_ARRAY); + + } + +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeLexicon.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeLexicon.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/ModeShapeLexicon.java (working copy) @@ -46,5 +46,6 @@ public class ModeShapeLexicon extends org.modeshape.repository.ModeShapeLexicon public static final Name REPOSITORIES = new BasicName(Namespace.URI, "repositories"); public static final Name SYSTEM = new BasicName(Namespace.URI, "system"); public static final Name URI = new BasicName(Namespace.URI, "uri"); + public static final Name VERSION_STORAGE = new BasicName(Namespace.URI, "versionStorage"); public static final Name WORKSPACE = new BasicName(Namespace.URI, "workspace"); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (revision 1692) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (working copy) @@ -57,7 +57,9 @@ import org.modeshape.common.util.Logger; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; import org.modeshape.graph.Location; +import org.modeshape.graph.Graph.Batch; import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.DateTime; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.NamespaceRegistry; @@ -739,6 +741,10 @@ class SessionCache { return isNodeType(node, JcrMixLexicon.REFERENCEABLE); } + public boolean isVersionable( Node node ) throws RepositoryException { + return isNodeType(node, JcrMixLexicon.VERSIONABLE); + } + /** * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties or children on the node identified by the * supplied identifier and path. The node must exist prior to this call, either as a node that exists in the workspace or as a @@ -1259,8 +1265,8 @@ class SessionCache { // The node definition changed, so try to set the property ... NodeEditor newChildEditor = getEditorFor(existingChild); try { - JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, defn.getId() - .getString()); + JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, + defn.getId().getString()); newChildEditor.setProperty(ModeShapeIntLexicon.NODE_DEFINITON, value); } catch (ConstraintViolationException e) { // We can't set this property on the node (according to the node definition). @@ -1436,8 +1442,8 @@ class SessionCache { // Create the initial properties ... Property primaryTypeProp = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName); - Property nodeDefinitionProp = propertyFactory.create(ModeShapeIntLexicon.NODE_DEFINITON, definition.getId() - .getString()); + Property nodeDefinitionProp = propertyFactory.create(ModeShapeIntLexicon.NODE_DEFINITON, + definition.getId().getString()); // Now add the "jcr:uuid" property if and only if referenceable ... Node result = null; @@ -2362,6 +2368,7 @@ class SessionCache { List mixinTypeNames = payload.getMixinTypeNames(); Set satisfiedChildNodes = new HashSet(); Set satisfiedProperties = new HashSet(); + UUID jcrUuid = null; // Is this node referenceable ... boolean referenceable = false; @@ -2371,7 +2378,11 @@ class SessionCache { throw new ValidationException(e.getLocalizedMessage()); } for (org.modeshape.graph.session.GraphSession.PropertyInfo property : node.getProperties()) { - if (property.getName().equals(JcrLexicon.UUID) && !referenceable) continue; + // Save the UUID in case the node is versionable + if (property.getName().equals(JcrLexicon.UUID) && !referenceable) { + jcrUuid = (UUID)property.getProperty().getFirstValue(); + continue; + } JcrPropertyPayload propPayload = property.getPayload(); JcrPropertyDefinition definition = findBestPropertyDefintion(primaryTypeName, mixinTypeNames, @@ -2454,6 +2465,85 @@ class SessionCache { } } + @Override + public void compute( Graph.Batch batch, + Node node ) { + try { + initializeVersionHistoryFor(batch, node); + } catch (RepositoryException re) { + throw new IllegalStateException(re); + } + } + + private void initializeVersionHistoryFor( Graph.Batch batch, + Node node ) throws RepositoryException { + /* + * Determine if the node has already had its verison history initialized based on whether the protected property + * jcr:isCheckedOut exists. + */ + + boolean initialized = node.getProperty(JcrLexicon.IS_CHECKED_OUT) != null; + + if (!isVersionable(node) || initialized) return; + + Graph systemGraph = session().repository().createSystemGraph(context()); + + JcrNodePayload payload = node.getPayload(); + + PropertyInfo jcrUuidProp = node.getProperty(JcrLexicon.UUID); + UUID jcrUuid = (UUID)jcrUuidProp.getProperty().getFirstValue(); + + Name nameSegment = factories().getNameFactory().create(jcrUuid.toString()); + Path historyPath = pathFactory().createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.VERSION_STORAGE, nameSegment); + + // try { + // systemGraph.getNodeAt(historyPath); + // return; + // } + // catch (org.modeshape.graph.property.PathNotFoundException pnfe) { + // // No version history exists yet + // } + + Batch systemBatch = systemGraph.batch(); + + Name primaryTypeName = payload.getPrimaryTypeName(); + List mixinTypeNames = payload.getMixinTypeNames(); + + UUID historyUuid = UUID.randomUUID(); + UUID versionUuid = UUID.randomUUID(); + + systemBatch.create(historyPath).with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION_HISTORY).and(JcrLexicon.VERSIONABLE_UUID, + jcrUuid).and(JcrLexicon.UUID, + historyUuid).and(); + + Path rootVersionPath = pathFactory().create(historyPath, JcrLexicon.ROOT_VERSION); + DateTime now = context().getValueFactories().getDateFactory().create(); + systemBatch.create(rootVersionPath).with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION).and(JcrLexicon.CREATED, now).and(JcrLexicon.UUID, + versionUuid).and(); + + Path frozenVersionPath = pathFactory().create(rootVersionPath, JcrLexicon.FROZEN_NODE); + systemBatch.create(frozenVersionPath).with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE).and(JcrLexicon.FROZEN_UUID, + jcrUuid).and(JcrLexicon.FROZEN_PRIMARY_TYPE, + primaryTypeName).and(JcrLexicon.FROZEN_MIXIN_TYPES, + mixinTypeNames).and(); + + systemBatch.execute(); + + // NodeEditor editor = getEditorFor(node); + // editor.setProperty(JcrLexicon.IS_CHECKED_OUT, valueFrom(PropertyType.BOOLEAN, true), false); + // editor.setProperty(JcrLexicon.VERSION_HISTORY, valueFrom(PropertyType.REFERENCE, historyUuid.toString()), false); + // editor.setProperty(JcrLexicon.BASE_VERSION, valueFrom(PropertyType.REFERENCE, versionUuid.toString()), false); + + PropertyFactory propFactory = context().getPropertyFactory(); + Property isCheckedOut = propFactory.create(JcrLexicon.IS_CHECKED_OUT, true); + Property versionHistory = propFactory.create(JcrLexicon.VERSION_HISTORY, historyUuid); + Property baseVersion = propFactory.create(JcrLexicon.BASE_VERSION, versionUuid); + + // This batch will get executed as part of the save + batch.set(isCheckedOut, versionHistory, baseVersion).on(node.getPath()).and(); + + } + /** * {@inheritDoc} * @@ -2517,11 +2607,11 @@ class SessionCache { } catch (ValueFormatException e) { String msg = "{0} value \"{1}\" on {2} in \"{3}\" workspace is not a valid name and is being ignored"; LOGGER.trace(e, - msg, - readable(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES), - value, - readable(location), - workspaceName()); + msg, + readable(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES), + value, + readable(location), + workspaceName()); } } return multiValuedPropertyNames; Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/dna_builtins.cnd =================================================================== --- modeshape-jcr/src/main/resources/org/modeshape/jcr/dna_builtins.cnd (revision 1692) +++ modeshape-jcr/src/main/resources/org/modeshape/jcr/dna_builtins.cnd (working copy) @@ -56,10 +56,14 @@ [dna:locks] > nt:base + * (dna:lock) = dna:lock protected ignore +[dna:versionStorage] > nt:base ++ * (nt:versionHistory) = nt:versionHistory protected ignore + [dna:system] > nt:base -+ dna:namespaces (dna:namespaces) = dna:namespaces autocreated mandatory protected version -+ dna:locks (dna:locks) = dna:locks autocreated mandatory protected ignore -+ jcr:nodeTypes (dna:nodeTypes) = dna:nodeTypes autocreated mandatory protected version ++ dna:namespaces (dna:namespaces) = dna:namespaces autocreated mandatory protected abort ++ dna:locks (dna:locks) = dna:locks autocreated mandatory protected abort ++ jcr:nodeTypes (dna:nodeTypes) = dna:nodeTypes autocreated mandatory protected abort ++ jcr:versionStorage (dna:versionStorage) = dna:versionStorage autocreated mandatory protected abort [dna:root] > nt:base, mix:referenceable orderable - * (undefined) multiple version Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_170_builtins.cnd =================================================================== --- modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_170_builtins.cnd (revision 1692) +++ modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_170_builtins.cnd (working copy) @@ -68,6 +68,13 @@ + jcr:propertyDefinition (nt:propertyDefinition) = nt:propertyDefinition multiple version + jcr:childNodeDefinition (nt:childNodeDefinition) = nt:childNodeDefinition multiple version +[mix:versionable] > mix:referenceable mixin + - jcr:versionHistory (reference) mandatory protected < 'nt:versionHistory' + - jcr:baseVersion (reference) mandatory protected ignore < 'nt:version' + - jcr:isCheckedOut (boolean) = 'true' mandatory autocreated protected ignore + - jcr:predecessors (reference) mandatory protected multiple < 'nt:version' + - jcr:mergeFailed (reference) protected multiple abort + [nt:versionLabels] - * (reference) protected abort < 'nt:version' Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (revision 1692) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (working copy) @@ -100,6 +100,35 @@ import org.apache.jackrabbit.test.api.query.GetPersistentQueryPathTest; import org.apache.jackrabbit.test.api.query.QueryResultNodeIteratorTest; import org.apache.jackrabbit.test.api.query.SaveTest; import org.apache.jackrabbit.test.api.query.XPathQueryLevel2Test; +import org.apache.jackrabbit.test.api.version.CheckinTest; +import org.apache.jackrabbit.test.api.version.CheckoutTest; +import org.apache.jackrabbit.test.api.version.GetContainingHistoryTest; +import org.apache.jackrabbit.test.api.version.GetCreatedTest; +import org.apache.jackrabbit.test.api.version.GetPredecessorsTest; +import org.apache.jackrabbit.test.api.version.GetReferencesNodeTest; +import org.apache.jackrabbit.test.api.version.GetVersionableUUIDTest; +import org.apache.jackrabbit.test.api.version.MergeCancelMergeTest; +import org.apache.jackrabbit.test.api.version.MergeCheckedoutSubNodeTest; +import org.apache.jackrabbit.test.api.version.MergeDoneMergeTest; +import org.apache.jackrabbit.test.api.version.MergeNodeIteratorTest; +import org.apache.jackrabbit.test.api.version.MergeNodeTest; +import org.apache.jackrabbit.test.api.version.MergeNonVersionableSubNodeTest; +import org.apache.jackrabbit.test.api.version.MergeSubNodeTest; +import org.apache.jackrabbit.test.api.version.OnParentVersionAbortTest; +import org.apache.jackrabbit.test.api.version.OnParentVersionComputeTest; +import org.apache.jackrabbit.test.api.version.OnParentVersionCopyTest; +import org.apache.jackrabbit.test.api.version.OnParentVersionIgnoreTest; +import org.apache.jackrabbit.test.api.version.OnParentVersionInitializeTest; +import org.apache.jackrabbit.test.api.version.RemoveVersionTest; +import org.apache.jackrabbit.test.api.version.RestoreTest; +import org.apache.jackrabbit.test.api.version.SessionMoveVersionExceptionTest; +import org.apache.jackrabbit.test.api.version.VersionGraphTest; +import org.apache.jackrabbit.test.api.version.VersionHistoryTest; +import org.apache.jackrabbit.test.api.version.VersionLabelTest; +import org.apache.jackrabbit.test.api.version.VersionStorageTest; +import org.apache.jackrabbit.test.api.version.VersionTest; +import org.apache.jackrabbit.test.api.version.WorkspaceMoveVersionExceptionTest; +import org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest; /** * Test suite to wrap Apache Jackrabbit JCR technology compatibility kit (TCK) unit tests. Note that technically these are not the @@ -125,9 +154,11 @@ public class JcrTckTest { // Or uncomment the following lines to execute the different sets/suites of tests ... TestSuite suite = new TestSuite("JCR 1.0 API tests"); - suite.addTest(new LevelOneFeatureTests()); - suite.addTest(new LevelTwoFeatureTests()); - suite.addTest(new OptionalFeatureTests()); + // suite.addTest(new LevelOneFeatureTests()); + // suite.addTest(new LevelTwoFeatureTests()); + // suite.addTest(new OptionalFeatureTests()); + suite.addTest(new VersioningTests()); // remove this and the ObservationTests inner class when all tests pass and + // uncomment return suite; } @@ -292,6 +323,7 @@ public class JcrTckTest { // See https://jira.jboss.org/jira/browse/ModeShape-285 addTest(new ObservationTests()); // remove this and the ObservationTests inner class when all tests pass and uncomment + addTest(new VersioningTests()); // remove this and the VersionTests inner class when all tests pass and uncomment // observation.TestAll // addTest(org.apache.jackrabbit.test.api.observation.TestAll.suite()); // addTest(org.apache.jackrabbit.test.api.version.TestAll.suite()); @@ -320,4 +352,43 @@ public class JcrTckTest { // addTestSuite(WorkspaceOperationTest.class); } } + + private static class VersioningTests extends TestSuite { + protected VersioningTests() { + super("JCR Versioning Tests"); + + addTestSuite(VersionTest.class); + addTestSuite(VersionHistoryTest.class); + addTestSuite(VersionStorageTest.class); + addTestSuite(VersionLabelTest.class); + addTestSuite(CheckoutTest.class); + addTestSuite(CheckinTest.class); + addTestSuite(VersionGraphTest.class); + addTestSuite(RemoveVersionTest.class); + addTestSuite(RestoreTest.class); + addTestSuite(WorkspaceRestoreTest.class); + addTestSuite(OnParentVersionAbortTest.class); + addTestSuite(OnParentVersionComputeTest.class); + addTestSuite(OnParentVersionCopyTest.class); + addTestSuite(OnParentVersionIgnoreTest.class); + addTestSuite(OnParentVersionInitializeTest.class); + addTestSuite(GetReferencesNodeTest.class); + addTestSuite(GetPredecessorsTest.class); + addTestSuite(GetCreatedTest.class); + addTestSuite(GetContainingHistoryTest.class); + addTestSuite(GetVersionableUUIDTest.class); + addTestSuite(SessionMoveVersionExceptionTest.class); + addTestSuite(WorkspaceMoveVersionExceptionTest.class); + addTestSuite(MergeCancelMergeTest.class); + addTestSuite(MergeCheckedoutSubNodeTest.class); + addTestSuite(MergeDoneMergeTest.class); + addTestSuite(MergeNodeIteratorTest.class); + addTestSuite(MergeNodeTest.class); + addTestSuite(MergeNonVersionableSubNodeTest.class); + addTestSuite(MergeSubNodeTest.class); + + // addTest(org.apache.jackrabbit.test.api.version.TestAll.suite()); + } + } + } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (revision 1692) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (working copy) @@ -1,5 +1,6 @@ package org.modeshape.jcr; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import java.util.Collections; @@ -13,6 +14,7 @@ import javax.jcr.Property; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.version.Version; import junit.framework.Test; import junit.framework.TestSuite; import org.apache.jackrabbit.test.AbstractJCRTest; @@ -460,4 +462,52 @@ public class ModeShapeTckTest extends AbstractJCRTest { superuser.logout(); } + + public void testShouldCreateProperVersionHistoryWhenSavingVersionedNode() throws Exception { + session = helper.getReadWriteSession(); + Node node = session.getRootNode().addNode("/test", "nt:unstructured"); + node.addMixin("mix:versionable"); + session.save(); + + assertThat(node.hasProperty("jcr:isCheckedOut"), is(true)); + assertThat(node.getProperty("jcr:isCheckedOut").getBoolean(), is(true)); + + assertThat(node.hasProperty("jcr:versionHistory"), is(true)); + Node history = node.getProperty("jcr:versionHistory").getNode(); + assertThat(history, is(notNullValue())); + + assertThat(node.hasProperty("jcr:baseVersion"), is(true)); + Node version = node.getProperty("jcr:baseVersion").getNode(); + assertThat(version, is(notNullValue())); + + assertThat(version.getParent(), is(history)); + + assertThat(node.hasProperty("jcr:uuid"), is(true)); + assertThat(node.getProperty("jcr:uuid").getString(), is(history.getProperty("jcr:versionableUuid").getString())); + + assertThat(node.getVersionHistory().getUUID(), is(history.getUUID())); + assertThat(node.getVersionHistory().getPath(), is(history.getPath())); + + assertThat(node.getBaseVersion().getUUID(), is(version.getUUID())); + assertThat(node.getBaseVersion().getPath(), is(version.getPath())); + + // Subgraph subgraph = graph.getSubgraphOfDepth(Integer.MAX_VALUE).at("/jcr:system/jcr:versionStorage"); + // System.out.println(subgraph); + // + // subgraph = graph.getSubgraphOfDepth(2).at("/test"); + // System.out.println(subgraph); + } + + public void testShouldCreateProperStructureForTheFirstCheckInOfANode() throws Exception { + session = helper.getReadWriteSession(); + Node node = session.getRootNode().addNode("/checkInTest", "nt:unstructured"); + node.addMixin("mix:versionable"); + session.save(); + + Version version = node.checkin(); + + assertThat(node.getProperty("jcr:isCheckedOut").getBoolean(), is(false)); + + } + } Index: modeshape-jcr/src/test/resources/repositoryStubImpl.properties =================================================================== --- modeshape-jcr/src/test/resources/repositoryStubImpl.properties (revision 1692) +++ modeshape-jcr/src/test/resources/repositoryStubImpl.properties (working copy) @@ -50,6 +50,10 @@ javax.jcr.tck.NodeOrderableChildNodesTest.testOrderBeforeUnsupportedRepositoryOp javax.jcr.tck.SaveTest.nodetype=nt\:query javax.jcr.tck.SetPropertyAssumeTypeTest.nodetype=modetest\:setPropertyAssumeTypeTest +# version test types +javax.jcr.tck.versionableNodeType=modetest\:versionableUnstructured +javax.jcr.tck.propertyValue=31337 + # Test users javax.jcr.tck.superuser.name=superuser javax.jcr.tck.superuser.pwd=superuser Index: modeshape-jcr/src/test/resources/tck/tck_test_types.cnd =================================================================== --- modeshape-jcr/src/test/resources/tck/tck_test_types.cnd (revision 1692) +++ modeshape-jcr/src/test/resources/tck/tck_test_types.cnd (working copy) @@ -26,3 +26,5 @@ - prop1 (PATH) copy - * (*) copy - * (*) multiple copy + +[modetest:versionableUnstructured] > nt:unstructured, mix:versionable