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,9 @@ 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,93 @@
+/*
+ * 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 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();
- public InfinispanRepository( String sourceName,
+ public InfinispanRepository( ExecutionContext context,
+ String sourceName,
UUID rootNodeUuid,
CacheManager cacheManager ) {
- super(sourceName, rootNodeUuid, null);
- assert cacheManager != null;
+ super(context, sourceName, rootNodeUuid, null);
this.cacheManager = cacheManager;
+ assert this.cacheManager != null;
initialize();
}
- public InfinispanRepository( String sourceName,
+ public InfinispanRepository( ExecutionContext context,
+ String sourceName,
UUID rootNodeUuid,
String defaultWorkspaceName,
CacheManager cacheManager ) {
- super(sourceName, rootNodeUuid, defaultWorkspaceName);
-
- assert cacheManager != null;
+ super(context, sourceName, rootNodeUuid, defaultWorkspaceName);
this.cacheManager = cacheManager;
-
+ assert this.cacheManager != null;
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}
+ * Get the cache manager used by this repository.
*
- * @see org.modeshape.graph.connector.map.MapRepository#startTransaction(boolean)
+ * @return the cacheManager; never null
*/
- @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();
- }
- };
+ protected CacheManager getCacheManager() {
+ return cacheManager;
}
- protected ReadWriteLock getLock() {
- return lock;
+ /**
+ * 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();
}
- 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
- }
+ /**
+ * {@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,14 +50,15 @@ 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;
@@ -76,7 +77,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;
/**
@@ -416,15 +417,22 @@ 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);
+ // Create the set of initial workspaces ...
+ 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 +441,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,132 @@
+/*
+ * 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;
+
+/**
+ *
+ */
+@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.getCacheManager().getCacheNames();
+ }
+
+ /**
+ * {@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) {
+
+ }
+ 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,6 @@
#
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
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