/* * 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 org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.jcr.query.JcrQuery; /** * Unit test for various operations around large unorderable collections. */ public class JcrUnorderedCollectionsTest extends MultiUseAbstractTest { @BeforeClass public static final void beforeAll() throws Exception { MultiUseAbstractTest.beforeAll(); // Import the node types and the data ... registerNodeTypes("cnd/large-collections.cnd"); } @AfterClass public static final void afterAll() throws Exception { MultiUseAbstractTest.afterAll(); } @Override public void afterEach() throws Exception { NodeIterator nodeIterator = session.getRootNode().getNodes(); while (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); if (!JcrLexicon.SYSTEM.getString().equals(node.getName())) { node.remove(); } } session.save(); super.afterEach(); } @Test @FixFor( "MODE-2109 ") public void shouldSupportBasicNodeOperations() throws Exception { Node tinyCollection = session.getRootNode().addNode("tinyCol", "test:tinyCollection"); int childCount = 11; Set<String> allChildren = new HashSet<>(childCount); for (int i = 0; i < childCount; i++) { String childName = "child_" + i; // add a top level child Node child = tinyCollection.addNode(childName); // add a regular sub child child.addNode("test"); allChildren.add("/tinyCol/" + childName); } session.save(); //get by path each child for (String childPath : allChildren) { Node child = assertNode(childPath); assertEquals(child, session.getNodeByIdentifier(child.getIdentifier())); Node subchild = assertNode(childPath + "/test"); assertEquals(subchild, session.getNodeByIdentifier(subchild.getIdentifier())); } //iterate the parent tinyCollection = session.getNode("/tinyCol"); NodeIterator nodeIterator = tinyCollection.getNodes(); assertEquals(childCount, nodeIterator.getSize()); // search by id String id = tinyCollection.getIdentifier(); tinyCollection = session.getNodeByIdentifier(id); //check insertion order Set<String> iteratedChildren = new LinkedHashSet<>(childCount); while (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); iteratedChildren.add(node.getPath()); } assertEquals("Incorrect iteration result", allChildren, iteratedChildren); //search using regexp assertEquals("Incorrect regexp iterator result", childCount, tinyCollection.getNodes("child*").getSize()); assertEquals(2, tinyCollection.getNodes("child_1|child_2").getSize()); assertEquals(0, tinyCollection.getNodes("child1|child2").getSize()); //query the children JcrQueryManager queryManager = session.getWorkspace().getQueryManager(); Query query = queryManager.createQuery( "select node.[jcr:path] from [nt:unstructured] as node where node.[jcr:name] like 'child%'", JcrQuery.JCR_SQL2); QueryResult rs = query.execute(); NodeIterator nodes = rs.getNodes(); assertEquals(childCount, nodes.getSize()); Set<String> results = new HashSet<>(); while (nodes.hasNext()) { results.add(nodes.nextNode().getPath()); } assertEquals("Incorrect query result", allChildren, results); //remove half the nodes int removeCount = childCount / 2; for (int i = 0; i < removeCount; i++) { String childName = "/tinyCol/child_" + i; session.getNode(childName).remove(); allChildren.remove(childName); } session.save(); tinyCollection = session.getNode("/tinyCol"); //iterate after removal nodeIterator = tinyCollection.getNodes(); assertEquals(childCount - removeCount, nodeIterator.getSize()); iteratedChildren = new LinkedHashSet<>(childCount); while (nodeIterator.hasNext()) { Node node = nodeIterator.nextNode(); iteratedChildren.add(node.getPath()); } assertEquals("Incorrect iteration result", allChildren, iteratedChildren); //remove the entire parent session.getNode("/tinyCol").remove(); session.save(); } @Test @FixFor( "MODE-2109 ") public void shouldSupportTransientNodeOperations() throws Exception { Set<String> allChildrenPaths = new HashSet<>(); Node col = session.getRootNode().addNode("smallCol", "test:smallCollection"); allChildrenPaths.add(col.addNode("child1").getPath()); session.save(); // add a transient node and iterate col = session.getNode("/smallCol"); allChildrenPaths.add(col.addNode("child2").getPath()); NodeIterator nodeIterator = col.getNodes(); assertEquals(allChildrenPaths.size(), nodeIterator.getSize()); Set<String> iterableChildren = new HashSet<>(); while (nodeIterator.hasNext()) { iterableChildren.add(nodeIterator.nextNode().getPath()); } assertEquals("Incorrect iteration result", allChildrenPaths, iterableChildren); assertEquals(allChildrenPaths.size(), col.getNodes("child*").getSize()); assertEquals(2, col.getNodes("child1|child2").getSize()); assertEquals(0, col.getNodes("child_1|child_2").getSize()); session.save(); // remove a node and iterate before saving AbstractJcrNode child1 = session.getNode("/smallCol/child1"); String child1Path = child1.getPath(); child1.remove(); allChildrenPaths.remove(child1Path); nodeIterator = session.getNode("/smallCol").getNodes(); assertEquals(allChildrenPaths.size(), nodeIterator.getSize()); iterableChildren = new HashSet<>(); while (nodeIterator.hasNext()) { iterableChildren.add(nodeIterator.nextNode().getPath()); } assertEquals("Incorrect iteration result", allChildrenPaths, iterableChildren); assertEquals(allChildrenPaths.size(), col.getNodes("child*").getSize()); // discard the changes session.refresh(false); allChildrenPaths.add(child1Path); // reiterate nodeIterator = session.getNode("/smallCol").getNodes(); assertEquals(allChildrenPaths.size(), nodeIterator.getSize()); iterableChildren = new HashSet<>(); while (nodeIterator.hasNext()) { iterableChildren.add(nodeIterator.nextNode().getPath()); } assertEquals("Incorrect iteration result", allChildrenPaths, iterableChildren); assertEquals(allChildrenPaths.size(), col.getNodes("child*").getSize()); } @Test @FixFor( "MODE-2109 ") public void shouldNotSupportSNS() throws Exception { Node col = session.getRootNode().addNode("smallCol", "test:smallCollection"); col.addNode("child"); try { col.addNode("child"); fail("Unorderable collections should not support SNS"); } catch (javax.jcr.ItemExistsException e) { //expected } session.refresh(false); col = session.getRootNode().addNode("smallCol", "test:smallCollection"); col.addNode("child"); session.save(); col = session.getRootNode().getNode("smallCol"); try { col.addNode("child"); fail("Unorderable collections should not support SNS"); } catch (javax.jcr.ItemExistsException e) { //expected } col.getNode("child").remove(); Node child = col.addNode("child"); child.setProperty("test", "test"); session.save(); NodeIterator nodes = session.getNode("/smallCol").getNodes(); assertEquals(1, nodes.getSize()); child = nodes.nextNode(); assertEquals("test", child.getProperty("test").getString()); } @Test @FixFor( "MODE-2109 ") public void shouldNotSupportReorderings() throws Exception { Node col = session.getRootNode().addNode("smallCol", "test:smallCollection"); col.addNode("child1"); col.addNode("child2"); session.save(); col = session.getNode("/smallCol"); try { col.orderBefore("child1", null); fail("Reorderings should not be supported for large collections"); } catch (UnsupportedRepositoryOperationException e) { // expected } } @Test @FixFor( "MODE-2109 ") public void shouldNotSupportRenamingsButShouldSupportMove() throws Exception { Node col1 = session.getRootNode().addNode("col1", "test:smallCollection"); session.getRootNode().addNode("col2", "test:smallCollection"); col1.addNode("child1"); col1.addNode("child2"); session.save(); try { // reject renamings session.move("/col1/child1", "/col1/child3"); fail("Renamings should not be supported for large collections"); } catch (ConstraintViolationException e) { // expected } // but allow a normal move session.move("/col1/child1", "/col2/child1"); session.save(); NodeIterator nodes = session.getNode("/col1").getNodes(); assertEquals(1, nodes.getSize()); assertEquals("/col1/child2", nodes.nextNode().getPath()); nodes = session.getNode("/col2").getNodes(); assertEquals(1, nodes.getSize()); assertEquals("/col2/child1", nodes.nextNode().getPath()); } @Test(expected = ConstraintViolationException.class) @FixFor( "MODE-2109 ") public void shouldNotAllowVersioning() throws Exception { Node col1 = session.getRootNode().addNode("col1", "test:smallCollection"); col1.addMixin("mix:versionable"); session.save(); } @Test @FixFor( "MODE-2109 ") public void shouldOnlyAllowCloningInSomeCases() throws Exception { session.getWorkspace().createWorkspace("other"); try { session.getRootNode().addNode("col1", "test:smallCollection"); Node regular = session.getRootNode().addNode("regular"); regular.addNode("regular1"); session.save(); // cloning a large collection is not allowed JcrWorkspace workspace = session.getWorkspace(); try { workspace.clone(workspace.getName(), "/col1", "/regular", false); fail("Should not allow cloning"); } catch (ConstraintViolationException e) { //expected } //clone a regular node into a large collection JcrSession otherSession = repository.login("other"); Node col2 = otherSession.getRootNode().addNode("col2", "test:smallCollection"); col2.addNode("child1"); otherSession.save(); otherSession.getWorkspace().clone(workspace.getName(), "/regular", "/col2/regular", false); NodeIterator nodes = otherSession.getNode("/col2").getNodes(); assertEquals(2, nodes.getSize()); } finally { session.getWorkspace().deleteWorkspace("other"); } } @Test @FixFor( "MODE-2109" ) public void shouldOnlyAllowCopyingInSomeCases() throws Exception { session.getWorkspace().createWorkspace("other"); try { session.getRootNode().addNode("col1", "test:smallCollection"); Node regular = session.getRootNode().addNode("regular"); regular.addNode("regular1"); session.save(); // cloning a large collection is not allowed JcrWorkspace workspace = session.getWorkspace(); try { workspace.copy("/col1", "/col3"); fail("Should not allow copying"); } catch (ConstraintViolationException e) { //expected } //copy a regular node into a large collection into this ws workspace.copy("/regular", "/col1/regular"); NodeIterator nodes = session.getNode("/col1").getNodes(); assertEquals(1, nodes.getSize()); JcrSession otherSession = repository.login("other"); Node col2 = otherSession.getRootNode().addNode("col2", "test:smallCollection"); col2.addNode("child1"); otherSession.save(); //copy a regular node into a large collection into another ws otherSession.getWorkspace().copy(workspace.getName(), "/regular", "/col2/regular"); nodes = otherSession.getNode("/col2").getNodes(); assertEquals(2, nodes.getSize()); } finally { session.getWorkspace().deleteWorkspace("other"); } } @Test @FixFor( "MODE-2109" ) public void shouldExportImport() throws Exception { Node col1 = session.getRootNode().addNode("col1", "test:smallCollection"); int childCount = 10; Set<String> childPaths = new HashSet<>(); for (int i = 0; i < childCount; i++) { Node child = col1.addNode("child_" + i); childPaths.add(child.getPath()); for (int j = 0; j < childCount; j++) { child.addNode("child_" + j); } } session.save(); ByteArrayOutputStream os = new ByteArrayOutputStream(); session.exportSystemView("/", os, true, false); session.getNode("/col1").remove(); session.save(); session.importXML("/", new ByteArrayInputStream(os.toByteArray()), ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); session.save(); col1 = session.getNode("/col1"); for (int i = 0; i < childCount; i++) { assertNode("/col1/child_" + i); assertNode("/col1/child_" + i + "/child_" + i); } NodeIterator nodeIterator = col1.getNodes(); assertEquals(childCount, nodeIterator.getSize()); while (nodeIterator.hasNext()) { childPaths.remove(nodeIterator.nextNode().getPath()); } assertTrue("Not all nodes imported correctly", childPaths.isEmpty()); } @Test(expected = ConstraintViolationException.class) @FixFor( "MODE-2109" ) public void shouldNotAllowProjections() throws Exception { session.getRootNode().addNode("col", "test:smallCollection"); session.save(); session.getWorkspace().getFederationManager().createProjection("/col", "dummy", "/", "dummy"); } @Test @FixFor( "MODE-2109" ) public void shouldAllowAddingAndRemovingMixinsOnlyIfNodeIsEmpty() throws Exception { Node collection = session.getRootNode().addNode("collection"); collection.addNode("child"); session.save(); String mixin = ModeShapeLexicon.LARGE_UNORDERED_COLLECTION.getString(); try { collection.addMixin(mixin); fail("Unordered collection mixin should not be allowed if node has children"); } catch (ConstraintViolationException e) { //expected } session.getNode("/collection/child").remove(); session.save(); collection.addMixin(mixin); collection.addNode("child1"); collection.addNode("child2"); session.save(); NodeIterator children = collection.getNodes(); assertEquals(2, children.getSize()); try { collection.removeMixin(mixin); fail("Unordered collection mixin should not be removed if node has children"); } catch (ConstraintViolationException e) { //expected } try { collection.addMixin("mix:versionable"); fail("Unordered collections should not be versionable"); } catch (ConstraintViolationException e) { //expected } while (children.hasNext()) { children.nextNode().remove(); } session.save(); try { collection.addMixin(ModeShapeLexicon.SMALL_UNORDERED_COLLECTION.getString()); fail("Second unordered collection mixin should not be allowed"); } catch (ConstraintViolationException e) { //expected } session.save(); } }