Index: b/extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/PersistentInfinispanConnectorTest.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/PersistentInfinispanConnectorTest.java(working copy)
@@ -0,0 +1,97 @@
+/*
+ * 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 javax.naming.Context;
+import org.infinispan.manager.CacheManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.modeshape.graph.ExecutionContext;
+import org.modeshape.graph.Graph;
+
+/**
+ *
+ */
+public class PersistentInfinispanConnectorTest {
+
+ protected static final String JNDI_NAME = "java/MyCacheManager";
+
+ private ExecutionContext context;
+ private InfinispanSource source;
+ private CacheManager cacheManager;
+ private Context mockJndi;
+ 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);
+ //
+ // 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);
+ //
+ // graph = Graph.create(source, context);
+ // graph.useWorkspace("default");
+ }
+
+ @After
+ public void afterEach() throws Exception {
+ // try {
+ // cacheManager.stop();
+ // } finally {
+ // // Delete all of the content stored on the file system ...
+ // File store = new File("target/infinispan/jcr");
+ // FileUtil.delete(store);
+ // }
+ }
+
+ @Test
+ public void placeholder() {
+
+ }
+
+ // @Test
+ // public void shouldStartUp() {
+ // assertThat(graph.getNodeAt("/"), is(notNullValue()));
+ // }
+ //
+ // @Test
+ // public void shouldAllowCreatingAndReReadingNodes() {
+ // graph.create("/a").with("prop1", "value1").and();
+ //
+ // }
+}
Index: b/extensions/modeshape-connector-infinispan/src/test/resources/infinispan_persistent_config.xml
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/extensions/modeshape-connector-infinispan/src/test/resources/infinispan_persistent_config.xml(working copy)
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: b/extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java
===================================================================
--- a/extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/RepositorySource.java
===================================================================
--- a/modeshape-graph/src/main/java/org/modeshape/graph/connector/RepositorySource.java(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseRepositorySource.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/base/BaseTransaction.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/base/Connection.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapNode.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/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: b/modeshape-graph/src/main/java/org/modeshape/graph/connector/base/MapTransaction.java
new file mode 100644
===================================================================
--- /dev/null(revision )
+++ b/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