/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.collection.IsArrayContaining.hasItemInArray; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.InputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.jcr.NamespaceException; import javax.jcr.NamespaceRegistry; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.ValueFormatException; import javax.jcr.Workspace; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.observation.Event; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.security.AccessControlList; import javax.jcr.security.AccessControlManager; import javax.jcr.security.AccessControlPolicyIterator; import javax.jcr.security.Privilege; import javax.transaction.TransactionManager; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.collection.Problems; import org.modeshape.common.statistic.Stopwatch; import org.modeshape.common.util.FileUtil; import org.modeshape.jcr.RepositoryStatistics.MetricHistory; import org.modeshape.jcr.api.monitor.DurationActivity; import org.modeshape.jcr.api.monitor.DurationMetric; import org.modeshape.jcr.api.monitor.History; import org.modeshape.jcr.api.monitor.Statistics; import org.modeshape.jcr.api.monitor.ValueMetric; import org.modeshape.jcr.api.monitor.Window; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.journal.JournalRecord; import org.modeshape.jcr.journal.LocalJournal; import org.modeshape.jcr.security.SimplePrincipal; public class JcrRepositoryTest { private JcrRepository repository; private JcrSession session; protected boolean print = false; @Before public void beforeEach() throws Exception { repository = new JcrRepository(new RepositoryConfiguration("repoName", new TestingEnvironment())); repository.start(); print = false; } @After public void afterEach() throws Exception { if (session != null) { try { session.logout(); } finally { session = null; } } shutdownDefaultRepository(); } private void shutdownDefaultRepository() { if (repository != null) { try { TestingUtil.killRepositories(repository); } finally { repository = null; } } } protected TransactionManager getTransactionManager() { return repository.transactionManager(); } @Test public void shouldAllowCreationOfSessionForDefaultWorkspaceWithoutUsingCredentials() throws Exception { JcrSession session1 = repository.login(); assertThat(session1.isLive(), is(true)); } @Test( expected = NoSuchWorkspaceException.class ) public void shouldNotAllowCreatingSessionForNonExistantWorkspace() throws Exception { repository.login("non-existant-workspace"); } @Test public void shouldAllowShuttingDownAndRestarting() throws Exception { JcrSession session1 = repository.login(); JcrSession session2 = repository.login(); assertThat(session1.isLive(), is(true)); assertThat(session2.isLive(), is(true)); session2.logout(); assertThat(session1.isLive(), is(true)); assertThat(session2.isLive(), is(false)); repository.shutdown().get(3L, TimeUnit.SECONDS); assertThat(session1.isLive(), is(false)); assertThat(session2.isLive(), is(false)); repository.start(); JcrSession session3 = repository.login(); assertThat(session1.isLive(), is(false)); assertThat(session2.isLive(), is(false)); assertThat(session3.isLive(), is(true)); session3.logout(); } @Test public void shouldAllowCreatingNewWorkspacesByDefault() throws Exception { // Verify the workspace does not exist yet ... try { repository.login("new-workspace"); } catch (NoSuchWorkspaceException e) { // expected } JcrSession session1 = repository.login(); assertThat(session1.getRootNode(), is(notNullValue())); session1.getWorkspace().createWorkspace("new-workspace"); // Now create a session to that workspace ... JcrSession session2 = repository.login("new-workspace"); assertThat(session2.getRootNode(), is(notNullValue())); } @Test public void shouldAllowDestroyingWorkspacesByDefault() throws Exception { // Verify the workspace does not exist yet ... try { repository.login("new-workspace"); } catch (NoSuchWorkspaceException e) { // expected } JcrSession session1 = repository.login(); assertThat(session1.getRootNode(), is(notNullValue())); session1.getWorkspace().createWorkspace("new-workspace"); // Now create a session to that workspace ... JcrSession session2 = repository.login("new-workspace"); assertThat(session2.getRootNode(), is(notNullValue())); } @Test public void shouldReturnNullForNullDescriptorKey() { assertThat(repository.getDescriptor(null), is(nullValue())); } @Test public void shouldReturnNullForEmptyDescriptorKey() { assertThat(repository.getDescriptor(""), is(nullValue())); } @Test public void shouldProvideBuiltInDescriptorKeys() { testDescriptorKeys(repository); } @Test public void shouldProvideDescriptorValues() { testDescriptorValues(repository); } @Test public void shouldProvideBuiltInDescriptorsWhenNotSuppliedDescriptors() throws Exception { testDescriptorKeys(repository); testDescriptorValues(repository); } @Test public void shouldProvideRepositoryWorkspaceNamesDescriptor() throws ValueFormatException { Set<String> workspaceNames = repository.repositoryCache().getWorkspaceNames(); Set<String> descriptorValues = new HashSet<String>(); for (JcrValue value : repository.getDescriptorValues(org.modeshape.jcr.api.Repository.REPOSITORY_WORKSPACES)) { descriptorValues.add(value.getString()); } assertThat(descriptorValues, is(workspaceNames)); } @Test public void shouldProvideStatisticsImmediatelyAfterStartup() throws Exception { History history = repository.getRepositoryStatistics() .getHistory(ValueMetric.WORKSPACE_COUNT, Window.PREVIOUS_60_SECONDS); Statistics[] stats = history.getStats(); assertThat(stats.length, is(not(0))); assertThat(history.getTotalDuration(TimeUnit.SECONDS), is(60L)); System.out.println(history); } /** * Skipping this test because it purposefully runs over 60 minutes (!!!), mostly just waiting for the statistics thread to * wake up once every 5 seconds. * * @throws Exception */ @Ignore @Test public void shouldProvideStatisticsForAVeryLongTime() throws Exception { final AtomicBoolean stop = new AtomicBoolean(false); final JcrRepository repository = this.repository; Thread worker = new Thread(() -> { JcrSession[] openSessions = new JcrSession[100 * 6]; int index = 0; while (!stop.get()) { try { for (int i = 0; i != 6; ++i) { JcrSession session1 = repository.login(); assertThat(session1.getRootNode(), is(notNullValue())); openSessions[index++] = session1; } if (index >= openSessions.length) { for (int i = 0; i != openSessions.length; ++i) { openSessions[i].logout(); openSessions[i] = null; } index = 0; } Thread.sleep(MILLISECONDS.convert(3, SECONDS)); } catch (Throwable t) { t.printStackTrace(); stop.set(true); break; } } }); worker.start(); // Status thread ... final Stopwatch sw = new Stopwatch(); Thread status = new Thread(new Runnable() { @Override public void run() { System.out.println("Starting ..."); sw.start(); int counter = 0; while (!stop.get()) { try { Thread.sleep(MILLISECONDS.convert(10, SECONDS)); if (!stop.get()) { ++counter; sw.lap(); System.out.println(" continuing after " + sw.getTotalDuration().toSimpleString()); } if (counter % 24 == 0) { History history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_60_SECONDS); System.out.println(history); } } catch (Throwable t) { t.printStackTrace(); stop.set(true); break; } } } }); status.start(); // wait for 65 minutes, so that the statistics have a value ... Thread.sleep(MILLISECONDS.convert(65, MINUTES)); stop.set(true); System.out.println(); Thread.sleep(MILLISECONDS.convert(5, SECONDS)); History history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_60_MINUTES); Statistics[] stats = history.getStats(); System.out.println(history); assertThat(stats.length, is(MetricHistory.MAX_MINUTES)); assertThat(stats[0], is(notNullValue())); assertThat(stats[11], is(notNullValue())); assertThat(stats[59], is(notNullValue())); assertThat(history.getTotalDuration(TimeUnit.MINUTES), is(60L)); history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_60_SECONDS); stats = history.getStats(); System.out.println(history); assertThat(stats.length, is(MetricHistory.MAX_SECONDS)); assertThat(stats[0], is(notNullValue())); assertThat(stats[11], is(notNullValue())); assertThat(history.getTotalDuration(TimeUnit.SECONDS), is(60L)); history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_24_HOURS); stats = history.getStats(); System.out.println(history); assertThat(stats.length, is(not(0))); assertThat(stats[0], is(nullValue())); assertThat(stats[23], is(notNullValue())); assertThat(history.getTotalDuration(TimeUnit.HOURS), is(24L)); } /** * Skipping this test because it purposefully runs over 6 seconds, mostly just waiting for the statistics thread to wake up * once every 5 seconds. * * @throws Exception */ @Ignore @Test public void shouldProvideStatistics() throws Exception { for (int i = 0; i != 3; ++i) { JcrSession session1 = repository.login(); assertThat(session1.getRootNode(), is(notNullValue())); } // wait for 6 seconds, so that the statistics have a value ... Thread.sleep(6000L); History history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_60_SECONDS); Statistics[] stats = history.getStats(); assertThat(stats.length, is(12)); assertThat(stats[0], is(nullValue())); assertThat(stats[11], is(notNullValue())); assertThat(stats[11].getMaximum(), is(3L)); assertThat(stats[11].getMinimum(), is(3L)); assertThat(history.getTotalDuration(TimeUnit.SECONDS), is(60L)); System.out.println(history); } /** * Skipping this test because it purposefully runs over 18 seconds, mostly just waiting for the statistics thread to wake up * once every 5 seconds. * * @throws Exception */ @Ignore @Test public void shouldProvideStatisticsForMultipleSeconds() throws Exception { LinkedList<JcrSession> sessions = new LinkedList<JcrSession>(); for (int i = 0; i != 5; ++i) { JcrSession session1 = repository.login(); assertThat(session1.getRootNode(), is(notNullValue())); sessions.addFirst(session1); Thread.sleep(1000L); } Thread.sleep(6000L); while (sessions.peek() != null) { JcrSession session = sessions.poll(); session.logout(); Thread.sleep(1000L); } History history = repository.getRepositoryStatistics().getHistory(ValueMetric.SESSION_COUNT, Window.PREVIOUS_60_SECONDS); Statistics[] stats = history.getStats(); assertThat(stats.length, is(12)); assertThat(history.getTotalDuration(TimeUnit.SECONDS), is(60L)); System.out.println(history); DurationActivity[] lifetimes = repository.getRepositoryStatistics().getLongestRunning(DurationMetric.SESSION_LIFETIME); System.out.println("Session lifetimes: "); for (DurationActivity activity : lifetimes) { System.out.println(" " + activity); } } @SuppressWarnings( "deprecation" ) private void testDescriptorKeys( Repository repository ) { String[] keys = repository.getDescriptorKeys(); assertThat(keys, notNullValue()); assertThat(keys.length >= 15, is(true)); assertThat(keys, hasItemInArray(Repository.LEVEL_1_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.LEVEL_2_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.OPTION_LOCKING_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.OPTION_OBSERVATION_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.OPTION_QUERY_SQL_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.OPTION_TRANSACTIONS_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.OPTION_VERSIONING_SUPPORTED)); assertThat(keys, hasItemInArray(Repository.QUERY_XPATH_DOC_ORDER)); assertThat(keys, hasItemInArray(Repository.QUERY_XPATH_POS_INDEX)); assertThat(keys, hasItemInArray(Repository.REP_NAME_DESC)); assertThat(keys, hasItemInArray(Repository.REP_VENDOR_DESC)); assertThat(keys, hasItemInArray(Repository.REP_VENDOR_URL_DESC)); assertThat(keys, hasItemInArray(Repository.REP_VERSION_DESC)); assertThat(keys, hasItemInArray(Repository.SPEC_NAME_DESC)); assertThat(keys, hasItemInArray(Repository.SPEC_VERSION_DESC)); } @SuppressWarnings( "deprecation" ) private void testDescriptorValues( Repository repository ) { assertThat(repository.getDescriptor(Repository.LEVEL_1_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.LEVEL_2_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.OPTION_QUERY_SQL_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.OPTION_TRANSACTIONS_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.OPTION_VERSIONING_SUPPORTED), is("true")); assertThat(repository.getDescriptor(Repository.QUERY_XPATH_DOC_ORDER), is("false")); assertThat(repository.getDescriptor(Repository.QUERY_XPATH_POS_INDEX), is("false")); assertThat(repository.getDescriptor(Repository.REP_NAME_DESC), is("ModeShape")); assertThat(repository.getDescriptor(Repository.REP_VENDOR_DESC), is("JBoss, a division of Red Hat")); assertThat(repository.getDescriptor(Repository.REP_VENDOR_URL_DESC), is("http://www.modeshape.org")); assertThat(repository.getDescriptor(Repository.REP_VERSION_DESC), is(notNullValue())); assertThat(repository.getDescriptor(Repository.REP_VERSION_DESC).startsWith("5."), is(true)); assertThat(repository.getDescriptor(Repository.SPEC_NAME_DESC), is(JcrI18n.SPEC_NAME_DESC.text())); assertThat(repository.getDescriptor(Repository.SPEC_VERSION_DESC), is("2.0")); } @Test public void shouldReturnNullWhenDescriptorKeyIsNull() { assertThat(repository.getDescriptor(null), is(nullValue())); } @Test public void shouldNotAllowEmptyDescriptorKey() { assertThat(repository.getDescriptor(""), is(nullValue())); } @Test public void shouldNotProvideRepositoryWorkspaceNamesDescriptorIfOptionSetToFalse() throws Exception { assertThat(repository.getDescriptor(org.modeshape.jcr.api.Repository.REPOSITORY_WORKSPACES), is(nullValue())); } @SuppressWarnings( "deprecation" ) @Test public void shouldHaveRootNode() throws Exception { session = createSession(); javax.jcr.Node root = session.getRootNode(); String uuid = root.getIdentifier(); // Should be referenceable ... assertThat(root.isNodeType("mix:referenceable"), is(true)); // Should have a UUID ... assertThat(root.getUUID(), is(uuid)); // Should have an identifier ... assertThat(root.getIdentifier(), is(uuid)); // Get the children of the root node ... javax.jcr.NodeIterator iter = root.getNodes(); javax.jcr.Node system = iter.nextNode(); assertThat(system.getName(), is("jcr:system")); // Add a child node ... javax.jcr.Node childA = root.addNode("childA", "nt:unstructured"); assertThat(childA, is(notNullValue())); iter = root.getNodes(); javax.jcr.Node system2 = iter.nextNode(); javax.jcr.Node childA2 = iter.nextNode(); assertThat(system2.getName(), is("jcr:system")); assertThat(childA2.getName(), is("childA")); } @Test public void shouldHaveSystemBranch() throws Exception { session = createSession(); javax.jcr.Node root = session.getRootNode(); AbstractJcrNode system = (AbstractJcrNode)root.getNode("jcr:system"); assertThat(system, is(notNullValue())); } @Test public void shouldHaveRegisteredModeShapeSpecificNamespacesNamespaces() throws Exception { session = createSession(); // Don't use the constants, since this needs to check that the actual values are correct assertThat(session.getNamespaceURI("mode"), is("http://www.modeshape.org/1.0")); } @Test( expected = NamespaceException.class ) public void shouldNotHaveModeShapeInternalNamespaceFromVersion2() throws Exception { session = createSession(); // Don't use the constants, since this needs to check that the actual values are correct session.getNamespaceURI("modeint"); } @Test public void shouldHaveRegisteredThoseNamespacesDefinedByTheJcrSpecification() throws Exception { session = createSession(); // Don't use the constants, since this needs to check that the actual values are correct assertThat(session.getNamespaceURI("mode"), is("http://www.modeshape.org/1.0")); assertThat(session.getNamespaceURI("jcr"), is("http://www.jcp.org/jcr/1.0")); assertThat(session.getNamespaceURI("mix"), is("http://www.jcp.org/jcr/mix/1.0")); assertThat(session.getNamespaceURI("nt"), is("http://www.jcp.org/jcr/nt/1.0")); assertThat(session.getNamespaceURI(""), is("")); } @Test public void shouldHaveRegisteredThoseNamespacesDefinedByTheJcrApiJavaDoc() throws Exception { session = createSession(); // Don't use the constants, since this needs to check that the actual values are correct assertThat(session.getNamespaceURI("sv"), is("http://www.jcp.org/jcr/sv/1.0")); assertThat(session.getNamespaceURI("xmlns"), is("http://www.w3.org/2000/xmlns/")); } protected JcrSession createSession() throws Exception { return repository.login(); } protected JcrSession createSession( final String workspace ) throws Exception { return repository.login(workspace); } @Test @FixFor( "MODE-2190" ) public void shouldCleanupLocks() throws Exception { JcrSession locker1 = repository.login(); // Create a node to lock javax.jcr.Node sessionLockedNode1 = locker1.getRootNode().addNode("sessionLockedNode1"); sessionLockedNode1.addMixin("mix:lockable"); javax.jcr.Node openLockedNode = locker1.getRootNode().addNode("openLockedNode"); openLockedNode.addMixin("mix:lockable"); locker1.save(); // Create a session-scoped lock (not deep) locker1.getWorkspace().getLockManager().lock(sessionLockedNode1.getPath(), false, true, 1, "me"); assertLocking(locker1, "/sessionLockedNode1", true); // Create an open-scoped lock (not deep) locker1.getWorkspace().getLockManager().lock(openLockedNode.getPath(), false, false, 1, "me"); assertLocking(locker1, "/openLockedNode", true); JcrSession locker2 = repository.login(); javax.jcr.Node sessionLockedNode2 = locker2.getRootNode().addNode("sessionLockedNode2"); sessionLockedNode2.addMixin("mix:lockable"); locker2.save(); locker2.getWorkspace().getLockManager().lock(sessionLockedNode2.getPath(), false, true, Long.MAX_VALUE, "me"); assertEquals(Long.MAX_VALUE, locker2.getWorkspace().getLockManager().getLock("/sessionLockedNode2").getSecondsRemaining()); assertEquals(Long.MAX_VALUE, locker1.getWorkspace().getLockManager().getLock("/sessionLockedNode2").getSecondsRemaining()); assertLocking(locker2, "/openLockedNode", true); assertLocking(locker2, "/sessionLockedNode1", true); assertLocking(locker2, "/sessionLockedNode2", true); javax.jcr.Session reader = repository.login(); assertLocking(locker1, "/openLockedNode", true); assertLocking(locker1, "/sessionLockedNode1", true); assertLocking(locker1, "/sessionLockedNode2", true); assertLocking(locker2, "/openLockedNode", true); assertLocking(locker2, "/sessionLockedNode1", true); assertLocking(locker2, "/sessionLockedNode2", true); assertLocking(reader, "/openLockedNode", true); assertLocking(reader, "/sessionLockedNode1", true); assertLocking(reader, "/sessionLockedNode2", true); //remove the 1st locking session internally, as if it had terminated unexpectedly repository.runningState().removeSession(locker1); //make sure the open lock also expires Thread.sleep(1001); // The locker1 thread should be inactive and both the session lock and the open lock cleaned up repository.runningState().cleanUpLocks(); assertLocking(locker1, "/openLockedNode", false); assertLocking(locker1, "/sessionLockedNode1", false); assertLocking(locker1, "/sessionLockedNode2", true); assertLocking(locker2, "/openLockedNode", false); assertLocking(locker2, "/sessionLockedNode1", false); assertLocking(locker2, "/sessionLockedNode2", true); assertLocking(reader, "/openLockedNode", false); assertLocking(reader, "/sessionLockedNode1", false); assertLocking(reader, "/sessionLockedNode2", true); assertEquals(Long.MAX_VALUE, locker2.getWorkspace().getLockManager().getLock("/sessionLockedNode2").getSecondsRemaining()); } @Test @FixFor( "MODE-2485" ) public void shouldCleanupLocksConcurrently() throws Exception { //simulate cleaning up locks from multiple threads concurrently //this is a scenario which should only occur in a cluster final JcrSession locker1 = repository.login(); // add a session scoped lock from session1 javax.jcr.Node sessionLockedNode1 = locker1.getRootNode().addNode("sessionLockedNode1"); sessionLockedNode1.addMixin("mix:lockable"); locker1.save(); locker1.getWorkspace().getLockManager().lock(sessionLockedNode1.getPath(), false, true, 1, "me"); // add a session scoped lock from session2 final JcrSession locker2 = repository.login(); javax.jcr.Node sessionLockedNode2 = locker2.getRootNode().addNode("sessionLockedNode2"); sessionLockedNode2.addMixin("mix:lockable"); locker2.save(); locker2.getWorkspace().getLockManager().lock(sessionLockedNode2.getPath(), false, true, 1, "me"); // validate that all sessions see all nodes as locked assertLocking(locker1, "/sessionLockedNode1", true); assertLocking(locker1, "/sessionLockedNode2", true); assertLocking(locker2, "/sessionLockedNode1", true); assertLocking(locker2, "/sessionLockedNode2", true); // run threads which concurrently terminate the sessions and cleanup the locks int nThreads = 2; ExecutorService executors = Executors.newFixedThreadPool(nThreads); List<Future<Void>> results = new ArrayList<>(nThreads); final CyclicBarrier barrier = new CyclicBarrier(nThreads); try { results.add(executors.submit(() -> { //remove the 1st locking session internally, as if it had terminated unexpectedly repository.runningState().removeSession(locker1); //make sure the open lock also expires Thread.sleep(1001); barrier.await(); repository.runningState().cleanUpLocks(); return null; })); results.add(executors.submit(() -> { //remove the 2nd locking session internally, as if it had terminated unexpectedly repository.runningState().removeSession(locker2); //make sure the open lock also expires Thread.sleep(1001); barrier.await(); repository.runningState().cleanUpLocks(); return null; })); for (Future<?> result : results) { result.get(3, TimeUnit.SECONDS); } // validate that all nodes are unlocked for all sessions assertLocking(locker1, "/sessionLockedNode1", false); assertLocking(locker1, "/sessionLockedNode2", false); assertLocking(locker2, "/sessionLockedNode1", false); assertLocking(locker2, "/sessionLockedNode2", false); } finally { executors.shutdownNow(); } } private void assertLocking( Session session, String path, boolean locked ) throws Exception { Node node = session.getNode(path); if (locked) { assertTrue(node.isLocked()); assertTrue(node.hasProperty(JcrLexicon.LOCK_IS_DEEP.getString())); assertTrue(node.hasProperty(JcrLexicon.LOCK_OWNER.getString())); } else { assertFalse(node.isLocked()); assertFalse(node.hasProperty(JcrLexicon.LOCK_IS_DEEP.getString())); assertFalse(node.hasProperty(JcrLexicon.LOCK_OWNER.getString())); } } @Test public void shouldAllowCreatingWorkspaces() throws Exception { shutdownDefaultRepository(); RepositoryConfiguration config = null; config = RepositoryConfiguration.read("{ \"name\" : \"repoName\", \"workspaces\" : { \"allowCreation\" : true } }"); config = new RepositoryConfiguration(config.getDocument(), "repoName", new TestingEnvironment()); repository = new JcrRepository(config); repository.start(); // Create several sessions ... Session session2 = null; Session session3 = null; try { session = createSession(); session2 = createSession(); // Create a new workspace ... String newWorkspaceName = "MyCarWorkspace"; session.getWorkspace().createWorkspace(newWorkspaceName); assertAccessibleWorkspace(session, newWorkspaceName); assertAccessibleWorkspace(session2, newWorkspaceName); session.logout(); session3 = createSession(); assertAccessibleWorkspace(session2, newWorkspaceName); assertAccessibleWorkspace(session3, newWorkspaceName); // Create a session for this new workspace ... session = createSession(newWorkspaceName); } finally { try { if (session2 != null) session2.logout(); } finally { if (session3 != null) session3.logout(); } } } protected void assertAccessibleWorkspace( Session session, String workspaceName ) throws Exception { assertContains(session.getWorkspace().getAccessibleWorkspaceNames(), workspaceName); } protected void assertContains( String[] actuals, String... expected ) { // Each expected must appear in the actuals ... for (String expect : expected) { if (expect == null) continue; boolean found = false; for (String actual : actuals) { if (expect.equals(actual)) { found = true; break; } } assertThat("Did not find '" + expect + "' in the actuals: " + actuals, found, is(true)); } } @Test @FixFor( "MODE-1269" ) public void shouldAllowReindexingEntireWorkspace() throws Exception { session = createSession(); session.getWorkspace().reindex(); } @Test @FixFor( "MODE-1269" ) public void shouldAllowReindexingSubsetOfWorkspace() throws Exception { session = createSession(); session.getWorkspace().reindex("/"); } @Test @FixFor( "MODE-1269" ) public void shouldAllowAsynchronousReindexingEntireWorkspace() throws Exception { session = createSession(); Future<Boolean> future = session.getWorkspace().reindexAsync(); assertThat(future, is(notNullValue())); assertThat(future.get(), is(true)); // get() blocks until done } @Test @FixFor( "MODE-1269" ) public void shouldAllowAsynchronousReindexingSubsetOfWorkspace() throws Exception { session = createSession(); Future<Boolean> future = session.getWorkspace().reindexAsync("/"); assertThat(future, is(notNullValue())); assertThat(future.get(), is(true)); // get() blocks until done } @FixFor( {"MODE-1498", "MODE-2202"} ) @Test public void shouldWorkWithUserDefinedTransactionsInSeparateThreads() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); CyclicBarrier completionBarrier = new CyclicBarrier(3); session = createSession(); // THREAD 1 ... SessionWorker worker1 = new SessionWorker(session, barrier, completionBarrier, "thread 1") { @Override protected void execute( Session session ) throws Exception { // STEP 1: Setup the listener, and check that the new nodes are not visible to the other session ... SimpleListener listener = addListener(3); // we'll create 3 nodes ... assertThat(listener.getActualEventCount(), is(0)); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 2: Make changes using a transaction, but do not commit the transaction ... final TransactionManager txnMgr = getTransactionManager(); txnMgr.begin(); assertThat(listener.getActualEventCount(), is(0)); Node txnNode1 = session.getRootNode().addNode("txnNode1"); Node txnNode1a = txnNode1.addNode("txnNodeA"); assertThat(txnNode1a, is(notNullValue())); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); session.save(); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); assertThat(listener.getActualEventCount(), is(0)); barrier.await(); // STEP 3: Wait for other session to verify that it can't see the new node we created but have not committed... // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should // not see the event, since we didn't commit ... Thread.sleep(100L); assertThat(listener.getActualEventCount(), is(0)); barrier.await(); // STEP 4: Create another new node using this transaction ... Node txnNode2 = session.getRootNode().addNode("txnNode2"); assertThat(txnNode2, is(notNullValue())); session.save(); assertThat(listener.getActualEventCount(), is(0)); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(true)); barrier.await(); // STEP 5: Wait for other session to verify that it can't see the new node we created but have not committed... // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should // not see the event, since we didn't commit ... Thread.sleep(100L); assertThat(listener.getActualEventCount(), is(0)); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(true)); barrier.await(); // STEP 6: Commit the txn ... txnMgr.commit(); barrier.await(); // STEP 7: Verify the commit resulted in the things we expect ... listener.waitForEvents(); nodeExists(session, "/", "txnNode1"); nodeExists(session, "/", "txnNode2"); } }; // THREAD 2 ... SessionWorker worker2 = new SessionWorker(createSession(), barrier, completionBarrier, "thread 2") { @Override protected void execute( Session session ) throws Exception { // STEP 1: Check that the new nodes are not visible to the other session ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 2: Wait for changes to be made in the other transaction ... Thread.sleep(50L); barrier.await(); // STEP 3: Check that we cannot see the new node created by the other session in the still-ongoing txn ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); session.refresh(false); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 4: Wait for changes to be made in the other transaction ... Thread.sleep(50L); barrier.await(); // STEP 5: Check that we cannot see the new node created by the other session in the still-ongoing txn ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); session.refresh(false); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 6: Wait for the transaction to be committed Thread.sleep(50L); barrier.await(); // STEP 7: Check that the nodes are now visible to this session ... nodeExists(session, "/", "txnNode1"); nodeExists(session, "/", "txnNode2"); session.refresh(false); nodeExists(session, "/", "txnNode1"); nodeExists(session, "/", "txnNode2"); } }; new Thread(worker1).start(); new Thread(worker2).start(); // Wait for the threads to complete ... completionBarrier.await(); } @FixFor( {"MODE-1498", "MODE-2202"} ) @Test public void shouldWorkWithUserDefinedTransactionsThatUseRollbackInSeparateThreads() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); CyclicBarrier completionBarrier = new CyclicBarrier(3); session = createSession(); // THREAD 1 ... SessionWorker worker1 = new SessionWorker(session, barrier, completionBarrier, "thread 1") { @Override protected void execute( Session session ) throws Exception { // STEP 1: Setup the listener, and check that the new nodes are not visible to the other session ... SimpleListener listener = addListener(3); // we'll create 3 nodes ... assertThat(listener.getActualEventCount(), is(0)); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 2: Make changes using a transaction, but do not commit the transaction ... final TransactionManager txnMgr = getTransactionManager(); txnMgr.begin(); assertThat(listener.getActualEventCount(), is(0)); Node txnNode1 = session.getRootNode().addNode("txnNode1"); Node txnNode1a = txnNode1.addNode("txnNodeA"); assertThat(txnNode1a, is(notNullValue())); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); session.save(); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); assertThat(listener.getActualEventCount(), is(0)); barrier.await(); // STEP 3: Wait for other session to verify that it can't see the new node we created but have not committed... // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should // not see the event, since we didn't commit ... Thread.sleep(100L); assertThat(listener.getActualEventCount(), is(0)); barrier.await(); // STEP 4: Create another new node using this transaction ... Node txnNode2 = session.getRootNode().addNode("txnNode2"); assertThat(txnNode2, is(notNullValue())); session.save(); assertThat(listener.getActualEventCount(), is(0)); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(true)); barrier.await(); // STEP 5: Wait for other session to verify that it can't see the new node we created but have not committed... // Meanwhile, sleep a bit to let any incorrect events propagate through the system. Our listener still should // not see the event, since we didn't commit ... Thread.sleep(100L); assertThat(listener.getActualEventCount(), is(0)); assertThat(session.getRootNode().hasNode("txnNode1"), is(true)); assertThat(session.getRootNode().hasNode("txnNode2"), is(true)); barrier.await(); // STEP 6: Rollback the txn ... txnMgr.rollback(); barrier.await(); // STEP 7: Check that there were no events and that the nodes are not visible anymore ... Thread.sleep(100L); assertThat(listener.getActualEventCount(), is(0)); assertThat(session.getRootNode().hasNode("txnNode1"), is(false)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); session.refresh(false); assertThat(session.getRootNode().hasNode("txnNode1"), is(false)); assertThat(session.getRootNode().hasNode("txnNode2"), is(false)); } }; // THREAD 2 ... SessionWorker worker2 = new SessionWorker(createSession(), barrier, completionBarrier, "thread 2") { @Override protected void execute( Session session ) throws Exception { // STEP 1: Check that the new nodes are not visible to the other session ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 2: Wait for changes to be made in the other transaction ... Thread.sleep(50L); barrier.await(); // STEP 3: Check that we cannot see the new node created by the other session in the still-ongoing txn ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); session.refresh(false); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 4: Wait for changes to be made in the other transaction ... Thread.sleep(50L); barrier.await(); // STEP 5: Check that we cannot see the new node created by the other session in the still-ongoing txn ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); session.refresh(false); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); barrier.await(); // STEP 6: Wait for the transaction to be rolled back ... Thread.sleep(50L); barrier.await(); // STEP 7: Check that the node IS NOT visible to this session ... nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); session.refresh(false); nodeDoesNotExist(session, "/", "txnNode1"); nodeDoesNotExist(session, "/", "txnNode2"); } }; new Thread(worker1).start(); new Thread(worker2).start(); // Wait for the threads to complete ... completionBarrier.await(10, TimeUnit.SECONDS); } protected abstract class SessionWorker implements Runnable { protected final CyclicBarrier barrier; private final CyclicBarrier completionBarrier; protected final JcrSession session; private final String desc; protected SessionWorker( JcrSession session, CyclicBarrier barrier, CyclicBarrier completionBarrier, String desc ) { this.barrier = barrier; this.session = session; this.completionBarrier = completionBarrier; this.desc = desc; } @Override public void run() { if (session == null) return; if (print) System.out.println("Start " + desc); try { execute(session); completionBarrier.await(); } catch (Exception e) { throw new RuntimeException(e); } finally { session.logout(); if (print) System.out.println("Stop " + desc); } } protected abstract void execute( Session session ) throws Exception; } @FixFor( "MODE-1828" ) @Test public void shouldAllowNodeTypeChangeAfterWrite() throws Exception { session = createSession(); session.workspace().getNodeTypeManager() .registerNodeTypes(getClass().getResourceAsStream("/cnd/nodeTypeChange-initial.cnd"), true); Node testRoot = session.getRootNode().addNode("/testRoot", "test:nodeTypeA"); testRoot.setProperty("fieldA", "foo"); session.save(); session.workspace().getNodeTypeManager() .registerNodeTypes(getClass().getResourceAsStream("/cnd/nodeTypeChange-next.cnd"), true); testRoot = session.getNode("/testRoot"); assertEquals("foo", testRoot.getProperty("fieldA").getString()); testRoot.setProperty("fieldB", "bar"); session.save(); testRoot = session.getNode("/testRoot"); assertEquals("foo", testRoot.getProperty("fieldA").getString()); assertEquals("bar", testRoot.getProperty("fieldB").getString()); } @FixFor( "MODE-1525" ) @Test public void shouldDiscoverCorrectChildNodeType() throws Exception { session = createSession(); InputStream cndStream = getClass().getResourceAsStream("/cnd/medical.cnd"); assertThat(cndStream, is(notNullValue())); session.getWorkspace().getNodeTypeManager().registerNodeTypes(cndStream, true); // Now create a person ... Node root = session.getRootNode(); Node person = root.addNode("jsmith", "inf:person"); person.setProperty("inf:firstName", "John"); person.setProperty("inf:lastName", "Smith"); session.save(); Node doctor = root.addNode("drBarnes", "inf:doctor"); doctor.setProperty("inf:firstName", "Sally"); doctor.setProperty("inf:lastName", "Barnes"); doctor.setProperty("inf:doctorProviderNumber", "12345678-AB"); session.save(); Node referral = root.addNode("referral", "nt:unstructured"); referral.addMixin("er:eReferral"); assertThat(referral.getMixinNodeTypes()[0].getName(), is("er:eReferral")); Node group = referral.addNode("er:gp"); assertThat(group.getPrimaryNodeType().getName(), is("inf:doctor")); // Check that group doesn't specify the first name and last name ... assertThat(group.hasProperty("inf:firstName"), is(false)); assertThat(group.hasProperty("inf:lastName"), is(false)); session.save(); // Check that group has a default first name and last name ... assertThat(group.getProperty("inf:firstName").getString(), is("defaultFirstName")); assertThat(group.getProperty("inf:lastName").getString(), is("defaultLastName")); Node docGroup = root.addNode("documentGroup", "inf:documentGroup"); assertThat(docGroup.getPrimaryNodeType().getName(), is("inf:documentGroup")); docGroup.addMixin("er:eReferral"); Node ergp = docGroup.addNode("er:gp"); assertThat(ergp.getPrimaryNodeType().getName(), is("inf:doctor")); // Check that group doesn't specify the first name and last name ... assertThat(ergp.hasProperty("inf:firstName"), is(false)); assertThat(ergp.hasProperty("inf:lastName"), is(false)); session.save(); // Check that group has a default first name and last name ... assertThat(ergp.getProperty("inf:firstName").getString(), is("defaultFirstName")); assertThat(ergp.getProperty("inf:lastName").getString(), is("defaultLastName")); } @FixFor( "MODE-1525" ) @Test public void shouldDiscoverCorrectChildNodeTypeButFailOnMandatoryPropertiesWithNoDefaultValues() throws Exception { session = createSession(); InputStream cndStream = getClass().getResourceAsStream("/cnd/medical-invalid-mandatories.cnd"); assertThat(cndStream, is(notNullValue())); session.getWorkspace().getNodeTypeManager().registerNodeTypes(cndStream, true); // Now create a person ... Node root = session.getRootNode(); Node person = root.addNode("jsmith", "inf:person"); person.setProperty("inf:firstName", "John"); person.setProperty("inf:lastName", "Smith"); session.save(); Node doctor = root.addNode("drBarnes", "inf:doctor"); doctor.setProperty("inf:firstName", "Sally"); doctor.setProperty("inf:lastName", "Barnes"); doctor.setProperty("inf:doctorProviderNumber", "12345678-AB"); session.save(); Node referral = root.addNode("referral", "nt:unstructured"); referral.addMixin("er:eReferral"); assertThat(referral.getMixinNodeTypes()[0].getName(), is("er:eReferral")); Node group = referral.addNode("er:gp"); assertThat(group.getPrimaryNodeType().getName(), is("inf:doctor")); try { session.save(); fail("Expected a constraint violation exception"); } catch (ConstraintViolationException e) { // expected, since "inf:firstName" is mandatory but doesn't have a default value } // Set the missing mandatory properties on the node ... group.setProperty("inf:firstName", "Sally"); group.setProperty("inf:lastName", "Barnes"); // and now Session.save() will work ... session.save(); } @Test @FixFor( "MODE-1807" ) public void shouldRegisterCNDFileWithResidualChildDefinition() throws Exception { session = createSession(); InputStream cndStream = getClass().getResourceAsStream("/cnd/orc.cnd"); assertThat(cndStream, is(notNullValue())); session.getWorkspace().getNodeTypeManager().registerNodeTypes(cndStream, true); session.getRootNode().addNode("patient", "orc:patient").addNode("patientcase", "orc:patientcase"); session.save(); assertNotNull(session.getNode("/patient/patientcase")); } @Test @FixFor( "MODE-1360" ) public void shouldHandleMultipleConcurrentReadWriteSessions() throws Exception { int numThreads = 100; final AtomicBoolean passed = new AtomicBoolean(true); final AtomicInteger counter = new AtomicInteger(0); final Repository repository = this.repository; ExecutorService executor = Executors.newFixedThreadPool(50); try { Runnable runnable = new Runnable() { @Override public void run() { try { Session session = repository.login(); session.getRootNode().addNode("n" + counter.getAndIncrement()); // unique name session.save(); session.logout(); } catch (Throwable e) { e.printStackTrace(); passed.set(false); } } }; for (int i = 0; i < numThreads; i++) { executor.execute(runnable); } executor.shutdown(); // Disable new tasks from being submitted } finally { if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { fail("timeout"); } if (!passed.get()) { fail("one or more threads got an exception"); } } } @Test @FixFor( "MODE-2056" ) public void shouldReturnActiveSessions() throws Exception { shutdownDefaultRepository(); RepositoryConfiguration config = new RepositoryConfiguration("repoName", new TestingEnvironment()); repository = new JcrRepository(config); assertEquals(0, repository.getActiveSessionsCount()); repository.start(); JcrSession session1 = repository.login(); JcrSession session2 = repository.login(); assertEquals(2, repository.getActiveSessionsCount()); session2.logout(); assertEquals(1, repository.getActiveSessionsCount()); session1.logout(); assertEquals(0, repository.getActiveSessionsCount()); repository.login(); repository.shutdown().get(); assertEquals(0, repository.getActiveSessionsCount()); } @FixFor( "MODE-2033" ) @Test public void shouldStartAndReturnStartupProblems() throws Exception { shutdownDefaultRepository(); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-with-startup-problems.json"); Problems problems = repository.getStartupProblems(); assertEquals("Expected 2 startup errors: " + problems.toString(), 2, problems.errorCount()); repository.shutdown().get(); problems = repository.getStartupProblems(); assertEquals("Invalid startup problems:" + problems.toString(), 2, problems.size()); } @FixFor( "MODE-1863" ) @Test public void shouldStartupWithJournalingEnabled() throws Exception { TestingUtil.waitUntilFolderCleanedUp("target/persistent_repository"); shutdownDefaultRepository(); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-journaling.json"); // add some nodes JcrSession session1 = repository.login(); int nodeCount = 10; for (int i = 0; i < nodeCount; i++) { Node node = session1.getRootNode().addNode("testNode_" + i); node.setProperty("int_prop", i); } session1.save(); // give the events a change to reach the journal Thread.sleep(300); // edit some nodes for (int i = 0; i < nodeCount / 2; i++) { session1.getNode("/testNode_" + i).setProperty("int_prop2", 2 * i); } session1.save(); // give the events a change to reach the journal Thread.sleep(300); // remove the nodes Set<NodeKey> expectedJournalKeys = new TreeSet<NodeKey>(); for (int i = 0; i < nodeCount; i++) { AbstractJcrNode node = session1.getNode("/testNode_" + i); expectedJournalKeys.add(node.key()); node.remove(); } expectedJournalKeys.add(session1.getRootNode().key()); session1.save(); // give the events a change to reach the journal Thread.sleep(300); // check the journal has entries LocalJournal.Records journalRecordsReversed = repository.runningState().journal().allRecords(true); assertTrue(journalRecordsReversed.size() > 0); JournalRecord lastRecord = journalRecordsReversed.iterator().next(); assertEquals(expectedJournalKeys, new TreeSet<NodeKey>(lastRecord.changedNodes())); repository.shutdown(); } @Test @FixFor( "MODE-2140" ) public void shouldNotAllowNodeTypeRemovalWithQueryPlaceholderConfiguration() throws Exception { shutdownDefaultRepository(); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-query-placeholder.json"); String namespaceName = "admb"; String namespaceUri = "http://www.admb.be/modeshape/admb/1.0"; String nodeTypeName = "test"; Session session = repository.login(); Workspace workspace = session.getWorkspace(); NamespaceRegistry namespaceRegistry = workspace.getNamespaceRegistry(); NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager(); namespaceRegistry.registerNamespace(namespaceName, namespaceUri); NodeTypeTemplate nodeTypeTemplate = nodeTypeManager.createNodeTypeTemplate(); nodeTypeTemplate.setName(namespaceName.concat(":").concat(nodeTypeName)); nodeTypeTemplate.setMixin(true); NodeType nodeType = nodeTypeManager.registerNodeType(nodeTypeTemplate, false); // Now create a node with the newly created nodeType Node rootNode = session.getRootNode(); Node newNode = rootNode.addNode("testNode"); newNode.addMixin(nodeType.getName()); session.save(); // sleep to make sure the node is indexed Thread.sleep(100); try { nodeTypeManager.unregisterNodeType(nodeType.getName()); fail("Should not be able to remove node type"); } catch (RepositoryException e) { // expected } session.logout(); } @Test @FixFor( "MODE-2167" ) public void shouldEnableOrDisableACLsBasedOnChanges() throws Exception { Session session = repository.login(); try { Node testNode = session.getRootNode().addNode("testNode"); testNode.addNode("node1"); testNode.addNode("node2"); session.save(); assertFalse(repository.repositoryCache().isAccessControlEnabled()); SimplePrincipal principalA = SimplePrincipal.newInstance("a"); SimplePrincipal principalB = SimplePrincipal.newInstance("b"); SimplePrincipal everyone = SimplePrincipal.newInstance("everyone"); AccessControlManager acm = session.getAccessControlManager(); Privilege[] allPriviledges = { acm.privilegeFromName(Privilege.JCR_ALL) }; AccessControlList aclNode1 = getACL(acm, "/testNode/node1"); aclNode1.addAccessControlEntry(principalA, allPriviledges); aclNode1.addAccessControlEntry(principalB, allPriviledges); aclNode1.addAccessControlEntry(everyone, allPriviledges); acm.setPolicy("/testNode/node1", aclNode1); AccessControlList aclNode2 = getACL(acm, "/testNode/node2"); aclNode2.addAccessControlEntry(principalA, allPriviledges); aclNode2.addAccessControlEntry(principalB, allPriviledges); aclNode2.addAccessControlEntry(everyone, allPriviledges); acm.setPolicy("/testNode/node2", aclNode2); //access control should not be enabled yet because we haven't saved the session assertFalse(repository.repositoryCache().isAccessControlEnabled()); session.save(); assertTrue(repository.repositoryCache().isAccessControlEnabled()); aclNode1.addAccessControlEntry(everyone, allPriviledges); acm.setPolicy("/testNode/node1", aclNode1); aclNode2.addAccessControlEntry(everyone, allPriviledges); acm.setPolicy("/testNode/node2", aclNode2); session.save(); assertTrue(repository.repositoryCache().isAccessControlEnabled()); acm.removePolicy("/testNode/node1", null); acm.removePolicy("/testNode/node2", null); session.save(); assertFalse(repository.repositoryCache().isAccessControlEnabled()); } finally { session.logout(); } } @Test @FixFor( "MODE-2343" ) public void shouldReleaseObservationManagerThreadsOnLogout() throws Exception { // take a snapshot of the current thread count int oldCount = Thread.activeCount(); int sessionsCount = 10; for (int i = 0; i < sessionsCount; ++i) { // create a new session final Session newSession = createSession(); // this will spawn a new thread for the observation manager newSession.getWorkspace().getObservationManager(); // this will spawn a new thread for the listener addListener(newSession, 0, 0, Event.NODE_ADDED, "/", true, null, null, false); newSession.logout(); } // each iteration creates and should release 1 new thread, but activeThreadCount is not accurate, since a thread may // have finished its work but is being kept alive in the thread pool. So we're only approximating the next assert assertTrue("Observation threads not released when session was logged out", Thread.activeCount() - oldCount < sessionsCount); } @Test @FixFor( "MODE-2387" ) public void shouldStartRepositoryWithCustomSettingsForLocalIndexProvider() throws Exception { shutdownDefaultRepository(); FileUtil.delete("target/local_index_custom_settings_test_repository"); repository = TestingUtil.startRepositoryWithConfig("config/local-index-provider-with-custom-settings.json"); repository.start(); } @Test(expected = RepositoryException.class) @FixFor( "MODE-1269" ) public void shouldNotAllowIncrementalIndexingIfJournalIsNotEnabled() throws Exception { session = createSession(); session.getWorkspace().reindexSince(System.currentTimeMillis()); } @Test(expected = RuntimeException.class) @FixFor( "MODE-2528" ) public void shouldNotStartRepositoryWithInvalidPersistence() throws Exception { shutdownDefaultRepository(); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-invalid-persistence.json"); repository.start(); } @Test public void allInitialDelaysShouldBeValid() throws Exception { repository.determineInitialDelay("00:00"); repository.determineInitialDelay("01:00"); repository.determineInitialDelay("02:00"); repository.determineInitialDelay("03:00"); repository.determineInitialDelay("04:00"); repository.determineInitialDelay("05:00"); repository.determineInitialDelay("06:00"); repository.determineInitialDelay("07:00"); repository.determineInitialDelay("08:00"); repository.determineInitialDelay("09:00"); repository.determineInitialDelay("10:00"); repository.determineInitialDelay("11:00"); repository.determineInitialDelay("12:00"); repository.determineInitialDelay("13:00"); repository.determineInitialDelay("14:00"); repository.determineInitialDelay("15:00"); repository.determineInitialDelay("16:00"); repository.determineInitialDelay("17:00"); repository.determineInitialDelay("18:00"); repository.determineInitialDelay("19:00"); repository.determineInitialDelay("20:00"); repository.determineInitialDelay("21:00"); repository.determineInitialDelay("22:00"); repository.determineInitialDelay("23:00"); } @Test @FixFor("MODE-2679") public void shouldStartRepositoryUpForTurkishLocale() throws Exception { Locale current = Locale.getDefault(); try { Locale.setDefault(Locale.forLanguageTag("tr")); shutdownDefaultRepository(); repository = TestingUtil.startRepositoryWithConfig("config/repo-config-query-placeholder.json"); session = repository.login(); session.getRootNode().addNode("Ii"); session.save(); assertNotNull(session.getNode("/Ii")); assertTrue(repository.shutdown().get()); } finally { Locale.setDefault(current); } } protected void nodeExists( Session session, String parentPath, String childName, boolean exists ) throws Exception { Node parent = session.getNode(parentPath); assertThat(parent.hasNode(childName), is(exists)); String path = parent.getPath(); if (parent.getDepth() != 0) path = path + "/"; path = path + childName; String sql = "SELECT * FROM [nt:base] WHERE PATH() = '" + path + "'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); QueryResult result = query.execute(); assertThat(result.getNodes().getSize(), is(exists ? 1L : 0L)); } protected void nodeExists( Session session, String parentPath, String childName ) throws Exception { nodeExists(session, parentPath, childName, true); } protected void nodeDoesNotExist( Session session, String parentPath, String childName ) throws Exception { nodeExists(session, parentPath, childName, false); } private static final int ALL_EVENTS = Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; SimpleListener addListener( int expectedEventsCount ) throws Exception { return addListener(expectedEventsCount, ALL_EVENTS, null, false, null, null, false); } SimpleListener addListener( int expectedEventsCount, int eventTypes, String absPath, boolean isDeep, String[] uuids, String[] nodeTypeNames, boolean noLocal ) throws Exception { return addListener(expectedEventsCount, 1, eventTypes, absPath, isDeep, uuids, nodeTypeNames, noLocal); } SimpleListener addListener( int expectedEventsCount, int numIterators, int eventTypes, String absPath, boolean isDeep, String[] uuids, String[] nodeTypeNames, boolean noLocal ) throws Exception { return addListener(this.session, expectedEventsCount, numIterators, eventTypes, absPath, isDeep, uuids, nodeTypeNames, noLocal); } SimpleListener addListener( Session session, int expectedEventsCount, int numIterators, int eventTypes, String absPath, boolean isDeep, String[] uuids, String[] nodeTypeNames, boolean noLocal ) throws Exception { SimpleListener listener = new SimpleListener(expectedEventsCount, numIterators, eventTypes); session.getWorkspace().getObservationManager() .addEventListener(listener, eventTypes, absPath, isDeep, uuids, nodeTypeNames, noLocal); return listener; } private AccessControlList getACL( AccessControlManager acm, String absPath ) throws Exception { AccessControlPolicyIterator it = acm.getApplicablePolicies(absPath); if (it.hasNext()) { return (AccessControlList)it.nextAccessControlPolicy(); } return (AccessControlList)acm.getPolicies(absPath)[0]; } }