Index: extensions/modeshape-connector-jdbc-metadata/pom.xml =================================================================== --- extensions/modeshape-connector-jdbc-metadata/pom.xml (revision 2574) +++ extensions/modeshape-connector-jdbc-metadata/pom.xml (working copy) @@ -37,10 +37,6 @@ test-jar test - - org.modeshape - modeshape-graph - org.modeshape modeshape-graph Index: extensions/modeshape-sequencer-cnd/src/main/java/org/modeshape/sequencer/cnd/CndSequencer.java =================================================================== --- extensions/modeshape-sequencer-cnd/src/main/java/org/modeshape/sequencer/cnd/CndSequencer.java (revision 2574) +++ extensions/modeshape-sequencer-cnd/src/main/java/org/modeshape/sequencer/cnd/CndSequencer.java (working copy) @@ -26,7 +26,6 @@ package org.modeshape.sequencer.cnd; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.modeshape.cnd.CndImporter; @@ -114,10 +113,10 @@ public class CndSequencer implements StreamSequencer { /** * {@inheritDoc} * - * @see org.modeshape.graph.io.Destination#create(org.modeshape.graph.property.Path, java.util.List) + * @see org.modeshape.graph.io.Destination#create(Path, Iterable) */ public void create( Path path, - List properties ) { + Iterable properties ) { path = checkPath(path); for (Property property : properties) { output.setProperty(path, property.getName(), property.getValuesAsArray()); Index: modeshape-graph/src/main/java/org/modeshape/graph/io/Destination.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/io/Destination.java (revision 2574) +++ modeshape-graph/src/main/java/org/modeshape/graph/io/Destination.java (working copy) @@ -23,7 +23,6 @@ */ package org.modeshape.graph.io; -import java.util.List; import net.jcip.annotations.NotThreadSafe; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.property.Path; @@ -50,7 +49,7 @@ public interface Destination { * @param properties the properties for the node; never null, but may be empty if there are no properties */ public void create( Path path, - List properties ); + Iterable properties ); /** * Create a node at the supplied path and with the supplied attributes. The path will be absolute. Index: modeshape-graph/src/main/java/org/modeshape/graph/io/GraphBatchDestination.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/io/GraphBatchDestination.java (revision 2574) +++ modeshape-graph/src/main/java/org/modeshape/graph/io/GraphBatchDestination.java (working copy) @@ -23,7 +23,6 @@ */ package org.modeshape.graph.io; -import java.util.List; import net.jcip.annotations.NotThreadSafe; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; @@ -89,17 +88,12 @@ public class GraphBatchDestination implements Destination { /** * {@inheritDoc} * - * @see org.modeshape.graph.io.Destination#create(org.modeshape.graph.property.Path, java.util.List) + * @see org.modeshape.graph.io.Destination#create(org.modeshape.graph.property.Path, Iterable) */ public void create( Path path, - List properties ) { + Iterable properties ) { assert properties != null; - Create create = null; - if (properties.isEmpty()) { - create = batch.create(path); - } else { - create = batch.create(path, properties); - } + Create create = batch.create(path, properties); assert create != null; NodeConflictBehavior behavior = createBehaviorFor(path); if (behavior != null) { Index: modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyFactory.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyFactory.java (revision 2574) +++ modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyFactory.java (working copy) @@ -97,4 +97,14 @@ public interface PropertyFactory { PropertyType desiredType, Iterator values ); + /** + * Create a property with the supplied name and {@link Path} value. This method is provided because Path implements + * Iterable<Segment>. + * + * @param name the property name; may not be null + * @param value the path value + * @return the resulting property + */ + Property create( Name name, + Path value ); } Index: modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPropertyFactory.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPropertyFactory.java (revision 2574) +++ modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPropertyFactory.java (working copy) @@ -30,6 +30,7 @@ import java.util.List; import net.jcip.annotations.Immutable; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.property.PropertyType; @@ -55,6 +56,17 @@ public class BasicPropertyFactory implements PropertyFactory { /** * {@inheritDoc} + * + * @see org.modeshape.graph.property.PropertyFactory#create(org.modeshape.graph.property.Name, + * org.modeshape.graph.property.Path) + */ + public Property create( Name name, + Path value ) { + return new BasicSingleValueProperty(name, value); + } + + /** + * {@inheritDoc} */ public Property create( Name name, Iterable values ) { @@ -93,6 +105,10 @@ public class BasicPropertyFactory implements PropertyFactory { if (values.length == 1) { Object value = values[0]; // Check whether the sole value was a collection ... + if (value instanceof Path) { + value = factory.create(value); + return new BasicSingleValueProperty(name, value); + } if (value instanceof Collection) { // The single value is a collection, so create property with the collection's contents ... return create(name, desiredType, (Iterable)value); Index: modeshape-graph/src/test/java/org/modeshape/graph/xml/XmlHandlerTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/xml/XmlHandlerTest.java (revision 2574) +++ modeshape-graph/src/test/java/org/modeshape/graph/xml/XmlHandlerTest.java (working copy) @@ -33,7 +33,6 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -526,7 +525,7 @@ public class XmlHandlerTest { private final String workspace = "Recording Workspace"; public void create( Path path, - List properties ) { + Iterable properties ) { assert path != null; Path parent = path.getParent(); Name child = path.getLastSegment().getName(); 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 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/jdbc/JcrDriverIntegrationTest.java (working copy) @@ -530,6 +530,7 @@ public class JcrDriverIntegrationTest extends AbstractMultiUseModeShapeTest { "Repo NULL mmcore:model VIEW Is Mixin: true NULL NULL NULL null DERIVED", "Repo NULL mmcore:tags VIEW Is Mixin: true NULL NULL NULL null DERIVED", "Repo NULL mode:defined VIEW Is Mixin: true NULL NULL NULL null DERIVED", + "Repo NULL mode:derived VIEW Is Mixin: true NULL NULL NULL null DERIVED", "Repo NULL mode:hashed VIEW Is Mixin: true NULL NULL NULL null DERIVED", "Repo NULL mode:lock VIEW Is Mixin: false NULL NULL NULL null DERIVED", "Repo NULL mode:locks VIEW Is Mixin: false NULL NULL NULL null DERIVED", @@ -603,7 +604,7 @@ public class JcrDriverIntegrationTest extends AbstractMultiUseModeShapeTest { while (rs.next()) { tableNames.add(rs.getString("TABLE_NAME")); } - assertThat(tableNames.size(), is(179)); + assertThat(tableNames.size(), is(180)); List tablesWithProblems = new ArrayList(); for (String table : tableNames) { try { Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/CndSequencerIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/CndSequencerIntegrationTest.java (revision 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/CndSequencerIntegrationTest.java (working copy) @@ -78,7 +78,7 @@ public class CndSequencerIntegrationTest extends AbstractSequencerTest { Node cnd = assertNode(path, "nt:unstructured"); printSubgraph(cnd); - Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); + Node file1 = assertNode(path + "/nt:activity", "nt:nodeType", "mode:derived"); assertThat(file1, is(notNullValue())); printQuery("SELECT * FROM [nt:nodeType]", 34); @@ -99,7 +99,7 @@ public class CndSequencerIntegrationTest extends AbstractSequencerTest { Node cnd = assertNode(path, "nt:unstructured"); printSubgraph(cnd); - Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); + Node file1 = assertNode(path + "/nt:activity", "nt:nodeType", "mode:derived"); assertThat(file1, is(notNullValue())); printQuery("SELECT * FROM [nt:nodeType]", 34); Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java (revision 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/JavaSequencerIntegrationTest.java (working copy) @@ -83,7 +83,7 @@ public class JavaSequencerIntegrationTest extends AbstractSequencerTest { Node java = assertNode(path, "nt:unstructured"); printSubgraph(java); - assertNode(path + "/ClusteringTest", "class:class"); + assertNode(path + "/ClusteringTest", "class:class", "mode:derived"); assertNode(path + "/ClusteringTest/class:constructors", "class:constructors"); assertNode(path + "/ClusteringTest/class:methods", "class:methods"); assertNode(path + "/ClusteringTest/class:methods/beforeAll()", "class:method"); @@ -132,7 +132,7 @@ public class JavaSequencerIntegrationTest extends AbstractSequencerTest { Node java = assertNode(path, "nt:unstructured"); printSubgraph(java); - assertNode(path + "/" + typeName, "class:class"); + assertNode(path + "/" + typeName, "class:class", "mode:derived"); assertNode(path + "/" + typeName + "/class:constructors", "class:constructors"); assertNode(path + "/" + typeName + "/class:methods", "class:methods"); // etc. @@ -190,7 +190,7 @@ public class JavaSequencerIntegrationTest extends AbstractSequencerTest { Node java = assertNode(path, "nt:unstructured"); printSubgraph(java); - assertNode(path + "/SequencerTest", "class:class"); + assertNode(path + "/SequencerTest", "class:class", "mode:derived"); assertNode(path + "/SequencerTest/class:constructors", "class:constructors"); assertNode(path + "/SequencerTest/class:methods", "class:methods"); // etc. Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/TeiidSequencerIntegrationTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/TeiidSequencerIntegrationTest.java (revision 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/TeiidSequencerIntegrationTest.java (working copy) @@ -98,7 +98,7 @@ public class TeiidSequencerIntegrationTest extends AbstractSequencerTest { Thread.sleep(200); // wait a bit while the new content is indexed // Find the sequenced node ... - Node vdb = assertNode("/sequenced/teiid/vdbs/qe", "vdb:virtualDatabase", "mix:referenceable"); + Node vdb = assertNode("/sequenced/teiid/vdbs/qe", "vdb:virtualDatabase", "mix:referenceable", "mode:derived"); printSubgraph(vdb); printQuery("SELECT * FROM [vdb:virtualDatabase]", 1); printQuery("SELECT * FROM [vdb:model]", 3); @@ -119,7 +119,7 @@ public class TeiidSequencerIntegrationTest extends AbstractSequencerTest { Thread.sleep(200); // wait a bit while the new content is indexed // Find the sequenced node ... - Node vdb = assertNode("/sequenced/teiid/vdbs/my/favorites/qe", "vdb:virtualDatabase", "mix:referenceable"); + Node vdb = assertNode("/sequenced/teiid/vdbs/my/favorites/qe", "vdb:virtualDatabase", "mix:referenceable", "mode:derived"); printSubgraph(vdb); printQuery("SELECT * FROM [vdb:virtualDatabase]", 1); printQuery("SELECT * FROM [vdb:model]", 3); @@ -140,7 +140,7 @@ public class TeiidSequencerIntegrationTest extends AbstractSequencerTest { Thread.sleep(200); // wait a bit while the new content is indexed // Find the sequenced node ... - Node vdb = assertNode("/sequenced/teiid/vdbs/PartsFromXml", "vdb:virtualDatabase", "mix:referenceable"); + Node vdb = assertNode("/sequenced/teiid/vdbs/PartsFromXml", "vdb:virtualDatabase", "mix:referenceable", "mode:derived"); printSubgraph(vdb); printQuery("SELECT * FROM [vdb:virtualDatabase]", 1); printQuery("SELECT * FROM [vdb:model]", 2); @@ -163,7 +163,7 @@ public class TeiidSequencerIntegrationTest extends AbstractSequencerTest { Thread.sleep(200); // wait a bit while the new content is indexed // Find the sequenced node ... - Node vdb = assertNode("/sequenced/teiid/vdbs/YahooUdfTest", "vdb:virtualDatabase", "mix:referenceable"); + Node vdb = assertNode("/sequenced/teiid/vdbs/YahooUdfTest", "vdb:virtualDatabase", "mix:referenceable", "mode:derived"); printSubgraph(vdb); printQuery("SELECT * FROM [vdb:virtualDatabase]", 1); printQuery("SELECT * FROM [vdb:model]", 4); 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 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/XmlSequencerIntegrationTest.java (working copy) @@ -76,7 +76,7 @@ public class XmlSequencerIntegrationTest extends AbstractSequencerTest { // Find the sequenced node ... printSubgraph(assertNode("/sequenced/xml", "nt:unstructured")); String path = "/sequenced/xml/jcr-import-test.xml"; - Node xml = assertNode(path, "modexml:document"); + Node xml = assertNode(path, "modexml:document", "mode:derived"); printSubgraph(xml); // Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); @@ -101,7 +101,7 @@ public class XmlSequencerIntegrationTest extends AbstractSequencerTest { // Find the sequenced node ... String path = "/sequenced/xml/a/b/jcr-import-test.xml"; - Node xml = assertNode(path, "modexml:document"); + Node xml = assertNode(path, "modexml:document", "mode:derived"); printSubgraph(xml); // Node file1 = assertNode(path + "/nt:activity", "nt:nodeType"); 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 2574) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/sequencer/ZipSequencerIntegrationTest.java (working copy) @@ -79,7 +79,7 @@ public class ZipSequencerIntegrationTest extends AbstractSequencerTest { // Find the sequenced node ... String path = "/sequenced/zip/test-files.zip"; - Node zipped = assertNode(path, "zip:file"); + Node zipped = assertNode(path, "zip:file", "mode:derived"); Node file1 = assertNode(path + "/MODE-966-fix.patch", "nt:file"); Node data1 = assertNode(path + "/MODE-966-fix.patch/jcr:content", "nt:resource"); Node fold1 = assertNode(path + "/testFolder", "nt:folder"); @@ -116,7 +116,7 @@ public class ZipSequencerIntegrationTest extends AbstractSequencerTest { // Find the sequenced node ... String path = "/sequenced/zip/a/b/test-files.zip"; - Node zipped = assertNode(path, "zip:file"); + Node zipped = assertNode(path, "zip:file", "mode:derived"); Node file1 = assertNode(path + "/MODE-966-fix.patch", "nt:file"); Node data1 = assertNode(path + "/MODE-966-fix.patch/jcr:content", "nt:resource"); Node fold1 = assertNode(path + "/testFolder", "nt:folder"); Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd =================================================================== --- modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd (revision 2574) +++ modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd (working copy) @@ -84,3 +84,6 @@ // A marker node type that can be used to denote areas into which files can be published. // Published areas have optional titles and descriptions. [mode:publishArea] > mix:title mixin + +[mode:derived] mixin +- mode:derivedFrom (path) Index: modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java =================================================================== --- modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java (revision 2574) +++ modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java (working copy) @@ -51,6 +51,8 @@ public class ModeShapeLexicon extends org.modeshape.graph.ModeShapeLexicon { public static final Name CLUSTERING = new BasicName(Namespace.URI, "clustering"); public static final Name CONFIGURATION = new BasicName(Namespace.URI, "configuration"); public static final Name CLUSTER_NAME = new BasicName(Namespace.URI, "clusterName"); + public static final Name DERIVED = new BasicName(Namespace.URI, "derived"); + public static final Name DERIVED_FROM = new BasicName(Namespace.URI, "derivedFrom"); public static final Name INITIAL_CONTENT = new BasicName(Namespace.URI, "initialContent"); public static final Name CONTENT = new BasicName(Namespace.URI, "content"); Index: modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java =================================================================== --- modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java (revision 2574) +++ modeshape-repository/src/main/java/org/modeshape/repository/sequencer/StreamSequencerAdapter.java (working copy) @@ -26,6 +26,7 @@ package org.modeshape.repository.sequencer; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -41,6 +42,7 @@ import org.modeshape.graph.JcrLexicon; import org.modeshape.graph.JcrNtLexicon; import org.modeshape.graph.Location; import org.modeshape.graph.Node; +import org.modeshape.graph.io.Destination; import org.modeshape.graph.observe.NetChangeObserver.NetChange; import org.modeshape.graph.property.Binary; import org.modeshape.graph.property.Name; @@ -53,6 +55,7 @@ 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.ModeShapeLexicon; import org.modeshape.repository.RepositoryI18n; import org.modeshape.repository.util.RepositoryNodePath; @@ -61,13 +64,22 @@ import org.modeshape.repository.util.RepositoryNodePath; */ public class StreamSequencerAdapter implements Sequencer { + public static final boolean DEFAULT_ADD_DEFAULT_MIXIN = true; + private static final Logger LOGGER = Logger.getLogger(StreamSequencerAdapter.class); private SequencerConfig configuration; private final StreamSequencer streamSequencer; + private final boolean addDerivedMixin; public StreamSequencerAdapter( StreamSequencer streamSequencer ) { + this(streamSequencer, DEFAULT_ADD_DEFAULT_MIXIN); + } + + public StreamSequencerAdapter( StreamSequencer streamSequencer, + boolean addDerivedMixin ) { this.streamSequencer = streamSequencer; + this.addDerivedMixin = addDerivedMixin; } /** @@ -187,6 +199,7 @@ public class StreamSequencerAdapter implements Sequencer { Set builtPaths = new HashSet(); // Find each output node and save the image metadata there ... + final Path inputPath = input.getLocation().getPath(); for (RepositoryNodePath outputPath : outputPaths) { // Get the name of the repository source, workspace and the path to the output node final String repositoryWorkspaceName = outputPath.getWorkspaceName(); @@ -199,7 +212,7 @@ public class StreamSequencerAdapter implements Sequencer { // Node outputNode = context.graph().getNodeAt(nodePath); // Now save the image metadata to the output node ... - saveOutput(nodePath, output, context, builtPaths); + saveOutput(inputPath, nodePath, output, context, builtPaths); } context.getDestination().submit(); @@ -257,19 +270,21 @@ public class StreamSequencerAdapter implements Sequencer { * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the * caller of this method. * - * @param nodePath the existing node onto (or below) which the output is to be written; never null + * @param inputPath the existing node that was sequenced; never null + * @param outputPath the existing node onto (or below) which the output is to be written; never null * @param output the (immutable) sequencing output; never null * @param context the execution context for this sequencing operation; never null * @param builtPaths a set of the paths that have already been created but not submitted in this batch */ - protected void saveOutput( String nodePath, + protected void saveOutput( Path inputPath, + String outputPath, SequencerOutputMap output, SequencerContext context, Set builtPaths ) { if (output.isEmpty()) return; final PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory(); final PropertyFactory propertyFactory = context.getExecutionContext().getPropertyFactory(); - final Path outputNodePath = pathFactory.create(nodePath); + final Path outputNodePath = pathFactory.create(outputPath); // Get the existing list of children under the output path ... PathStrategy pathStrategy = null; @@ -297,6 +312,8 @@ public class StreamSequencerAdapter implements Sequencer { // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by // prefix and name) + final Set pathsOfTopLevelNodes = new HashSet(); + final Destination destination = context.getDestination(); for (SequencerOutputMap.Entry entry : output) { Path path = entry.getPath(); Path targetNodePath = pathStrategy.validate(path); @@ -304,7 +321,7 @@ public class StreamSequencerAdapter implements Sequencer { // Resolve this path relative to the output node path, handling any parent or self references ... Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath); - List properties = new LinkedList(); + Collection properties = new LinkedList(); // Set all of the properties on this for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) { Object value = property.getValue(); @@ -313,14 +330,65 @@ public class StreamSequencerAdapter implements Sequencer { // TODO: Handle reference properties - currently passed in as Paths } + if (targetNodePath.size() <= 1 && addDerivedMixin && pathsOfTopLevelNodes.add(absolutePath)) { + properties = addDerivedProperties(properties, context, inputPath); + } + if (absolutePath.getParent() != null) { buildPathTo(absolutePath.getParent(), context, builtPaths); } - context.getDestination().create(absolutePath, properties); + destination.create(absolutePath, properties); builtPaths.add(absolutePath); } } + protected Collection addDerivedProperties( Collection properties, + SequencerContext context, + Path inputPath ) { + Map propertiesByName = new HashMap(); + for (Property property : properties) { + propertiesByName.put(property.getName(), property); + } + + // Find the mixinTypes property and figure out the new value(s) for this property ... + Object values = null; + Property mixinTypes = propertiesByName.get(JcrLexicon.MIXIN_TYPES); + if (mixinTypes == null || mixinTypes.isEmpty()) { + values = ModeShapeLexicon.DERIVED; + } else { + // Add the mixin to the value(s) ... + if (mixinTypes.isSingle()) { + Name name = context.getExecutionContext().getValueFactories().getNameFactory().create(mixinTypes.getFirstValue()); + values = new Object[] {name, ModeShapeLexicon.DERIVED}; + } else { + Object[] oldValues = mixinTypes.getValuesAsArray(); + Object[] newValues = new Object[oldValues.length + 1]; + newValues[0] = ModeShapeLexicon.DERIVED; + System.arraycopy(oldValues, 0, newValues, 1, oldValues.length); + values = newValues; + } + } + PropertyFactory propertyFactory = context.getExecutionContext().getPropertyFactory(); + Property newMixinTypes = propertyFactory.create(JcrLexicon.MIXIN_TYPES, values); + propertiesByName.put(newMixinTypes.getName(), newMixinTypes); + + // Add the other 'mode:derived' property/properties ... + Property derivedFrom = propertiesByName.get(ModeShapeLexicon.DERIVED_FROM); + if (derivedFrom == null) { + // Only do this if the sequencer didn't already do this ... + assert inputPath != null; + if (!inputPath.isRoot() && inputPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) { + // We want to point to the sequenced 'nt:file' node, not the 'jcr:content' node ... + inputPath = inputPath.getParent(); + } + derivedFrom = propertyFactory.create(ModeShapeLexicon.DERIVED_FROM, inputPath); + propertiesByName.put(derivedFrom.getName(), derivedFrom); + } + + // Return the properties ... + return propertiesByName.values(); + } + protected String[] extractMixinTypes( Object value ) { if (value instanceof String[]) return (String[])value; if (value instanceof String) return new String[] {(String)value}; Index: modeshape-repository/src/test/java/org/modeshape/repository/sequencer/StreamSequencerAdapterTest.java =================================================================== --- modeshape-repository/src/test/java/org/modeshape/repository/sequencer/StreamSequencerAdapterTest.java (revision 2574) +++ modeshape-repository/src/test/java/org/modeshape/repository/sequencer/StreamSequencerAdapterTest.java (working copy) @@ -46,6 +46,7 @@ import org.modeshape.common.collection.Problems; import org.modeshape.common.collection.SimpleProblems; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; +import org.modeshape.graph.JcrLexicon; import org.modeshape.graph.Location; import org.modeshape.graph.Node; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; @@ -58,6 +59,7 @@ import org.modeshape.graph.property.Property; import org.modeshape.graph.sequencer.SequencerOutput; import org.modeshape.graph.sequencer.StreamSequencer; import org.modeshape.graph.sequencer.StreamSequencerContext; +import org.modeshape.repository.ModeShapeLexicon; import org.modeshape.repository.util.RepositoryNodePath; /** @@ -108,7 +110,7 @@ public class StreamSequencerAdapterTest { } } }; - sequencer = new StreamSequencerAdapter(streamSequencer); + sequencer = new StreamSequencerAdapter(streamSequencer, false); seqContext = new SequencerContext(context, graph, graph); } @@ -549,7 +551,7 @@ public class StreamSequencerAdapterTest { @Test public void shouldNotCreateExtraNodesWhenSavingOutput() throws Exception { SequencerOutputMap output = new SequencerOutputMap(context.getValueFactories()); - Map props; + Map props = null; /* * Create several output properties and make sure the resulting graph @@ -562,7 +564,7 @@ public class StreamSequencerAdapterTest { output.setProperty(path("a/b[2]/c"), name("property1"), "value1"); Set builtPaths = new HashSet(); - sequencer.saveOutput("/", output, seqContext, builtPaths); + sequencer.saveOutput(path("/input/path"), "/", output, seqContext, builtPaths); seqContext.getDestination().submit(); Node rootNode = graph.getNodeAt("/"); @@ -644,6 +646,60 @@ public class StreamSequencerAdapterTest { } + @FixFor( "MODE-1033" ) + @Test + public void shouldAddDerivedMixinAndDerivedFromPropertyToRootNodeOfSequencedOutput() { + sequencer = new StreamSequencerAdapter(streamSequencer, true); + SequencerOutputMap output = new SequencerOutputMap(context.getValueFactories()); + Map props = null; + + /* + * Create several output properties and make sure the resulting graph + * does not contain duplicate nodes + */ + output.setProperty(path("a"), name("property1"), "value1"); + output.setProperty(path("a/b"), name("property1"), "value1"); + output.setProperty(path("a/b"), name("property2"), "value2"); + output.setProperty(path("a/b[2]"), name("property1"), "value1"); + output.setProperty(path("a/b[2]/c"), name("property1"), "value1"); + + Set builtPaths = new HashSet(); + Path inputPath = path("/input/path"); + sequencer.saveOutput(inputPath, "/", output, seqContext, builtPaths); + seqContext.getDestination().submit(); + + Node rootNode = graph.getNodeAt("/"); + assertThat(rootNode.getChildren().size(), is(1)); + + Node nodeA = graph.getNodeAt("/a"); + props = nodeA.getPropertiesByName(); + + assertThat(nodeA.getChildren().size(), is(2)); + assertThat(props.size(), is(4)); // Need to add one to account for dna:uuid, jcr:mixinTypes and mode:derivedFrom + assertThat(props.get(nameFor("property1")).getFirstValue().toString(), is("value1")); + assertThat(props.get(JcrLexicon.MIXIN_TYPES).getFirstValue(), is((Object)ModeShapeLexicon.DERIVED)); + assertThat(props.get(ModeShapeLexicon.DERIVED_FROM).getFirstValue(), is((Object)inputPath)); + + Node nodeB = graph.getNodeAt("/a/b[1]"); + props = nodeB.getPropertiesByName(); + + assertThat(props.size(), is(3)); // Need to add one to account for dna:uuid + assertThat(props.get(nameFor("property1")).getFirstValue().toString(), is("value1")); + assertThat(props.get(nameFor("property2")).getFirstValue().toString(), is("value2")); + + Node nodeB2 = graph.getNodeAt("/a/b[2]"); + props = nodeB2.getPropertiesByName(); + + assertThat(props.size(), is(2)); // Need to add one to account for dna:uuid + assertThat(props.get(nameFor("property1")).getFirstValue().toString(), is("value1")); + + Node nodeC = graph.getNodeAt("/a/b[2]/c"); + props = nodeC.getPropertiesByName(); + + assertThat(props.size(), is(2)); // Need to add one to account for dna:uuid + assertThat(props.get(nameFor("property1")).getFirstValue().toString(), is("value1")); + } + private void verifyProperty( StreamSequencerContext context, String name, Object... values ) { Index: utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java =================================================================== --- utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java (revision 2574) +++ utils/modeshape-jdbc/src/test/java/org/modeshape/jdbc/JcrDriverIntegrationTest.java (working copy) @@ -484,6 +484,7 @@ public class JcrDriverIntegrationTest extends ConnectionResultsComparator { "cars NULL mix:title VIEW Is Mixin: true NULL NULL NULL null DERIVED", "cars NULL mix:versionable VIEW Is Mixin: true NULL NULL NULL null DERIVED", "cars NULL mode:defined VIEW Is Mixin: true NULL NULL NULL null DERIVED", + "cars NULL mode:derived VIEW Is Mixin: true NULL NULL NULL null DERIVED", "cars NULL mode:hashed VIEW Is Mixin: true NULL NULL NULL null DERIVED", "cars NULL mode:lock VIEW Is Mixin: false NULL NULL NULL null DERIVED", "cars NULL mode:locks VIEW Is Mixin: false NULL NULL NULL null DERIVED",