Index: modeshape-graph/src/main/java/org/modeshape/graph/Graph.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/Graph.java (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/Graph.java (working copy) @@ -2972,6 +2972,30 @@ public class Graph { return importXmlFrom(file.toURI()); } + /** + * Merge the content in the supplied graph with the content in this graph, ensuring that all of the content in the supplied + * graph exists in this graph. Note that any extra content is this graph but not in the supplied graph will not be affected. + * + * @param otherContent the content that should exist within this graph or, if it does not exist, added to this graph + * @return the interface for additional requests or actions + */ + public Conjunction merge( Graph otherContent ) { + GraphMerger initializer = new GraphMerger(otherContent); + Batch batch = batch(); + initializer.merge(this, batch); + batch.execute(); + return new Conjunction() { + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.Graph.Conjunction#and() + */ + public Graph and() { + return Graph.this; + } + }; + } + protected Path createPath( String path ) { return getContext().getValueFactories().getPathFactory().create(path); } @@ -4759,6 +4783,28 @@ public class Graph { } /** + * Ensure that this graph contains the content in the supplied graph. Any changes are recorded as operations on this + * batch, and require {@link #execute() execution}. + * + * @param otherContent the content that should exist within this graph or, if it does not exist, added to this graph + * @return the interface for additional requests or actions + */ + public Conjunction merge( Graph otherContent ) { + GraphMerger initializer = new GraphMerger(otherContent); + initializer.merge(this.getGraph(), this); + return new Conjunction() { + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.Graph.Conjunction#and() + */ + public Batch and() { + return Batch.this; + } + }; + } + + /** * {@inheritDoc} * * @see org.modeshape.graph.Graph.Executable#execute() @@ -6793,6 +6839,14 @@ public class Graph { return request.getChildren(); } + public List getChildren( Name namePattern ) { + List result = new ArrayList(); + for (Location child : getChildren()) { + if (child.getPath().endsWith(namePattern)) result.add(child); + } + return result; + } + public boolean hasChildren() { return request.getChildren().size() > 0; } @@ -7089,6 +7143,14 @@ public class Graph { return children; } + public List getChildren( Name namePattern ) { + List result = new ArrayList(); + for (Location child : getChildren()) { + if (child.getPath().endsWith(namePattern)) result.add(child); + } + return result; + } + public boolean hasChildren() { return children.size() != 0; } @@ -7315,6 +7377,14 @@ public class Graph { return children; } + public List getChildren( Name namePattern ) { + List result = new ArrayList(); + for (Location child : getChildren()) { + if (child.getPath().endsWith(namePattern)) result.add(child); + } + return result; + } + public Graph getGraph() { return Graph.this; } Index: modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (working copy) @@ -139,6 +139,11 @@ public final class GraphI18n { public static I18n updatesAllowedPropertyLabel; public static I18n updatesAllowedPropertyCategory; + /* XML File Connector */ + public static I18n contentPropertyDescription; + public static I18n contentPropertyLabel; + public static I18n contentPropertyCategory; + /* Federation Connection */ public static I18n namePropertyIsRequiredForFederatedRepositorySource; public static I18n propertyIsRequiredForFederatedRepositorySource; Index: modeshape-graph/src/main/java/org/modeshape/graph/GraphMerger.java new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/GraphMerger.java (working copy) @@ -0,0 +1,164 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.graph; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.common.collection.Collections; +import org.modeshape.common.util.CheckArg; +import org.modeshape.graph.Graph.Batch; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathNotFoundException; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.ValueComparators; + +/** + * A class that can ensure that specific content either exists within the workspace or is created as required. + */ +@NotThreadSafe +class GraphMerger { + + private final Graph initialContent; + private final Set ignoredProperties = Collections.unmodifiableSet(JcrLexicon.UUID, ModeShapeLexicon.UUID); + + protected GraphMerger( Graph initialContent ) { + CheckArg.isNotNull(initialContent, "initialContent"); + this.initialContent = initialContent; + } + + protected void merge( Graph actualGraph, + Graph.Batch batch ) { + CheckArg.isNotNull(actualGraph, "actualGraph"); + // Read the initial content ... + Subgraph subgraph = this.initialContent.getSubgraphOfDepth(Integer.MAX_VALUE).at("/"); + SubgraphNode desiredNode = subgraph.getRoot(); + + // Go through all of the areas and ensure the nodes exist ... + boolean checkProperties = true; + if (desiredNode.getLocation().getPath().isRoot()) { + // Don't need to check the root's properties ... + checkProperties = false; + } + Path path = desiredNode.getLocation().getPath(); + Node actualNode = actualGraph.getNodeAt(path); + matchNode(batch, actualGraph, actualNode, desiredNode, checkProperties, true); + } + + protected void createSubgraph( Batch batch, + SubgraphNode initialNode, + Path pathOfInitialNode ) { + // Create the node with the properties ... + batch.create(pathOfInitialNode).and(initialNode.getProperties()).ifAbsent().and(); + // And create the children ... + for (Location childLocation : initialNode.getChildren()) { + Path path = childLocation.getPath(); + SubgraphNode initialChild = initialNode.getNode(path.getLastSegment()); + createSubgraph(batch, initialChild, path); + } + } + + protected void matchProperties( Batch batch, + Node actualNode, + Node desiredNode ) { + Location actualLocation = actualNode.getLocation(); + assert actualLocation != null; + Collection desiredProperties = desiredNode.getProperties(); + if (desiredProperties.isEmpty()) return; + for (Property desiredProperty : desiredProperties) { + Property actual = actualNode.getProperty(desiredProperty.getName()); + boolean performSet = false; + if (actual == null) { + performSet = true; + } else { + if (ignoredProperties.contains(actual.getName())) continue; + // the actual property already exists ... + Iterator actualValues = actual.getValues(); + Iterator desiredValues = desiredProperty.getValues(); + while (actualValues.hasNext() && desiredValues.hasNext()) { + Object actualValue = actualValues.next(); + Object desiredValue = desiredValues.next(); + if (ValueComparators.OBJECT_COMPARATOR.compare(actualValue, desiredValue) != 0) { + performSet = true; + break; + } + } + if (!performSet && (actualValues.hasNext() || desiredValues.hasNext())) { + performSet = true; + } + } + if (performSet) { + batch.set(desiredProperty).on(actualLocation); + } + } + } + + protected void matchNode( Batch batch, + Graph actualContent, + Node actualNode, + SubgraphNode desiredNode, + boolean matchProperties, + boolean matchChildren ) { + Location actualLocation = actualNode.getLocation(); + if (actualLocation == null) { + // The node does not yet exist ... + Path path = desiredNode.getLocation().getPath(); + createSubgraph(batch, desiredNode, path); + batch.create(path).and(desiredNode.getProperties()).ifAbsent().and(); + } else { + // The node does exist ... + if (matchProperties) { + // So first check the properties ... + matchProperties(batch, actualNode, desiredNode); + } + + if (matchChildren) { + // Check the children ... + matchChildren(batch, actualContent, actualNode, desiredNode); + } + } + } + + protected void matchChildren( Batch batch, + Graph actualGraph, + Node actualNode, + SubgraphNode desiredNode ) { + // Go through the initial node and make sure all children exist under the actual ... + for (Location childLocation : desiredNode.getChildren()) { + Path path = childLocation.getPath(); + SubgraphNode desiredChild = desiredNode.getNode(path.getLastSegment()); + try { + Node actualChild = actualGraph.getNodeAt(childLocation); + // The child exists, so match up the node properties and children ... + matchNode(batch, actualGraph, actualChild, desiredChild, true, true); + } catch (PathNotFoundException e) { + // The node does not exist ... + createSubgraph(batch, desiredChild, path); + } + } + } + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/Node.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/Node.java (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/Node.java (working copy) @@ -97,6 +97,14 @@ public interface Node extends Iterable { List getChildren(); /** + * Get the children that have the supplied name. + * + * @param namePattern the name that each returned node should have + * @return the list of locations for each child with the supplied name; never null + */ + List getChildren( Name namePattern ); + + /** * Get the list of child {@link Path.Segment segments}. * * @return the list containing a segment for each child Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/xmlfile/XmlFileRepositorySource.java new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/xmlfile/XmlFileRepositorySource.java (working copy) @@ -0,0 +1,384 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.graph.connector.xmlfile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import javax.naming.BinaryRefAddr; +import javax.naming.Context; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; +import net.jcip.annotations.GuardedBy; +import net.jcip.annotations.ThreadSafe; +import org.modeshape.common.annotation.Category; +import org.modeshape.common.annotation.Description; +import org.modeshape.common.annotation.Label; +import org.modeshape.common.annotation.ReadOnly; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Graph; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.Subgraph; +import org.modeshape.graph.connector.RepositoryConnection; +import org.modeshape.graph.connector.RepositoryConnectionFactory; +import org.modeshape.graph.connector.RepositoryContext; +import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.RepositorySourceCapabilities; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; +import org.modeshape.graph.observe.Observer; +import org.xml.sax.SAXException; + +/** + * A {@link RepositorySource} for a in-memory repository with content defined by an XML file. Note that any changes made to the + * content are not currently persisted back to the XML file. + */ +@ThreadSafe +public class XmlFileRepositorySource implements RepositorySource, ObjectFactory { + + /** + * The initial version is 1 + */ + private static final long serialVersionUID = 1L; + + /** + * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source. + */ + public static final int DEFAULT_RETRY_LIMIT = 0; + + /** + * The default name for the workspace used by this source, which is a blank string. + */ + public static final String DEFAULT_WORKSPACE_NAME = ""; + + protected static final RepositorySourceCapabilities CAPABILITIES = new RepositorySourceCapabilities(true, false, false, + false, true); + + protected static final String CONTENT_ATTR = "content"; + protected static final String SOURCE_NAME_ATTR = "sourceName"; + protected static final String DEFAULT_WORKSPACE_NAME_ATTR = "defaultWorkspaceName"; + protected static final String RETRY_LIMIT_ATTR = "retryLimit"; + + @Description( i18n = GraphI18n.class, value = "namePropertyDescription" ) + @Label( i18n = GraphI18n.class, value = "namePropertyLabel" ) + @Category( i18n = GraphI18n.class, value = "namePropertyCategory" ) + @GuardedBy( "sourcesLock" ) + private String name; + + @Description( i18n = GraphI18n.class, value = "contentPropertyDescription" ) + @Label( i18n = GraphI18n.class, value = "contentPropertyLabel" ) + @Category( i18n = GraphI18n.class, value = "contentPropertyCategory" ) + @GuardedBy( "this" ) + private String content; + + @Description( i18n = GraphI18n.class, value = "defaultWorkspaceNamePropertyDescription" ) + @Label( i18n = GraphI18n.class, value = "defaultWorkspaceNamePropertyLabel" ) + @Category( i18n = GraphI18n.class, value = "defaultWorkspaceNamePropertyCategory" ) + private String defaultWorkspaceName = DEFAULT_WORKSPACE_NAME; + + @Description( i18n = GraphI18n.class, value = "retryLimitPropertyDescription" ) + @Label( i18n = GraphI18n.class, value = "retryLimitPropertyLabel" ) + @Category( i18n = GraphI18n.class, value = "retryLimitPropertyCategory" ) + private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); + + private transient InMemoryRepositorySource inMemorySource; + private transient ExecutionContext defaultContext = new ExecutionContext(); + private transient RepositoryContext repositoryContext = new DefaultRepositoryContext(); + + protected class DefaultRepositoryContext implements RepositoryContext { + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositoryContext#getExecutionContext() + */ + @SuppressWarnings( "synthetic-access" ) + public ExecutionContext getExecutionContext() { + return defaultContext; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositoryContext#getConfiguration(int) + */ + public Subgraph getConfiguration( int depth ) { + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositoryContext#getObserver() + */ + public Observer getObserver() { + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositoryContext#getRepositoryConnectionFactory() + */ + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return null; + } + } + + /** + * Create a repository source instance. + */ + public XmlFileRepositorySource() { + super(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#initialize(org.modeshape.graph.connector.RepositoryContext) + */ + public void initialize( RepositoryContext context ) throws RepositorySourceException { + this.repositoryContext = context != null ? context : new DefaultRepositoryContext(); + } + + /** + * @return repositoryContext + */ + public RepositoryContext getRepositoryContext() { + return repositoryContext; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#getRetryLimit() + */ + public int getRetryLimit() { + return retryLimit.get(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#setRetryLimit(int) + */ + public void setRetryLimit( int limit ) { + retryLimit.set(limit < 0 ? 0 : limit); + } + + /** + * Get the name of the workspace that should be used by default. + * + * @return the name of the default workspace + */ + public String getDefaultWorkspaceName() { + return defaultWorkspaceName; + } + + /** + * Set the default workspace name. + * + * @param defaultWorkspaceName the name of the workspace that should be used by default, or null if "" should be used + */ + public void setDefaultWorkspaceName( String defaultWorkspaceName ) { + this.defaultWorkspaceName = defaultWorkspaceName != null ? defaultWorkspaceName : DEFAULT_WORKSPACE_NAME; + } + + /** + * {@inheritDoc} + */ + public String getName() { + return this.name; + } + + /** + * @param name Sets name to the specified value. + */ + public void setName( String name ) { + this.name = name; + } + + /** + * Get the location where the initial content is defined. + * + * @return the URL, file path, or classpath resource path to the file containing the content, or null if there is no such + * content + */ + public String getContentLocation() { + return content; + } + + /** + * Set the location where the initial content is defined. + * + * @param uriOrFilePathOrResourcePath the URL, file path, or classpath resource path to the file containing the content + */ + public void setContentLocation( String uriOrFilePathOrResourcePath ) { + this.content = uriOrFilePathOrResourcePath; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#getConnection() + */ + public synchronized RepositoryConnection getConnection() throws RepositorySourceException { + if (inMemorySource == null) { + // Initialize the source and load the content ... + inMemorySource = new InMemoryRepositorySource(); + inMemorySource.setName(name); + + if (content != null && content.length() != 0) { + try { + ExecutionContext context = repositoryContext != null ? repositoryContext.getExecutionContext() : defaultContext; + Graph graph = Graph.create(inMemorySource, context); + graph.useWorkspace(defaultWorkspaceName); + graph.importXmlFrom(content).into("/"); + } catch (IOException e) { + inMemorySource = null; + throw new RepositorySourceException(getName(), e); + } catch (SAXException e) { + inMemorySource = null; + throw new RepositorySourceException(getName(), e); + } catch (RepositorySourceException e) { + inMemorySource = null; + throw e; + } catch (RuntimeException e) { + inMemorySource = null; + throw e; + } + } + } + return inMemorySource.getConnection(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#close() + */ + public synchronized void close() { + // Null the reference to the in-memory repository; open connections still reference it and can continue to work ... + this.inMemorySource = null; + } + + /** + * {@inheritDoc} + */ + public synchronized Reference getReference() { + String className = getClass().getName(); + String factoryClassName = this.getClass().getName(); + Reference ref = new Reference(className, factoryClassName, null); + + if (getName() != null) { + ref.add(new StringRefAddr(SOURCE_NAME_ATTR, getName())); + } + if (getContentLocation() != null) { + ref.add(new StringRefAddr(CONTENT_ATTR, getContentLocation().toString())); + } + if (getDefaultWorkspaceName() != null) { + ref.add(new StringRefAddr(DEFAULT_WORKSPACE_NAME_ATTR, getDefaultWorkspaceName())); + } + ref.add(new StringRefAddr(RETRY_LIMIT_ATTR, Integer.toString(getRetryLimit()))); + return ref; + } + + /** + * {@inheritDoc} + */ + public Object getObjectInstance( Object obj, + javax.naming.Name name, + Context nameCtx, + Hashtable environment ) throws Exception { + if (obj instanceof Reference) { + Map values = new HashMap(); + Reference ref = (Reference)obj; + Enumeration en = ref.getAll(); + while (en.hasMoreElements()) { + RefAddr subref = (RefAddr)en.nextElement(); + if (subref instanceof StringRefAddr) { + String key = subref.getType(); + Object value = subref.getContent(); + if (value != null) values.put(key, value.toString()); + } else if (subref instanceof BinaryRefAddr) { + String key = subref.getType(); + Object value = subref.getContent(); + if (value instanceof byte[]) { + // Deserialize ... + ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value); + ObjectInputStream ois = new ObjectInputStream(bais); + value = ois.readObject(); + values.put(key, value); + } + } + } + String sourceName = (String)values.get(SOURCE_NAME_ATTR); + String contentLocation = (String)values.get(CONTENT_ATTR); + String defaultWorkspaceName = (String)values.get(DEFAULT_WORKSPACE_NAME_ATTR); + String retryLimit = (String)values.get(RETRY_LIMIT_ATTR); + + // Create the source instance ... + XmlFileRepositorySource source = new XmlFileRepositorySource(); + if (sourceName != null) source.setName(sourceName); + if (contentLocation != null) source.setContentLocation(contentLocation); + if (defaultWorkspaceName != null) source.setDefaultWorkspaceName(defaultWorkspaceName); + if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); + return source; + } + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositorySource#getCapabilities() + */ + public RepositorySourceCapabilities getCapabilities() { + return CAPABILITIES; + } + + @Description( i18n = GraphI18n.class, value = "updatesAllowedPropertyDescription" ) + @Label( i18n = GraphI18n.class, value = "updatesAllowedPropertyLabel" ) + @Category( i18n = GraphI18n.class, value = "updatesAllowedPropertyCategory" ) + @ReadOnly + public boolean areUpdatesAllowed() { + return getCapabilities().supportsUpdates(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "The \"" + name + "\" XML file repository"; + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/xmlfile/package-info.java new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/xmlfile/package-info.java (working copy) @@ -0,0 +1,33 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +/** + * The XML File connector is a {@link org.modeshape.graph.connector.RepositorySource connector} that maintains + * a graph in transient in-process memory, where the graph is loaded initially from an XML file. (Note that + * any changes to the content are not serialized back to the XML file.) + * This connector is a good solution for a simple repository source + * that is very lightweight and of small to moderate sizes. + */ + +package org.modeshape.graph.connector.xmlfile; + Index: modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties =================================================================== --- modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (revision 2536) +++ modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (working copy) @@ -128,6 +128,10 @@ updatesAllowedPropertyDescription = Specifies whether the source content can be updatesAllowedPropertyLabel = Allows Updates updatesAllowedPropertyCategory = Advanced +# XML File connector +contentPropertyDescription = Specifies the URL, classpath resource path, or file system path for the XML file where this repository source is to find its initial content +contentPropertyLabel = Content Location +contentPropertyCategory = # Federation connector namePropertyIsRequiredForFederatedRepositorySource = The "{0}" property is required on each federated repository source Index: modeshape-graph/src/test/java/org/modeshape/graph/GraphInitializerTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-graph/src/test/java/org/modeshape/graph/GraphInitializerTest.java (working copy) @@ -0,0 +1,128 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.graph; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import java.net.URL; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; +import org.modeshape.graph.connector.xmlfile.XmlFileRepositorySource; +import org.modeshape.graph.property.PathNotFoundException; + +/** + * + */ +public class GraphInitializerTest { + + private ExecutionContext context; + private InMemoryRepositorySource graphSource; + private XmlFileRepositorySource initialContentSource; + private Graph graph; + private Graph initialContent; + private boolean print = false; + + @Before + public void beforeEaach() { + context = new ExecutionContext(); + + graphSource = new InMemoryRepositorySource(); + graphSource.setName("Graph source"); + + initialContentSource = new XmlFileRepositorySource(); + initialContentSource.setName("Initial content source"); + initialContentSource.setContentLocation(resourceUrl("aircraft.xml")); + + graph = Graph.create(graphSource, context); + initialContent = Graph.create(initialContentSource, context); + } + + @Test + public void shouldHaveNonEmptyInitialContent() { + assertNodeExists(initialContent, "Aircraft"); + } + + @Test + public void shouldInitializeEmptySource() { + assertNoChildren(graph, "/"); + graph.merge(initialContent); + // print = true; + printSubgraph(graph, "/"); + assertNodeExists(initialContent, "Aircraft"); + assertNodeDoesNotExist(initialContent, "Aircraft[2]"); + } + + @Test + public void shouldInitializeSourceWithOnlySomeNodes() { + assertNoChildren(graph, "/"); + graph.create("/Aircraft").and(); + graph.merge(initialContent); + // print = true; + printSubgraph(graph, "/"); + assertNodeExists(initialContent, "Aircraft"); + assertNodeDoesNotExist(initialContent, "Aircraft[2]"); + } + + protected void assertNoChildren( Graph graph, + String path ) { + assertNodeExists(graph, path); + List children = graph.getChildren().of(path); + assertThat(children.isEmpty(), is(true)); + } + + protected Node assertNodeExists( Graph graph, + String path ) { + Node node = graph.getNodeAt(path); + assertThat(node, is(notNullValue())); + return node; + } + + protected void assertNodeDoesNotExist( Graph graph, + String path ) { + try { + graph.getNodeAt(path); + fail("Node does exist at \"" + path + "\""); + } catch (PathNotFoundException e) { + // expected ... + } + } + + protected void printSubgraph( Graph graph, + String path ) { + if (print) { + Subgraph subgraph = graph.getSubgraphOfDepth(Integer.MAX_VALUE).at(path); + System.out.println(subgraph); + } + } + + protected String resourceUrl( String path ) { + URL url = this.getClass().getClassLoader().getResource(path); + return url != null ? url.toExternalForm() : path; + } + +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java (revision 2536) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java (working copy) @@ -57,6 +57,7 @@ import org.modeshape.graph.Subgraph; import org.modeshape.graph.connector.RepositoryConnectionFactory; import org.modeshape.graph.connector.RepositorySource; import org.modeshape.graph.connector.RepositorySourceCapabilities; +import org.modeshape.graph.connector.xmlfile.XmlFileRepositorySource; import org.modeshape.graph.io.GraphBatchDestination; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NamespaceRegistry; @@ -320,7 +321,7 @@ public class JcrEngine extends ModeShapeEngine implements Repositories { Path.Segment segment = optionLocation.getPath().getLastSegment(); Property valueProperty = optionNode.getProperty(ModeShapeLexicon.VALUE); if (valueProperty == null) { - log.warn(JcrI18n.noOptionValueProvided,segment.getName().getLocalName()); + log.warn(JcrI18n.noOptionValueProvided, segment.getName().getLocalName()); continue; } Option option = Option.findOption(segment.getName().getLocalName()); @@ -370,12 +371,65 @@ public class JcrEngine extends ModeShapeEngine implements Repositories { throw new RepositoryException(JcrI18n.repositoryReferencesNonExistantSource.text(repositoryName, sourceName)); } + // Read the initial content ... + String initialContentForNewWorkspaces = null; + for (Location initialContentLocation : subgraph.getRoot().getChildren(ModeShapeLexicon.INITIAL_CONTENT)) { + Node initialContent = subgraph.getNode(initialContentLocation); + if (initialContent == null) continue; + + // Determine where to load the initial content from ... + Property contentReference = initialContent.getProperty(ModeShapeLexicon.CONTENT); + if (contentReference == null || contentReference.isEmpty()) { + String readableName = readable(ModeShapeLexicon.CONTENT); + String readablePath = readable(initialContentLocation); + String msg = JcrI18n.propertyNotFoundOnNode.text(readableName, + readablePath, + configuration.getCurrentWorkspaceName()); + throw new RepositoryException(msg); + } + String contentRef = string(contentReference.getFirstValue()); + + // Determine which workspaces this should apply to ... + Property workspaces = initialContent.getProperty(ModeShapeLexicon.WORKSPACES); + if (workspaces == null || workspaces.isEmpty()) { + String readableName = readable(ModeShapeLexicon.WORKSPACES); + String readablePath = readable(initialContentLocation); + String msg = JcrI18n.propertyNotFoundOnNode.text(readableName, + readablePath, + configuration.getCurrentWorkspaceName()); + throw new RepositoryException(msg); + } + + // Load the initial content into a transient source ... + XmlFileRepositorySource initialContentSource = new XmlFileRepositorySource(); + initialContentSource.setName("Initial content for " + repositoryName); + initialContentSource.setContentLocation(contentRef); + Graph initialContentGraph = Graph.create(initialContentSource, context); + Graph sourceGraph = Graph.create(sourceName, connectionFactory, context); + + // And initialize the source with the content (if not already there) ... + for (Object value : workspaces) { + String workspaceName = string(value); + if (workspaceName != null && workspaceName.trim().length() != 0) { + // Load the content into the workspace with this name ... + sourceGraph.useWorkspace(workspaceName); + sourceGraph.merge(initialContentGraph); + } + } + + // Determine if this initial content should apply to new workspaces ... + Property applyToNewWorkspaces = initialContent.getProperty(ModeShapeLexicon.APPLY_TO_NEW_WORKSPACES); + if (applyToNewWorkspaces != null && !applyToNewWorkspaces.isEmpty() && isTrue(applyToNewWorkspaces.getFirstValue())) { + initialContentForNewWorkspaces = contentRef; // may overwrite the value if seen more than once! + } + } + // Find the capabilities ... RepositorySourceCapabilities capabilities = source.getCapabilities(); // Create the repository ... JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, getRepositoryService().getRepositoryLibrary(), capabilities, descriptors, - options); + options, initialContentForNewWorkspaces); // Register all the the node types ... Node nodeTypesNode = subgraph.getNode(JcrLexicon.NODE_TYPES); @@ -466,6 +520,10 @@ public class JcrEngine extends ModeShapeEngine implements Repositories { return context.getValueFactories().getStringFactory().create(value); } + protected final boolean isTrue( Object value ) { + return context.getValueFactories().getBooleanFactory().create(value); + } + /** * @return descriptors */ Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 2536) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -84,6 +84,7 @@ import org.modeshape.graph.connector.federation.FederatedRepositorySource; import org.modeshape.graph.connector.federation.Projection; import org.modeshape.graph.connector.federation.ProjectionParser; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; +import org.modeshape.graph.connector.xmlfile.XmlFileRepositorySource; import org.modeshape.graph.observe.Changes; import org.modeshape.graph.observe.Observable; import org.modeshape.graph.observe.Observer; @@ -481,6 +482,9 @@ public class JcrRepository implements Repository { // Until the federated connector supports queries, we have to use a search engine ... private final RepositoryQueryManager queryManager; + /* The location of the XML file containing the initial content for newly-created workspaces */ + private final String initialContentForNewWorkspaces; + // package-scoped to facilitate testing final WeakHashMap activeSessions = new WeakHashMap(); @@ -496,6 +500,8 @@ public class JcrRepository implements Repository { * known * @param descriptors the {@link #getDescriptorKeys() descriptors} for this repository; may be null. * @param options the optional {@link Option settings} for this repository; may be null + * @param initialContentForNewWorkspaces the URL, file system path, or classpath resource path to the XML file containing the + * initial content for newly-created workspaces; may be null * @throws RepositoryException if there is a problem setting up this repository * @throws IllegalArgumentException If executionContext, connectionFactory, * repositorySourceName, or repositoryObservable is null. @@ -507,7 +513,8 @@ public class JcrRepository implements Repository { Observable repositoryObservable, RepositorySourceCapabilities repositorySourceCapabilities, Map descriptors, - Map options ) throws RepositoryException { + Map options, + String initialContentForNewWorkspaces ) throws RepositoryException { CheckArg.isNotNull(executionContext, "executionContext"); CheckArg.isNotNull(connectionFactory, "connectionFactory"); CheckArg.isNotNull(repositorySourceName, "repositorySourceName"); @@ -589,6 +596,7 @@ public class JcrRepository implements Repository { assert this.systemSourceName != null; assert this.connectionFactory != null; this.sourceName = repositorySourceName; + this.initialContentForNewWorkspaces = initialContentForNewWorkspaces; // Set up the "/jcr:system" branch ... Graph systemGraph = Graph.create(this.systemSourceName, this.connectionFactory, executionContext); @@ -869,6 +877,15 @@ public class JcrRepository implements Repository { } else { graphWorkspace = graph.createWorkspace().named(workspaceName); } + + // Ensure the workspace contains the initial content (if there is any) ... + if (initialContentForNewWorkspaces != null) { + XmlFileRepositorySource initialContentSource = new XmlFileRepositorySource(); + initialContentSource.setName("Initial content for " + sourceName); + initialContentSource.setContentLocation(initialContentForNewWorkspaces); + Graph initialContentGraph = Graph.create(initialContentSource, executionContext); + graph.merge(initialContentGraph); // uses its own batch + } String actualName = graphWorkspace.getName(); addWorkspace(actualName, false); updateWorkspaceNames(); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrAccessTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrAccessTest.java (revision 2536) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrAccessTest.java (working copy) @@ -83,10 +83,9 @@ public abstract class AbstractJcrAccessTest { } }; - repository = new JcrRepository(context, connectionFactory, "unused", new MockObservable(), null, null, null); + repository = new JcrRepository(context, connectionFactory, "unused", new MockObservable(), null, null, null, null); - SecurityContext mockSecurityContext = new MockSecurityContext("testuser", - Collections.singleton(ModeShapeRoles.READWRITE)); + SecurityContext mockSecurityContext = new MockSecurityContext("testuser", Collections.singleton(ModeShapeRoles.READWRITE)); session = (JcrSession)repository.login(new JcrSecurityContextCredentials(mockSecurityContext)); } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/ImportExportTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/ImportExportTest.java (revision 2536) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/ImportExportTest.java (working copy) @@ -92,7 +92,7 @@ public class ImportExportTest { } }; - repository = new JcrRepository(context, connectionFactory, "unused", new MockObservable(), null, null, null); + repository = new JcrRepository(context, connectionFactory, "unused", new MockObservable(), null, null, null, null); SecurityContext mockSecurityContext = new MockSecurityContext("testuser", Collections.singleton(ModeShapeRoles.ADMIN)); session = (JcrSession)repository.login(new JcrSecurityContextCredentials(mockSecurityContext)); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrEngineTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrEngineTest.java (revision 2536) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrEngineTest.java (working copy) @@ -126,6 +126,22 @@ public class JcrEngineTest { engine.start(); } + @Test + public void shouldCreateRepositoryEngineFromConfigurationFileWithInitialContentInRepository() throws Exception { + configuration = new JcrConfiguration().loadFrom("src/test/resources/config/configRepositoryWithInitialContent.xml"); + engine = configuration.build(); + engine.start(); + repository = engine.getRepository("My Repository"); + session = repository.login(); + + javax.jcr.Node cars = session.getRootNode().getNode("Cars"); + javax.jcr.Node prius = session.getRootNode().getNode("Cars/Hybrid/Toyota Prius"); + javax.jcr.Node g37 = session.getRootNode().getNode("Cars/Sports/Infiniti G37"); + assertThat(cars, is(notNullValue())); + assertThat(prius, is(notNullValue())); + assertThat(g37, is(notNullValue())); + } + // @Test // public void shouldCreateRepositoryConfiguredWithOneXmlNodeTypeDefinitionFiles() throws Exception { // configuration = new JcrConfiguration(); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (revision 2536) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (working copy) @@ -43,6 +43,7 @@ import javax.jcr.Repository; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.ValueFormatException; +import javax.jcr.nodetype.NodeTypeDefinition; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import org.jboss.security.config.IDTrustConfiguration; @@ -121,7 +122,8 @@ public class JcrRepositoryTest { // Set up the repository ... descriptors = new HashMap(); - repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null, + null); // Set up the graph that goes directly to the source ... sourceGraph = Graph.create(source(), context); @@ -153,27 +155,27 @@ public class JcrRepositoryTest { @Test public void shouldAllowNullDescriptors() throws Exception { - new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, null, null); + new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, null, null, null); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowNullExecutionContext() throws Exception { - new JcrRepository(null, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + new JcrRepository(null, connectionFactory, sourceName, new MockObservable(), null, descriptors, null, null); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowNullConnectionFactories() throws Exception { - new JcrRepository(context, null, sourceName, new MockObservable(), null, descriptors, null); + new JcrRepository(context, null, sourceName, new MockObservable(), null, descriptors, null, null); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowNullObservable() throws Exception { - new JcrRepository(context, connectionFactory, sourceName, null, null, null, null); + new JcrRepository(context, connectionFactory, sourceName, null, null, null, null, null); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowNullSourceName() throws Exception { - new JcrRepository(context, connectionFactory, null, new MockObservable(), null, descriptors, null); + new JcrRepository(context, connectionFactory, null, new MockObservable(), null, descriptors, null, null); } @Test( expected = IllegalArgumentException.class ) @@ -199,7 +201,7 @@ public class JcrRepositoryTest { @Test public void shouldProvideBuiltInDescriptorsWhenNotSuppliedDescriptors() throws Exception { Repository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, null); + descriptors, null, null); testDescriptorKeys(repository); testDescriptorValues(repository); } @@ -218,7 +220,9 @@ public class JcrRepositoryTest { public void shouldNotProvideRepositoryWorkspaceNamesDescriptorIfOptionSetToFalse() throws Exception { JcrConfiguration config = new JcrConfiguration(); config.repositorySource("Store").usingClass(InMemoryRepositorySource.class); - config.repository("JCR").setOption(JcrRepository.Option.EXPOSE_WORKSPACE_NAMES_IN_DESCRIPTOR, Boolean.FALSE.toString()).setSource("Store"); + config.repository("JCR") + .setOption(JcrRepository.Option.EXPOSE_WORKSPACE_NAMES_IN_DESCRIPTOR, Boolean.FALSE.toString()) + .setSource("Store"); JcrEngine engine = config.build(); engine.start(); @@ -242,7 +246,7 @@ public class JcrRepositoryTest { @Test public void shouldHaveDefaultOptionsWhenNotOverridden() throws Exception { JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, null); + descriptors, null, null); assertThat(repository.getOptions().get(JcrRepository.Option.PROJECT_NODE_TYPES), is(JcrRepository.DefaultOption.PROJECT_NODE_TYPES)); } @@ -252,7 +256,7 @@ public class JcrRepositoryTest { Map descriptors = new HashMap(); descriptors.put("property", "value"); Repository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, null); + descriptors, null, null); testDescriptorKeys(repository); testDescriptorValues(repository); assertThat(repository.getDescriptor("property"), is("value")); @@ -263,7 +267,8 @@ public class JcrRepositoryTest { // This would work iff this code was executing in a privileged block, but it's not Map options = new HashMap(); options.put(Option.ANONYMOUS_USER_ROLES, ""); // disable anonymous authentication - repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, options); + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, options, + null); repository.login(); } @@ -300,7 +305,7 @@ public class JcrRepositoryTest { Map options = new HashMap(); options.put(JcrRepository.Option.ANONYMOUS_USER_ROLES, ModeShapeRoles.READONLY); JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, options); + descriptors, options, null); session = (JcrSession)repository.login(); @@ -498,6 +503,23 @@ public class JcrRepositoryTest { return session; } + protected JcrSession createSession( final String workspace ) throws Exception { + LoginContext login = new LoginContext("modeshape-jcr", new UserPasswordCallbackHandler("superuser", + "superuser".toCharArray())); + login.login(); + + Subject subject = login.getSubject(); + JcrSession session = (JcrSession)Subject.doAsPrivileged(subject, new PrivilegedExceptionAction() { + + @SuppressWarnings( "synthetic-access" ) + public Session run() throws Exception { + return repository.login(workspace); + } + + }, AccessController.getContext()); + return session; + } + @SuppressWarnings( "deprecation" ) private void testDescriptorKeys( Repository repository ) { String[] keys = repository.getDescriptorKeys(); @@ -547,7 +569,7 @@ public class JcrRepositoryTest { Map options = new HashMap(); options.put(JcrRepository.Option.ANONYMOUS_USER_ROLES, ModeShapeRoles.ADMIN); JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, options); + descriptors, options, null); Session session; @@ -573,7 +595,7 @@ public class JcrRepositoryTest { Map options = new HashMap(); options.put(JcrRepository.Option.ANONYMOUS_USER_ROLES, ModeShapeRoles.ADMIN); JcrRepository repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, - descriptors, options); + descriptors, options, null); String lockedNodeName = "lockedNode"; JcrSession locker = (JcrSession)repository.login(); @@ -619,7 +641,8 @@ public class JcrRepositoryTest { sourceGraph = Graph.create(source(), context); // Create the repository ... - repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null, + null); // Get the available workspaces ... session = createSession(); @@ -640,7 +663,8 @@ public class JcrRepositoryTest { sourceGraph = Graph.create(source(), context); // Create the repository ... - repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null); + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), null, descriptors, null, + null); // Get the available workspaces ... session = createSession(); @@ -655,4 +679,38 @@ public class JcrRepositoryTest { return org.modeshape.common.collection.Collections.unmodifiableSet(values); } + @Test + public void shouldInitializeContentForNewlyCreatedWorkspacesIfDefined() throws Exception { + String urlToResourceFile = getClass().getClassLoader().getResource("initialWorkspaceContent.xml").toExternalForm(); + String urlToCndFile = getClass().getClassLoader().getResource("cars.cnd").toExternalForm(); + + // Create the JcrRepositoyr instance ... + assertThat(source.getCapabilities().supportsCreatingWorkspaces(), is(true)); + descriptors.put(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED, "true"); + repository = new JcrRepository(context, connectionFactory, sourceName, new MockObservable(), source.getCapabilities(), + descriptors, null, urlToResourceFile); + + // Load the node types ... + session = createSession(); + CndNodeTypeReader nodeTypeReader = new CndNodeTypeReader(session); + nodeTypeReader.read(urlToCndFile); + NodeTypeDefinition[] defns = nodeTypeReader.getNodeTypeDefinitions(); + session.getWorkspace().getNodeTypeManager().registerNodeTypes(defns, true); + session.logout(); + + // Create a new workspace ... + session = createSession(); + session.getWorkspace().createWorkspace("MyCarWorkspace"); + session.logout(); + + // Check that the new workspace contains the initial content ... + session = createSession("MyCarWorkspace"); + javax.jcr.Node cars = session.getRootNode().getNode("Cars"); + javax.jcr.Node prius = session.getRootNode().getNode("Cars/Hybrid/Toyota Prius"); + javax.jcr.Node g37 = session.getRootNode().getNode("Cars/Sports/Infiniti G37"); + assertThat(cars, is(notNullValue())); + assertThat(prius, is(notNullValue())); + assertThat(g37, is(notNullValue())); + } + } Index: modeshape-jcr/src/test/resources/config/configRepositoryWithInitialContent.xml new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-jcr/src/test/resources/config/configRepositoryWithInitialContent.xml (working copy) @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + src/test/resources/cars.cnd + + + + + Index: modeshape-jcr/src/test/resources/initialWorkspaceContent.xml new file mode 100644 =================================================================== --- /dev/null (revision 2536) +++ modeshape-jcr/src/test/resources/initialWorkspaceContent.xml (working copy) @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + Index: modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java =================================================================== --- modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java (revision 2536) +++ modeshape-repository/src/main/java/org/modeshape/repository/ModeShapeLexicon.java (working copy) @@ -51,4 +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 INITIAL_CONTENT = new BasicName(Namespace.URI, "initialContent"); + public static final Name CONTENT = new BasicName(Namespace.URI, "content"); + public static final Name APPLY_TO_NEW_WORKSPACES = new BasicName(Namespace.URI, "applyToNewWorkspaces"); }