Index: extensions/modeshape-sequencer-xml/src/main/java/org/modeshape/sequencer/xml/XmlSequencerHandler.java =================================================================== --- extensions/modeshape-sequencer-xml/src/main/java/org/modeshape/sequencer/xml/XmlSequencerHandler.java (revision 2570) +++ extensions/modeshape-sequencer-xml/src/main/java/org/modeshape/sequencer/xml/XmlSequencerHandler.java (working copy) @@ -172,7 +172,12 @@ public class XmlSequencerHandler extends DefaultHandler2 { assert this.namespaceRegistry != null; // Set up the initial path ... - this.currentPath = this.pathFactory.createRelativePath(); + Path inputPath = context.getInputPath(); + if (!inputPath.isRoot() && inputPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) { + inputPath = inputPath.getParent(); + } + this.currentPath = inputPath.isRoot() ? this.pathFactory.createRelativePath() : this.pathFactory.createRelativePath(inputPath.getLastSegment() + .getName()); assert this.currentPath != null; } Index: extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/InheritingXmlSequencerTest.java =================================================================== --- extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/InheritingXmlSequencerTest.java (revision 2570) +++ extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/InheritingXmlSequencerTest.java (working copy) @@ -38,9 +38,6 @@ import org.modeshape.graph.sequencer.MockSequencerContext; import org.modeshape.graph.sequencer.MockSequencerOutput; import org.modeshape.graph.sequencer.StreamSequencerContext; -/** - * @author John Verhaeg - */ public class InheritingXmlSequencerTest { private static final String DOCUMENT = "modexml:document"; @@ -50,11 +47,13 @@ public class InheritingXmlSequencerTest { private MockSequencerOutput output; private URL xsd; private StreamSequencerContext context; + private String inputNodeName; @Before public void beforeEach() { + inputNodeName = "input"; sequencer = new InheritingXmlSequencer(); - context = new MockSequencerContext(); + context = new MockSequencerContext("/some/" + inputNodeName); output = new MockSequencerOutput(context); xsd = this.getClass().getClassLoader().getResource("Descriptor.1.0.xsd"); assertThat(xsd, is(notNullValue())); @@ -85,6 +84,7 @@ public class InheritingXmlSequencerTest { private T verify( String nodePath, String property, Class expectedClass ) { + nodePath = nodePath.length() == 0 ? inputNodeName : inputNodeName + "/" + nodePath; Object[] values = output.getPropertyValues(nodePath.length() == 0 ? "" : nodePath, property); assertThat(values, notNullValue()); assertThat(values.length, is(1)); Index: extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerHandlerTest.java =================================================================== --- extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerHandlerTest.java (revision 2570) +++ extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerHandlerTest.java (working copy) @@ -53,9 +53,6 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; -/** - * @author Randall Hauch - */ public class XmlSequencerHandlerTest { private XmlSequencerHandler handler; @@ -66,10 +63,12 @@ public class XmlSequencerHandlerTest { private Name nameAttribute; private XmlSequencer.AttributeScoping scoping; private LinkedList pathsInCreationOrder; + private String sequencedNodeName; @Before public void beforeEach() { - context = new MockSequencerContext(); + sequencedNodeName = "input"; + context = new MockSequencerContext("/some/" + sequencedNodeName); output = new MockSequencerOutput(context, true); context.getNamespaceRegistry().register(JcrLexicon.Namespace.PREFIX, JcrLexicon.Namespace.URI); context.getNamespaceRegistry().register(JcrNtLexicon.Namespace.PREFIX, JcrNtLexicon.Namespace.URI); @@ -498,6 +497,9 @@ public class XmlSequencerHandlerTest { protected void assertNode( String path, String... properties ) { + // Must prepend the name of the node that was sequenced, as the XML sequencer now outputs that ... + path = path.length() != 0 ? sequencedNodeName + "/" + path : sequencedNodeName; + // Append an index to the path if not there ... if (path.length() != 0 && !path.endsWith("]")) { path = path + "[1]"; @@ -562,6 +564,9 @@ public class XmlSequencerHandlerTest { protected void assertCdata( String path, String content ) { + // Must prepend the name of the node that was sequenced, as the XML sequencer now outputs that ... + path = path.length() != 0 ? sequencedNodeName + "/" + path : sequencedNodeName; + // Append an index to the path if not there ... if (path.length() != 0 && !path.endsWith("]")) { path = path + "[1]"; Index: extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerTest.java =================================================================== --- extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerTest.java (revision 2570) +++ extensions/modeshape-sequencer-xml/src/test/java/org/modeshape/sequencer/xml/XmlSequencerTest.java (working copy) @@ -64,11 +64,13 @@ public class XmlSequencerTest { private URL xml4; private URL xsd; private StreamSequencerContext context; + private String inputNodeName; @Before public void beforeEach() { + inputNodeName = "node"; sequencer = new XmlSequencer(); - context = new MockSequencerContext(); + context = new MockSequencerContext("/some/" + inputNodeName); output = new MockSequencerOutput(context); xml2 = this.getClass().getClassLoader().getResource("master.xml"); assertThat(xml2, is(notNullValue())); @@ -165,6 +167,7 @@ public class XmlSequencerTest { private T verify( String nodePath, String property, Class expectedClass ) { + nodePath = nodePath.length() == 0 ? inputNodeName : inputNodeName + "/" + nodePath; Object[] values = output.getPropertyValues(nodePath.length() == 0 ? "" : nodePath, property); assertThat(values, notNullValue()); assertThat(values.length, is(1)); Index: modeshape-common/src/main/java/org/modeshape/common/collection/ImmutableAppendedList.java =================================================================== --- modeshape-common/src/main/java/org/modeshape/common/collection/ImmutableAppendedList.java (revision 2570) +++ modeshape-common/src/main/java/org/modeshape/common/collection/ImmutableAppendedList.java (working copy) @@ -246,7 +246,7 @@ public class ImmutableAppendedList implements List { // The bounds are the same as this list, so just return this list ... return this; } - if (toIndex == size || fromIndex == (size - 1)) { + if (toIndex == size && fromIndex == (size - 1)) { // The only list is the last element ... return Collections.singletonList(element); } Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/jdbc/JcrDriverIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/jdbc/JcrDriverIntegrationTest.java (revision 2570) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/jdbc/JcrDriverIntegrationTest.java (working copy) @@ -1109,7 +1109,7 @@ public class JcrDriverIntegrationTest extends AbstractMultiUseModeShapeTest { uploadFile("org/modeshape/test/integration/sequencer/ddl/create_schema.ddl", "/files/"); uploadFile("org/modeshape/test/integration/sequencer/ddl/standard_test_statements.ddl", "/files/"); - Thread.sleep(1000L); + Thread.sleep(2000L); print = true; Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/XmlSequencerIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/XmlSequencerIntegrationTest.java (revision 2570) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/XmlSequencerIntegrationTest.java (working copy) @@ -70,12 +70,13 @@ public class XmlSequencerIntegrationTest extends AbstractSequencerTest { // print = true; uploadFile("jcr-import-test.xml", "/files/"); waitUntilSequencedNodesIs(1); - Thread.sleep(200); // wait a bit while the new content is indexed + Thread.sleep(1000); // wait a bit while the new content is indexed // printSubgraph(assertNode("/")); // Find the sequenced node ... + printSubgraph(assertNode("/sequenced/xml", "nt:unstructured")); String path = "/sequenced/xml/jcr-import-test.xml"; - Node xml = assertNode(path, "nt:unstructured"); + Node xml = assertNode(path, "modexml:document"); printSubgraph(xml); // Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); @@ -95,12 +96,12 @@ public class XmlSequencerIntegrationTest extends AbstractSequencerTest { // print = true; uploadFile("jcr-import-test.xml", "/files/a/b"); waitUntilSequencedNodesIs(1); - Thread.sleep(200); // wait a bit while the new content is indexed + Thread.sleep(1000); // wait a bit while the new content is indexed // printSubgraph(assertNode("/")); // Find the sequenced node ... String path = "/sequenced/xml/a/b/jcr-import-test.xml"; - Node xml = assertNode(path, "nt:unstructured"); + Node xml = assertNode(path, "modexml:document"); printSubgraph(xml); // Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); @@ -114,20 +115,24 @@ public class XmlSequencerIntegrationTest extends AbstractSequencerTest { printQuery("SELECT * FROM [modexml:element] WHERE NAME() = 'xhtml:p'", 2); printQuery("SELECT * FROM [modexml:elementContent]", 13); } - + /* * Validates FixFor( "MODE-981" ) */ @Test public void shouldSequence2XmlFiles2() throws Exception { // print = true; - uploadFile("docWithComments.xml", "/files/"); - waitUntilSequencedNodesIs(1); - printQuery("SELECT * FROM [nt:base] ORDER BY [jcr:path]", 18); + uploadFile("docWithComments.xml", "/files/"); + waitUntilSequencedNodesIs(1); + printQuery("SELECT * FROM [nt:base] ORDER BY [jcr:path]", 17); uploadFile("docWithComments2.xml", "/files/"); - waitUntilSequencedNodesIs(1); + waitUntilSequencedNodesIs(2); - printQuery("SELECT * FROM [nt:base] ORDER BY [jcr:path]", 20); - } + printQuery("SELECT * FROM [nt:base] ORDER BY [jcr:path]", 30); + printSubgraph(assertNode("/sequenced/xml", "nt:unstructured")); + uploadFile("docWithComments.xml", "/files/"); + waitUntilSequencedNodesIs(3); + printSubgraph(assertNode("/sequenced/xml", "nt:unstructured")); + } } Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/ZipSequencerIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/ZipSequencerIntegrationTest.java (revision 2570) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/ZipSequencerIntegrationTest.java (working copy) @@ -75,7 +75,7 @@ public class ZipSequencerIntegrationTest extends AbstractSequencerTest { // print = true; uploadFile("sequencers/zip/test-files.zip", "/files/"); waitUntilSequencedNodesIs(1); - Thread.sleep(200); // wait a bit while the new content is indexed + Thread.sleep(1000); // wait a bit while the new content is indexed // Find the sequenced node ... String path = "/sequenced/zip/test-files.zip"; @@ -112,7 +112,7 @@ public class ZipSequencerIntegrationTest extends AbstractSequencerTest { // print = true; uploadFile("sequencers/zip/test-files.zip", "/files/a/b"); waitUntilSequencedNodesIs(1); - Thread.sleep(200); // wait a bit while the new content is indexed + Thread.sleep(1000); // wait a bit while the new content is indexed // Find the sequenced node ... String path = "/sequenced/zip/a/b/test-files.zip"; Index: modeshape-integration-tests/src/test/resources/config/configRepositoryForXmlSequencing.xml =================================================================== --- modeshape-integration-tests/src/test/resources/config/configRepositoryForXmlSequencing.xml (revision 2570) +++ modeshape-integration-tests/src/test/resources/config/configRepositoryForXmlSequencing.xml (working copy) @@ -48,7 +48,7 @@ Sequences XML files loaded into the repository under '/files', extracting the contents into the equivalent JCR graph structure. - /files(//(*.xml[*]))/jcr:content[@jcr:data] => /sequenced/xml/$1 + /files(//)*.xml[*]/jcr:content[@jcr:data] => /sequenced/xml/$1 Index: modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java =================================================================== --- modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java (revision 2570) +++ modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java (working copy) @@ -26,17 +26,20 @@ package org.modeshape.repository.sequencer; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.modeshape.common.collection.Collections; import org.modeshape.common.collection.Problems; import org.modeshape.common.util.Logger; import org.modeshape.common.util.Reflection; import org.modeshape.graph.JcrLexicon; import org.modeshape.graph.JcrNtLexicon; +import org.modeshape.graph.Location; import org.modeshape.graph.Node; import org.modeshape.graph.observe.NetChangeObserver.NetChange; import org.modeshape.graph.property.Binary; @@ -47,6 +50,7 @@ import org.modeshape.graph.property.PathNotFoundException; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.property.ValueFactories; +import org.modeshape.graph.property.Path.Segment; import org.modeshape.graph.sequencer.StreamSequencer; import org.modeshape.graph.sequencer.StreamSequencerContext; import org.modeshape.repository.RepositoryI18n; @@ -267,10 +271,35 @@ public class StreamSequencerAdapter implements Sequencer { final PropertyFactory propertyFactory = context.getExecutionContext().getPropertyFactory(); final Path outputNodePath = pathFactory.create(nodePath); + // Get the existing list of children under the output path ... + PathStrategy pathStrategy = null; + try { + List children = context.destinationGraph().getChildren().of(outputNodePath); + Map existingSnsByNodeName = new HashMap(); + for (Location child : children) { + Segment childSegment = child.getPath().getLastSegment(); + Name childName = child.getPath().getLastSegment().getName(); + AtomicInteger sns = existingSnsByNodeName.get(childName); + if (sns == null) { + sns = new AtomicInteger(childSegment.getIndex()); + existingSnsByNodeName.put(childName, sns); + } else { + if (childSegment.getIndex() > sns.get()) { + sns.set(childSegment.getIndex()); + } + } + } + pathStrategy = new NextSnsPathStrategy(existingSnsByNodeName, pathFactory); + } catch (PathNotFoundException e) { + // The target node doesn't yet exist, so just continue ... + pathStrategy = new PassThroughStrategy(); + } + // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by // prefix and name) for (SequencerOutputMap.Entry entry : output) { - Path targetNodePath = entry.getPath(); + Path path = entry.getPath(); + Path targetNodePath = pathStrategy.validate(path); // Resolve this path relative to the output node path, handling any parent or self references ... Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath); @@ -346,4 +375,75 @@ public class StreamSequencerAdapter implements Sequencer { } throw err; } + + protected interface PathStrategy { + Path validate( Path path ); + } + + protected class PassThroughStrategy implements PathStrategy { + /** + * {@inheritDoc} + * + * @see org.modeshape.repository.sequencer.StreamSequencerAdapter.PathStrategy#validate(org.modeshape.graph.property.Path) + */ + @Override + public Path validate( Path path ) { + return path; + } + } + + protected class NextSnsPathStrategy implements PathStrategy { + + private final Map existingSnsByNodeName; + private final PathFactory pathFactory; + private final Map topLevelSegmentReplacements = new HashMap(); + + protected NextSnsPathStrategy( Map existingSnsByNodeName, + PathFactory pathFactory ) { + this.existingSnsByNodeName = existingSnsByNodeName; + assert this.existingSnsByNodeName != null; + this.pathFactory = pathFactory; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.repository.sequencer.StreamSequencerAdapter.PathStrategy#validate(org.modeshape.graph.property.Path) + */ + @Override + public Path validate( Path path ) { + if (path.size() == 0) return path; + if (path.isAbsolute()) return path; + Segment firstSegment = path.getSegment(0); + if (path.size() == 1) { + // This is a top-level node (there may be more than one), so we need to determine whether this + // node already exists and what to do in that case ... + Name nodeName = firstSegment.getName(); + AtomicInteger lastSns = existingSnsByNodeName.get(nodeName); + if (lastSns == null) { + // This name was not yet used in the children ... + lastSns = new AtomicInteger(1); + existingSnsByNodeName.put(nodeName, lastSns); + } else { + // Increment the SNS ... + lastSns.incrementAndGet(); + } + int sns = lastSns.get(); + if (sns != firstSegment.getIndex()) { + // The SNS is different than the output expected ... + Segment newSegment = pathFactory.createSegment(nodeName, sns); + path = pathFactory.createRelativePath(newSegment); + topLevelSegmentReplacements.put(firstSegment, path); + } + } else { + // Is the first segment in the path replaced with something else? + Path newBasePath = topLevelSegmentReplacements.get(firstSegment); + if (newBasePath != null) { + // Yup, so replace it ... + path = pathFactory.create(newBasePath, path.subpath(1)); + } + } + return path; + } + } }