Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanConnectorI18n.java =================================================================== --- extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanConnectorI18n.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanConnectorI18n.java (working copy) @@ -10,17 +10,10 @@ import org.modeshape.common.i18n.I18n; public final class InfinispanConnectorI18n { public static I18n connectorName; - public static I18n nodeDoesNotExist; public static I18n propertyIsRequired; public static I18n errorSerializingCachePolicyInSource; public static I18n objectFoundInJndiWasNotCacheManager; - // public static I18n unableToCloneWorkspaces; - // public static I18n unableToCreateWorkspaces; public static I18n unableToCreateWorkspace; - public static I18n workspaceAlreadyExists; - public static I18n workspaceDoesNotExist; - public static I18n workspaceNameWasNotValidConfiguration; - public static I18n defaultCacheManagerConfigurationNameWasNotValidConfiguration; static { try { Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanNode.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanNode.java (working copy) @@ -0,0 +1,96 @@ +/* + * 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.connector.infinispan; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.modeshape.graph.connector.base.MapNode; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.Path.Segment; + +/** + * A specialization of the {@link MapNode}. + */ +public class InfinispanNode extends MapNode { + + private static final long serialVersionUID = 1L; + + /** + * Create a new node for storage inside Infinispan. + * + * @param uuid the desired UUID; never null + * @param name the name of the new node; may be null if the name is not known and there is no parent + * @param parent the UUID of the parent node; may be null if this is the root node and there is no name + * @param properties the properties; may be null if there are no properties + * @param children the list of child nodes; may be null + */ + public InfinispanNode( UUID uuid, + Segment name, + UUID parent, + Map properties, + List children ) { + super(uuid, name, parent, properties, children); + } + + /** + * Create a new node for storage inside Infinispan. + * + * @param uuid the desired UUID; never null + * @param name the name of the new node; may be null if the name is not known and there is no parent + * @param parent the UUID of the parent node; may be null if this is the root node and there is no name + * @param properties the properties; may be null if there are no properties + * @param children the list of child nodes; may be null + */ + public InfinispanNode( UUID uuid, + Segment name, + UUID parent, + Iterable properties, + List children ) { + super(uuid, name, parent, properties, children); + } + + /** + * Create a new node for storage inside Infinispan. + * + * @param uuid the desired UUID; never null + */ + public InfinispanNode( UUID uuid ) { + super(uuid); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.MapNode#freeze() + */ + @Override + public InfinispanNode freeze() { + if (!hasChanges()) return this; + return new InfinispanNode(getUuid(), getName(), getParent(), changes.getUnmodifiableProperties(), + changes.getUnmodifiableChildren()); + } + +} Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java =================================================================== --- extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java (working copy) @@ -1,136 +1,105 @@ +/* + * 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.connector.infinispan; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.infinispan.Cache; +import net.jcip.annotations.ThreadSafe; import org.infinispan.manager.CacheManager; import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.connector.LockFailedException; -import org.modeshape.graph.connector.map.AbstractMapWorkspace; -import org.modeshape.graph.connector.map.LockBasedTransaction; -import org.modeshape.graph.connector.map.MapNode; -import org.modeshape.graph.connector.map.MapRepository; -import org.modeshape.graph.connector.map.MapRepositoryTransaction; -import org.modeshape.graph.connector.map.MapWorkspace; -import org.modeshape.graph.request.LockBranchRequest.LockScope; +import org.modeshape.graph.connector.base.Repository; /** - * The repository that uses an Infinispan instance. + * The representation of an in-memory repository and its content. */ -public class InfinispanRepository extends MapRepository { +@ThreadSafe +public class InfinispanRepository extends Repository { - protected final ReadWriteLock lock = new ReentrantReadWriteLock(); private final CacheManager cacheManager; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Set predefinedWorkspaceNames; - public InfinispanRepository( String sourceName, - UUID rootNodeUuid, - CacheManager cacheManager ) { - super(sourceName, rootNodeUuid, null); - assert cacheManager != null; - this.cacheManager = cacheManager; - initialize(); - } - - public InfinispanRepository( String sourceName, + public InfinispanRepository( ExecutionContext context, + String sourceName, UUID rootNodeUuid, String defaultWorkspaceName, - CacheManager cacheManager ) { - super(sourceName, rootNodeUuid, defaultWorkspaceName); - - assert cacheManager != null; + CacheManager cacheManager, + String... predefinedWorkspaceNames ) { + super(context, sourceName, rootNodeUuid, defaultWorkspaceName); this.cacheManager = cacheManager; - + assert this.cacheManager != null; + Set workspaceNames = new HashSet(); + for (String workspaceName : predefinedWorkspaceNames) { + workspaceNames.add(workspaceName); + } + this.predefinedWorkspaceNames = Collections.unmodifiableSet(workspaceNames); initialize(); } - @Override - protected MapWorkspace createWorkspace( ExecutionContext context, - String name ) { - assert name != null; - assert cacheManager != null; - Cache newWorkspaceCache = cacheManager.getCache(name); - return new Workspace(this, name, newWorkspaceCache); - } - /** * {@inheritDoc} * - * @see org.modeshape.graph.connector.map.MapRepository#startTransaction(boolean) + * @see org.modeshape.graph.connector.base.Repository#getWorkspaceNames() */ @Override - public MapRepositoryTransaction startTransaction( boolean readonly ) { - return new LockBasedTransaction(readonly ? lock.readLock() : lock.writeLock()) { - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.LockBasedTransaction#commit() - */ - @Override - public void commit() { - super.commit(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.LockBasedTransaction#rollback() - */ - @Override - public void rollback() { - super.rollback(); - } - }; + public Set getWorkspaceNames() { + Set names = new HashSet(super.getWorkspaceNames()); + names.addAll(predefinedWorkspaceNames); + return Collections.unmodifiableSet(names); } - protected ReadWriteLock getLock() { - return lock; + /** + * Get the cache manager used by this repository. + * + * @return the cacheManager; never null + */ + protected CacheManager getCacheManager() { + return cacheManager; } - protected class Workspace extends AbstractMapWorkspace { - private final Cache workspaceCache; - - public Workspace( MapRepository repository, - String name, - Cache workspaceCache ) { - super(repository, name); - - this.workspaceCache = workspaceCache; - initialize(); - } - - @Override - protected void addNodeToMap( MapNode node ) { - assert node != null; - workspaceCache.put(node.getUuid(), node); - } - - @Override - protected MapNode removeNodeFromMap( UUID nodeUuid ) { - assert nodeUuid != null; - return workspaceCache.remove(nodeUuid); - } - - @Override - protected void removeAllNodesFromMap() { - workspaceCache.clear(); - } - - @Override - public MapNode getNode( UUID nodeUuid ) { - assert nodeUuid != null; - return workspaceCache.get(nodeUuid); - } - - public void lockNode( MapNode node, - LockScope lockScope, - long lockTimeoutInMillis ) throws LockFailedException { - // Locking is not supported by this connector - } - - public void unlockNode( MapNode node ) { - // Locking is not supported by this connector - } + /** + * This method shuts down the workspace and makes it no longer usable. This method should also only be called once. + */ + public void shutdown() { + this.cacheManager.stop(); } + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Repository#startTransaction(org.modeshape.graph.ExecutionContext, boolean) + */ + @Override + public InfinispanTransaction startTransaction( ExecutionContext context, + boolean readonly ) { + final Lock lock = readonly ? this.lock.readLock() : this.lock.writeLock(); + lock.lock(); + return new InfinispanTransaction(this, getRootNodeUuid(), lock); + } } Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanSource.java =================================================================== --- extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanSource.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanSource.java (working copy) @@ -50,16 +50,16 @@ import org.infinispan.manager.DefaultCacheManager; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.HashCode; import org.modeshape.common.util.StringUtil; +import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.cache.CachePolicy; import org.modeshape.graph.connector.RepositoryConnection; 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.map.MapRepositoryConnection; -import org.modeshape.graph.connector.map.MapRepositorySource; +import org.modeshape.graph.connector.base.BaseRepositorySource; +import org.modeshape.graph.connector.base.Connection; import org.modeshape.graph.observe.Observer; -import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; /** * A repository source that uses an Infinispan instance to manage the content. This source is capable of using an existing @@ -76,7 +76,7 @@ import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior *

*/ @ThreadSafe -public class InfinispanSource implements MapRepositorySource, ObjectFactory { +public class InfinispanSource implements BaseRepositorySource, ObjectFactory { private static final long serialVersionUID = 2L; /** @@ -346,6 +346,9 @@ public class InfinispanSource implements MapRepositorySource, ObjectFactory { * @see #getPredefinedWorkspaceNames() */ public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) { + if (predefinedWorkspaceNames != null && predefinedWorkspaceNames.length == 1) { + predefinedWorkspaceNames = predefinedWorkspaceNames[0].split("\\s*,\\s*"); + } this.predefinedWorkspaces = predefinedWorkspaceNames; } @@ -416,15 +419,25 @@ public class InfinispanSource implements MapRepositorySource, ObjectFactory { if (cacheManager == null) cacheManager = new DefaultCacheManager(); // Now create the repository ... - this.repository = new InfinispanRepository(this.name, this.rootNodeUuid, this.defaultWorkspace, cacheManager); - - // Create the set of initial names ... - for (String initialName : getPredefinedWorkspaceNames()) - repository.createWorkspace(null, initialName, CreateConflictBehavior.DO_NOT_CREATE); - + ExecutionContext execContext = repositoryContext.getExecutionContext(); + this.repository = new InfinispanRepository(execContext, this.name, this.rootNodeUuid, this.defaultWorkspace, + cacheManager, this.predefinedWorkspaces); + + // // Create the set of initial workspaces ... + // String[] workspaceNames = getPredefinedWorkspaceNames(); + // if (workspaceNames.length != 0) { + // InfinispanTransaction txn = repository.startTransaction(execContext, false); + // try { + // for (String initialName : getPredefinedWorkspaceNames()) { + // repository.createWorkspace(txn, initialName, CreateConflictBehavior.DO_NOT_CREATE, null); + // } + // } finally { + // txn.commit(); + // } + // } } - return new MapRepositoryConnection(this, this.repository); + return new Connection(this, repository); } /** @@ -433,8 +446,13 @@ public class InfinispanSource implements MapRepositorySource, ObjectFactory { * @see org.modeshape.graph.connector.RepositorySource#close() */ public synchronized void close() { - // Null the reference to the repository; open connections still reference it and can continue to work ... - this.repository = null; + if (this.repository != null) { + try { + this.repository.shutdown(); + } finally { + this.repository = null; + } + } } /** Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanTransaction.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanTransaction.java (working copy) @@ -0,0 +1,134 @@ +/* + * 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.connector.infinispan; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import net.jcip.annotations.NotThreadSafe; +import org.infinispan.Cache; +import org.modeshape.graph.connector.base.MapTransaction; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.Path.Segment; +import org.modeshape.graph.request.InvalidWorkspaceException; + +/** + * + */ +@NotThreadSafe +public class InfinispanTransaction extends MapTransaction { + + private final InfinispanRepository repository; + private final Lock lock; + + protected InfinispanTransaction( InfinispanRepository repository, + UUID rootNodeUuid, + Lock lock ) { + super(repository, rootNodeUuid); + this.repository = repository; + this.lock = lock; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getWorkspaceNames() + */ + public Set getWorkspaceNames() { + return repository.getWorkspaceNames(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getWorkspace(java.lang.String, + * org.modeshape.graph.connector.base.Workspace) + */ + public InfinispanWorkspace getWorkspace( String name, + InfinispanWorkspace originalToClone ) { + Cache workspaceCache = repository.getCacheManager().getCache(name); + if (workspaceCache == null) { + String msg = InfinispanConnectorI18n.unableToCreateWorkspace.text(name, repository.getSourceName()); + throw new InvalidWorkspaceException(msg); + } + if (originalToClone != null) { + return new InfinispanWorkspace(name, workspaceCache, originalToClone); + } + return new InfinispanWorkspace(name, workspaceCache, new InfinispanNode(repository.getRootNodeUuid())); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#destroyWorkspace(org.modeshape.graph.connector.base.Workspace) + */ + public boolean destroyWorkspace( InfinispanWorkspace workspace ) { + // Can't seem to tell Infinispan to destroy the cache, so perhaps we should destroy all the content ... + workspace.destroy(); + return true; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.MapTransaction#createNode(java.util.UUID, + * org.modeshape.graph.property.Path.Segment, java.util.UUID, java.lang.Iterable) + */ + @Override + protected InfinispanNode createNode( UUID uuid, + Segment name, + UUID parentUuid, + Iterable properties ) { + return new InfinispanNode(uuid, name, parentUuid, properties, null); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#commit() + */ + @Override + public void commit() { + try { + super.commit(); + } finally { + lock.unlock(); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#rollback() + */ + @Override + public void rollback() { + try { + super.rollback(); + } finally { + lock.unlock(); + } + } + +} Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanWorkspace.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanWorkspace.java (working copy) @@ -0,0 +1,76 @@ +/* + * 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.connector.infinispan; + +import java.util.UUID; +import org.infinispan.Cache; +import org.modeshape.graph.connector.base.MapWorkspace; + +/** + * + */ +public class InfinispanWorkspace extends MapWorkspace { + + private Cache workspaceCache; + + /** + * Create a new workspace instance. + * + * @param name the name of the workspace + * @param workspaceCache the Infinispan cache containing the workspace content + * @param rootNode the root node for the workspace + */ + public InfinispanWorkspace( String name, + Cache workspaceCache, + InfinispanNode rootNode ) { + super(name, workspaceCache, rootNode); + this.workspaceCache = workspaceCache; + } + + /** + * Create a new workspace instance. + * + * @param name the name of the workspace + * @param workspaceCache the Infinispan cache containing the workspace content + * @param originalToClone the workspace that is to be cloned + */ + public InfinispanWorkspace( String name, + Cache workspaceCache, + InfinispanWorkspace originalToClone ) { + super(name, workspaceCache, originalToClone); + this.workspaceCache = workspaceCache; + } + + public void destroy() { + this.workspaceCache.clear(); + } + + /** + * This method shuts down the workspace and makes it no longer usable. This method should also only be called once. + */ + public void shutdown() { + this.workspaceCache.stop(); + } + +} Index: extensions/modeshape-connector-infinispan/src/main/resources/org/modeshape/connector/infinispan/InfinispanConnectorI18n.properties =================================================================== --- extensions/modeshape-connector-infinispan/src/main/resources/org/modeshape/connector/infinispan/InfinispanConnectorI18n.properties (revision 1804) +++ extensions/modeshape-connector-infinispan/src/main/resources/org/modeshape/connector/infinispan/InfinispanConnectorI18n.properties (working copy) @@ -23,14 +23,7 @@ # connectorName = Infinispan Connector -nodeDoesNotExist = Could not find an existing node at {0} propertyIsRequired = The {0} property is required but has no value errorSerializingCachePolicyInSource = Error serializing a {0} instance owned by the {1} InfinispanSource objectFoundInJndiWasNotCacheManager = Object in JNDI at {0} found by InfinispanSource {1} was expected to be a org.infinispan.CacheManager but instead was {2} -#unableToCloneWorkspaces = The InfinispanSource {0} does not allow creating workspaces, so unable to clone workspace "{1}" into "{2}" -#unableToCreateWorkspaces = The InfinispanSource {0} does not allow creating workspaces, so unable to create workspace "{1}" -unableToCreateWorkspace = Unable to create new workspace "{1}" in the InfinispanSource {0} -workspaceAlreadyExists = The workspace "{1}" already exists in the InfinispanSource {0} -workspaceDoesNotExist = The workspace "{1}" does not exist in the InfinispanSource {0} -workspaceNameWasNotValidConfiguration = The workspace name "{0}" was not a valid Infinispan Cache Manager configuration name: {1} -defaultCacheManagerConfigurationNameWasNotValidConfiguration = "{0}" is not a valid Infinispan Cache Manager configuration name +unableToCreateWorkspace = Unable to create the Infinispan cache for the "{0}" workspace in the {1} InfinispanSource \ No newline at end of file Index: extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorReadableTest.java =================================================================== --- extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorReadableTest.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorReadableTest.java (working copy) @@ -23,7 +23,6 @@ public class InfinispanConnectorReadableTest extends ReadableConnectorTest { */ @Override protected RepositorySource setUpSource() throws NamingException { - // Set the connection properties to be use the content of "./src/test/resources/repositories" as a repository ... String[] predefinedWorkspaceNames = new String[] {"aircraft", "cars"}; InfinispanSource source = new InfinispanSource(); source.setName("Test Repository"); Index: extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorWritableTest.java =================================================================== --- extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorWritableTest.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanConnectorWritableTest.java (working copy) @@ -34,9 +34,6 @@ public class InfinispanConnectorWritableTest extends WritableConnectorTest { when(mockJndi.lookup(anyString())).thenReturn(null); source.setContext(mockJndi); - Graph graph = Graph.create(source, context); - graph.useWorkspace("default"); - return source; } Index: extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanSourceTest.java =================================================================== --- extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanSourceTest.java (revision 1804) +++ extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanSourceTest.java (working copy) @@ -41,18 +41,21 @@ import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; import org.infinispan.manager.CacheManager; -import org.modeshape.graph.cache.BasicCachePolicy; -import org.modeshape.graph.connector.RepositoryConnection; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.MockitoAnnotations; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.cache.BasicCachePolicy; +import org.modeshape.graph.connector.RepositoryConnection; +import org.modeshape.graph.connector.RepositoryContext; /** */ public class InfinispanSourceTest { + private ExecutionContext context; private InfinispanSource source; private RepositoryConnection connection; private String validName; @@ -63,10 +66,14 @@ public class InfinispanSourceTest { private Context jndiContext; @Mock private CacheManager cacheManager; + @Mock + private RepositoryContext repositoryContext; @Before public void beforeEach() throws Exception { MockitoAnnotations.initMocks(this); + context = new ExecutionContext(); + when(repositoryContext.getExecutionContext()).thenReturn(context); validName = "cache source"; validCacheConfigurationName = "cache config name"; validCacheManagerJndiName = "cache factory jndi name"; @@ -211,6 +218,7 @@ public class InfinispanSourceTest { public void shouldCreateCacheUsingDefaultCacheManagerWhenNoCacheOrCacheManagerOrCacheConfigurationNameIsFound() throws Exception { source.setName(validName); + source.initialize(repositoryContext); connection = source.getConnection(); assertThat(connection, is(notNullValue())); // assertThat(connection.getCache(), is(notNullValue())); Index: extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/PersistentInfinispanConnectorTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/PersistentInfinispanConnectorTest.java (working copy) @@ -0,0 +1,131 @@ +/* + * 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.connector.infinispan; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.io.File; +import javax.naming.Context; +import org.infinispan.manager.CacheManager; +import org.infinispan.manager.DefaultCacheManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.common.util.FileUtil; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Graph; +import org.modeshape.graph.Subgraph; +import org.modeshape.graph.connector.RepositoryConnection; +import org.modeshape.graph.connector.RepositoryContext; + +/** + * + */ +public class PersistentInfinispanConnectorTest { + + protected static final String JNDI_NAME = "java/MyCacheManager"; + + private ExecutionContext context; + private InfinispanSource source; + private CacheManager cacheManager; + private Context mockJndi; + private RepositoryContext mockRepositoryContext; + private Graph graph; + + @Before + public void beforeEach() throws Exception { + context = new ExecutionContext(); + + // Create the cache manager ... + cacheManager = new DefaultCacheManager("infinispan_persistent_config.xml"); // looks on classpath first + // Set up the mock JNDI ... + mockJndi = mock(Context.class); + when(mockJndi.lookup(anyString())).thenReturn(null); + when(mockJndi.lookup(JNDI_NAME)).thenReturn(cacheManager); + + mockRepositoryContext = mock(RepositoryContext.class); + when(mockRepositoryContext.getExecutionContext()).thenReturn(context); + + String[] predefinedWorkspaceNames = new String[] {"default"}; + source = new InfinispanSource(); + source.setName("Test Repository"); + source.setPredefinedWorkspaceNames(predefinedWorkspaceNames); + source.setDefaultWorkspaceName(predefinedWorkspaceNames[0]); + source.setCreatingWorkspacesAllowed(true); + source.setContext(mockJndi); + source.setCacheManagerJndiName(JNDI_NAME); + source.initialize(mockRepositoryContext); + } + + @After + public void afterEach() throws Exception { + graph = null; + try { + source.close(); // stops the cache manager + } finally { + // Delete all of the content stored on the file system ... + File store = new File("target/infinispan/jcr"); + FileUtil.delete(store); + } + } + + protected Graph graph() { + if (graph == null) { + graph = Graph.create(source, context); + } + return graph; + } + + @Test + public void shouldShutdownWithoutOpeningConnections() { + + } + + @Test + public void shouldShutdownAfterOpeningConnections() { + RepositoryConnection connection = source.getConnection(); + connection.close(); + } + + @Test + public void shouldHaveRootNode() { + assertThat(graph().getNodeAt("/"), is(notNullValue())); + } + + @Test + public void shouldAllowCreatingAndReReadingNodes() { + Subgraph subgraph = graph().getSubgraphOfDepth(10).at("/"); + assertThat(subgraph.getNode("/"), is(notNullValue())); + // System.out.println(subgraph); + graph().create("/a").with("prop1", "value1").and(); + subgraph = graph().getSubgraphOfDepth(10).at("/"); + assertThat(subgraph.getNode("/"), is(notNullValue())); + assertThat(subgraph.getNode("/a").getProperty("prop1").getFirstValue(), is((Object)"value1")); + // System.out.println(subgraph); + } +} Index: extensions/modeshape-connector-infinispan/src/test/resources/infinispan_persistent_config.xml new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ extensions/modeshape-connector-infinispan/src/test/resources/infinispan_persistent_config.xml (working copy) @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file Index: extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java =================================================================== --- extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java (revision 1804) +++ extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java (working copy) @@ -44,6 +44,7 @@ import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import net.jcip.annotations.Immutable; +import net.jcip.annotations.ThreadSafe; import org.hibernate.SessionFactory; import org.hibernate.ejb.Ejb3Configuration; import org.modeshape.common.i18n.I18n; @@ -67,6 +68,7 @@ import org.modeshape.graph.connector.RepositorySourceException; * Persistence API as the interface to the database, with Hibernate as the JPA implementation. (Note that some Hibernate-specific * features are used.) */ +@ThreadSafe public class JpaSource implements RepositorySource, ObjectFactory { private final Logger LOGGER = Logger.getLogger(JpaSource.class); Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/RepositorySource.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/RepositorySource.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/RepositorySource.java (working copy) @@ -28,7 +28,7 @@ import java.math.BigDecimal; import java.net.URI; import java.util.UUID; import javax.naming.Referenceable; -import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.property.Binary; import org.modeshape.graph.property.DateTime; @@ -44,11 +44,11 @@ import org.modeshape.graph.request.CacheableRequest; *

* Typically this interface is implemented by classes that provide standard-style getters and setters for the various properties * necessary for proper configuration via reflection or introspection. This interface expects nor defines any such properties, - * leaving that entirely to the implementation classes. Although any types can be used for these setters, other ModeShape components use - * reflection to set these properties and work best when the setters accept a single parameter that is a primitive, an array of - * primitives, a value compatible with {@link PropertyType} (e.g., {@link Path}, {@link Name}, {@link URI}, {@link UUID}, - * {@link Reference}, {@link Binary}, {@link Long}, {@link Double}, {@link BigDecimal}, {@link DateTime}, etc.), or an array of - * values that are compatible with {@link PropertyType}. + * leaving that entirely to the implementation classes. Although any types can be used for these setters, other ModeShape + * components use reflection to set these properties and work best when the setters accept a single parameter that is a primitive, + * an array of primitives, a value compatible with {@link PropertyType} (e.g., {@link Path}, {@link Name}, {@link URI}, + * {@link UUID}, {@link Reference}, {@link Binary}, {@link Long}, {@link Double}, {@link BigDecimal}, {@link DateTime}, etc.), or + * an array of values that are compatible with {@link PropertyType}. *

*

* Implementations should also provide a no-arg constructor so that it is possible to easily create instances and initialize using @@ -62,19 +62,19 @@ import org.modeshape.graph.request.CacheableRequest; *

*

Pooling connections

*

- * If the connections created by a RepositorySource are expensive to create, then connection pooling is recommended. ModeShape provides - * this capability with a powerful and flexible {@link RepositoryConnectionPool} class. This is the pooling mechanism used by - * ModeShape, but you are free to use your own pools. + * If the connections created by a RepositorySource are expensive to create, then connection pooling is recommended. ModeShape + * provides this capability with a powerful and flexible {@link RepositoryConnectionPool} class. This is the pooling mechanism + * used by ModeShape, but you are free to use your own pools. *

*

Cache Policy

*

- * Each connector is responsible for determining whether and how long ModeShape is to cache the content made available by the connector. - * This is referred to as the caching policy, and consists of a time to live value representing the number of milliseconds that a - * piece of data may be cached. After the TTL has passed, the information is no longer used. + * Each connector is responsible for determining whether and how long ModeShape is to cache the content made available by the + * connector. This is referred to as the caching policy, and consists of a time to live value representing the number of + * milliseconds that a piece of data may be cached. After the TTL has passed, the information is no longer used. *

*

- * ModeShape allows a connector to use a flexible and powerful caching policy. First, each connection returns the default caching policy - * for all information returned by that connection. Often this policy can be configured via properties on the + * ModeShape allows a connector to use a flexible and powerful caching policy. First, each connection returns the default caching + * policy for all information returned by that connection. Often this policy can be configured via properties on the * {@link RepositorySource} implementation. This is optional, meaning the connector can return null if it does not wish to have a * default caching policy. *

@@ -83,9 +83,9 @@ import org.modeshape.graph.request.CacheableRequest; * this is optional, meaning that a null caching policy on a request implies that the request has no overridden caching policy. *

*

- * Third, if the connector has no default caching policy and none is set on the individual requests, ModeShape uses whatever caching - * policy is set up for that component using the connector. For example, the federating connector allows a default caching policy - * to be specified, and this policy is used should the sources being federated not define their own caching policy. + * Third, if the connector has no default caching policy and none is set on the individual requests, ModeShape uses whatever + * caching policy is set up for that component using the connector. For example, the federating connector allows a default caching + * policy to be specified, and this policy is used should the sources being federated not define their own caching policy. *

*

* In summary, a connector has total control over whether and for how long the information it provides is cached. @@ -135,7 +135,7 @@ import org.modeshape.graph.request.CacheableRequest; * security properties. *

*/ -@NotThreadSafe +@ThreadSafe public interface RepositorySource extends Referenceable, Serializable { /** Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseRepositorySource.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseRepositorySource.java (working copy) @@ -0,0 +1,66 @@ +/* + * 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.base; + +import org.modeshape.graph.cache.CachePolicy; +import org.modeshape.graph.connector.RepositoryContext; + +/** + * An extension of the {@link BaseRepositorySource} class that provides a {@link CachePolicy cache policy} and a + * {@link RepositoryContext repository context}. + */ +public interface BaseRepositorySource extends org.modeshape.graph.connector.RepositorySource { + + /** + * Get whether this source allows updates. + * + * @return true if this source allows updates by clients, or false if no updates are allowed + * @see #setUpdatesAllowed(boolean) + */ + boolean areUpdatesAllowed(); + + /** + * Set whether this source allows updates to data within workspaces + * + * @param updatesAllowed true if this source allows updates to data within workspaces clients, or false if updates are not + * allowed. + * @see #areUpdatesAllowed() + */ + void setUpdatesAllowed( boolean updatesAllowed ); + + /** + * Returns the {@link CachePolicy cache policy} for the repository source + * + * @return the {@link CachePolicy cache policy} for the repository source + */ + CachePolicy getDefaultCachePolicy(); + + /** + * Returns the {@link RepositoryContext repository context} for the repository source + * + * @return the {@link RepositoryContext repository context} for the repository source + */ + RepositoryContext getRepositoryContext(); + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseTransaction.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseTransaction.java (working copy) @@ -0,0 +1,190 @@ +/* + * 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.base; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.LockFailedException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.NameFactory; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathFactory; +import org.modeshape.graph.property.PathNotFoundException; +import org.modeshape.graph.property.PropertyFactory; +import org.modeshape.graph.property.ValueFactories; +import org.modeshape.graph.request.LockBranchRequest.LockScope; + +/** + * @param the type of node + * @param the type of workspace + */ +@NotThreadSafe +public abstract class BaseTransaction + implements Transaction { + + protected final UUID rootNodeUuid; + protected final ExecutionContext context; + protected final PathFactory pathFactory; + protected final NameFactory nameFactory; + protected final PropertyFactory propertyFactory; + protected final ValueFactories valueFactories; + + protected BaseTransaction( ExecutionContext context, + UUID rootNodeUuid ) { + this.rootNodeUuid = rootNodeUuid; + this.context = context; + this.propertyFactory = context.getPropertyFactory(); + this.valueFactories = context.getValueFactories(); + this.pathFactory = valueFactories.getPathFactory(); + this.nameFactory = valueFactories.getNameFactory(); + } + + protected String readable( Object obj ) { + return valueFactories.getStringFactory().create(obj); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getContext() + */ + public ExecutionContext getContext() { + return context; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getRootNode(org.modeshape.graph.connector.base.Workspace) + */ + public NodeType getRootNode( WorkspaceType workspace ) { + return getNode(workspace, Location.create(rootNodeUuid)); + } + + protected NodeType getNode( WorkspaceType workspace, + Path path, + Location location ) { + NodeType node = getRootNode(workspace); + for (Path.Segment segment : path) { + NodeType child = getChild(workspace, node, segment); + if (child == null) { + List segments = new LinkedList(); + for (Path.Segment seg : path) { + if (seg != segment) segments.add(seg); + else break; + } + Path lowestExisting = pathFactory.createAbsolutePath(segments); + throw new PathNotFoundException(location, lowestExisting, GraphI18n.nodeDoesNotExist.text(path)); + } + node = child; + } + return node; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#pathFor(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + public Path pathFor( WorkspaceType workspace, + NodeType node ) { + assert node != null; + assert pathFactory != null; + + LinkedList segments = new LinkedList(); + do { + segments.addFirst(node.getName()); + node = getParent(workspace, node); + } while (node != null); + segments.removeFirst(); // remove the root name, which is meaningless + + return pathFactory.createAbsolutePath(segments); + } + + /** + * {@inheritDoc} + *

+ * This method is implemented by iterating through the children, looking for the first child that has a matching name. + * Obviously this may be implemented more efficiently in certain systems. For example, an implementation might create a path + * by appending the child name to the supplied parent, and find the node with this path. + *

+ * + * @see org.modeshape.graph.connector.base.Transaction#getFirstChild(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name) + */ + public NodeType getFirstChild( WorkspaceType workspace, + NodeType parent, + Name childName ) { + for (NodeType child : getChildren(workspace, parent)) { + if (child.getName().getName().equals(childName)) return child; + } + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#lockNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.request.LockBranchRequest.LockScope, long) + */ + public void lockNode( WorkspaceType workspace, + NodeType node, + LockScope lockScope, + long lockTimeoutInMillis ) throws LockFailedException { + // do nothing by default + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#unlockNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + public void unlockNode( WorkspaceType workspace, + NodeType node ) { + // do nothing by default + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#commit() + */ + public void commit() { + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#rollback() + */ + public void rollback() { + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Connection.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Connection.java (working copy) @@ -0,0 +1,168 @@ +/* + * 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.base; + +import java.util.concurrent.TimeUnit; +import javax.transaction.xa.XAResource; +import net.jcip.annotations.ThreadSafe; +import org.modeshape.common.statistic.Stopwatch; +import org.modeshape.common.util.Logger; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.cache.CachePolicy; +import org.modeshape.graph.connector.RepositoryConnection; +import org.modeshape.graph.connector.RepositoryContext; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.observe.Observer; +import org.modeshape.graph.request.Request; +import org.modeshape.graph.request.processor.RequestProcessor; + +/** + * A connection to a {@link Repository}. + * + * @param the node type + * @param the workspace type + */ +@ThreadSafe +public class Connection implements RepositoryConnection { + private final BaseRepositorySource source; + private final Repository repository; + + public Connection( BaseRepositorySource source, + Repository repository ) { + assert source != null; + assert repository != null; + this.source = source; + this.repository = repository; + } + + /** + * {@inheritDoc} + */ + public String getSourceName() { + return source.getName(); + } + + /** + * {@inheritDoc} + */ + public CachePolicy getDefaultCachePolicy() { + return source.getDefaultCachePolicy(); + } + + /** + * {@inheritDoc} + */ + public XAResource getXAResource() { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean ping( long time, + TimeUnit unit ) { + return true; + } + + /** + * {@inheritDoc} + */ + public void close() { + // do nothing + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.RepositoryConnection#execute(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.request.Request) + */ + public void execute( ExecutionContext context, + Request request ) throws RepositorySourceException { + Logger logger = context.getLogger(getClass()); + Stopwatch sw = null; + if (logger.isTraceEnabled()) { + sw = new Stopwatch(); + sw.start(); + } + // Do any commands update/write? + + boolean commit = true; + Transaction txn = repository.startTransaction(context, request.isReadOnly()); + + RepositoryContext repositoryContext = this.source.getRepositoryContext(); + Observer observer = repositoryContext != null ? repositoryContext.getObserver() : null; + RequestProcessor processor = new Processor(txn, this.repository, observer, + source.areUpdatesAllowed()); + try { + // Obtain the lock and execute the commands ... + processor.process(request); + if (request.hasError() && !request.isReadOnly()) { + // The changes failed, so we need to rollback so we have 'all-or-nothing' behavior + commit = false; + } + } catch (Throwable error) { + commit = false; + } finally { + try { + processor.close(); + } finally { + // Now commit or rollback ... + try { + if (commit) { + txn.commit(); + } else { + // Need to rollback the changes made to the repository ... + txn.rollback(); + } + } catch (Throwable commitOrRollbackError) { + if (commit && !request.hasError() && !request.isFrozen()) { + // Record the error on the request ... + request.setError(commitOrRollbackError); + } + commit = false; // couldn't do it + } + if (commit) { + // Now that we've closed our transaction, we can notify the observer of the committed changes ... + processor.notifyObserverOfChanges(); + } + } + } + if (logger.isTraceEnabled()) { + assert sw != null; + sw.stop(); + logger.trace("MapRepositoryConnection.execute(...) took " + sw.getTotalDuration()); + } + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Connection to the \"" + getSourceName() + "\" " + repository.getClass().getSimpleName(); + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapNode.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapNode.java (working copy) @@ -0,0 +1,606 @@ +/* + * 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.base; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.ModeShapeLexicon; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.Path.Segment; + +/** + * A {@link Node} implementation used by the map-based connector (see {@link MapWorkspace} and {@link MapTransaction}), which + * stores all node state in a map. + *

+ * Strictly speaking, this class is not immutable or thread safe. However, the persisted state cannot be changed. Instead, any + * changes made to the object are stored in a transient area and are made "persistable"via the {@link #freeze()} method. + *

+ *

+ * The {@link MapTransaction} maintains an unfrozen, changed instance within it transactional state, and always puts the + * {@link #freeze() frozen}, read-only representation inside the + */ +public class MapNode implements Node, Serializable, Cloneable { + + private static final long serialVersionUID = 1L; + + /* These members MUST be treated as "final", even though they cannot be to correctly implement Serializable */ + private/*final*/UUID uuid; + private/*final*/Segment name; + private/*final*/UUID parent; + private/*final*/Map properties; + private/*final*/List children; + private/*final*/int version = 1; + + /** The changes made to this object, making it unfrozen */ + protected transient Changes changes; + + /** + * Create a new node instance. + * + * @param uuid the UUID of the node; may not be null + * @param name the name of the node; may be null only if the parent is also null + * @param parent the UUID of the parent node; may be null only if the name is null + * @param properties the unmodifiable map of properties; may be null or empty + * @param children the unmodificable list of child UUIDs; may be null or empty + */ + public MapNode( UUID uuid, + Segment name, + UUID parent, + Map properties, + List children ) { + this.uuid = uuid; + this.name = name; + this.parent = parent; + this.properties = properties != null ? properties : Collections.emptyMap(); + this.children = children != null ? children : Collections.emptyList(); + assert this.uuid != null; + assert this.properties != null; + assert this.children != null; + assert this.name != null ? this.parent != null : this.parent == null; + } + + /** + * Create a new node instance. + * + * @param uuid the UUID of the node; may not be null + * @param name the name of the node; may be null only if the parent is also null + * @param parent the UUID of the parent node; may be null only if the name is null + * @param properties the unmodifiable map of properties; may be null or empty + * @param children the unmodificable list of child UUIDs; may be null or empty + * @param version the version number + */ + protected MapNode( UUID uuid, + Segment name, + UUID parent, + Map properties, + List children, + int version ) { + this.uuid = uuid; + this.name = name; + this.parent = parent; + this.properties = properties != null ? properties : Collections.emptyMap(); + this.children = children != null ? children : Collections.emptyList(); + this.version = version; + assert this.uuid != null; + assert this.properties != null; + assert this.children != null; + assert this.name != null ? this.parent != null : this.parent == null; + } + + /** + * Create a new node instance. + * + * @param uuid the UUID of the node; may not be null + * @param name the name of the node; may be null only if the parent is also null + * @param parent the UUID of the parent node; may be null only if the name is null + * @param properties the properties that are to be copied into the new node; may be null or empty + * @param children the unmodificable list of child UUIDs; may be null or empty + */ + public MapNode( UUID uuid, + Segment name, + UUID parent, + Iterable properties, + List children ) { + this.uuid = uuid; + this.name = name; + this.parent = parent; + if (properties != null) { + Map props = new HashMap(); + for (Property prop : properties) { + props.put(prop.getName(), prop); + } + this.properties = props.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(props); + } else { + this.properties = Collections.emptyMap(); + } + this.children = children != null ? children : Collections.emptyList(); + assert this.uuid != null; + assert this.properties != null; + assert this.children != null; + assert this.name != null ? this.parent != null : this.parent == null; + } + + /** + * Create a root node with the supplied UUID. + * + * @param uuid the UUID of the root node; may not be null + */ + public MapNode( UUID uuid ) { + this.uuid = uuid; + this.name = null; + this.parent = null; + this.properties = Collections.emptyMap(); + this.children = Collections.emptyList(); + assert this.uuid != null; + assert this.properties != null; + assert this.children != null; + } + + /** + * Get the version number of this node. + * + * @return the version number + */ + public int getVersion() { + return version; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Node#getUuid() + */ + public UUID getUuid() { + return uuid; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Node#getName() + */ + public Segment getName() { + return changes != null ? changes.getName() : name; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Node#getProperties() + */ + public Map getProperties() { + return changes != null ? changes.getProperties(false) : properties; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Node#getProperty(org.modeshape.graph.property.Name) + */ + public Property getProperty( Name name ) { + return getProperties().get(name); + } + + /** + * @return parent + */ + public UUID getParent() { + return changes != null ? changes.getParent() : parent; + } + + /** + * @return children + */ + public List getChildren() { + return changes != null ? changes.getChildren(false) : children; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return uuid.hashCode(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof Node) { + Node that = (Node)obj; + if (!this.getUuid().equals(that.getUuid())) return false; + return true; + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (this.name == null) { + sb.append("("); + } else { + sb.append(this.name).append(" ("); + } + sb.append(uuid).append(")"); + return sb.toString(); + } + + /** + * {@inheritDoc} + *

+ * This method never clones the {@link #hasChanges() changes}. + *

+ * + * @see java.lang.Object#clone() + */ + @Override + public MapNode clone() { + return new MapNode(uuid, name, parent, properties, children); + } + + /** + * Determine if this node has any unsaved changes. + * + * @return true if there are unsaved changes, or false otherwise + */ + protected boolean hasChanges() { + return changes != null; + } + + /** + * Create the {@link Changes} implementation. Subclasses that require a specialized class should overwrite this method. Note + * that this method does not modify any internal state; it should just instantiate and return the correct Changes class. + * + * @return the changes object. + */ + protected Changes newChanges() { + return new Changes(); + } + + /** + * Return the frozen node with all internal state reflective of any changes. If this node has no changes, this method simply + * returns this same node. Otherwise, this method creates a new node that has no changes and that mirrors this node's current + * state, and this new node will have an incremented {@link #getVersion() version} number. + * + * @return the unfrozen node; never null + */ + public MapNode freeze() { + if (!hasChanges()) return this; + return new MapNode(uuid, changes.getName(), changes.getParent(), changes.getUnmodifiableProperties(), + changes.getUnmodifiableChildren(), version + 1); + } + + /** + * Create a copy of this node except using the supplied parent. + * + * @param parent Sets parent to the specified value. + * @return the new map node; never null + */ + public MapNode withParent( UUID parent ) { + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setParent(parent); + return copy; + } + changes.setParent(parent); + return this; + } + + /** + * Create a copy of this node except using the supplied name. + * + * @param name Sets name to the specified value. + * @return the new map node; never null + */ + public MapNode withName( Segment name ) { + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setName(name); + return copy; + } + changes.setName(name); + return this; + } + + /** + * Create a copy of this node except adding the supplied node at the end of the existing children. + * + * @param child the UUID of the child that is to be added; may not be null + * @return the new map node; never null + */ + public MapNode withChild( UUID child ) { + assert child != null; + if (getChildren().indexOf(child) != -1) return this; + if (changes == null) { + MapNode copy = clone(); + List children = new LinkedList(getChildren()); + assert !children.contains(child); + children.add(child); + copy.changes = newChanges(); + copy.changes.setChildren(children); + return copy; + } + changes.getChildren(true).add(child); + return this; + } + + /** + * Create a copy of this node except adding the supplied node into the existing children at the specified index. + * + * @param index the index at which the child is to appear + * @param child the UUID of the child that is to be added at the end of the existing children + * @return the new map node; never null + */ + public MapNode withChild( int index, + UUID child ) { + assert child != null; + assert index >= 0; + int existingIndex = getChildren().indexOf(child); + if (existingIndex == index) { + // No need to add twice, so simply return (have not yet made any changes) + return this; + } + if (changes == null) { + MapNode copy = clone(); + List children = new LinkedList(getChildren()); + if (existingIndex >= 0) { + // The child is moving positions, so remove it before we add it ... + children.remove(existingIndex); + if (existingIndex < index) --index; + } + children.add(index, child); + copy.changes = newChanges(); + copy.changes.setChildren(children); + return copy; + } + List children = changes.getChildren(true); + if (existingIndex >= 0) { + // The child is moving positions, so remove it before we add it ... + children.remove(existingIndex); + if (existingIndex < index) --index; + } + children.add(index, child); + return this; + } + + /** + * Create a copy of this node except without the supplied child node. + * + * @param child the UUID of the child that is to be removed; may not be null + * @return the new map node; never null + */ + public MapNode withoutChild( UUID child ) { + assert child != null; + if (changes == null) { + MapNode copy = clone(); + List children = new LinkedList(getChildren()); + children.remove(child); + copy.changes = newChanges(); + copy.changes.setChildren(children); + return copy; + } + changes.getChildren(true).remove(child); + return this; + } + + /** + * Create a copy of this node except with none of the children. + * + * @return the new map node; never null + */ + public MapNode withoutChildren() { + if (getChildren().isEmpty()) return this; + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setChildren(new LinkedList()); + return copy; + } + changes.getChildren(true).clear(); + return this; + } + + /** + * Create a copy of this node except with the changes to the properties. + * + * @param propertiesToSet the properties that are to be set; may be null if no properties are to be set + * @param propertiesToRemove the names of the properties that are to be removed; may be null if no properties are to be + * removed + * @param removeAllExisting true if all existing properties should be removed + * @return the unfrozen map node; never null + */ + public MapNode withProperties( Iterable propertiesToSet, + Iterable propertiesToRemove, + boolean removeAllExisting ) { + if (propertiesToSet == null && propertiesToRemove == null && !removeAllExisting) { + // no changes ... + return this; + } + Map newProperties = null; + MapNode result = this; + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setProperties(new HashMap(this.properties)); + newProperties = copy.changes.getProperties(true); + result = copy; + } else { + newProperties = changes.getProperties(true); + } + if (removeAllExisting) { + newProperties.clear(); + } else { + if (propertiesToRemove != null) { + for (Name name : propertiesToRemove) { + if (JcrLexicon.UUID.equals(name) || ModeShapeLexicon.UUID.equals(name)) continue; + newProperties.remove(name); + } + } else if (propertiesToSet == null) { + return this; + } + } + if (propertiesToSet != null) { + for (Property property : propertiesToSet) { + newProperties.put(property.getName(), property); + } + } + return result; + } + + /** + * Create a copy of this node except with the new property. + * + * @param property the property to set + * @return this map node + */ + public MapNode withProperty( Property property ) { + if (property == null) return this; + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + Map newProps = new HashMap(this.properties); + newProps.put(property.getName(), property); + copy.changes.setProperties(newProps); + return copy; + } + changes.getProperties(true).put(property.getName(), property); + return this; + } + + /** + * Create a copy of this node except with the new property. + * + * @param propertyName the name of the property that is to be removed + * @return this map node, or this node if the named properties does not exist on this node + */ + public MapNode withoutProperty( Name propertyName ) { + if (propertyName == null || !getProperties().containsKey(propertyName)) return this; + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setProperties(new HashMap(this.properties)); + return copy; + } + changes.getProperties(true).remove(propertyName); + return this; + } + + public MapNode withoutProperties() { + if (getProperties().isEmpty()) return this; + if (changes == null) { + MapNode copy = clone(); + copy.changes = newChanges(); + copy.changes.setProperties(new HashMap()); + return copy; + } + changes.getProperties(true).clear(); + return this; + + } + + @SuppressWarnings( "synthetic-access" ) + protected class Changes { + private Segment name; + private UUID parent; + private Map properties; + private List children; + + public Segment getName() { + return name != null ? name : MapNode.this.name; + } + + public void setName( Segment name ) { + this.name = name; + } + + public UUID getParent() { + return parent != null ? parent : MapNode.this.parent; + } + + public void setParent( UUID parent ) { + this.parent = parent; + } + + public Map getProperties( boolean createIfMissing ) { + if (properties == null) { + if (createIfMissing) { + properties = new HashMap(MapNode.this.properties); + return properties; + } + return MapNode.this.properties; + } + return properties; + } + + public Map getUnmodifiableProperties() { + return properties != null ? Collections.unmodifiableMap(properties) : MapNode.this.properties; + } + + public void setProperties( Map properties ) { + this.properties = properties; + } + + public List getChildren( boolean createIfMissing ) { + if (children == null) { + if (createIfMissing) { + children = new LinkedList(); + return children; + } + return MapNode.this.children; + } + return children; + } + + public List getUnmodifiableChildren() { + return children != null ? Collections.unmodifiableList(children) : MapNode.this.children; + } + + public void setChildren( List children ) { + this.children = children; + } + } + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapTransaction.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapTransaction.java (working copy) @@ -0,0 +1,1241 @@ +/* + * 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.base; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.common.util.StringUtil; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.connector.UuidAlreadyExistsException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.NamespaceRegistry; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathNotFoundException; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.PropertyType; +import org.modeshape.graph.property.Reference; +import org.modeshape.graph.property.UuidFactory; +import org.modeshape.graph.property.ValueFactory; +import org.modeshape.graph.property.Path.Segment; +import org.modeshape.graph.query.QueryResults; +import org.modeshape.graph.request.AccessQueryRequest; +import org.modeshape.graph.request.FullTextSearchRequest; + +/** + * An implementation of {@link Transaction} that maintains a cache of nodes by their UUID. + * + * @param the type of workspace + * @param the type of node + */ +@NotThreadSafe +public abstract class MapTransaction> + extends BaseTransaction { + + /** The repository against which this transaction is operating */ + private final Repository repository; + /** The set of changes to the workspaces that have been made by this transaction */ + private Map changesByWorkspaceName; + + /** + * Create a new transaction. + * + * @param repository the repository against which the transaction will be operating; may not be null + * @param rootNodeUuid the UUID of the root node; may not be null + */ + protected MapTransaction( Repository repository, + UUID rootNodeUuid ) { + super(repository.getContext(), rootNodeUuid); + this.repository = repository; + } + + /** + * Obtain the repository object against which this transaction is running. + * + * @return the repository object; never null + */ + protected Repository getRepository() { + return repository; + } + + /** + * Get the changes for the supplied workspace, optionally creating the necessary object if it does not yet exist. The changes + * object is used to record the changes made to the workspace by operations within this transaction, which are either pushed + * into the workspace upon {@link #commit()} or cleared upon {@link #rollback()}. + * + * @param workspace the workspace + * @param createIfMissing true if the changes object should be created if it does not yet exist, or false otherwise + * @return the changes object; may be null if createIfMissing is false and the changes object does + * not yet exist, or never null if createIfMissing is true + */ + protected WorkspaceChanges getChangesFor( WorkspaceType workspace, + boolean createIfMissing ) { + if (changesByWorkspaceName == null) { + if (!createIfMissing) return null; + WorkspaceChanges changes = new WorkspaceChanges(workspace); + changesByWorkspaceName = new HashMap(); + changesByWorkspaceName.put(workspace.getName(), changes); + return changes; + } + WorkspaceChanges changes = changesByWorkspaceName.get(workspace.getName()); + if (changes == null && createIfMissing) { + changes = new WorkspaceChanges(workspace); + changesByWorkspaceName.put(workspace.getName(), changes); + } + return changes; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.Location) + */ + public NodeType getNode( WorkspaceType workspace, + Location location ) { + assert location != null; + // First look for the UUID ... + UUID uuid = location.getUuid(); + if (uuid != null) { + WorkspaceChanges changes = getChangesFor(workspace, false); + NodeType node = null; + if (changes != null) { + // See if the node we're looking for was deleted ... + if (changes.isRemoved(uuid)) { + // This node was removed within this transaction ... + Path lowestExisting = null; + if (location.hasPath()) { + getNode(workspace, location.getPath(), location); // should fail + assert false; + } + lowestExisting = pathFactory.createRootPath(); + throw new PathNotFoundException(location, lowestExisting, GraphI18n.nodeDoesNotExist.text(readable(location))); + } + // Not deleted, but maybe changed in this transaction ... + node = changes.getChangedOrAdded(uuid); + if (node != null) return node; + } + // It hasn't been loaded already, so attempt to load it from the map owned by the workspace ... + node = workspace.getNode(uuid); + if (node != null) return node; + } + // Otherwise, look by path ... + if (location.hasPath()) { + return getNode(workspace, location.getPath(), location); + } + // Unable to find by UUID or by path, so fail ... + Path lowestExisting = pathFactory.createRootPath(); + throw new PathNotFoundException(location, lowestExisting, GraphI18n.nodeDoesNotExist.text(readable(location))); + } + + /** + * Attempt to find the node with the supplied UUID. + * + * @param workspace the workspace; may not be null + * @param uuid the UUID of the node; may not be null + * @return the node, or null if no such node exists + */ + protected NodeType findNode( WorkspaceType workspace, + UUID uuid ) { + WorkspaceChanges changes = getChangesFor(workspace, false); + NodeType node = null; + if (changes != null) { + // See if the node we're looking for was deleted ... + if (changes.isRemoved(uuid)) { + // This node was removed within this transaction ... + return null; + } + // Not deleted, but maybe changed in this transaction ... + node = changes.getChangedOrAdded(uuid); + if (node != null) return node; + } + // It hasn't been loaded already, so attempt to load it from the map owned by the workspace ... + node = workspace.getNode(uuid); + return node; + } + + /** + * Destroy the node. + * + * @param workspace the workspace; never null + * @param node the node to be destroyed + */ + protected void destroyNode( WorkspaceType workspace, + NodeType node ) { + WorkspaceChanges changes = getChangesFor(workspace, true); + destroyNode(changes, workspace, node); + } + + /** + * Destroy the node and it's contents. + * + * @param changes the record of the workspace changes; never null + * @param workspace the workspace; never null + * @param node the node to be destroyed + */ + private void destroyNode( WorkspaceChanges changes, + WorkspaceType workspace, + NodeType node ) { + UUID uuid = node.getUuid(); + changes.removed(uuid); + for (UUID child : node.getChildren()) { + destroyNode(changes, workspace, workspace.getNode(child)); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#addChild(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, int, java.util.UUID, java.lang.Iterable) + */ + @SuppressWarnings( "unchecked" ) + public NodeType addChild( WorkspaceType workspace, + NodeType parent, + Name name, + int index, + UUID uuid, + Iterable properties ) { + if (uuid == null) { + uuid = UUID.randomUUID(); + } + WorkspaceChanges changes = getChangesFor(workspace, true); + + // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes + if (!parent.hasChanges()) { + parent = findNode(workspace, parent.getUuid()); + } + + NodeType newNode = null; + if (index < 0) { + // Figure out the SNS of the new node ... + int snsIndex = 1; + for (NodeType child : getChildren(workspace, parent)) { + if (child.getName().getName().equals(name)) ++snsIndex; + } + + // Create the new node ... + newNode = createNode(uuid, pathFactory.createSegment(name, snsIndex), parent.getUuid(), properties); + // And add to the parent ... + parent = (NodeType)parent.withChild(uuid); + } else { + int snsIndex = 0; + ListIterator existingSiblings = getChildren(workspace, parent).listIterator(index); + while (existingSiblings.hasNext()) { + NodeType existingSibling = existingSiblings.next(); + Segment existingSegment = existingSibling.getName(); + if (existingSegment.getName().equals(name)) { + int existingIndex = existingSegment.getIndex(); + if (snsIndex == 0) snsIndex = existingIndex; + existingSibling = (NodeType)existingSibling.withName(pathFactory.createSegment(name, existingIndex + 1)); + changes.changed(existingSibling); + } + } + // Create the new node ... + newNode = createNode(uuid, pathFactory.createSegment(name, snsIndex + 1), parent.getUuid(), properties); + // And add to the parent ... + parent = (NodeType)parent.withChild(index, uuid); + } + changes.created(newNode); + changes.changed(parent); + return newNode; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#addChild(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Node, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name) + */ + @SuppressWarnings( "unchecked" ) + public Location addChild( WorkspaceType workspace, + NodeType parent, + NodeType newChild, + NodeType beforeOtherChild, + Name desiredName ) { + // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes + if (!parent.hasChanges()) { + parent = findNode(workspace, parent.getUuid()); + } + + // Get some information about the child ... + Segment newChildSegment = newChild.getName(); + Name newChildName = newChildSegment.getName(); + int snsIndex = newChildSegment.getIndex(); + UUID newChildUuid = newChild.getUuid(); + + // Find the existing parent of the new child ... + NodeType oldParent = getParent(workspace, newChild); + + // Find the changes for this workspace ... + WorkspaceChanges changes = getChangesFor(workspace, true); + + // if (oldParent == parent && beforeOtherChild == null) { + // // this node is being renamed, so find the correct index ... + // index = parent.getChildren().indexOf(newChildUuid); + // assert index >= 0; + // } else if (oldParent != null) { + if (oldParent != null) { + // Remove the node from it's parent ... + int oldIndex = oldParent.getChildren().indexOf(newChildUuid); + if (oldParent == parent) { + oldParent = (NodeType)oldParent.withoutChild(newChildUuid); + changes.changed(oldParent); + parent = oldParent; + } else { + oldParent = (NodeType)oldParent.withoutChild(newChildUuid); + changes.changed(oldParent); + } + + // Now find any siblings with the same name that appear after the node in the parent's list of children ... + List siblings = getChildren(workspace, oldParent); + for (ListIterator iter = siblings.listIterator(oldIndex); iter.hasNext();) { + NodeType sibling = iter.next(); + if (sibling.getName().getName().equals(newChildName)) { + sibling = (NodeType)sibling.withName(pathFactory.createSegment(newChildName, snsIndex++)); + changes.changed(sibling); + } + } + } + + // Find the index of the other child ... + int index = parent.getChildren().size(); + if (beforeOtherChild != null) { + if (!beforeOtherChild.getParent().equals(parent.getUuid())) { + // The other child doesn't exist in the parent ... + throw new RepositorySourceException(null); + } + UUID otherChild = beforeOtherChild.getUuid(); + index = parent.getChildren().indexOf(otherChild); + } + + // Determine the desired new name for the node ... + newChildName = desiredName != null ? desiredName : newChildName; + + // Find the SNS index for the new child ... + ListIterator existingSiblings = getChildren(workspace, parent).listIterator(); // makes a copy + int i = 0; + snsIndex = 1; + Segment childName = null; + while (existingSiblings.hasNext()) { + NodeType existingSibling = existingSiblings.next(); + Segment existingSegment = existingSibling.getName(); + if (i < index) { + // Nodes before the insertion point + if (existingSegment.getName().equals(newChildName)) { + ++snsIndex; + } + } else { + if (i == index) { + // Add the child node ... + childName = pathFactory.createSegment(newChildName, snsIndex); + } + if (existingSegment.getName().equals(newChildName)) { + existingSibling = (NodeType)existingSibling.withName(pathFactory.createSegment(newChildName, ++snsIndex)); + changes.changed(existingSibling); + } + } + ++i; + } + if (childName == null) { + // Must be appending the child ... + childName = pathFactory.createSegment(newChildName, snsIndex); + } + + // Change the name of the new node ... + newChild = (NodeType)newChild.withName(childName).withParent(parent.getUuid()); + parent = (NodeType)parent.withChild(index, newChildUuid); + changes.changed(newChild); + changes.changed(parent); + return Location.create(pathFor(workspace, newChild), newChildUuid); + } + + /** + * Create a new instance of the node, given the supplied UUID. This method should do nothing but instantiate the new node; the + * caller will add to the appropriate maps. + * + * @param uuid the desired UUID; never null + * @param name the name of the new node; may be null if the name is not known + * @param parentUuid the UUID of the parent node; may be null if this is the root node + * @param properties the properties; may be null if there are no properties + * @return the new node; never null + */ + @SuppressWarnings( "unchecked" ) + protected NodeType createNode( UUID uuid, + Segment name, + UUID parentUuid, + Iterable properties ) { + return (NodeType)new MapNode(uuid, name, parentUuid, properties, null); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getChild(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Path.Segment) + */ + public NodeType getChild( WorkspaceType workspace, + NodeType parent, + Segment childSegment ) { + List children = new Children(parent.getChildren(), workspace); // don't make a copy + for (NodeType child : children) { + if (child.getName().equals(childSegment)) return child; + } + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getChildren(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + public List getChildren( WorkspaceType workspace, + NodeType node ) { + List children = node.getChildren(); // make a copy + if (children.isEmpty()) return Collections.emptyList(); + return new Children(children, workspace); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getParent(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + public NodeType getParent( WorkspaceType workspace, + NodeType node ) { + UUID parentUuid = node.getParent(); + if (parentUuid == null) return null; + return getNode(workspace, Location.create(parentUuid)); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#removeAllChildren(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + @SuppressWarnings( "unchecked" ) + public void removeAllChildren( WorkspaceType workspace, + NodeType node ) { + for (NodeType child : getChildren(workspace, node)) { + destroyNode(workspace, child); + } + node = (NodeType)node.withoutChildren(); + getChangesFor(workspace, true).changed(node); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#removeNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node) + */ + @SuppressWarnings( "unchecked" ) + public Location removeNode( WorkspaceType workspace, + NodeType node ) { + NodeType parent = getParent(workspace, node); + if (parent == null) { + // The root node is being removed, which means we should just delete everything (except the root) ... + WorkspaceChanges changes = getChangesFor(workspace, true); + changes.removeAll(createNode(rootNodeUuid, null, null, null)); + return Location.create(pathFactory.createRootPath(), rootNodeUuid); + } + Location result = Location.create(pathFor(workspace, node), node.getUuid()); + + // Find the index of the node in it's parent ... + int index = parent.getChildren().indexOf(node.getUuid()); + assert index != -1; + Name name = node.getName().getName(); + int snsIndex = node.getName().getIndex(); + WorkspaceChanges changes = getChangesFor(workspace, true); + // Remove the node from the parent ... + parent = (NodeType)parent.withoutChild(node.getUuid()); + changes.changed(parent); + + // Now find any siblings with the same name that appear after the node in the parent's list of children ... + List siblings = getChildren(workspace, parent); + if (index < siblings.size()) { + for (ListIterator iter = siblings.listIterator(index); iter.hasNext();) { + NodeType sibling = iter.next(); + if (sibling.getName().getName().equals(name)) { + sibling = (NodeType)sibling.withName(pathFactory.createSegment(name, snsIndex++)); + changes.changed(sibling); + } + } + } + // Destroy the subgraph starting at the node, and record the change on the parent ... + destroyNode(changes, workspace, node); + return result; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#removeProperty(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name) + */ + @SuppressWarnings( "unchecked" ) + public NodeType removeProperty( WorkspaceType workspace, + NodeType node, + Name propertyName ) { + NodeType copy = (NodeType)node.withoutProperty(propertyName); + if (copy != node) { + WorkspaceChanges changes = getChangesFor(workspace, true); + changes.changed(copy); + } + return copy; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#setProperties(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, java.lang.Iterable, java.lang.Iterable, boolean) + */ + @SuppressWarnings( "unchecked" ) + public NodeType setProperties( WorkspaceType workspace, + NodeType node, + Iterable propertiesToSet, + Iterable propertiesToRemove, + boolean removeAllExisting ) { + NodeType copy = (NodeType)node.withProperties(propertiesToSet, propertiesToRemove, removeAllExisting); + if (copy != node) { + WorkspaceChanges changes = getChangesFor(workspace, true); + changes.changed(copy); + } + return copy; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#copyNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, boolean) + */ + @SuppressWarnings( "unchecked" ) + public NodeType copyNode( WorkspaceType originalWorkspace, + NodeType original, + WorkspaceType newWorkspace, + NodeType newParent, + Name desiredName, + boolean recursive ) { + if (desiredName == null) desiredName = original.getName().getName(); + // Create a copy of the original under the new parent ... + UUID uuid = UUID.randomUUID(); + NodeType copy = addChild(newWorkspace, newParent, desiredName, -1, uuid, original.getProperties().values()); + + Map oldToNewUuids = new HashMap(); + oldToNewUuids.put(original.getUuid(), uuid); + + if (recursive) { + WorkspaceChanges changes = getChangesFor(newWorkspace, true); + // Walk through the original branch in its workspace ... + for (NodeType originalChild : getChildren(originalWorkspace, original)) { + NodeType newChild = copyBranch(originalWorkspace, + originalChild, + changes, + newWorkspace, + copy, + false, + oldToNewUuids); + copy = (NodeType)copy.withChild(newChild.getUuid()); + } + } + + // Record the latest changes on the newly-created node .. + WorkspaceChanges changes = getChangesFor(newWorkspace, true); + changes.changed(copy); + + // Now, adjust any references in the new subgraph to objects in the original subgraph + // (because they were internal references, and need to be internal to the new subgraph) + UuidFactory uuidFactory = context.getValueFactories().getUuidFactory(); + ValueFactory referenceFactory = context.getValueFactories().getReferenceFactory(); + for (Map.Entry oldToNew : oldToNewUuids.entrySet()) { + NodeType newNode = findNode(newWorkspace, oldToNew.getValue()); + assert newNode != null; + // Iterate over the properties of the new ... + for (Map.Entry entry : newNode.getProperties().entrySet()) { + Property property = entry.getValue(); + // Now see if any of the property values are references ... + List newValues = new ArrayList(); + boolean foundReference = false; + for (Iterator iter = property.getValues(); iter.hasNext();) { + Object value = iter.next(); + PropertyType type = PropertyType.discoverType(value); + if (type == PropertyType.REFERENCE) { + UUID oldReferencedUuid = uuidFactory.create(value); + UUID newReferencedUuid = oldToNewUuids.get(oldReferencedUuid); + if (newReferencedUuid != null) { + newValues.add(referenceFactory.create(newReferencedUuid)); + foundReference = true; + } + } else { + newValues.add(value); + } + } + // If we found at least one reference, we have to build a new Property object ... + if (foundReference) { + Property newProperty = propertyFactory.create(property.getName(), newValues); + newNode = (NodeType)newNode.withProperty(newProperty); + changes.changed(newNode); + } + } + } + + return copy; + } + + protected void print( WorkspaceType workspace, + NodeType node, + int level ) { + StringBuilder sb = new StringBuilder(); + sb.append(StringUtil.createString(' ', level * 2)); + sb.append(readable(node.getName())).append(" (").append(node.getUuid()).append(") {"); + boolean first = true; + for (Property property : node.getProperties().values()) { + if (first) first = false; + else sb.append(','); + sb.append(readable(property.getName())).append('='); + if (property.isMultiple()) sb.append(property.getValuesAsArray()); + else sb.append(readable(property.getFirstValue())); + } + sb.append('}'); + System.out.println(sb); + for (NodeType child : getChildren(workspace, node)) { + print(workspace, child, level + 1); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#cloneNode(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.connector.base.Node, org.modeshape.graph.property.Name, org.modeshape.graph.property.Path.Segment, + * boolean, java.util.Set) + */ + @SuppressWarnings( "unchecked" ) + public NodeType cloneNode( WorkspaceType originalWorkspace, + NodeType original, + WorkspaceType newWorkspace, + NodeType newParent, + Name desiredName, + Segment desiredSegment, + boolean removeExisting, + java.util.Set removedExistingNodes ) + throws org.modeshape.graph.connector.UuidAlreadyExistsException { + + WorkspaceChanges changes = getChangesFor(newWorkspace, true); + NodeType newNode = null; + + // System.out.println("Original workspace under " + pathFor(originalWorkspace, original)); + // print(originalWorkspace, original, 0); + // System.out.println("New workspace under " + pathFor(newWorkspace, newParent)); + // print(newWorkspace, newParent, 0); + Set uuidsInFromBranch = getUuidsUnderNode(originalWorkspace, original); + NodeType existing = null; + if (removeExisting) { + // Remove all of the nodes that have a UUID from under the original node, but DO NOT yet remove + // a node that has the UUID of the original node (as this will be handled later) ... + for (UUID uuid : uuidsInFromBranch) { + if (null != (existing = findNode(newWorkspace, uuid))) { + if (removedExistingNodes != null) { + Path path = pathFor(newWorkspace, existing); + removedExistingNodes.add(Location.create(path, uuid)); + } + removeNode(newWorkspace, existing); + } + } + // System.out.println("New workspace under " + pathFor(newWorkspace, newParent) + " (after removing existing nodes)"); + // print(newWorkspace, newParent, 0); + } else { + uuidsInFromBranch.add(original.getUuid()); // uuidsInFromBranch does not include the UUID of the original + for (UUID uuid : uuidsInFromBranch) { + if (null != (existing = findNode(newWorkspace, uuid))) { + NamespaceRegistry namespaces = context.getNamespaceRegistry(); + String path = pathFor(newWorkspace, existing).getString(namespaces); + throw new UuidAlreadyExistsException(repository.getSourceName(), uuid, path, newWorkspace.getName()); + } + } + } + + UUID uuid = original.getUuid(); + if (desiredSegment != null) { + // Look for an existing child under 'newParent' that has that name ... + int index = 0; + List children = getChildren(newWorkspace, newParent); + NodeType existingWithSameName = null; + for (NodeType child : children) { + if (child.getName().equals(desiredSegment)) { + existingWithSameName = child; + break; + } + ++index; + } + if (index == children.size()) { + // Couldn't find the desired segment, so just append ... + newNode = addChild(newWorkspace, newParent, original.getName().getName(), -1, uuid, original.getProperties() + .values()); + } else { + // Create the new node with the desired name ... + newNode = createNode(uuid, desiredSegment, newParent.getUuid(), original.getProperties().values()); + + // Destroy the existing node ... + assert existingWithSameName != null; + destroyNode(newWorkspace, existingWithSameName); + + // Replace the existing node ... + newParent = (NodeType)newParent.withoutChild(existingWithSameName.getUuid()).withChild(index, uuid); + } + } else { + // Need to remove the existing node with the same UUID ... + existing = findNode(newWorkspace, original.getUuid()); + if (existing != null) { + if (removedExistingNodes != null) { + Path path = pathFor(newWorkspace, existing); + removedExistingNodes.add(Location.create(path, original.getUuid())); + } + removeNode(newWorkspace, existing); + } + + if (desiredName != null) { + // Simply add a new node with the desired name ... + newNode = addChild(newWorkspace, newParent, desiredName, -1, uuid, original.getProperties().values()); + } else { + // Simply append and use the original's name ... + newNode = addChild(newWorkspace, newParent, original.getName().getName(), -1, uuid, original.getProperties() + .values()); + } + } + + // If the parent doesn't already have changes, we need to find the new parent in the newWorkspace's changes + if (!newParent.hasChanges()) { + newParent = findNode(newWorkspace, newParent.getUuid()); + } + + // Walk through the original branch in its workspace ... + for (NodeType originalChild : getChildren(originalWorkspace, original)) { + NodeType newChild = copyBranch(originalWorkspace, originalChild, changes, newWorkspace, newNode, true, null); + newNode = (NodeType)newNode.withChild(newChild.getUuid()); + } + changes.created(newNode); + changes.changed(newParent); + + return newNode; + } + + /** + * Returns all of the UUIDs in the branch rooted at {@code node}. The UUID of {@code node} will be included in the set + * of returned UUIDs. + * + * @param workspace the workspace + * @param node the root of the branch + * @return all of the UUIDs in the branch rooted at {@code node} + */ + protected Set getUuidsUnderNode( WorkspaceType workspace, + NodeType node ) { + Set uuids = new HashSet(); + uuidsUnderNode(workspace, node, uuids); + return uuids; + } + + private void uuidsUnderNode( WorkspaceType workspace, + NodeType node, + Set accumulator ) { + for (NodeType child : getChildren(workspace, node)) { + accumulator.add(child.getUuid()); + uuidsUnderNode(workspace, child, accumulator); + } + } + + @SuppressWarnings( "unchecked" ) + protected NodeType copyBranch( WorkspaceType originalWorkspace, + NodeType original, + WorkspaceChanges newWorkspaceChanges, + WorkspaceType newWorkspace, + NodeType newParent, + boolean reuseUuid, + Map oldToNewUuids ) { + // Create the new node (or reuse the original if we can) ... + UUID copyUuid = reuseUuid ? original.getUuid() : UUID.randomUUID(); + NodeType copy = createNode(copyUuid, original.getName(), newParent.getUuid(), original.getProperties().values()); + newWorkspaceChanges.created(copy); + if (!reuseUuid) { + assert oldToNewUuids != null; + oldToNewUuids.put(original.getUuid(), copy.getUuid()); + } + + // Walk through the children and call this method recursively ... + for (NodeType originalChild : getChildren(originalWorkspace, original)) { + NodeType newChild = copyBranch(originalWorkspace, + originalChild, + newWorkspaceChanges, + newWorkspace, + copy, + reuseUuid, + oldToNewUuids); + copy = (NodeType)copy.withChild(newChild.getUuid()); + } + newWorkspaceChanges.changed(copy); + return copy; + } + + /** + * {@inheritDoc} + *

+ * This implementation does not support querying the repository contents, so this method returns null. Subclasses can override + * this if they do support querying. + *

+ * + * @see org.modeshape.graph.connector.base.Transaction#query(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.request.AccessQueryRequest) + */ + public QueryResults query( WorkspaceType workspace, + AccessQueryRequest accessQuery ) { + return null; + } + + /** + * {@inheritDoc} + *

+ * This implementation does not support searching the repository contents, so this method returns null. Subclasses can + * override this if they do support searching. + *

+ * + * @see org.modeshape.graph.connector.base.Transaction#search(org.modeshape.graph.connector.base.Workspace, + * org.modeshape.graph.request.FullTextSearchRequest) + */ + public QueryResults search( WorkspaceType workspace, + FullTextSearchRequest search ) { + return null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.BaseTransaction#commit() + */ + @Override + public void commit() { + super.commit(); + // Push all of the changes or added nodes onto the workspace ... + if (changesByWorkspaceName != null) { + for (WorkspaceChanges changes : changesByWorkspaceName.values()) { + changes.commit(); + } + changesByWorkspaceName.clear(); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.BaseTransaction#rollback() + */ + @Override + public void rollback() { + super.rollback(); + if (changesByWorkspaceName != null) { + changesByWorkspaceName.clear(); + } + } + + /** + * Record of the changes made to a particular workspace. + */ + protected class WorkspaceChanges { + private final WorkspaceType workspace; + private final Map changedOrAddedNodes = new HashMap(); + private final Set removedNodes = new HashSet(); + private boolean removeAll = false; + + protected WorkspaceChanges( WorkspaceType workspace ) { + this.workspace = workspace; + } + + public WorkspaceType getWorkspace() { + return workspace; + } + + public void removeAll( NodeType newRootNode ) { + changedOrAddedNodes.clear(); + removedNodes.clear(); + removeAll = true; + changedOrAddedNodes.put(newRootNode.getUuid(), newRootNode); + } + + public boolean isRemoved( UUID uuid ) { + return removedNodes.contains(uuid); + } + + public NodeType getChangedOrAdded( UUID uuid ) { + return changedOrAddedNodes.get(uuid); + } + + public void removed( UUID uuid ) { + removedNodes.add(uuid); + changedOrAddedNodes.remove(uuid); + } + + public void created( NodeType node ) { + UUID uuid = node.getUuid(); + removedNodes.remove(uuid); + changedOrAddedNodes.put(uuid, node); + } + + public void changed( NodeType node ) { + UUID uuid = node.getUuid(); + assert !removedNodes.contains(uuid); // should not be removed + changedOrAddedNodes.put(uuid, node); + } + + @SuppressWarnings( "unchecked" ) + public void commit() { + if (removeAll) { + workspace.removeAll(); + } + for (NodeType changed : changedOrAddedNodes.values()) { + workspace.putNode((NodeType)changed.freeze()); + } + for (UUID uuid : removedNodes) { + workspace.removeNode(uuid); + } + } + } + + protected class Children implements List { + private final List uuids; + private final WorkspaceType workspace; + private final List cache; + + protected Children( List uuids, + WorkspaceType workspace ) { + this.uuids = uuids; + this.workspace = workspace; + this.cache = new ArrayList(uuids.size()); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#size() + */ + public int size() { + return uuids.size(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#contains(java.lang.Object) + */ + public boolean contains( Object o ) { + if (o instanceof MapNode) { + return uuids.contains(((MapNode)o).getUuid()); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#containsAll(java.util.Collection) + */ + public boolean containsAll( Collection c ) { + for (Object o : c) { + if (!contains(o)) return false; + } + return true; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#get(int) + */ + public NodeType get( int index ) { + NodeType result = null; + if (cache.size() > index) { + result = cache.get(index); + } + if (result == null) { + UUID uuid = uuids.get(index); + // Do the fast lookup first, but this returns a null if not found ... + result = findNode(workspace, uuid); + if (result == null) { + // Wasn't found, so we need to throw an exception ... + result = getNode(workspace, Location.create(uuid)); + } + while (cache.size() <= index) { + cache.add(null); + } + cache.set(index, result); + } + return result; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#indexOf(java.lang.Object) + */ + public int indexOf( Object o ) { + if (o instanceof MapNode) { + return uuids.indexOf(((MapNode)o).getUuid()); + } + return -1; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#lastIndexOf(java.lang.Object) + */ + public int lastIndexOf( Object o ) { + // The list of UUIDs cannot contain duplicates, so just do the simple thing here ... + return indexOf(0); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#isEmpty() + */ + public boolean isEmpty() { + return uuids.isEmpty(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#iterator() + */ + public Iterator iterator() { + return listIterator(0); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#listIterator() + */ + public ListIterator listIterator() { + return listIterator(0); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#listIterator(int) + */ + public ListIterator listIterator( final int index ) { + final int maxIndex = size(); + return new ListIterator() { + private int current = index; + + public boolean hasNext() { + return current < maxIndex; + } + + public NodeType next() { + return get(current++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void add( NodeType e ) { + throw new UnsupportedOperationException(); + } + + public boolean hasPrevious() { + return current > 0; + } + + public int nextIndex() { + return current; + } + + public NodeType previous() { + return get(--current); + } + + public int previousIndex() { + return current - 1; + } + + public void set( NodeType e ) { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#subList(int, int) + */ + public List subList( int fromIndex, + int toIndex ) { + return new Children(uuids.subList(fromIndex, toIndex), workspace); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#toArray() + */ + public Object[] toArray() { + final int length = uuids.size(); + Object[] result = new Object[length]; + for (int i = 0; i != length; ++i) { + result[i] = get(i); + } + return result; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#toArray(T[]) + */ + @SuppressWarnings( "unchecked" ) + public T[] toArray( T[] a ) { + final int length = uuids.size(); + for (int i = 0; i != length; ++i) { + a[i] = (T)get(i); + } + return a; + } + + /** + * {@inheritDoc} + * + * @see java.util.List#add(java.lang.Object) + */ + public boolean add( NodeType e ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#add(int, java.lang.Object) + */ + public void add( int index, + NodeType element ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#addAll(java.util.Collection) + */ + public boolean addAll( Collection c ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#addAll(int, java.util.Collection) + */ + public boolean addAll( int index, + Collection c ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#clear() + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#remove(java.lang.Object) + */ + public boolean remove( Object o ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#remove(int) + */ + public NodeType remove( int index ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#removeAll(java.util.Collection) + */ + public boolean removeAll( Collection c ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#retainAll(java.util.Collection) + */ + public boolean retainAll( Collection c ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.List#set(int, java.lang.Object) + */ + public NodeType set( int index, + NodeType element ) { + throw new UnsupportedOperationException(); + } + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapWorkspace.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapWorkspace.java (working copy) @@ -0,0 +1,159 @@ +/* + * 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.base; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.locks.ReadWriteLock; +import net.jcip.annotations.NotThreadSafe; + +/** + * The {@link Workspace} implementation that represents all nodes as {@link MapNode} objects and stores them within a {@link Map} + * keyed by their UUID. + *

+ * Subclasses are required to provide thread-safe access and modification of the state within the encapsulated map, since multiple + * {@link Transaction} implementations may be {@link Transaction#commit() committing} changes to the map at the same time. + * However, this class does not provide any thread-safety, since the nature of the thread-safety will almost certainly depend on + * the actual map implementation. For example, a subclass may use a {@link ReadWriteLock lock}, or it may use a map implementation + * that provides the thread-safety. + *

+ * + * @param the type of node + */ +@NotThreadSafe +public class MapWorkspace implements Workspace { + + private final String name; + private final UUID rootNodeUuid; + private final Map nodesByUuid; + + /** + * Create a new instance of the workspace. + * + * @param name the workspace name; may not be null + * @param nodesByUuid the map of nodes keyed by their UUIDs; may not be null + * @param rootNode the root node that is expected to already exist in the map + */ + public MapWorkspace( String name, + Map nodesByUuid, + NodeType rootNode ) { + this.name = name; + this.rootNodeUuid = rootNode.getUuid(); + this.nodesByUuid = nodesByUuid; + if (!this.nodesByUuid.containsKey(rootNodeUuid)) { + this.nodesByUuid.put(rootNodeUuid, rootNode); + } + assert this.name != null; + assert this.rootNodeUuid != null; + assert this.nodesByUuid != null; + } + + /** + * Create a new instance of the workspace. + * + * @param name the workspace name; may not be null + * @param nodesByUuid the map of nodes keyed by their UUIDs; may not be null + * @param originalToClone the workspace that is to be cloned; may not be null + */ + public MapWorkspace( String name, + Map nodesByUuid, + MapWorkspace originalToClone ) { + this.name = name; + this.nodesByUuid = nodesByUuid; + this.nodesByUuid.putAll(originalToClone.nodesByUuid); // make a copy + this.rootNodeUuid = originalToClone.getRootNode().getUuid(); + assert this.name != null; + assert this.rootNodeUuid != null; + assert this.nodesByUuid != null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Workspace#getName() + */ + public String getName() { + return name; + } + + /** + * Get the root node in this workspace. + * + * @return the root node; never null + */ + public NodeType getRootNode() { + return nodesByUuid.get(rootNodeUuid); + } + + /** + * Get the node with the supplied UUID. + * + * @param uuid the UUID of the node + * @return the node state as known by this workspace, or null if no such node exists in this workspace + */ + public NodeType getNode( UUID uuid ) { + return nodesByUuid.get(uuid); + } + + /** + * Add the node into this workspace's map, overwriting any previous record of the node + * + * @param node the new node; may not be null + * @return the previous node state, or null if the node is new to this workspace + */ + public NodeType putNode( NodeType node ) { + return nodesByUuid.put(node.getUuid(), node); + } + + /** + * Remove and return the node with the supplied UUID. This method will never remove the root node. + * + * @param uuid the UUID of the node to be removed + * @return the node that was removed, or null if the supplied UUID is the root node's UUID or if this workspace does not + * contain a node with the supplied UUID + */ + public NodeType removeNode( UUID uuid ) { + return rootNodeUuid.equals(uuid) ? null : nodesByUuid.remove(uuid); + } + + /** + * Remove all of the nodes in this workspace, and make sure there is a single root node with no properties and no children. + */ + @SuppressWarnings( "unchecked" ) + public void removeAll() { + NodeType newRootNode = (NodeType)getRootNode().withoutChildren().withoutProperties().freeze(); + nodesByUuid.clear(); + nodesByUuid.put(newRootNode.getUuid(), newRootNode); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return name; + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Node.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Node.java (working copy) @@ -0,0 +1,68 @@ +/* + * 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.base; + +import java.util.Map; +import java.util.UUID; +import net.jcip.annotations.Immutable; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.Property; + +/** + * A snapshot of a single node within a map-based repository. + */ +@Immutable +public interface Node { + + /** + * Returns the UUID for this node + * + * @return the UUID for this node + */ + UUID getUuid(); + + /** + * Returns the name of this node along with its SNS index within its parent's children + * + * @return the name of this node along with its SNS index within its parent's children, or null for the root node + */ + Path.Segment getName(); + + /** + * Returns properties of this node. + * + * @return an immutable map of properties keyed by their name. + */ + Map getProperties(); + + /** + * Returns the property with the supplied name. + * + * @param name the name of the property + * @return the property, or null if this node has no such property + */ + Property getProperty( Name name ); + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Processor.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Processor.java (working copy) @@ -0,0 +1,633 @@ +/* + * 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.base; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.Location; +import org.modeshape.graph.ModeShapeLexicon; +import org.modeshape.graph.observe.Observer; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathFactory; +import org.modeshape.graph.property.PathNotFoundException; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.PropertyFactory; +import org.modeshape.graph.property.Path.Segment; +import org.modeshape.graph.query.QueryResults; +import org.modeshape.graph.request.AccessQueryRequest; +import org.modeshape.graph.request.CloneBranchRequest; +import org.modeshape.graph.request.CloneWorkspaceRequest; +import org.modeshape.graph.request.CopyBranchRequest; +import org.modeshape.graph.request.CreateNodeRequest; +import org.modeshape.graph.request.CreateWorkspaceRequest; +import org.modeshape.graph.request.DeleteBranchRequest; +import org.modeshape.graph.request.DestroyWorkspaceRequest; +import org.modeshape.graph.request.FullTextSearchRequest; +import org.modeshape.graph.request.GetWorkspacesRequest; +import org.modeshape.graph.request.InvalidRequestException; +import org.modeshape.graph.request.InvalidWorkspaceException; +import org.modeshape.graph.request.LockBranchRequest; +import org.modeshape.graph.request.MoveBranchRequest; +import org.modeshape.graph.request.ReadAllChildrenRequest; +import org.modeshape.graph.request.ReadAllPropertiesRequest; +import org.modeshape.graph.request.ReadNodeRequest; +import org.modeshape.graph.request.Request; +import org.modeshape.graph.request.UnlockBranchRequest; +import org.modeshape.graph.request.UpdatePropertiesRequest; +import org.modeshape.graph.request.VerifyWorkspaceRequest; +import org.modeshape.graph.request.processor.RequestProcessor; + +/** + * The default implementation of the {@link RequestProcessor} for map repositories. + * + * @param the node type + * @param the workspace type + */ +public class Processor extends RequestProcessor { + private final PathFactory pathFactory; + private final PropertyFactory propertyFactory; + private final Repository repository; + private final boolean updatesAllowed; + private final Transaction txn; + + public Processor( Transaction txn, + Repository repository, + Observer observer, + boolean updatesAllowed ) { + super(repository.getSourceName(), txn.getContext(), observer); + this.txn = txn; + this.repository = repository; + this.pathFactory = txn.getContext().getValueFactories().getPathFactory(); + this.propertyFactory = txn.getContext().getPropertyFactory(); + this.updatesAllowed = updatesAllowed; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadNodeRequest) + */ + @Override + public void process( ReadNodeRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.at()); + if (node == null) { + assert request.hasError(); + return; + } + + Location actualLocation = getActualLocation(workspace, request.at(), node); + assert actualLocation != null; + Path path = actualLocation.getPath(); + // Get the names of the children ... + List children = txn.getChildren(workspace, node); + for (Node child : children) { + Segment childName = child.getName(); + Path childPath = pathFactory.create(path, childName); + request.addChild(childPath, propertyFactory.create(ModeShapeLexicon.UUID, child.getUuid())); + } + + // Get the properties of the node ... + request.addProperty(propertyFactory.create(ModeShapeLexicon.UUID, node.getUuid())); + request.addProperties(node.getProperties().values()); + + request.setActualLocationOfNode(actualLocation); + setCacheableInfo(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadAllChildrenRequest) + */ + @Override + public void process( ReadAllChildrenRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.of()); + if (node == null) { + assert request.hasError(); + return; + } + + Location actualLocation = getActualLocation(workspace, request.of(), node); + assert actualLocation != null; + Path path = actualLocation.getPath(); + // Get the names of the children ... + List children = txn.getChildren(workspace, node); + for (Node child : children) { + Segment childName = child.getName(); + Path childPath = pathFactory.create(path, childName); + request.addChild(childPath, propertyFactory.create(ModeShapeLexicon.UUID, child.getUuid())); + } + request.setActualLocationOfNode(actualLocation); + setCacheableInfo(request); + } + + @Override + public void process( LockBranchRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.at()); + if (node == null) return; + + txn.lockNode(workspace, node, request.lockScope(), request.lockTimeoutInMillis()); + + Location actualLocation = getActualLocation(workspace, request.at(), node); + request.setActualLocation(actualLocation); + recordChange(request); + } + + @Override + public void process( UnlockBranchRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.at()); + if (node == null) return; + + txn.unlockNode(workspace, node); + + Location actualLocation = getActualLocation(workspace, request.at(), node); + request.setActualLocation(actualLocation); + recordChange(request); + } + + @Override + public void process( ReadAllPropertiesRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.at()); + if (node == null) { + assert request.hasError(); + return; + } + + // Get the properties of the node ... + Location actualLocation = getActualLocation(workspace, request.at(), node); + request.addProperty(propertyFactory.create(ModeShapeLexicon.UUID, node.getUuid())); + request.addProperties(node.getProperties().values()); + + assert actualLocation != null; + request.setActualLocationOfNode(actualLocation); + setCacheableInfo(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneBranchRequest) + */ + @Override + public void process( CloneBranchRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.fromWorkspace()); + WorkspaceType newWorkspace = getWorkspace(request, request.intoWorkspace()); + if (workspace == null || newWorkspace == null) return; + NodeType node = getTargetNode(workspace, request, request.from()); + if (node == null) return; + + // Look up the new parent, which must exist ... + Path newParentPath = request.into().getPath(); + NodeType newParent = txn.getNode(newWorkspace, request.into()); + Set removedExistingNodes = new HashSet(); + NodeType newNode = txn.cloneNode(workspace, + node, + newWorkspace, + newParent, + request.desiredName(), + request.desiredSegment(), + request.removeExisting(), + removedExistingNodes); + Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, newNode.getName()); + Location oldLocation = getActualLocation(workspace, request.from(), node); + Location newLocation = Location.create(newPath, newNode.getUuid()); + request.setActualLocations(oldLocation, newLocation); + request.setRemovedNodes(Collections.unmodifiableSet(removedExistingNodes)); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CopyBranchRequest) + */ + @Override + public void process( CopyBranchRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.fromWorkspace()); + WorkspaceType newWorkspace = getWorkspace(request, request.intoWorkspace()); + if (workspace == null || newWorkspace == null) return; + NodeType node = getTargetNode(workspace, request, request.from()); + if (node == null) return; + + // Look up the new parent, which must exist ... + Path newParentPath = request.into().getPath(); + Name desiredName = request.desiredName(); + NodeType newParent = getTargetNode(newWorkspace, request, request.into()); + if (newParent == null) return; + NodeType newNode = txn.copyNode(workspace, node, newWorkspace, newParent, desiredName, true); + Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, newNode.getName()); + Location oldLocation = getActualLocation(workspace, request.from(), node); + Location newLocation = Location.create(newPath, newNode.getUuid()); + request.setActualLocations(oldLocation, newLocation); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateNodeRequest) + */ + @Override + public void process( CreateNodeRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + if (workspace == null) return; + + // Look up the parent node, which must exist ... + NodeType parentNode = txn.getNode(workspace, request.under()); + + UUID uuid = null; + // Make a list of the properties that we will store: all props except dna:uuid and jcr:uuid + List propsToStore = new ArrayList(request.properties().size()); + for (Property property : request.properties()) { + if (property.getName().equals(ModeShapeLexicon.UUID) || property.getName().equals(JcrLexicon.UUID)) { + uuid = getExecutionContext().getValueFactories().getUuidFactory().create(property.getValues().next()); + } else { + if (property.size() > 0) propsToStore.add(property); + } + } + + NodeType node = null; + switch (request.conflictBehavior()) { + case APPEND: + node = txn.addChild(workspace, parentNode, request.named(), -1, uuid, propsToStore); + break; + case DO_NOT_REPLACE: + node = txn.getFirstChild(workspace, parentNode, request.named()); + if (node == null) { + node = txn.addChild(workspace, parentNode, request.named(), -1, uuid, propsToStore); + } + break; + case REPLACE: + // See if the node already exists (this doesn't record an error on the request) ... + node = txn.getFirstChild(workspace, parentNode, request.named()); + if (node != null) { + txn.removeNode(workspace, node); + node = txn.addChild(workspace, parentNode, request.named(), 1, uuid, propsToStore); + } else { + node = txn.addChild(workspace, parentNode, request.named(), -1, uuid, propsToStore); + } + break; + case UPDATE: + // See if the node already exists (this doesn't record an error on the request) ... + node = txn.getFirstChild(workspace, parentNode, request.named()); + if (node == null) { + node = txn.addChild(workspace, parentNode, request.named(), -1, uuid, propsToStore); + } else { + // otherwise, we found it and add the properties (which we'll do later on)... + } + break; + } + assert node != null; + Path path = txn.pathFor(workspace, node); + Location actualLocation = getActualLocation(workspace, Location.create(path), node); + request.setActualLocationOfNode(actualLocation); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DeleteBranchRequest) + */ + @Override + public void process( DeleteBranchRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + if (workspace == null) return; + NodeType node = getTargetNode(workspace, request, request.at()); + if (node == null) return; + Location actualLocation = txn.removeNode(workspace, node); + request.setActualLocationOfNode(actualLocation); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.MoveBranchRequest) + */ + @Override + public void process( MoveBranchRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + if (workspace == null) return; + + NodeType beforeNode = request.before() != null ? getTargetNode(workspace, request, request.before()) : null; + NodeType node = getTargetNode(workspace, request, request.from()); + if (node == null) return; + if (request.hasError()) return; // if beforeNode could not be found + // Look up the new parent, which must exist ... + + NodeType newParent = null; + Location newLocation = null; + if (request.into() != null) { + newParent = txn.getNode(workspace, request.into()); // this will fail if there is no node + newLocation = txn.addChild(workspace, newParent, node, null, request.desiredName()); + } else { + // into or before cannot both be null + assert beforeNode != null; + // The new parent is the parent of the before node ... + newParent = txn.getParent(workspace, beforeNode); + if (newParent == null) { + // The before node must be the root ... + request.setError(new PathNotFoundException(request.into(), pathFactory.createRootPath(), + GraphI18n.nodeDoesNotExist.text("parent of root"))); + return; + } + // Move the node into the parent of the beforeNode ... + newLocation = txn.addChild(workspace, newParent, node, beforeNode, request.desiredName()); + } + assert newParent != null; + + Location oldLocation = getActualLocation(workspace, request.from(), node); + request.setActualLocations(oldLocation, newLocation); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.UpdatePropertiesRequest) + */ + @Override + public void process( UpdatePropertiesRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = getWorkspace(request, request.inWorkspace()); + NodeType node = getTargetNode(workspace, request, request.on()); + if (node == null) return; + // Figure out which properties should be set ... + List propertiesToSet = null; + Set propertiesToRemove = null; + for (Map.Entry propertyEntry : request.properties().entrySet()) { + Property property = propertyEntry.getValue(); + if (property == null) { + if (propertiesToRemove == null) propertiesToRemove = new HashSet(); + propertiesToRemove.add(propertyEntry.getKey()); + continue; + } + Name propName = property.getName(); + if (!propName.equals(ModeShapeLexicon.UUID)) { + if (node.getProperties().get(propName) == null) { + // It is a new property ... + request.setNewProperty(propName); + } + if (propertiesToSet == null) propertiesToSet = new LinkedList(); + propertiesToSet.add(property); + } + } + + txn.setProperties(workspace, node, propertiesToSet, propertiesToRemove, request.removeOtherProperties()); + Location actualLocation = getActualLocation(workspace, request.on(), node); + request.setActualLocationOfNode(actualLocation); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateWorkspaceRequest) + */ + @Override + public void process( CreateWorkspaceRequest request ) { + if (!updatesAllowed(request)) return; + + String clonedWorkspaceName = null; + WorkspaceType workspace = repository.createWorkspace(txn, + request.desiredNameOfNewWorkspace(), + request.conflictBehavior(), + clonedWorkspaceName); + if (workspace == null) { + String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(request.desiredNameOfNewWorkspace(), + repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + } else { + Node root = txn.getRootNode(workspace); + request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); + request.setActualWorkspaceName(workspace.getName()); + recordChange(request); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DestroyWorkspaceRequest) + */ + @Override + public void process( DestroyWorkspaceRequest request ) { + if (!updatesAllowed(request)) return; + + WorkspaceType workspace = repository.getWorkspace(txn, request.workspaceName()); + if (workspace != null) { + Node root = txn.getRootNode(workspace); + try { + txn.destroyWorkspace(workspace); + request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); + recordChange(request); + } catch (RuntimeException e) { + request.setError(e); + } + } else { + String msg = GraphI18n.workspaceDoesNotExistInRepository.text(request.workspaceName(), repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.GetWorkspacesRequest) + */ + @Override + public void process( GetWorkspacesRequest request ) { + // Always ask the transaction (in case a new workspace was added by another process) ... + Set names = txn.getWorkspaceNames(); + request.setAvailableWorkspaceNames(new HashSet(names)); + setCacheableInfo(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.VerifyWorkspaceRequest) + */ + @Override + public void process( VerifyWorkspaceRequest request ) { + WorkspaceType original = getWorkspace(request, request.workspaceName()); + if (original != null) { + Path path = getExecutionContext().getValueFactories().getPathFactory().createRootPath(); + Node root = txn.getRootNode(original); + request.setActualRootLocation(Location.create(path, root.getUuid())); + request.setActualWorkspaceName(original.getName()); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneWorkspaceRequest) + */ + @Override + public void process( CloneWorkspaceRequest request ) { + if (!updatesAllowed(request)) return; + + // Find the original workspace that we're cloning ... + String targetWorkspaceName = request.desiredNameOfTargetWorkspace(); + String nameOfWorkspaceToBeCloned = request.nameOfWorkspaceToBeCloned(); + WorkspaceType original = repository.getWorkspace(txn, nameOfWorkspaceToBeCloned); + WorkspaceType target = repository.getWorkspace(txn, targetWorkspaceName); + + if (target != null) { + String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + return; + } + + if (original == null) { + switch (request.cloneConflictBehavior()) { + case DO_NOT_CLONE: + String msg = GraphI18n.workspaceDoesNotExistInRepository.text(nameOfWorkspaceToBeCloned, + repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + return; + case SKIP_CLONE: + nameOfWorkspaceToBeCloned = null; + break; + } + } + assert original != null; + target = repository.createWorkspace(txn, targetWorkspaceName, request.targetConflictBehavior(), nameOfWorkspaceToBeCloned); + assert target != null; + NodeType root = txn.getRootNode(target); + request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid())); + request.setActualWorkspaceName(target.getName()); + recordChange(request); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.AccessQueryRequest) + */ + @Override + public void process( AccessQueryRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.workspace()); + if (workspace == null) return; + QueryResults results = txn.query(workspace, request); + if (results != null) { + request.setResults(results.getTuples(), results.getStatistics()); + } else { + super.processUnknownRequest(request); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.FullTextSearchRequest) + */ + @Override + public void process( FullTextSearchRequest request ) { + WorkspaceType workspace = getWorkspace(request, request.workspace()); + if (workspace == null) return; + QueryResults results = txn.search(workspace, request); + if (results != null) { + request.setResults(results.getColumns(), results.getTuples(), results.getStatistics()); + } else { + super.processUnknownRequest(request); + } + } + + protected Location getActualLocation( WorkspaceType workspace, + Location location, + NodeType node ) { + Path path = location.getPath(); + if (path == null) { + // Find the path on the node ... + path = txn.pathFor(workspace, node); + } + // If there is a UUID in the location, it should match the node's. + assert location.getUuid() == null || location.getUuid().equals(node.getUuid()); + if (location.hasIdProperties()) { + return location.with(path); + } + return Location.create(path, node.getUuid()); + } + + protected WorkspaceType getWorkspace( Request request, + String workspaceName ) { + // Get the workspace for this request ... + WorkspaceType workspace = repository.getWorkspace(txn, workspaceName); + if (workspace == null) { + String msg = GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, repository.getSourceName()); + request.setError(new InvalidWorkspaceException(msg)); + } + return workspace; + } + + protected boolean updatesAllowed( Request request ) { + if (!updatesAllowed) { + request.setError(new InvalidRequestException(GraphI18n.sourceIsReadOnly.text(getSourceName()))); + } + return !request.hasError(); + } + + protected NodeType getTargetNode( WorkspaceType workspace, + Request request, + Location location ) { + if (workspace != null) { + try { + return txn.getNode(workspace, location); + } catch (PathNotFoundException e) { + request.setError(e); + } catch (RuntimeException e) { + request.setError(e); + } + } + return null; + } +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Repository.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Repository.java (working copy) @@ -0,0 +1,269 @@ +/* + * 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.base; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import net.jcip.annotations.ThreadSafe; +import org.modeshape.common.util.CheckArg; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.request.InvalidWorkspaceException; +import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; + +/** + * A representation of a repository as a set of named workspaces. Workspaces can be + * {@link #createWorkspace(Transaction, String, CreateConflictBehavior, String) created} or {@link #destroyWorkspace(String) + * destroyed}, though the exact type of {@link Workspace} is dictated by the {@link Transaction}. All workspaces contain a root + * node with the same {@link #getRootNodeUuid() UUID}. + *

+ * Note that this class is thread-safe, since a {@link BaseRepositorySource} will contain a single instance of a concrete subclass + * of this class. Often, the Workspace objects are used to hold onto the workspace-related content, but access to the content is + * always done through a {@link #startTransaction(ExecutionContext, boolean) transaction}. + *

+ * + * @param the node type + * @param the workspace type + */ +@ThreadSafe +public abstract class Repository { + + protected final UUID rootNodeUuid; + protected final ExecutionContext context; + private final String sourceName; + private final String defaultWorkspaceName; + private final Map workspaces = new HashMap(); + private final ReadWriteLock workspacesLock = new ReentrantReadWriteLock(); + + /** + * Creates a {@code MapRepository} with the given repository source name, root node UUID, and a default workspace with the + * given name. + * + * @param context the context in which this repository exists + * @param sourceName the name of the repository source for use in error and informational messages + * @param rootNodeUuid the UUID that will be used as the root node UUID for each workspace in the repository + * @param defaultWorkspaceName the name of the default, auto-created workspace; if null, then a blank name (e.g., "") is + * assumed + * @throws IllegalArgumentException if the repository context or root node UUID are null, or if the source name is null or + * empty + */ + protected Repository( ExecutionContext context, + String sourceName, + UUID rootNodeUuid, + String defaultWorkspaceName ) { + CheckArg.isNotNull(context, "repositoryContext"); + CheckArg.isNotEmpty(sourceName, "sourceName"); + CheckArg.isNotNull(rootNodeUuid, "rootNodeUUID"); + this.context = context; + this.rootNodeUuid = rootNodeUuid; + this.sourceName = sourceName; + this.defaultWorkspaceName = defaultWorkspaceName != null ? defaultWorkspaceName : ""; + } + + /** + * Initializes the repository by creating the default workspace. + *

+ * Due to the ordering restrictions on constructor chaining, this method cannot be called until the repository is fully + * initialized. This method MUST be called at the end of the constructor by any class that implements {@code MapRepository} + * . + */ + protected void initialize() { + Transaction txn = startTransaction(context, false); + try { + // Create the default workspace ... + workspaces.put(this.defaultWorkspaceName, createWorkspace(txn, + this.defaultWorkspaceName, + CreateConflictBehavior.DO_NOT_CREATE, + null)); + } finally { + txn.commit(); + } + + } + + public ExecutionContext getContext() { + return context; + } + + protected String getDefaultWorkspaceName() { + return defaultWorkspaceName; + } + + /** + * Returns the UUID used by the root nodes in each workspace. + *

+ * Note that the root nodes themselves are distinct objects in each workspace and a change to the root node of one workspace + * does not imply a change to the root nodes of any other workspaces. However, the JCR specification mandates that all + * referenceable root nodes in a repository use a common UUID (in support of node correspondence); therefore this must be + * supported by ModeShape. + * + * @return the root node UUID + */ + public final UUID getRootNodeUuid() { + return rootNodeUuid; + } + + /** + * Returns the logical name (as opposed to the class name) of the repository source that defined this instance of the + * repository for use in error, informational, and other contextual messages. + * + * @return sourceName the logical name for the repository source name + */ + public String getSourceName() { + return sourceName; + } + + /** + * Get the names of the available workspaces that have been loaded. + * + * @return the immutable names of the workspaces. + */ + public Set getWorkspaceNames() { + try { + workspacesLock.readLock().lock(); + return Collections.unmodifiableSet(new HashSet(workspaces.keySet())); + } finally { + workspacesLock.readLock().unlock(); + } + } + + /** + * Returns the workspace with the given name. + * + * @param txn the transaction attempting to get the workspace, and which may be used to create the workspace object if needed; + * may not be null + * @param name the name of the workspace to return + * @return the workspace with the given name; may be null if no workspace with the given name exists + */ + public WorkspaceType getWorkspace( Transaction txn, + String name ) { + if (name == null) name = defaultWorkspaceName; + Lock lock = workspacesLock.readLock(); + try { + lock.lock(); + WorkspaceType workspace = workspaces.get(name); + if (workspace == null && getWorkspaceNames().contains(name)) { + workspace = txn.getWorkspace(name, null); + } + return workspace; + } finally { + lock.unlock(); + } + } + + /** + * Attempts to create a workspace with the given name with name-collision behavior determined by the behavior parameter. + *

+ * This method will first check to see if a workspace already exists with the given name. If no such workspace exists, the + * method will create a new workspace with the given name, add it to the {@code #workspaces workspaces map}, and return it. If + * a workspace with the requested name already exists and the {@code behavior} is {@link CreateConflictBehavior#DO_NOT_CREATE} + * , this method will return {@code null} without modifying the state of the repository. If a workspace with the requested + * name already exists and the {@code behavior} is {@link CreateConflictBehavior#CREATE_WITH_ADJUSTED_NAME}, this method will + * generate a unique new name for the workspace, create a new workspace with the given name, added it to the {@code + * #workspaces workspaces map}, and return it. + *

+ *

+ * If {@code nameOfWorkspaceToClone} is given, this method will clone the content in this original workspace into the new + * workspace. However, if no workspace with the name {@code nameOfWorkspaceToClone} exists, the method will create an empty + * workspace. + *

+ * + * @param txn the transaction creating the workspace; may not be null + * @param name the requested name of the workspace. The name of the workspace that is returned from this method may not be the + * same as the requested name; may not be null + * @param existingWorkspaceBehavior the behavior to use in case a workspace with the requested name already exists in the + * repository + * @param nameOfWorkspaceToClone the name of the workspace from which the content should be cloned; be null if the new + * workspace is to be empty (other than the root node) + * @return the newly created workspace with an exact copy of the contents from the workspace named {@code + * nameOfWorkspaceToClone} or {@code null} if a workspace with the requested name already exists in the repository and + * {@code behavior == CreateConflictBehavior#DO_NOT_CREATE}. + * @throws InvalidWorkspaceException if the workspace could not be created + */ + public WorkspaceType createWorkspace( Transaction txn, + String name, + CreateConflictBehavior existingWorkspaceBehavior, + String nameOfWorkspaceToClone ) throws InvalidWorkspaceException { + String newName = name; + Lock lock = workspacesLock.writeLock(); + try { + lock.lock(); + boolean conflictingName = workspaces.containsKey(newName); + if (conflictingName) { + switch (existingWorkspaceBehavior) { + case DO_NOT_CREATE: + return null; + case CREATE_WITH_ADJUSTED_NAME: + int counter = 0; + do { + newName = name + (++counter); + } while (workspaces.containsKey(newName)); + break; + } + } + assert workspaces.containsKey(newName) == false; + + WorkspaceType original = nameOfWorkspaceToClone != null ? getWorkspace(txn, nameOfWorkspaceToClone) : null; + WorkspaceType workspace = txn.getWorkspace(name, original); + workspaces.put(name, workspace); + return workspace; + } finally { + lock.unlock(); + } + } + + /** + * Removes the named workspace from the {@code #workspaces workspaces map}. + * + * @param name the name of the workspace to remove + * @return {@code true} if a workspace with that name previously existed in the map + */ + public boolean destroyWorkspace( String name ) { + try { + workspacesLock.writeLock().lock(); + return workspaces.remove(name) != null; + } finally { + workspacesLock.writeLock().unlock(); + } + } + + /** + * Begin a transaction, hinting whether the transaction will be used only to read the content. If this is called, then the + * transaction must be either {@link Transaction#commit() committed} or {@link Transaction#rollback() rolled back}. + * + * @param context the context in which the transaction is to be performed; may not be null + * @param readonly true if the transaction will not modify any content, or false if changes are to be made + * @return the transaction; never null + * @see Transaction#commit() + * @see Transaction#rollback() + */ + public abstract Transaction startTransaction( ExecutionContext context, + boolean readonly ); +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Transaction.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Transaction.java (working copy) @@ -0,0 +1,375 @@ +/* + * 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.base; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.LockFailedException; +import org.modeshape.graph.connector.UuidAlreadyExistsException; +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.query.QueryResults; +import org.modeshape.graph.request.AccessQueryRequest; +import org.modeshape.graph.request.FullTextSearchRequest; +import org.modeshape.graph.request.InvalidWorkspaceException; +import org.modeshape.graph.request.LockBranchRequest.LockScope; + +/** + * A transaction in which all read and write operations against a repository are performed. The actual transaction instance is + * obtained by calling {@link Repository#startTransaction(ExecutionContext,boolean)}. + *

+ * Note that implementations are not required to be thread-safe, since they (and their corresonding {@link Connection}) are + * expected to be used by a single thread. + *

+ * + * @param the type of node + * @param the type of workspace + */ +@NotThreadSafe +public interface Transaction { + + /** + * Get the context in which this operator executes. + * + * @return the execution context; never null + */ + ExecutionContext getContext(); + + /** + * Get the names of the existing workspaces. + * + * @return the immutable set of workspace names; never null + */ + Set getWorkspaceNames(); + + /** + * Creates a new workspace with the given name containing only a root node. If the workspace already exists, it is left + * untouched and returned. + * + * @param name the name of the workspace; may not be null + * @param originalToClone the workspace that should be cloned, or null if the new workspace is to only contain a root node + * @return the newly created workspace; may not be null + * @throws InvalidWorkspaceException if the workspace could not be created + */ + WorkspaceType getWorkspace( String name, + WorkspaceType originalToClone ) throws InvalidWorkspaceException; + + /** + * Destroy the workspace with the supplied name. + * + * @param workspace the workspace that is to be destroyed; may not be null + * @return true if the workspace was destroyed, or false if the workspace did not exist + * @throws InvalidWorkspaceException if the workspace could not be destroyed + */ + boolean destroyWorkspace( WorkspaceType workspace ) throws InvalidWorkspaceException; + + /** + * Get the root node of the repository workspace. + * + * @param workspace the workspace; may not be null + * @return the root node; never null + */ + NodeType getRootNode( WorkspaceType workspace ); + + /** + * Find the node with the supplied unique identifier. + * + * @param workspace the workspace; may not be null + * @param location of the node; may not be null + * @return the node, or null if there is no node with the supplied identifier + * @throws PathNotFoundException if the node at the given location could not be found + */ + NodeType getNode( WorkspaceType workspace, + Location location ); + + /** + * Returns the path for the given node with this workspace if one exists, or a {@code null} if no node exists at the given + * path. + * + * @param workspace the workspace; may not be null + * @param node the node for which the path should be retrieved; may not be null + * @return the path for the given node with this workspace if one exists or null if the node does not exist in this workspace + */ + Path pathFor( WorkspaceType workspace, + NodeType node ); + + /** + * Returns the parent of the supplied node. This method returns null if the supplied node is the root node. + * + * @param workspace the workspace; may not be null + * @param node the child node; may not be null + * @return the parent of this node; or null if the node is the root node for its workspace + */ + NodeType getParent( WorkspaceType workspace, + NodeType node ); + + /** + * Find in the supplied parent node the child with the supplied name and same-name-sibling index. This method returns null if + * the parent has no such child. + * + * @param workspace the workspace; may not be null + * @param parent the parent node; may not be null + * @param childSegment the segment of the child; may not be null + * @return the child of this node; or null if no such child exists + */ + NodeType getChild( WorkspaceType workspace, + NodeType parent, + Path.Segment childSegment ); + + /** + * Find in the supplied parent node the first child with the supplied name. This method returns null if the parent has no such + * child. + * + * @param workspace the workspace; may not be null + * @param parent the parent node; may not be null + * @param childName the name of the child; may not be null + * @return the child of this node; or null if no such child exists + */ + NodeType getFirstChild( WorkspaceType workspace, + NodeType parent, + Name childName ); + + /** + * Get the children for the supplied node. + * + * @param workspace the workspace; may not be null + * @param node the node whose children are to be returned; may not be null + * @return the children, never null but possibly empty + */ + List getChildren( WorkspaceType workspace, + NodeType node ); + + /** + * Removes all of the children for this node in a single operation. + * + * @param workspace the workspace; may not be null + * @param node the node whose children are to be removed; may not be null + */ + void removeAllChildren( WorkspaceType workspace, + NodeType node ); + + /** + * Creates a new child node under the supplied parent, where the new child will have the specified name, properties, and + * (optionally) UUID. The child will be appended to the list of children, and will be given the appropriate same-name-sibling + * index. + * + * @param workspace the workspace; may not be null + * @param parent the parent node; may not be null + * @param name the name; may not be null + * @param index index at which the specified child is to be inserted, or -1 if the child is to be appended + * @param uuid the UUID of the node, or null if the UUID is to be generated + * @param properties the properties for the new node; may be null if there are no other properties + * @return the representation of the new node + */ + NodeType addChild( WorkspaceType workspace, + NodeType parent, + Name name, + int index, + UUID uuid, + Iterable properties ); + + /** + * Inserts the specified child at the specified position in the list of children. Shifts the child currently at that position + * (if any) and any subsequent children to the right (adds one to their indices). The child is automatically removed from its + * existing parent (if it has one), though this method can be used to reorder a child within the same parent. + *

+ * This method can also be used to rename an existing child by 'moving' the child node to the existing parent and a new + * desired name. However, if no 'beforeOtherChild' is supplied, then the node being renamed will also be moved to the end of + * the children. + *

+ * + * @param workspace the workspace; may not be null + * @param parent the parent node; may not be null + * @param newChild the node that is to be added as a child of the parent; may not be null + * @param beforeOtherChild the existing child before which the child is to be added; may be null if the child is to be added + * at the end + * @param desiredName the desired name for the node; may be null if the new child node's name is to be kept + * @return the actual location of the node, or null if the node didn't exist + */ + Location addChild( WorkspaceType workspace, + NodeType parent, + NodeType newChild, + NodeType beforeOtherChild, + Name desiredName ); + + /** + * Removes the given node from the repository. + * + * @param workspace the workspace; may not be null + * @param node the node to be removed; may not be null + * @return the actual location of the node, or null if the node didn't exist + */ + Location removeNode( WorkspaceType workspace, + NodeType node ); + + /** + * Sets the given properties in a single operation, overwriting any previous properties for the same name. This bulk mutator + * should be used when multiple properties are being set in order to allow underlying implementations to optimize their access + * to their respective persistent storage mechanism. The implementation should not change the identification + * properties. + * + * @param workspace the workspace; may not be null + * @param node the node; may not be null + * @param propertiesToSet the properties to set; may be null or empty if there are no properties being added or set + * @param propertiesToRemove the names of the properties that are to be removed; may be null or empty if no properties are + * being removed + * @param removeAllExisting true if all existing, non-identification properties should be removed, or false otherwise + * @return this map node + */ + NodeType setProperties( WorkspaceType workspace, + NodeType node, + Iterable propertiesToSet, + Iterable propertiesToRemove, + boolean removeAllExisting ); + + /** + * Removes the property with the given name + * + * @param workspace the workspace; may not be null + * @param node the node; may not be null + * @param propertyName the name of the property to remove; may not be null + * @return this map node + */ + NodeType removeProperty( WorkspaceType workspace, + NodeType node, + Name propertyName ); + + /** + * This should clone the subgraph given by the original node and place the cloned copy under the supplied new parent. Note + * that internal references between nodes within the original subgraph must be reflected as internal nodes within the new + * subgraph. + * + * @param originalWorkspace the workspace containing the original node that is being cloned; may not be null + * @param original the node to be cloned; may not be null + * @param newWorkspace the workspace containing the new parent node; may not be null + * @param newParent the parent where the clone is to be placed; may not be null + * @param desiredName the desired name for the node; if null, the name will be calculated from {@code desiredSegment}; Exactly + * one of {@code desiredSegment} and {@code desiredName} must be non-null + * @param desiredSegment the exact segment at which the clone should be rooted; if null, the name will be inferred from + * {@code desiredName}; Exactly one of {@code desiredSegment} and {@code desiredName} must be non-null + * @param removeExisting true if existing nodes in the new workspace with the same UUIDs as nodes in the branch rooted at + * {@code original} should be removed; if false, a UuidAlreadyExistsException will be thrown if a UUID conflict is + * detected + * @param removedExistingNodes the set into which should be placed all of the existing nodes that were removed as a result of + * this clone operation, or null if these nodes need not be collected + * @return the new node, which is the top of the new subgraph + * @throws UuidAlreadyExistsException if {@code removeExisting} is true and and a UUID in the source tree already exists in + * the new workspace + */ + NodeType cloneNode( WorkspaceType originalWorkspace, + NodeType original, + WorkspaceType newWorkspace, + NodeType newParent, + Name desiredName, + Path.Segment desiredSegment, + boolean removeExisting, + Set removedExistingNodes ) throws UuidAlreadyExistsException; + + /** + * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note that + * internal references between nodes within the original subgraph must be reflected as internal nodes within the new subgraph. + * + * @param originalWorkspace the workspace containing the original node that is being cloned; may not be null + * @param original the node to be copied; may not be null + * @param newWorkspace the workspace containing the new parent node; may not be null + * @param newParent the parent where the copy is to be placed; may not be null + * @param desiredName the desired name for the node; if null, the name will be obtained from the original node + * @param recursive true if the copy should be recursive + * @return the new node, which is the top of the new subgraph + */ + NodeType copyNode( WorkspaceType originalWorkspace, + NodeType original, + WorkspaceType newWorkspace, + NodeType newParent, + Name desiredName, + boolean recursive ); + + /** + * Perform a query of this workspace. + * + * @param workspace the workspace to be searched; may not be null + * @param accessQuery the access query; may not be null + * @return the query results, or null if the query is not supported + */ + QueryResults query( WorkspaceType workspace, + AccessQueryRequest accessQuery ); + + /** + * Perform a full-text search of this workspace. + * + * @param workspace the workspace to be searched; may not be null + * @param search the full-text search; may not be null + * @return the query results, or null if the query is not supported + */ + QueryResults search( WorkspaceType workspace, + FullTextSearchRequest search ); + + /** + * Attempts to lock the given node with the given timeout. If the lock attempt fails, a {@link LockFailedException} will be + * thrown. + * + * @param workspace the workspace; may not be null + * @param node the node to be locked; may not be null + * @param lockScope the scope of the lock (i.e., whether descendants of {@code node} should be included in the lock + * @param lockTimeoutInMillis the maximum lifetime of the lock in milliseconds; zero (0) indicates that the connector default + * should be used + * @throws LockFailedException if the implementing connector supports locking but the lock could not be acquired. + */ + void lockNode( WorkspaceType workspace, + NodeType node, + LockScope lockScope, + long lockTimeoutInMillis ) throws LockFailedException; + + /** + * Attempts to unlock the given node. + * + * @param workspace the workspace; may not be null + * @param node the node to be unlocked; may not be null + */ + void unlockNode( WorkspaceType workspace, + NodeType node ); + + /** + * Commit any changes that have been made to the repository. This method may throw runtime exceptions if there are failures + * committing the changes, but the transaction is still expected to be closed. + * + * @see #rollback() + */ + void commit(); + + /** + * Rollback any changes that have been made to this repository. This method may throw runtime exceptions if there are failures + * rolling back the changes, but the transaction is still expected to be closed. + * + * @see #commit() + */ + void rollback(); + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Workspace.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Workspace.java (working copy) @@ -0,0 +1,39 @@ +/* + * 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.base; + + +/** + * The {@code MapWorkspace} stores state and other information about a workspace in a way that is independent of + * {@link Transaction}s. + */ +public interface Workspace { + + /** + * Returns the name of the workspace. There can only be one workspace with a given name per repository. + * + * @return the name of the workspace + */ + String getName(); +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/federation/FederatedRepositorySource.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/federation/FederatedRepositorySource.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/federation/FederatedRepositorySource.java (working copy) @@ -39,15 +39,15 @@ import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.CheckArg; import org.modeshape.common.util.HashCode; import org.modeshape.common.util.NamedThreadFactory; -import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.GraphI18n; import org.modeshape.graph.Location; +import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.Node; import org.modeshape.graph.Subgraph; import org.modeshape.graph.SubgraphNode; @@ -69,7 +69,7 @@ import org.modeshape.graph.property.ValueFactory; /** * A {@link RepositorySource} for a federated repository. */ -@NotThreadSafe +@ThreadSafe public class FederatedRepositorySource implements RepositorySource, ObjectFactory { /** Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryNode.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryNode.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryNode.java (working copy) @@ -23,180 +23,74 @@ */ package org.modeshape.graph.connector.inmemory; -import java.util.Set; +import java.util.List; +import java.util.Map; import java.util.UUID; -import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.connector.inmemory.InMemoryRepository.InMemoryNodeState; -import org.modeshape.graph.connector.map.DefaultMapNode; -import org.modeshape.graph.connector.map.MapNode; +import org.modeshape.graph.connector.base.MapNode; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.Path.Segment; /** - * An {@link MapNode} implementation used by the {@link InMemoryRepository}. + * A specialization of the {@link MapNode}. */ -public class InMemoryNode extends DefaultMapNode { +public class InMemoryNode extends MapNode { - private final ChangeListener listener; + private static final long serialVersionUID = 1L; /** - * @param listener the listener that is to be notified of any changes - * @param uuid the UUID of the node - */ - public InMemoryNode( ChangeListener listener, - UUID uuid ) { - super(uuid); - this.listener = listener; - assert this.listener != null; - } - - /** - * The interface that {@link InMemoryNode} objects use to signal when they are about to change. - */ - public static interface ChangeListener { - /** - * Signal that the supplied node is about to change. - * - * @param node the node that is about to change - */ - void prepareForChange( InMemoryNode node ); - } - - /** - * Restore the state of this node from the supplied snapshot. - * - * @param state the snapshot of the state; may not be null - */ - protected void restoreFrom( InMemoryNodeState state ) { - super.setParent(state.getParent()); - super.setName(state.getName()); - // Restore the properties ... - super.setProperties(state.getProperties()); - // Restore the children ... - super.clearChildren(); - for (MapNode originalChild : state.getChildren()) { - originalChild.setParent(this); - super.addChild(originalChild); - } - Set uniqueChildNames = super.getUniqueChildNames(); - uniqueChildNames.clear(); - uniqueChildNames.addAll(state.getUniqueChildNames()); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#addChild(int, org.modeshape.graph.connector.map.MapNode) - */ - @Override - public void addChild( int index, - MapNode child ) { - listener.prepareForChange(this); - super.addChild(index, child); - } - - /** - * {@inheritDoc} + * Create a new in-memory node. * - * @see org.modeshape.graph.connector.map.DefaultMapNode#addChild(org.modeshape.graph.connector.map.MapNode) + * @param uuid the desired UUID; never null + * @param name the name of the new node; may be null if the name is not known and there is no parent + * @param parent the UUID of the parent node; may be null if this is the root node and there is no name + * @param properties the properties; may be null if there are no properties + * @param children the list of child nodes; may be null */ - @Override - public void addChild( MapNode child ) { - listener.prepareForChange(this); - super.addChild(child); + public InMemoryNode( UUID uuid, + Segment name, + UUID parent, + Map properties, + List children ) { + super(uuid, name, parent, properties, children); } /** - * {@inheritDoc} + * Create a new in-memory node. * - * @see org.modeshape.graph.connector.map.DefaultMapNode#clearChildren() + * @param uuid the desired UUID; never null + * @param name the name of the new node; may be null if the name is not known and there is no parent + * @param parent the UUID of the parent node; may be null if this is the root node and there is no name + * @param properties the properties; may be null if there are no properties + * @param children the list of child nodes; may be null */ - @Override - public void clearChildren() { - listener.prepareForChange(this); - super.clearChildren(); + public InMemoryNode( UUID uuid, + Segment name, + UUID parent, + Iterable properties, + List children ) { + super(uuid, name, parent, properties, children); } /** - * {@inheritDoc} + * Create a new in-memory node. * - * @see org.modeshape.graph.connector.map.DefaultMapNode#removeChild(org.modeshape.graph.connector.map.MapNode) + * @param uuid the desired UUID; never null */ - @Override - public boolean removeChild( MapNode child ) { - listener.prepareForChange(this); - return super.removeChild(child); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#removeProperty(org.modeshape.graph.property.Name) - */ - @Override - public MapNode removeProperty( Name propertyName ) { - listener.prepareForChange(this); - return super.removeProperty(propertyName); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#setName(org.modeshape.graph.property.Path.Segment) - */ - @Override - public void setName( Segment name ) { - // This method only sets this name and does not change the name of any other node ... - listener.prepareForChange(this); - super.setName(name); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#setParent(org.modeshape.graph.connector.map.MapNode) - */ - @Override - public void setParent( MapNode parent ) { - listener.prepareForChange(this); - super.setParent(parent); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#setProperties(java.lang.Iterable) - */ - @Override - public MapNode setProperties( Iterable properties ) { - listener.prepareForChange(this); - return super.setProperties(properties); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.DefaultMapNode#setProperty(org.modeshape.graph.ExecutionContext, java.lang.String, - * java.lang.Object[]) - */ - @Override - public MapNode setProperty( ExecutionContext context, - String name, - Object... values ) { - listener.prepareForChange(this); - return super.setProperty(context, name, values); + public InMemoryNode( UUID uuid ) { + super(uuid); } /** * {@inheritDoc} * - * @see org.modeshape.graph.connector.map.DefaultMapNode#setProperty(org.modeshape.graph.property.Property) + * @see org.modeshape.graph.connector.base.MapNode#freeze() */ @Override - public MapNode setProperty( Property property ) { - listener.prepareForChange(this); - return super.setProperty(property); + public InMemoryNode freeze() { + if (!hasChanges()) return this; + return new InMemoryNode(getUuid(), getName(), getParent(), changes.getUnmodifiableProperties(), + changes.getUnmodifiableChildren()); } } Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java (working copy) @@ -4,13 +4,13 @@ * 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. + * 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 @@ -23,316 +23,47 @@ */ package org.modeshape.graph.connector.inmemory; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.UUID; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.Immutable; -import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.connector.LockFailedException; -import org.modeshape.graph.connector.map.AbstractMapWorkspace; -import org.modeshape.graph.connector.map.LockBasedTransaction; -import org.modeshape.graph.connector.map.MapNode; -import org.modeshape.graph.connector.map.MapRepository; -import org.modeshape.graph.connector.map.MapRepositoryTransaction; -import org.modeshape.graph.connector.map.MapWorkspace; -import org.modeshape.graph.property.Name; -import org.modeshape.graph.property.Path; -import org.modeshape.graph.property.Property; -import org.modeshape.graph.request.LockBranchRequest.LockScope; +import org.modeshape.graph.connector.base.Repository; /** - * A specialized {@link MapRepository} that represents an in-memory repository. + * The representation of an in-memory repository and its content. */ -@NotThreadSafe -public class InMemoryRepository extends MapRepository { +@ThreadSafe +public class InMemoryRepository extends Repository { protected final ReadWriteLock lock = new ReentrantReadWriteLock(); - public InMemoryRepository( String sourceName, + public InMemoryRepository( ExecutionContext context, + String sourceName, UUID rootNodeUuid ) { - super(sourceName, rootNodeUuid, null); + super(context, sourceName, rootNodeUuid, null); initialize(); } - public InMemoryRepository( String sourceName, + public InMemoryRepository( ExecutionContext context, + String sourceName, UUID rootNodeUuid, String defaultWorkspaceName ) { - super(sourceName, rootNodeUuid, defaultWorkspaceName); + super(context, sourceName, rootNodeUuid, defaultWorkspaceName); initialize(); } - @Override - @GuardedBy( "getLock()" ) - protected MapWorkspace createWorkspace( ExecutionContext context, - String name ) { - return new Workspace(this, name); - } - /** * {@inheritDoc} * - * @see org.modeshape.graph.connector.map.MapRepository#startTransaction(boolean) + * @see org.modeshape.graph.connector.base.Repository#startTransaction(org.modeshape.graph.ExecutionContext, boolean) */ @Override - public MapRepositoryTransaction startTransaction( boolean readonly ) { - return new LockBasedTransaction(readonly ? lock.readLock() : lock.writeLock()) { - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.LockBasedTransaction#commit() - */ - @Override - public void commit() { - // Get rid of the state of each workspace ... - releaseBackups(); - // Then let the superclass do its thing (like releasing the lock) ... - super.commit(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.LockBasedTransaction#rollback() - */ - @Override - public void rollback() { - // Restore the state of each workspace ... - restoreBackups(); - // Then let the superclass do its thing (like releasing the lock) ... - super.rollback(); - } - }; - } - - protected ReadWriteLock getLock() { - return lock; - } - - @GuardedBy( "lock" ) - protected void restoreBackups() { - for (String name : getWorkspaceNames()) { - Workspace workspace = (Workspace)getWorkspace(name); - workspace.restoreBackups(); - } - } - - @GuardedBy( "lock" ) - protected void releaseBackups() { - for (String name : getWorkspaceNames()) { - Workspace workspace = (Workspace)getWorkspace(name); - workspace.releaseBackups(); - } - } - - protected class Workspace extends AbstractMapWorkspace implements InMemoryNode.ChangeListener { - private final Map nodesByUuid = new HashMap(); - /** - * The record of the node state before changes were made. This is used by the - * {@link InMemoryRepository#startTransaction(boolean) transactions} to restore the state upon a - * {@link MapRepositoryTransaction#rollback() rollback}. - *

- * This will be created the first time a node is changed, and is then used (and cleared) when #rollbackChanges() rolling - * back changes}. - *

- */ - private Map stateBeforeChanges; - - public Workspace( MapRepository repository, - String name ) { - super(repository, name); - initialize(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.map.AbstractMapWorkspace#createMapNode(java.util.UUID) - */ - @Override - protected MapNode createMapNode( UUID uuid ) { - assert uuid != null; - return new InMemoryNode(this, uuid); - } - - @Override - protected void addNodeToMap( MapNode node ) { - assert node != null; - assert nodesByUuid != null; - nodesByUuid.put(node.getUuid(), node); - } - - @Override - protected MapNode removeNodeFromMap( UUID nodeUuid ) { - assert nodeUuid != null; - return nodesByUuid.remove(nodeUuid); - } - - @Override - protected void removeAllNodesFromMap() { - nodesByUuid.clear(); - } - - @Override - public MapNode getNode( UUID nodeUuid ) { - assert nodeUuid != null; - return nodesByUuid.get(nodeUuid); - } - - /** - * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note - * that internal references between nodes within the original subgraph must be reflected as internal nodes within the new - * subgraph. - *

- * This method is trivially overridden for testing purposes. - *

- * - * @param context the context; may not be null - * @param original the node to be copied; may not be null - * @param newWorkspace the workspace containing the new parent node; may not be null - * @param newParent the parent where the copy is to be placed; may not be null - * @param desiredName the desired name for the node; if null, the name will be obtained from the original node - * @param recursive true if the copy should be recursive - * @param oldToNewUuids the map of UUIDs of nodes in the new subgraph keyed by the UUIDs of nodes in the original; may be - * null if the UUIDs are to be maintained - * @return the new node, which is the top of the new subgraph - */ - @Override - protected MapNode copyNode( ExecutionContext context, - MapNode original, - MapWorkspace newWorkspace, - MapNode newParent, - Name desiredName, - boolean recursive, - Map oldToNewUuids ) { - return super.copyNode(context, original, newWorkspace, newParent, desiredName, recursive, oldToNewUuids); - } - - public void lockNode( MapNode node, - LockScope lockScope, - long lockTimeoutInMillis ) throws LockFailedException { - // Locking is not supported by this connector - } - - public void unlockNode( MapNode node ) { - // Locking is not supported by this connector - } - - /** - * Method added to support testing - * - * @return the number of nodes in the backing map - */ - final int size() { - return nodesByUuid.size(); - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.inmemory.InMemoryNode.ChangeListener#prepareForChange(org.modeshape.graph.connector.inmemory.InMemoryNode) - */ - public void prepareForChange( InMemoryNode node ) { - assert node != null; - UUID uuid = node.getUuid(); - if (!nodesByUuid.containsKey(uuid)) return; - if (stateBeforeChanges == null) { - stateBeforeChanges = new HashMap(); - stateBeforeChanges.put(uuid, new InMemoryNodeState(node)); - } else { - assert stateBeforeChanges != null; - if (!stateBeforeChanges.containsKey(uuid)) { - stateBeforeChanges.put(uuid, new InMemoryNodeState(node)); - } - } - } - - protected void restoreBackups() { - if (this.stateBeforeChanges != null) { - for (Map.Entry originalState : stateBeforeChanges.entrySet()) { - UUID uuid = originalState.getKey(); - InMemoryNodeState state = originalState.getValue(); - InMemoryNode node = (InMemoryNode)nodesByUuid.get(uuid); - if (node == null) { - // It must have been deleted ... - node = state.getOriginalNode(); - } - node.restoreFrom(state); - } - stateBeforeChanges = null; - } - } - - protected void releaseBackups() { - this.stateBeforeChanges = null; - } - } - - @Immutable - protected static final class InMemoryNodeState { - private Path.Segment name; - private final Set properties; - private final List children; - private final Set uniqueChildNames; - private final InMemoryNode node; - private final MapNode parent; - - protected InMemoryNodeState( InMemoryNode node ) { - this.parent = node.getParent(); - this.node = node; - this.name = node.getName(); - this.properties = new HashSet(node.getProperties().values()); - this.children = new ArrayList(node.getChildren()); - this.uniqueChildNames = new HashSet(node.getUniqueChildNames()); - } - - /** - * @return name - */ - public Path.Segment getName() { - return name; - } - - /** - * @return children - */ - public List getChildren() { - return children; - } - - /** - * @return properties - */ - public Set getProperties() { - return properties; - } - - /** - * @return uniqueChildNames - */ - public Set getUniqueChildNames() { - return uniqueChildNames; - } - - /** - * @return node - */ - public InMemoryNode getOriginalNode() { - return node; - } - - /** - * @return parent - */ - public MapNode getParent() { - return parent; - } + public InMemoryTransaction startTransaction( ExecutionContext context, + boolean readonly ) { + final Lock lock = readonly ? this.lock.readLock() : this.lock.writeLock(); + lock.lock(); + return new InMemoryTransaction(this, getRootNodeUuid(), lock); } } Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepositorySource.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepositorySource.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepositorySource.java (working copy) @@ -44,9 +44,11 @@ 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.i18n.I18n; import org.modeshape.common.util.CheckArg; import org.modeshape.common.util.StringUtil; +import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.GraphI18n; import org.modeshape.graph.cache.CachePolicy; import org.modeshape.graph.connector.RepositoryConnection; @@ -54,15 +56,16 @@ 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.map.MapRepositoryConnection; -import org.modeshape.graph.connector.map.MapRepositorySource; +import org.modeshape.graph.connector.base.BaseRepositorySource; +import org.modeshape.graph.connector.base.Connection; import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; /** * A {@link RepositorySource} for an in-memory repository. Each {@link InMemoryRepositorySource} instance contains its own * repository, and the lifetime of the source dictates the lifetime of the repository and its content. */ -public class InMemoryRepositorySource implements MapRepositorySource, ObjectFactory { +@ThreadSafe +public class InMemoryRepositorySource implements BaseRepositorySource, ObjectFactory { /** * The initial version is 1 @@ -101,6 +104,7 @@ public class InMemoryRepositorySource implements MapRepositorySource, ObjectFact private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); private transient InMemoryRepository repository; private transient RepositoryContext repositoryContext; + private transient ExecutionContext defaultContext = new ExecutionContext(); /** * Create a repository source instance. @@ -261,14 +265,15 @@ public class InMemoryRepositorySource implements MapRepositorySource, ObjectFact */ public synchronized RepositoryConnection getConnection() throws RepositorySourceException { if (repository == null) { - repository = new InMemoryRepository(name, rootNodeUuid, defaultWorkspaceName); + ExecutionContext context = repositoryContext != null ? repositoryContext.getExecutionContext() : defaultContext; + repository = new InMemoryRepository(context, name, rootNodeUuid, defaultWorkspaceName); // Create the set of initial workspaces ... - for (String initialName : getPredefinedWorkspaceNames()) - repository.createWorkspace(null, initialName, CreateConflictBehavior.DO_NOT_CREATE); - + for (String initialName : getPredefinedWorkspaceNames()) { + repository.createWorkspace(null, initialName, CreateConflictBehavior.DO_NOT_CREATE, null); + } } - return new MapRepositoryConnection(this, repository); + return new Connection(this, repository); } /** Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryTransaction.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryTransaction.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.connector.inmemory; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import net.jcip.annotations.NotThreadSafe; +import org.modeshape.graph.connector.base.MapTransaction; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.Path.Segment; + +/** + * + */ +@NotThreadSafe +public class InMemoryTransaction extends MapTransaction { + + private final InMemoryRepository repository; + private final Lock lock; + + protected InMemoryTransaction( InMemoryRepository repository, + UUID rootNodeUuid, + Lock lock ) { + super(repository, rootNodeUuid); + this.repository = repository; + this.lock = lock; + assert this.lock != null; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getWorkspaceNames() + */ + public Set getWorkspaceNames() { + return repository.getWorkspaceNames(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#getWorkspace(java.lang.String, + * org.modeshape.graph.connector.base.Workspace) + */ + public InMemoryWorkspace getWorkspace( String name, + InMemoryWorkspace originalToClone ) { + if (originalToClone != null) { + return new InMemoryWorkspace(name, originalToClone); + } + return new InMemoryWorkspace(name, new InMemoryNode(repository.getRootNodeUuid())); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#destroyWorkspace(org.modeshape.graph.connector.base.Workspace) + */ + public boolean destroyWorkspace( InMemoryWorkspace workspace ) { + // The InMemoryRepository is holding onto the Workspace objects for us and will be cleaned up properly, + // so we can just return true here + return true; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.MapTransaction#createNode(java.util.UUID, + * org.modeshape.graph.property.Path.Segment, java.util.UUID, java.lang.Iterable) + */ + @Override + protected InMemoryNode createNode( UUID uuid, + Segment name, + UUID parentUuid, + Iterable properties ) { + return new InMemoryNode(uuid, name, parentUuid, properties, null); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#commit() + */ + @Override + public void commit() { + try { + super.commit(); + } finally { + this.lock.unlock(); + } + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.connector.base.Transaction#rollback() + */ + @Override + public void rollback() { + try { + super.rollback(); + } finally { + this.lock.unlock(); + } + } + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryWorkspace.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryWorkspace.java (working copy) @@ -0,0 +1,55 @@ +/* + * 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.inmemory; + +import java.util.HashMap; +import java.util.UUID; +import org.modeshape.graph.connector.base.MapWorkspace; + +/** + * + */ +public class InMemoryWorkspace extends MapWorkspace { + + /** + * @param name + * @param rootNode + */ + public InMemoryWorkspace( String name, + InMemoryNode rootNode ) { + super(name, new HashMap(), rootNode); + + } + + /** + * @param name + * @param originalToClone + */ + public InMemoryWorkspace( String name, + InMemoryWorkspace originalToClone ) { + super(name, new HashMap(), originalToClone); + + } + +} Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/package-info.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/package-info.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/package-info.java (working copy) @@ -24,7 +24,7 @@ /** * The In-Memory Connector represents a {@link org.modeshape.graph.connector.RepositorySource connector} that maintains * a graph in transient in-process memory. This connector is a good solution for a readable and writable repository source - * that is very lightweight and of small to moderate sizes. + * that is very lightweight and of small to moderate sizes, but that does require automated persistence. */ package org.modeshape.graph.connector.inmemory; Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/map/package-info.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/map/package-info.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/map/package-info.java (working copy) @@ -63,7 +63,6 @@ * *

* - * @see org.modeshape.graph.connector.inmemory.InMemoryRepository * @see org.modeshape.graph.connector.inmemory.InMemoryRepositorySource */ Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/path/AbstractPathRepositorySource.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/path/AbstractPathRepositorySource.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/path/AbstractPathRepositorySource.java (working copy) @@ -34,6 +34,7 @@ import javax.naming.BinaryRefAddr; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.StringRefAddr; +import net.jcip.annotations.ThreadSafe; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.connector.RepositoryContext; import org.modeshape.graph.connector.RepositorySourceException; @@ -45,6 +46,7 @@ import org.modeshape.graph.connector.path.cache.PathRepositoryCache; * Basic implementation of the trivial {@link PathRepositorySource} methods and the {@link org.modeshape.graph.connector.path path * repository cache life cycle}. */ +@ThreadSafe public abstract class AbstractPathRepositorySource implements PathRepositorySource { private static final long serialVersionUID = 1L; Index: modeshape-graph/src/main/java/org/modeshape/graph/property/Property.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/property/Property.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/property/Property.java (working copy) @@ -23,6 +23,7 @@ */ package org.modeshape.graph.property; +import java.io.Serializable; import java.util.Iterator; import net.jcip.annotations.Immutable; @@ -56,7 +57,7 @@ import net.jcip.annotations.Immutable; *

*/ @Immutable -public interface Property extends Iterable, Comparable, Readable { +public interface Property extends Iterable, Comparable, Readable, Serializable { /** * Get the name of the property. Index: modeshape-graph/src/main/java/org/modeshape/graph/request/processor/RequestProcessor.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/request/processor/RequestProcessor.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/request/processor/RequestProcessor.java (working copy) @@ -346,7 +346,11 @@ public abstract class RequestProcessor { Request embedded = iter.next(); assert embedded != null; if (embedded.isCancelled()) return; - process(embedded); + try { + process(embedded); + } catch (RuntimeException e) { + embedded.setError(e); + } if (!hasErrors && embedded.hasError()) { hasErrors = true; if (!readonly && !embedded.isReadOnly()) { Index: modeshape-graph/src/main/java/org/modeshape/graph/search/SearchableRepositorySource.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/search/SearchableRepositorySource.java (revision 1804) +++ modeshape-graph/src/main/java/org/modeshape/graph/search/SearchableRepositorySource.java (working copy) @@ -33,6 +33,7 @@ import javax.naming.NamingException; import javax.naming.Reference; import javax.transaction.xa.XAResource; import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.CheckArg; import org.modeshape.common.util.Logger; @@ -61,6 +62,7 @@ import org.modeshape.graph.request.processor.RequestProcessor; * {@link RepositorySourceCapabilities#supportsQueries() non-querable} RepositorySource instance to provide search and query * capability. */ +@ThreadSafe public class SearchableRepositorySource implements RepositorySource { private static final long serialVersionUID = 1L; Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/TimeDelayingRepositorySource.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/TimeDelayingRepositorySource.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/TimeDelayingRepositorySource.java (working copy) @@ -40,8 +40,6 @@ import org.modeshape.graph.request.Request; /** * A simple {@link RepositorySource} that simulates an imaginary source with a built-in delay mechanism. - * - * @author Randall Hauch */ @ThreadSafe public class TimeDelayingRepositorySource implements RepositorySource { Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/base/MapNodeTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/base/MapNodeTest.java (working copy) @@ -0,0 +1,163 @@ +/* + * 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.base; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.IsNull.nullValue; +import static org.hamcrest.core.IsSame.sameInstance; +import static org.junit.Assert.assertThat; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.Path.Segment; + +public class MapNodeTest { + + private MapNode node; + private ExecutionContext context; + + @Before + public void beforeEach() { + context = new ExecutionContext(); + + UUID uuid = UUID.randomUUID(); + Segment name = segment("myNode[3]"); + UUID parent = UUID.randomUUID(); + List props = properties(property("propA", "valueA"), property("propB", "valueB")); + node = new MapNode(uuid, name, parent, props, null); + } + + public Name name( String name ) { + return context.getValueFactories().getNameFactory().create(name); + } + + public Segment segment( String segment ) { + return context.getValueFactories().getPathFactory().createSegment(segment); + } + + public Property property( String name, + Object... values ) { + return context.getPropertyFactory().create(name(name), values); + } + + public List properties( Property... properties ) { + return Arrays.asList(properties); + } + + @Test + public void shouldInstantiateNodeWithUuid() { + UUID uuid = UUID.randomUUID(); + node = new MapNode(uuid); + assertThat(node.getUuid(), is(sameInstance(uuid))); + assertThat(node.getParent(), is(nullValue())); + assertThat(node.getName(), is(nullValue())); + assertThat(node.getChildren().isEmpty(), is(true)); + assertThat(node.getProperties().isEmpty(), is(true)); + } + + @Test + public void shouldInstantiateNodeWithUuidAndNameAndParent() { + UUID uuid = UUID.randomUUID(); + Segment name = segment("myNode[3]"); + UUID parent = UUID.randomUUID(); + node = new MapNode(uuid, name, parent, (Map)null, null); + assertThat(node.getUuid(), is(sameInstance(uuid))); + assertThat(node.getParent(), is(parent)); + assertThat(node.getName(), is(name)); + assertThat(node.getChildren().isEmpty(), is(true)); + assertThat(node.getProperties().isEmpty(), is(true)); + } + + @Test + public void shouldInstantiateNodeWithUuidAndNoNameOrParent() { + UUID uuid = UUID.randomUUID(); + node = new MapNode(uuid, null, null, (Map)null, null); + assertThat(node.getUuid(), is(sameInstance(uuid))); + assertThat(node.getParent(), is(nullValue())); + assertThat(node.getName(), is(nullValue())); + assertThat(node.getChildren().isEmpty(), is(true)); + assertThat(node.getProperties().isEmpty(), is(true)); + } + + @Test + public void shouldInstantiateNodeWithUuidAndNameAndParentAndProperties() { + UUID uuid = UUID.randomUUID(); + Segment name = segment("myNode[3]"); + UUID parent = UUID.randomUUID(); + List props = properties(property("propA", "valueA"), property("propB", "valueB")); + node = new MapNode(uuid, name, parent, props, null); + assertThat(node.getUuid(), is(sameInstance(uuid))); + assertThat(node.getParent(), is(parent)); + assertThat(node.getName(), is(name)); + assertThat(node.getChildren().isEmpty(), is(true)); + assertThat(node.getProperties().size(), is(2)); + assertThat(node.getProperty(name("propA")), is(props.get(0))); + assertThat(node.getProperty(name("propB")), is(props.get(1))); + } + + @Test + public void shouldCreateCopyWithNewParent() { + UUID uuid = UUID.randomUUID(); + assertThat(node.getParent(), is(not(uuid))); + node = node.withParent(uuid); + assertThat(node.getParent(), is(uuid)); + } + + @Test + public void shouldCreateCopyWithNewChild() { + UUID uuid = UUID.randomUUID(); + node = node.withChild(uuid); + assertThat(node.getChildren().size(), is(1)); + assertThat(node.getChildren().get(0), is(uuid)); + } + + @Test + public void shouldCreateCopyWithNewChildAtParticularIndex() { + UUID uuid = UUID.randomUUID(); + node = node.withChild(0, uuid); + assertThat(node.getChildren().size(), is(1)); + assertThat(node.getChildren().get(0), is(uuid)); + + UUID uuid2 = UUID.randomUUID(); + node = node.withChild(1, uuid2); + assertThat(node.getChildren().size(), is(2)); + assertThat(node.getChildren().get(0), is(uuid)); + assertThat(node.getChildren().get(1), is(uuid2)); + + UUID uuid3 = UUID.randomUUID(); + node = node.withChild(1, uuid3); + assertThat(node.getChildren().size(), is(3)); + assertThat(node.getChildren().get(0), is(uuid)); + assertThat(node.getChildren().get(1), is(uuid3)); + assertThat(node.getChildren().get(2), is(uuid2)); + } + +} Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/base/MapWorkspaceTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/base/MapWorkspaceTest.java (working copy) @@ -0,0 +1,174 @@ +/* + * 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.base; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; +import static org.hamcrest.core.IsSame.sameInstance; +import static org.junit.Assert.assertThat; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; + +public class MapWorkspaceTest { + + private ExecutionContext context; + private Map store; + private MapWorkspace workspace; + private UUID rootUuid; + + @Before + public void beforeEach() throws Exception { + context = new ExecutionContext(); + store = new HashMap(); + rootUuid = UUID.randomUUID(); + workspace = new MapWorkspace("workspace", store, new MapNode(rootUuid)); + } + + @Test + public void shouldHaveName() { + assertThat(workspace.getName(), is("workspace")); + } + + @Test + public void shouldHaveRootNode() { + assertThat(workspace.getRootNode(), is(notNullValue())); + assertThat(workspace.getRootNode().getUuid(), is(rootUuid)); + } + + @Test + public void shouldPlaceRootNodeIntoStoreIfNoExistingNode() { + assertThat(store.get(rootUuid), is(notNullValue())); + assertThat(store.get(rootUuid).getUuid(), is(rootUuid)); + assertThat(store.get(rootUuid), is(sameInstance(workspace.getRootNode()))); + } + + @Test + public void shouldNotReplaceRootNodeInStoreIfExistingNode() { + MapNode root = new MapNode(rootUuid); + store.clear(); + store.put(rootUuid, root); + workspace = new MapWorkspace("workspace", store, new MapNode(rootUuid)); + assertStored(root); + } + + @Test + public void shouldAddNodes() { + assertStored(addNodes(10)); + } + + @Test + public void shouldReplaceExistingNodes() { + MapNode[] original = addNodes(10); + assertStored(original); + MapNode[] modified = addProperty(original, "foo", "value1"); + assertStored(modified); + } + + @Test + public void shouldRemoveNodes() { + for (MapNode node : addNodes(10)) { + assertThat(workspace.removeNode(node.getUuid()), is(sameInstance(node))); + } + assertThat(store.size(), is(1)); + assertThat(store.get(rootUuid), is(sameInstance(workspace.getRootNode()))); + } + + @Test + public void shouldNotRemoveRootNode() { + assertThat(workspace.removeNode(rootUuid), is(nullValue())); + assertThat(store.containsKey(rootUuid), is(true)); + assertThat(workspace.getRootNode(), is(sameInstance(store.get(rootUuid)))); + } + + @Test + public void shouldRemoveAllNodesExceptTheRootNode() { + MapNode[] nodes = addProperty(addNodes(10), "foo", "value1"); + MapNode root = addProperty(workspace.getRootNode(), "bar", "value2"); + assertThat(store.size(), is(11)); + assertStored(root); + assertStored(nodes); + // Now remove all nodes ... + workspace.removeAll(); + // Verify there is only one node left, and it has no children or (non-UUID) properties ... + assertThat(store.size(), is(1)); + assertThat(workspace.getRootNode(), is(sameInstance(store.get(rootUuid)))); + assertThat(workspace.getRootNode(), is(not(sameInstance(root)))); + assertThat(workspace.getRootNode().getChildren().isEmpty(), is(true)); + assertThat(workspace.getRootNode().getProperties().isEmpty(), is(true)); + } + + protected MapNode[] addNodes( int number ) { + MapNode[] results = new MapNode[number]; + for (int i = 0; i != number; ++i) { + results[i] = new MapNode(UUID.randomUUID()); + workspace.putNode(results[i]); + assertThat(store.get(results[i].getUuid()), is(sameInstance(results[i]))); + } + return results; + } + + protected void assertStored( MapNode... nodes ) { + for (MapNode node : nodes) { + assertThat(store.get(node.getUuid()), is(sameInstance(node))); + assertThat(store.get(node.getUuid()), is(sameInstance(workspace.getNode(node.getUuid())))); + } + } + + protected MapNode addProperty( MapNode original, + String name, + Object... values ) { + MapNode newNode = original.withProperty(property(name, values)); + assertThat(workspace.putNode(newNode), is(sameInstance(original))); + return newNode; + } + + protected MapNode[] addProperty( MapNode[] nodes, + String name, + Object... values ) { + MapNode[] results = new MapNode[nodes.length]; + int i = 0; + for (MapNode original : nodes) { + results[i++] = addProperty(original, name, values); + } + return results; + } + + protected Property property( String name, + Object... values ) { + return context.getPropertyFactory().create(name(name), values); + } + + protected Name name( String name ) { + return context.getValueFactories().getNameFactory().create(name); + } + +} Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorReadableTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorReadableTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorReadableTest.java (working copy) @@ -23,17 +23,15 @@ */ package org.modeshape.graph.connector.inmemory; +import org.junit.Test; import org.modeshape.common.statistic.Stopwatch; import org.modeshape.graph.Graph; import org.modeshape.graph.Location; import org.modeshape.graph.Subgraph; import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.connector.test.ReadableConnectorTest; -import org.junit.Test; -/** - * @author Randall Hauch - */ public class InMemoryConnectorReadableTest extends ReadableConnectorTest { /** Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorTransactionalTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorTransactionalTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorTransactionalTest.java (working copy) @@ -25,6 +25,7 @@ package org.modeshape.graph.connector.inmemory; import org.modeshape.graph.Graph; import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.connector.test.TransactionalConnectorTest; /** @@ -53,14 +54,4 @@ public class InMemoryConnectorTransactionalTest extends TransactionalConnectorTe protected void initializeContent( Graph graph ) { } - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.connector.test.TransactionalConnectorTest#shouldNotChangePersistedStateAfterMakingMultipleChangesWithOneFailure() - */ - @Override - public void shouldNotChangePersistedStateAfterMakingMultipleChangesWithOneFailure() { - super.shouldNotChangePersistedStateAfterMakingMultipleChangesWithOneFailure(); - } - } Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWorkspaceTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWorkspaceTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWorkspaceTest.java (working copy) @@ -26,6 +26,7 @@ package org.modeshape.graph.connector.inmemory; import java.io.IOException; import org.modeshape.graph.Graph; import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.connector.test.WorkspaceConnectorTest; import org.xml.sax.SAXException; Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWritableTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWritableTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryConnectorWritableTest.java (working copy) @@ -25,11 +25,9 @@ package org.modeshape.graph.connector.inmemory; import org.modeshape.graph.Graph; import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.connector.test.WritableConnectorTest; -/** - * @author Randall Hauch - */ public class InMemoryConnectorWritableTest extends WritableConnectorTest { /** Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryRepositoryTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryRepositoryTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryRepositoryTest.java (working copy) @@ -23,220 +23,31 @@ */ package org.modeshape.graph.connector.inmemory; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.hamcrest.core.IsNull.nullValue; -import static org.hamcrest.core.IsSame.sameInstance; -import static org.junit.Assert.assertThat; -import static org.junit.matchers.JUnitMatchers.hasItems; -import java.util.Collection; -import java.util.Collections; import java.util.UUID; -import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.connector.map.MapNode; -import org.modeshape.graph.connector.map.MapWorkspace; -import org.modeshape.graph.property.Name; -import org.modeshape.graph.property.NameFactory; -import org.modeshape.graph.property.PathFactory; -import org.modeshape.graph.property.Property; -import org.modeshape.graph.property.PropertyFactory; -import org.modeshape.graph.property.ValueFactories; -import org.modeshape.graph.property.ValueFactory; -import org.modeshape.graph.property.basic.BasicPropertyFactory; -import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; import org.junit.Before; import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.connector.inmemory.InMemoryRepository; -/** - * @author Randall Hauch - */ public class InMemoryRepositoryTest { - private InMemoryRepository repository; - private String repositoryName; private UUID rootUuid; private ExecutionContext context; - private PathFactory pathFactory; - private NameFactory nameFactory; - private PropertyFactory propertyFactory; @Before public void beforeEach() throws Exception { context = new ExecutionContext(); - ValueFactories valueFactories = context.getValueFactories(); - pathFactory = valueFactories.getPathFactory(); - nameFactory = valueFactories.getNameFactory(); - propertyFactory = new BasicPropertyFactory(valueFactories); - repositoryName = "Test repository"; rootUuid = UUID.randomUUID(); - repository = new InMemoryRepository(repositoryName, rootUuid); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowNullNameInConstructor() { - new InMemoryRepository(null, rootUuid); + new InMemoryRepository(context, null, rootUuid); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowBlankNameInConstructor() { - new InMemoryRepository(" \t ", rootUuid); - } - - @Test - public void shouldHaveLock() { - assertThat(repository.getLock(), is(notNullValue())); - } - - @Test - public void shouldNotCreateWorkspaceIfNameIsAlreadyUsedAndConflictOptionIsToNotCreate() { - String workspaceName = "New Workspace"; - assertThat(repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE), is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName)); - assertThat(repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE), is(nullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName)); + new InMemoryRepository(context, " \t ", rootUuid); } - - @Test - public void shouldCreateWorkspaceWithUniqueNameIfSpecifiedNameIsAlreadyUsedAndConflictOptionIsToCreateWithAdjustedName() { - String workspaceName = "New Workspace"; - assertThat(repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE), is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName)); - MapWorkspace secondWorkspace = repository.createWorkspace(context, - workspaceName, - CreateConflictBehavior.CREATE_WITH_ADJUSTED_NAME); - assertThat(secondWorkspace, is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName, secondWorkspace.getName())); - } - - @Test - public void shouldNotDestroyWorkspaceIfNameDoesNotMatchExistingWorkspace() { - String workspaceName = "New Workspace"; - assertThat(repository.getWorkspaceNames().contains(workspaceName), is(false)); - assertThat(repository.destroyWorkspace(workspaceName), is(false)); - } - - @Test - public void shouldDestroyWorkspaceIfNameMatchesExistingWorkspace() { - String workspaceName = "New Workspace"; - assertThat(repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE), is(notNullValue())); - assertThat(repository.getWorkspaceNames().contains(workspaceName), is(true)); - assertThat(repository.destroyWorkspace(workspaceName), is(true)); - } - - @Test - public void shouldCloneWorkspaceAndCopyContentsIfWorkspaceWithSpecifiedNameExists() { - String workspaceName = "Original Workspace"; - MapWorkspace workspace = repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE); - assertThat(workspace, is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName)); - - // Populate the workspace with a few nodes ... - MapNode root = workspace.getRoot(); - final Collection NO_PROPS = Collections.emptySet(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = context.getValueFactories().getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(((InMemoryRepository.Workspace) workspace).size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - // Now clone the workspace ... - String newWorkspaceName = "New Workspace"; - MapWorkspace new_workspace = repository.createWorkspace(context, - newWorkspaceName, - CreateConflictBehavior.DO_NOT_CREATE, - workspaceName); - assertThat(new_workspace, is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName, newWorkspaceName)); - - // Now check that the original workspace still has its content ... - assertThat(((InMemoryRepository.Workspace) workspace).size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - // Now check that the new workspace has its content ... - assertThat(((InMemoryRepository.Workspace) new_workspace).size(), is(7)); - - // Since we cloned workspaces, the UUIDs should be the same in each workspace ... - assertThat(workspace.getNode(pathFactory.create("/")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/a")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/a")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/a/b")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/a/b/c")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/d")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/d")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/d/e")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/d/e")).getUuid())); - assertThat(workspace.getNode(pathFactory.create("/d/b")).getUuid(), - is(new_workspace.getNode(pathFactory.create("/d/b")).getUuid())); - } - - @Test - public void shouldCloneWorkspaceButShouldNotCopyContentsIfWorkspaceWithSpecifiedNameDoesNotExist() { - String workspaceName = "Original Workspace"; - MapWorkspace workspace = repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE); - assertThat(workspace, is(notNullValue())); - assertThat(repository.getWorkspaceNames(), hasItems(workspaceName)); - - // Populate the workspace with a few nodes ... - MapNode root = workspace.getRoot(); - final Collection NO_PROPS = Collections.emptySet(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = context.getValueFactories().getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(((InMemoryRepository.Workspace) workspace).size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - // Now clone the workspace ... - String newWorkspaceName = "New Workspace"; - MapWorkspace new_workspace = repository.createWorkspace(context, - newWorkspaceName, - CreateConflictBehavior.DO_NOT_CREATE, - "non-existant workspace"); - assertThat(new_workspace.getRoot(), is(notNullValue())); - assertThat(new_workspace.getRoot().getUuid(), is(rootUuid)); - assertThat(new_workspace.getRoot().getChildren().isEmpty(), is(true)); - } - } Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java deleted file mode 100644 =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java (revision 1804) +++ /dev/null (working copy) @@ -1,642 +0,0 @@ -/* - * 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.inmemory; - -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.hamcrest.core.IsNull.nullValue; -import static org.hamcrest.core.IsSame.sameInstance; -import static org.junit.Assert.assertThat; -import static org.junit.matchers.JUnitMatchers.hasItems; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.connector.map.MapNode; -import org.modeshape.graph.property.Name; -import org.modeshape.graph.property.NameFactory; -import org.modeshape.graph.property.PathFactory; -import org.modeshape.graph.property.Property; -import org.modeshape.graph.property.PropertyFactory; -import org.modeshape.graph.property.ValueFactories; -import org.modeshape.graph.property.ValueFactory; -import org.modeshape.graph.property.basic.BasicPropertyFactory; -import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; -import org.junit.Before; -import org.junit.Test; - -/** - * @author Randall Hauch - */ -public class InMemoryRepositoryWorkspaceTest { - - private InMemoryRepository repository; - private String repositoryName; - private UUID rootUuid; - private String workspaceName; - private InMemoryRepository.Workspace workspace; - - private ExecutionContext context; - private ValueFactories valueFactories; - private PathFactory pathFactory; - private NameFactory nameFactory; - private PropertyFactory propertyFactory; - private final Collection NO_PROPS = Collections.emptySet(); - - @Before - public void beforeEach() throws Exception { - context = new ExecutionContext(); - valueFactories = context.getValueFactories(); - pathFactory = valueFactories.getPathFactory(); - nameFactory = valueFactories.getNameFactory(); - propertyFactory = new BasicPropertyFactory(valueFactories); - repositoryName = "Test repository"; - rootUuid = UUID.randomUUID(); - repository = new InMemoryRepository(repositoryName, rootUuid); - workspaceName = "My Workspace"; - workspace = (InMemoryRepository.Workspace) repository.createWorkspace(context, workspaceName, CreateConflictBehavior.DO_NOT_CREATE); - } - - @Test - public void shouldHaveRootNodeAfterInstantiating() { - assertThat(workspace.getRoot(), is(notNullValue())); - } - - @Test - public void shouldHaveNameAfterInstantiating() { - assertThat(workspace.getName(), is(workspaceName)); - } - - @Test - public void shouldHaveRootNodeWithRootUuid() { - assertThat(workspace.getRoot().getUuid(), is(rootUuid)); - } - - @Test - public void shouldAllowRootToBeRemoved() { - workspace.removeNode(context, workspace.getRoot()); - assertThat(workspace.getRoot().getChildren().size(), is(0)); - // assertThat(workspace.getRoot().getProperties().size(), is(0)); - } - - @Test - public void shouldCreateNodesByPath() { - Name name_a = nameFactory.create("a"); - MapNode node_a = workspace.createNode(context, workspace.getRoot(), name_a, null, NO_PROPS); - assertThat(node_a, is(notNullValue())); - assertThat(node_a.getParent(), is(workspace.getRoot())); - assertThat(node_a.getName().getName(), is(name_a)); - assertThat(node_a.getName().hasIndex(), is(false)); - - Name name_b = nameFactory.create("b"); - MapNode node_b = workspace.createNode(context, node_a, name_b, null, NO_PROPS); - assertThat(node_b, is(notNullValue())); - assertThat(node_b.getParent(), is(node_a)); - assertThat(node_b.getName().getName(), is(name_b)); - assertThat(node_b.getName().hasIndex(), is(false)); - - Name name_c = nameFactory.create("c"); - MapNode node_c = workspace.createNode(context, node_b, name_c, null, NO_PROPS); - assertThat(node_c, is(notNullValue())); - assertThat(node_c.getParent(), is(node_b)); - assertThat(node_c.getName().getName(), is(name_c)); - assertThat(node_c.getName().hasIndex(), is(false)); - - assertThat(workspace.size(), is(4)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - } - - @Test - public void shouldNotFindNodesThatDoNotExist() { - MapNode node_a = workspace.createNode(context, workspace.getRoot(), nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - /*Node node_c =*/workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - - assertThat(workspace.size(), is(4)); - assertThat(workspace.getNode(pathFactory.create("/a")), is(node_a)); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(node_b)); - assertThat(workspace.getNode(pathFactory.create("/a[1]")), is(node_a)); - assertThat(workspace.getNode(pathFactory.create("/a/b[1]")), is(node_b)); - assertThat(workspace.getNode(pathFactory.create("/a[1]/b[1]")), is(node_b)); - assertThat(workspace.getNode(pathFactory.create("/a[2]")), is(nullValue())); - assertThat(workspace.getNode(pathFactory.create("/b[2]")), is(nullValue())); - assertThat(workspace.getNode(pathFactory.create("/d")), is(nullValue())); - } - - @Test - public void shouldCorrectlyManageIndexesOfSiblingsWithSameNames() { - Name name_a1 = nameFactory.create("a"); - MapNode node_a1 = workspace.createNode(context, workspace.getRoot(), name_a1, null, NO_PROPS); - assertThat(node_a1, is(notNullValue())); - assertThat(node_a1.getParent(), is(workspace.getRoot())); - assertThat(node_a1.getName().getName(), is(name_a1)); - assertThat(node_a1.getName().hasIndex(), is(false)); - - Name name_a2 = nameFactory.create("a"); - MapNode node_a2 = workspace.createNode(context, workspace.getRoot(), name_a2, null, NO_PROPS); - assertThat(node_a2, is(notNullValue())); - assertThat(node_a2.getParent(), is(workspace.getRoot())); - assertThat(node_a2.getName().getName(), is(name_a2)); - assertThat(node_a2.getName().hasIndex(), is(true)); - assertThat(node_a2.getName().getIndex(), is(2)); - - // node 1 should now have an index .. - assertThat(node_a1.getName().getIndex(), is(1)); - - // Add another node without the same name .. - Name name_b = nameFactory.create("b"); - MapNode node_b = workspace.createNode(context, workspace.getRoot(), name_b, null, NO_PROPS); - assertThat(node_b, is(notNullValue())); - assertThat(node_b.getParent(), is(workspace.getRoot())); - assertThat(node_b.getName().getName(), is(name_b)); - assertThat(node_b.getName().hasIndex(), is(false)); - - // Add a third node with the same name .. - Name name_a3 = nameFactory.create("a"); - MapNode node_a3 = workspace.createNode(context, workspace.getRoot(), name_a3, null, NO_PROPS); - assertThat(node_a3, is(notNullValue())); - assertThat(node_a3.getParent(), is(workspace.getRoot())); - assertThat(node_a3.getName().getName(), is(name_a3)); - assertThat(node_a3.getName().hasIndex(), is(true)); - assertThat(node_a3.getName().getIndex(), is(3)); - - // Check the number of children .. - assertThat(workspace.getRoot().getChildren().size(), is(4)); - assertThat(workspace.getRoot().getChildren(), hasItems(node_a1, node_a2, node_b, node_a3)); - assertThat(workspace.size(), is(5)); - assertThat(workspace.getNode(pathFactory.create("/a[1]")), is(sameInstance(node_a1))); - assertThat(workspace.getNode(pathFactory.create("/a[2]")), is(sameInstance(node_a2))); - assertThat(workspace.getNode(pathFactory.create("/a[3]")), is(sameInstance(node_a3))); - assertThat(workspace.getNode(pathFactory.create("/b")), is(sameInstance(node_b))); - - // Removing a node with the same name will reduce the index .. - workspace.removeNode(context, node_a2); - assertThat(workspace.getRoot().getChildren().size(), is(3)); - assertThat(workspace.getRoot().getChildren(), hasItems(node_a1, node_b, node_a3)); - assertThat(node_a1.getName().getIndex(), is(1)); - assertThat(node_b.getName().hasIndex(), is(false)); - assertThat(node_a3.getName().getIndex(), is(2)); - - // Removing a node with the same name will reduce the index .. - workspace.removeNode(context, node_a1); - assertThat(workspace.getRoot().getChildren().size(), is(2)); - assertThat(workspace.getRoot().getChildren(), hasItems(node_b, node_a3)); - assertThat(node_b.getName().hasIndex(), is(false)); - assertThat(node_a3.getName().hasIndex(), is(false)); - assertThat(workspace.size(), is(3)); - } - - @Test - public void shouldMoveNodesWithinSameWorkspace() { - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - workspace.moveNode(context, node_b, null, workspace, node_d, null); - - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]/c")), is(sameInstance(node_c))); - - workspace.moveNode(context, node_b, null, workspace, node_e, null); - - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/e/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/d/e/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - } - - @Test - public void shouldMoveNodeBeforeAnother() { - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - Name propName = nameFactory.create("prop"); - node_b.setProperty(propertyFactory.create(propName, "node_b")); - node_b2.setProperty(propertyFactory.create(propName, "node_b2")); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propName).getFirstValue().toString(), is("node_b")); - assertThat(workspace.getNode(pathFactory.create("/d/b")).getProperty(propName).getFirstValue().toString(), is("node_b2")); - - // Move before a node with the same name - workspace.moveNode(context, node_b, null, workspace, node_d, node_b2); - - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/d/b[1]/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d/b[1]")).getProperty(propName).getFirstValue().toString(), - is("node_b")); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]")).getProperty(propName).getFirstValue().toString(), - is("node_b2")); - - // Move after the last node - workspace.moveNode(context, node_b, null, workspace, root, null); - - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - } - - @Test - public void shouldMoveNodesFromOneWorkspaceToAnother() { - // Populate the workspace with some content .. - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - // Create the second workspace and populate it with some content .. - InMemoryRepository.Workspace new_workspace = (InMemoryRepository.Workspace) repository.createWorkspace(context, "Second Workspace", CreateConflictBehavior.DO_NOT_CREATE); - assertThat(new_workspace, is(notNullValue())); - - MapNode new_root = new_workspace.getRoot(); - MapNode new_node_a = new_workspace.createNode(context, new_root, nameFactory.create("a"), null, NO_PROPS); - MapNode new_node_b = new_workspace.createNode(context, new_node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode new_node_c = new_workspace.createNode(context, new_node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode new_node_d = new_workspace.createNode(context, new_root, nameFactory.create("d"), null, NO_PROPS); - MapNode new_node_e = new_workspace.createNode(context, new_node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode new_node_b2 = new_workspace.createNode(context, new_node_d, nameFactory.create("b"), null, NO_PROPS); - - assertThat(new_workspace.size(), is(7)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(new_node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(new_node_c))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(new_node_b2))); - - // Move 'workspace::/a/b' into 'newWorkspace::/d' - workspace.moveNode(context, node_b, null, new_workspace, new_node_d, null); - - assertThat(workspace.size(), is(5)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(new_workspace.size(), is(9)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(new_node_b2))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]")), is(sameInstance(node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]/c")), is(sameInstance(node_c))); - } - - @Test - public void shouldCopyNodesWithinSameWorkspace() { - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = valueFactories.getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - workspace.copyNode(context, node_b, workspace, node_d, null, true, new HashMap()); - - assertThat(workspace.size(), is(9)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]")), is(notNullValue())); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]/c")), is(notNullValue())); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - assertThat(workspace.getNode(pathFactory.create("/d/b[2]")).getProperty(propertyName), is(property)); - } - - @Test - public void shouldCopyNodesFromOneWorkspaceToAnotherAndKeepSameUuids() { - // Populate the workspace with some content .. - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = valueFactories.getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - // Create the second workspace and populate it with some content .. - InMemoryRepository.Workspace new_workspace = (InMemoryRepository.Workspace) repository.createWorkspace(context, "Second Workspace", CreateConflictBehavior.DO_NOT_CREATE); - assertThat(new_workspace, is(notNullValue())); - - MapNode new_root = new_workspace.getRoot(); - MapNode new_node_a = new_workspace.createNode(context, new_root, nameFactory.create("a"), null, NO_PROPS); - MapNode new_node_b = new_workspace.createNode(context, new_node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode new_node_c = new_workspace.createNode(context, new_node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode new_node_d = new_workspace.createNode(context, new_root, nameFactory.create("d"), null, NO_PROPS); - MapNode new_node_e = new_workspace.createNode(context, new_node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode new_node_b2 = new_workspace.createNode(context, new_node_d, nameFactory.create("b"), null, NO_PROPS); - - assertThat(new_workspace.size(), is(7)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(new_node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(new_node_c))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(new_node_b2))); - - // Copy 'workspace::/a/b' into 'newWorkspace::/d' - workspace.copyNode(context, node_b, new_workspace, new_node_d, null, true, (Map) null); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - assertThat(new_workspace.size(), is(9)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(new_node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(new_node_c))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(new_node_b2))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]")), is(notNullValue())); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]/c")), is(notNullValue())); - - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]")).getProperty(propertyName), is(property)); - - // The new copy should have the same UUIDs as in the original, since we specified no UUID map .. - MapNode new_copy_b = new_workspace.getNode(pathFactory.create("/d/b[2]")); - MapNode new_copy_c = new_workspace.getNode(pathFactory.create("/d/b[2]/c")); - assertThat(new_copy_b, is(notNullValue())); - assertThat(new_copy_c, is(notNullValue())); - assertThat(new_copy_b.getUuid(), is(node_b.getUuid())); - assertThat(new_copy_c.getUuid(), is(node_c.getUuid())); - } - - @Test - public void shouldCopyNodesFromOneWorkspaceToAnotherAndGenerateNewUuids() { - // Populate the workspace with some content .. - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = valueFactories.getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - // Create the second workspace and populate it with some content .. - InMemoryRepository.Workspace new_workspace = (InMemoryRepository.Workspace) repository.createWorkspace(context, "Second Workspace", CreateConflictBehavior.DO_NOT_CREATE); - assertThat(new_workspace, is(notNullValue())); - - MapNode new_root = new_workspace.getRoot(); - MapNode new_node_a = new_workspace.createNode(context, new_root, nameFactory.create("a"), null, NO_PROPS); - MapNode new_node_b = new_workspace.createNode(context, new_node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode new_node_c = new_workspace.createNode(context, new_node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode new_node_d = new_workspace.createNode(context, new_root, nameFactory.create("d"), null, NO_PROPS); - MapNode new_node_e = new_workspace.createNode(context, new_node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode new_node_b2 = new_workspace.createNode(context, new_node_d, nameFactory.create("b"), null, NO_PROPS); - - assertThat(new_workspace.size(), is(7)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(new_node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(new_node_c))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(new_node_b2))); - - // Copy 'workspace::/a/b' into 'newWorkspace::/d' - Map oldToNewUuids = new HashMap(); - workspace.copyNode(context, node_b, new_workspace, new_node_d, null, true, oldToNewUuids); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - assertThat(new_workspace.size(), is(9)); - assertThat(new_workspace.getNode(pathFactory.create("/")), is(sameInstance(new_root))); - assertThat(new_workspace.getNode(pathFactory.create("/a")), is(sameInstance(new_node_a))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(new_node_b))); - assertThat(new_workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(new_node_c))); - assertThat(new_workspace.getNode(pathFactory.create("/d")), is(sameInstance(new_node_d))); - assertThat(new_workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(new_node_e))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[1]")), is(sameInstance(new_node_b2))); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]")), is(notNullValue())); - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]/c")), is(notNullValue())); - - assertThat(new_workspace.getNode(pathFactory.create("/d/b[2]")).getProperty(propertyName), is(property)); - - // The new copy should have different UUIDs than in the original, since we did specify a UUID map .. - MapNode new_copy_b = new_workspace.getNode(pathFactory.create("/d/b[2]")); - MapNode new_copy_c = new_workspace.getNode(pathFactory.create("/d/b[2]/c")); - assertThat(new_copy_b, is(notNullValue())); - assertThat(new_copy_c, is(notNullValue())); - assertThat(new_copy_b.getUuid(), is(not(node_b.getUuid()))); - assertThat(new_copy_c.getUuid(), is(not(node_c.getUuid()))); - assertThat(new_copy_b.getUuid(), is(oldToNewUuids.get(node_b.getUuid()))); - assertThat(new_copy_c.getUuid(), is(oldToNewUuids.get(node_c.getUuid()))); - } - - @Test - public void shouldCopyNodesWhenDesiredNameIsSpecified() { - MapNode root = workspace.getRoot(); - MapNode node_a = workspace.createNode(context, root, nameFactory.create("a"), null, NO_PROPS); - MapNode node_b = workspace.createNode(context, node_a, nameFactory.create("b"), null, NO_PROPS); - MapNode node_c = workspace.createNode(context, node_b, nameFactory.create("c"), null, NO_PROPS); - MapNode node_d = workspace.createNode(context, root, nameFactory.create("d"), null, NO_PROPS); - MapNode node_e = workspace.createNode(context, node_d, nameFactory.create("e"), null, NO_PROPS); - MapNode node_b2 = workspace.createNode(context, node_d, nameFactory.create("b"), null, NO_PROPS); - - ValueFactory stringFactory = valueFactories.getStringFactory(); - Name propertyName = nameFactory.create("something"); - Property property = propertyFactory.create(propertyName, stringFactory.create("Worth the wait")); - node_b.setProperty(property); - - assertThat(workspace.size(), is(7)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - - workspace.copyNode(context, node_b, workspace, node_d, nameFactory.create("x"), true, new HashMap()); - - assertThat(workspace.size(), is(9)); - assertThat(workspace.getNode(pathFactory.create("/")), is(sameInstance(workspace.getRoot()))); - assertThat(workspace.getNode(pathFactory.create("/a")), is(sameInstance(node_a))); - assertThat(workspace.getNode(pathFactory.create("/a/b")), is(sameInstance(node_b))); - assertThat(workspace.getNode(pathFactory.create("/a/b/c")), is(sameInstance(node_c))); - assertThat(workspace.getNode(pathFactory.create("/d")), is(sameInstance(node_d))); - assertThat(workspace.getNode(pathFactory.create("/d/e")), is(sameInstance(node_e))); - assertThat(workspace.getNode(pathFactory.create("/d/b")), is(sameInstance(node_b2))); - assertThat(workspace.getNode(pathFactory.create("/d/x")), is(notNullValue())); - assertThat(workspace.getNode(pathFactory.create("/d/x/c")), is(notNullValue())); - - assertThat(workspace.getNode(pathFactory.create("/a/b")).getProperty(propertyName), is(property)); - assertThat(workspace.getNode(pathFactory.create("/d/x")).getProperty(propertyName), is(property)); - } - - @Test - public void shouldCreateRepositoryStructure() { - workspace.createNode(context, "/a", NO_PROPS) - .setProperty(context, "name", "value") -.setProperty(context, - "desc", - "Some description"); - workspace.createNode(context, "/a/b", NO_PROPS).setProperty(context, "name", "value2").setProperty(context, - "desc", - "Some description 2"); - assertThat(workspace.getNode(context, "/a").getProperty(context, "name").getValuesAsArray(), is(new Object[] {"value"})); - assertThat(workspace.getNode(context, "/a").getProperty(context, "desc").getValuesAsArray(), - is(new Object[] {"Some description"})); - assertThat(workspace.getNode(context, "/a/b").getProperty(context, "name").getValuesAsArray(), - is(new Object[] {"value2"})); - assertThat(workspace.getNode(context, "/a/b").getProperty(context, "desc").getValuesAsArray(), - is(new Object[] {"Some description 2"})); - } -} Index: modeshape-graph/src/test/java/org/modeshape/graph/connector/test/WritableConnectorTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/connector/test/WritableConnectorTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/connector/test/WritableConnectorTest.java (working copy) @@ -28,19 +28,22 @@ import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; import static org.modeshape.graph.IsNodeWithChildren.hasChild; import static org.modeshape.graph.IsNodeWithChildren.hasChildren; import static org.modeshape.graph.IsNodeWithChildren.isEmpty; import static org.modeshape.graph.IsNodeWithProperty.hasProperty; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeNoException; import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.modeshape.common.statistic.Stopwatch; import org.modeshape.common.util.IoUtil; -import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.Graph; import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.ModeShapeLexicon; import org.modeshape.graph.Node; import org.modeshape.graph.Subgraph; import org.modeshape.graph.connector.RepositorySource; @@ -48,9 +51,6 @@ import org.modeshape.graph.connector.UuidAlreadyExistsException; import org.modeshape.graph.property.PathNotFoundException; import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.property.Reference; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; /** * A class that provides standard writing verification tests for connectors that are able to store any content (in any structure). @@ -929,7 +929,6 @@ public abstract class WritableConnectorTest extends AbstractConnectorTest { graph.create("/newUuids").and(); // Copy once to get the UUID into the default workspace - // graph.copy("/node1").replacingExistingNodesWithSameUuids().fromWorkspace(workspaceName).to("/newUuids/node1"); graph.clone("/node1") .fromWorkspace(workspaceName) .as(name("node1")) @@ -945,8 +944,7 @@ public abstract class WritableConnectorTest extends AbstractConnectorTest { graph.create("/newUuids/node1/shouldBeRemoved"); // Copy again to test the behavior now that the UUIDs are already in the default workspace - // This should remove /newUuids/node1/shouldBeRemoved - // graph.copy("/node1").replacingExistingNodesWithSameUuids().fromWorkspace(workspaceName).to("/newUuids/otherNode"); + // This should first remove /newUuids/node1, which of course will remove /newUuids/node1/shouldBeRemoved graph.clone("/node1") .fromWorkspace(workspaceName) .as(name("otherNode")) @@ -1082,13 +1080,16 @@ public abstract class WritableConnectorTest extends AbstractConnectorTest { boolean batch = true; createSubgraph(graph, initialPath, depth, numChildrenPerNode, numPropertiesPerNode, batch, sw, System.out, null); + // Check starting point ... + assertThat(graph.getChildren().of("/"), hasChildren(segment("node1"), segment("node2"), segment("node3"))); + // Move last node to front ... graph.move("/node3").before("/node1"); assertThat(graph.getChildren().of("/"), hasChildren(segment("node3"), segment("node1"), segment("node2"))); // Move last node to front ... - graph.move("/node3").before("/node1"); + graph.move("/node2").before("/node3"); assertThat(graph.getChildren().of("/"), hasChildren(segment("node2"), segment("node3"), segment("node1"))); @@ -1745,8 +1746,8 @@ public abstract class WritableConnectorTest extends AbstractConnectorTest { assertThat(graph.getChildren().of("/node3"), hasChildren(segment("node1"), segment("node2[1]"), - segment("node3"), - segment("node2[2]"))); + segment("node2[2]"), + segment("node3"))); assertThat(graph.getChildren().of("/node3/node2[1]"), hasChildren(segment("node1"), segment("node2"), segment("node3"))); assertThat(graph.getChildren().of("/node3/node3"), hasChildren(segment("node1"), segment("node2"), segment("node3"))); assertThat(graph.getChildren().of("/node3/node3/node1"), hasChildren()); Index: modeshape-graph/src/test/java/org/modeshape/graph/io/GraphSequencerOutput.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/io/GraphSequencerOutput.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/io/GraphSequencerOutput.java (working copy) @@ -48,6 +48,7 @@ public class GraphSequencerOutput implements SequencerOutput { private final PathFactory pathFactory; private final Set paths = new HashSet(); + private final Path startingPath; /** * Create a graph sequencer output instance using {@link Graph.Batch} object. @@ -59,6 +60,7 @@ public class GraphSequencerOutput implements SequencerOutput { this.batch = batch; ExecutionContext context = batch.getGraph().getContext(); this.pathFactory = context.getValueFactories().getPathFactory(); + this.startingPath = this.pathFactory.createRootPath(); } /** @@ -80,6 +82,7 @@ public class GraphSequencerOutput implements SequencerOutput { Object... values ) { assert valuesAreNotIterators(values); Path path = pathFactory.create(nodePath); + path = absolute(path); if (paths.add(path)) { batch.create(path).and(); } @@ -96,12 +99,17 @@ public class GraphSequencerOutput implements SequencerOutput { Name propertyName, Object... values ) { assert valuesAreNotIterators(values); + nodePath = absolute(nodePath); if (paths.add(nodePath)) { batch.create(nodePath).and(); } batch.set(propertyName).on(nodePath).to(values); } + private final Path absolute( Path path ) { + return path.isAbsolute() ? path : path.resolveAgainst(startingPath); + } + /** * {@inheritDoc} * @@ -111,6 +119,7 @@ public class GraphSequencerOutput implements SequencerOutput { String propertyName, String... paths ) { Path path = pathFactory.create(nodePath); + path = absolute(path); if (this.paths.add(path)) { batch.create(path).and(); } Index: modeshape-graph/src/test/java/org/modeshape/graph/session/GraphSessionTest.java =================================================================== --- modeshape-graph/src/test/java/org/modeshape/graph/session/GraphSessionTest.java (revision 1804) +++ modeshape-graph/src/test/java/org/modeshape/graph/session/GraphSessionTest.java (working copy) @@ -577,6 +577,8 @@ public class GraphSessionTest { Node cars = cache.findNodeWith(path("/Cars")); assertConnectionsUsed(1); // "Utility" was found because it is child of "Cars" loaded when "Sports" was loaded + assertChildren(cars, "Hybrid", "Sports", "Luxury", "Utility"); + cars.orderChildBefore(utility.getSegment(), sports.getSegment()); assertNoMoreConnectionsUsed(); Index: modeshape-integration-tests/src/test/resources/tck/infinispan/configRepository.xml =================================================================== --- modeshape-integration-tests/src/test/resources/tck/infinispan/configRepository.xml (revision 1804) +++ modeshape-integration-tests/src/test/resources/tck/infinispan/configRepository.xml (working copy) @@ -34,7 +34,11 @@ - + + default + otherWorkspace + + 1.0.1-alpha-2 start-container