Index: docs/reference/src/main/docbook/en-US/content/connectors/infinispan.xml =================================================================== --- docs/reference/src/main/docbook/en-US/content/connectors/infinispan.xml (revision 1847) +++ docs/reference/src/main/docbook/en-US/content/connectors/infinispan.xml (working copy) @@ -158,6 +158,21 @@ config.repositorySource("Infinispan Store") + + Considerations for Distributed Sources + + The &InfinispanSource; can be used to provide access to an Infinispan cluster, but be sure to use the DIST_SYNC + cache mode. Using other modes will likely lead to data inconsistency. + + + Additionally, some operating systems (e.g., OS X) require you to set either the java.net.preferIPv4Stack + or the java.net.preferIPv6Addresses system property to true. These properties are used by + JGroups, the communications library that underlies Infinispan, to help determine which address type to use. + + + The rootNodeUuid property must be set to the same value for all Infinispan sources in the cluster. + + Index: extensions/modeshape-connector-infinispan/pom.xml =================================================================== --- extensions/modeshape-connector-infinispan/pom.xml (revision 1847) +++ extensions/modeshape-connector-infinispan/pom.xml (working copy) @@ -45,7 +45,7 @@ test org.infinispan 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 1847) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanSource.java (working copy) @@ -107,7 +107,7 @@ public class InfinispanSource implements BaseRepositorySource, ObjectFactory { protected static final String UPDATES_ALLOWED = "updatesAllowed"; private volatile String name; - private volatile UUID rootNodeUuid = UUID.randomUUID(); + private volatile UUID rootNodeUuid = UUID.fromString("cafebabe-cafe-babe-cafe-babecafebabe"); private volatile CachePolicy defaultCachePolicy; private volatile String cacheConfigurationName; private volatile String cacheManagerJndiName; @@ -380,6 +380,46 @@ public class InfinispanSource implements BaseRepositorySource, ObjectFactory { capabilities.supportsReferences()); } + private CacheManager createCacheManager() { + CacheManager cacheManager; + + String configName = getCacheConfigurationName(); + if (configName == null) { + cacheManager = new DefaultCacheManager(); + } else { + /* + * First try treating the config name as a classpath resource, then as a file name. + */ + InputStream configStream = getClass().getResourceAsStream(configName); + try { + if (configStream == null) { + configStream = new FileInputStream(configName); + } + } catch (IOException ioe) { + I18n msg = InfinispanConnectorI18n.configFileNotFound; + throw new RepositorySourceException(this.name, msg.text(configName), ioe); + } + + try { + cacheManager = new DefaultCacheManager(configStream); + } catch (IOException ioe) { + I18n msg = InfinispanConnectorI18n.configFileNotValid; + throw new RepositorySourceException(this.name, msg.text(configName), ioe); + } finally { + try { + configStream.close(); + } catch (IOException ioe) { + } + } + } + + return cacheManager; + } + + final CacheManager cacheManager() { + return repository.getCacheManager(); + } + /** * {@inheritDoc} * @@ -418,36 +458,7 @@ public class InfinispanSource implements BaseRepositorySource, ObjectFactory { } } if (cacheManager == null) { - String configName = getCacheConfigurationName(); - if (configName == null) { - cacheManager = new DefaultCacheManager(); - } else { - /* - * First try treating the config name as a classpath resource, then as a file name. - */ - InputStream configStream = getClass().getResourceAsStream(configName); - try { - if (configStream == null) { - configStream = new FileInputStream(configName); - } - } catch (IOException ioe) { - I18n msg = InfinispanConnectorI18n.configFileNotFound; - throw new RepositorySourceException(this.name, msg.text(configName), ioe); - } - - try { - cacheManager = new DefaultCacheManager(configStream); - } catch (IOException ioe) { - I18n msg = InfinispanConnectorI18n.configFileNotValid; - throw new RepositorySourceException(this.name, msg.text(configName), ioe); - } finally { - try { - configStream.close(); - } catch (IOException ioe) { - } - } - - } + cacheManager = createCacheManager(); } // Now create the repository ... Index: extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanClusterTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1847) +++ extensions/modeshape-connector-infinispan/src/test/java/org/modeshape/connector/infinispan/InfinispanClusterTest.java (working copy) @@ -0,0 +1,107 @@ +package org.modeshape.connector.infinispan; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import java.util.UUID; +import org.infinispan.Cache; +import org.junit.Test; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Graph; +import org.modeshape.graph.Node; +import org.modeshape.graph.Subgraph; +import org.modeshape.graph.connector.RepositoryConnection; +import org.modeshape.graph.connector.RepositoryConnectionFactory; +import org.modeshape.graph.connector.RepositoryContext; +import org.modeshape.graph.connector.RepositorySource; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.observe.Observer; +import org.modeshape.graph.property.PathNotFoundException; + +/* + * Quick test that two clustered InfinispanSources can share data. + */ +public class InfinispanClusterTest { + + private static final String CONFIG_FILE = "./src/test/resources/infinispan_clustered_config.xml"; + + + private final ExecutionContext context = new ExecutionContext(); + + @Test + public void shouldDistributeGraphNodes() { + InfinispanSource source1 = new InfinispanSource(); + source1.setName("source1"); + source1.setCacheConfigurationName(CONFIG_FILE); + source1.initialize(repositoryContextFor(source1)); + + InfinispanSource source2 = new InfinispanSource(); + source2.setName("source2"); + source2.setCacheConfigurationName(CONFIG_FILE); + source2.initialize(repositoryContextFor(source2)); + + Graph graph1 = Graph.create(source1, context); + Graph graph2 = Graph.create(source2, context); + + assertThat(source1.getRootNodeUuid(), is(source2.getRootNodeUuid())); + + Node root1 = graph1.getNodeAt("/"); + Node root2 = graph2.getNodeAt("/"); + + assertThat(root1.getLocation(), is(root2.getLocation())); + + graph1.create("/foo").and(); + + Cache cache1 = source1.cacheManager().getCache(""); + Cache cache2 = source2.cacheManager().getCache(""); + // System.out.println(cache1 + " -> " + cache1.entrySet()); + // System.out.println(cache2 + " -> " + cache2.entrySet()); + + assertThat(cache1.size(), is(2)); + assertThat(cache2.size(), is(2)); + + graph2.getNodeAt("/foo"); + + graph2.delete("/foo"); + + try { + graph1.getNodeAt("/foo"); + fail("/foo was deleted by the other source and should no longer exist"); + } catch (PathNotFoundException expected) { + + } + } + + private final RepositoryContext repositoryContextFor( final RepositorySource source ) { + return new RepositoryContext() { + + @Override + public Subgraph getConfiguration( int depth ) { + return null; + } + + @Override + public ExecutionContext getExecutionContext() { + return context; + } + + @Override + public Observer getObserver() { + return null; + } + + @Override + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return new RepositoryConnectionFactory() { + + @Override + public RepositoryConnection createConnection( String sourceName ) throws RepositorySourceException { + return source.getConnection(); + } + + }; + } + + }; + } +} Index: extensions/modeshape-connector-infinispan/src/test/resources/infinispan_clustered_config.xml new file mode 100644 =================================================================== --- /dev/null (revision 1847) +++ extensions/modeshape-connector-infinispan/src/test/resources/infinispan_clustered_config.xml (working copy) @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file Index: modeshape-integration-tests/pom.xml =================================================================== --- modeshape-integration-tests/pom.xml (revision 1847) +++ modeshape-integration-tests/pom.xml (working copy) @@ -80,6 +80,12 @@ modeshape-connector-jbosscache ${project.version} test + + + jgroups + jgroups + + org.modeshape Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/infinispan/InfinispanClusterTest.java new file mode 100644 =================================================================== --- /dev/null (revision 1847) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/infinispan/InfinispanClusterTest.java (working copy) @@ -0,0 +1,95 @@ +package org.modeshape.test.integration.infinispan; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import java.util.concurrent.TimeUnit; +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.Repository; +import javax.jcr.Session; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.common.collection.Problem; +import org.modeshape.jcr.JcrConfiguration; +import org.modeshape.jcr.JcrEngine; + +public class InfinispanClusterTest { + + private JcrEngine engine1; + private JcrEngine engine2; + + @Before + public void beforeEach() throws Exception { + engine1 = new JcrConfiguration().loadFrom("./src/test/resources/infinispan/configRepository.xml").build(); + engine2 = new JcrConfiguration().loadFrom("./src/test/resources/infinispan/configRepository.xml").build(); + + engine1.start(); + if (engine1.getProblems().hasProblems()) { + for (Problem problem : engine1.getProblems()) { + System.err.println(problem.getMessageString()); + } + } + + engine2.start(); + if (engine2.getProblems().hasProblems()) { + for (Problem problem : engine2.getProblems()) { + System.err.println(problem.getMessageString()); + } + } + } + + @After + public void afterEach() throws Exception { + if (engine1 != null) { + engine1.shutdown(); + engine1.awaitTermination(5, TimeUnit.SECONDS); + } + + if (engine2 != null) { + engine2.shutdown(); + engine2.awaitTermination(5, TimeUnit.SECONDS); + } + } + + @Test + public void shouldShareContent() throws Exception { + assertThat(engine1, is(notNullValue())); + assertThat(engine2, is(notNullValue())); + + Repository repo1 = engine1.getRepository("Test Repository Source"); + Repository repo2 = engine2.getRepository("Test Repository Source"); + + Session session1 = repo1.login(); + Session session2 = repo2.login(); + + Node fooNode = session1.getRootNode().addNode("foo"); + fooNode.addMixin("mix:referenceable"); + + checkNoNodeAt(session2, "/foo"); + + session1.save(); + + session2.refresh(false); + Item foo2 = session2.getItem("/foo"); + foo2.remove(); + session2.save(); + + session1.refresh(false); + checkNoNodeAt(session1, "/foo"); + + } + + private void checkNoNodeAt( Session session, + String path ) throws Exception { + try { + session.getItem(path); + fail("No node should exist at path: " + path); + } catch (PathNotFoundException expected) { + + } + } +} Index: modeshape-integration-tests/src/test/resources/infinispan/configRepository.xml new file mode 100644 =================================================================== --- /dev/null (revision 1847) +++ modeshape-integration-tests/src/test/resources/infinispan/configRepository.xml (working copy) @@ -0,0 +1,92 @@ + + + + + + + + default + otherWorkspace + + + + + + Standard extension-based MIME type detector + + org.modeshape.graph.mimetype.ExtensionBasedMimeTypeDetector + + + + + + + + + + Store + + + + + + + + + + + + Index: modeshape-integration-tests/src/test/resources/infinispan/infinispan_clustered_config.xml new file mode 100644 =================================================================== --- /dev/null (revision 1847) +++ modeshape-integration-tests/src/test/resources/infinispan/infinispan_clustered_config.xml (working copy) @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file Index: pom.xml =================================================================== --- pom.xml (revision 1847) +++ pom.xml (working copy) @@ -822,6 +822,10 @@ java.io.tmpdir ${basedir}/target + + java.net.preferIPv4Stack + true +