/* * Copyright (C) 2011 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * 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. * * This software 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.exoplatform.services.jcr.ext.distribution; import org.exoplatform.services.jcr.access.PermissionType; import org.exoplatform.services.jcr.core.ExtendedNode; import org.exoplatform.services.jcr.ext.BaseStandaloneTest; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; /** * @author <a href="mailto:nicolas.filotto@exoplatform.com">Nicolas Filotto</a> * @version $Id$ * */ public class TestDataDistributionManager extends BaseStandaloneTest { private DataDistributionManager manager; private Node parentNode; /** * @see org.exoplatform.services.jcr.ext.BaseStandaloneTest#setUp() */ @Override public void setUp() throws Exception { super.setUp(); manager = (DataDistributionManager)container.getComponentInstanceOfType(DataDistributionManager.class); parentNode = root.addNode("TestDataDistributionManager"); session.save(); } /** * @see org.exoplatform.services.jcr.ext.BaseStandaloneTest#tearDown() */ @Override protected void tearDown() throws Exception { manager = null; if (parentNode != null) { parentNode.remove(); session.save(); parentNode = null; } super.tearDown(); } public void testDataDistributionModeNone() throws Exception { DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.NONE); String dataId = "/a/a/a/a/"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } Node node = type.getOrCreateDataNode(parentNode, dataId); assertTrue(node.isSame(type.getOrCreateDataNode(parentNode, dataId))); assertEquals(1, node.getParent().getNodes().getSize()); Node node2 = type.getDataNode(parentNode, dataId); assertTrue(node.isSame(node2)); Node node3 = parentNode.getNode("a/a/a/a"); assertTrue(node.isSame(node3)); Node node4 = type.getOrCreateDataNode(parentNode, "a/a/a/b", "nt:folder"); assertFalse(node.isSame(node4)); assertTrue(node.getParent().isSame(node4.getParent())); assertTrue(node4.isNodeType("nt:folder")); assertTrue(node4.canAddMixin("mix:referenceable")); assertTrue(node4.canAddMixin("exo:privilegeable")); dataId = "b/a/a/a"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } Node node5 = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable")); assertFalse(node.isSame(node5)); assertFalse(node.getParent().isSame(node5.getParent())); assertTrue(node5.isNodeType("nt:folder")); assertFalse(node5.canAddMixin("mix:referenceable")); assertTrue(node5.canAddMixin("exo:privilegeable")); assertTrue(node5.getParent().isNodeType("nt:folder")); assertFalse(node5.getParent().canAddMixin("mix:referenceable")); assertTrue(node5.getParent().canAddMixin("exo:privilegeable")); assertTrue(node5.getParent().getParent().isNodeType("nt:folder")); assertFalse(node5.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node5.getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node5.getParent().getParent().getParent().isNodeType("nt:folder")); assertFalse(node5.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node5.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL); type.removeDataNode(parentNode, dataId); try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } dataId = "c/a/a/a"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } Node node6 = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertFalse(node.isSame(node6)); assertFalse(node.getParent().isSame(node6.getParent())); assertTrue(node6.isNodeType("nt:folder")); assertFalse(node6.canAddMixin("mix:referenceable")); assertFalse(node6.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node6).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node6).getACL().getPermissions("root")); assertTrue(node6.getParent().isNodeType("nt:folder")); assertFalse(node6.getParent().canAddMixin("mix:referenceable")); assertFalse(node6.getParent().canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node6.getParent()).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node6.getParent()).getACL().getPermissions("root")); assertTrue(node6.getParent().getParent().isNodeType("nt:folder")); assertFalse(node6.getParent().getParent().canAddMixin("mix:referenceable")); assertFalse(node6.getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node6.getParent().getParent()).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node6.getParent().getParent()).getACL().getPermissions("root")); assertTrue(node6.getParent().getParent().getParent().isNodeType("nt:folder")); assertFalse(node6.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertFalse(node6.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node6.getParent().getParent().getParent()).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node6.getParent().getParent().getParent()).getACL().getPermissions("root")); type.removeDataNode(parentNode, dataId); try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } } public void testDataDistributionModeReadable() throws Exception { DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.READABLE); String dataId = "john.smith"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL); Node node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("j___/jo___/joh___/john.smith")); dataId = "bob"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("b___/bo___/bob")); dataId = "john___"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("j___/jo___/joh___/john___")); dataId = "joh___"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("j___/jo___/joh___/joh___")); dataId = "jo___"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("j___/jo___/jo____/jo___")); dataId = "j___"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("j___/j____/j_____/j___")); type.removeDataNode(parentNode, dataId); try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } } public void testDataDistributionModeOptimized() throws Exception { DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.OPTIMIZED); String dataId = "john.smith"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL); Node node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("1/2/s/john.smith")); dataId = "mary"; try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } node = type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"), permissions); assertTrue(node.isNodeType("nt:folder")); assertFalse(node.canAddMixin("mix:referenceable")); assertFalse(node.canAddMixin("exo:privilegeable")); assertTrue(((ExtendedNode)node).getACL().hasPermissions()); assertNotNull(((ExtendedNode)node).getACL().getPermissions("root")); assertFalse(node.getParent().isNodeType("nt:folder")); assertTrue(node.getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable")); assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder")); assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable")); assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable")); assertTrue(node.getPath().endsWith("5/o/s/mary")); type.removeDataNode(parentNode, dataId); try { type.getDataNode(parentNode, dataId); fail("a PathNotFoundException is expected"); } catch (PathNotFoundException e) { // expected behavior } } public void testMultiThreadingNone() throws Throwable { testMultiThreading(manager.getDataDistributionType(DataDistributionMode.NONE), "key/"); } public void testMultiThreadingReadable() throws Throwable { testMultiThreading(manager.getDataDistributionType(DataDistributionMode.READABLE), "key-"); } public void testMultiThreadingOptimized() throws Throwable { testMultiThreading(manager.getDataDistributionType(DataDistributionMode.OPTIMIZED), ""); } public void testMultiThreading(final DataDistributionType type, final String dataIdPrefix) throws Throwable { final int totalElement = 20; final int totalTimes = 3; int reader = 8; int writer = 4; int remover = 2; final CountDownLatch startSignalWriter = new CountDownLatch(1); final CountDownLatch startSignalOthers = new CountDownLatch(1); final CountDownLatch doneSignal = new CountDownLatch(reader + writer + remover); final List<Throwable> errors = Collections.synchronizedList(new ArrayList<Throwable>()); for (int i = 0; i < writer; i++) { final int index = i; Thread thread = new Thread() { public void run() { Session session = null; try { startSignalWriter.await(); for (int j = 0; j < totalTimes; j++) { session = repository.login(credentials, WS_NAME); Node node = (Node)session.getItem(parentNode.getPath()); for (int i = 0; i < totalElement; i++) { try { Node n = type.getOrCreateDataNode(node, dataIdPrefix + i); assertFalse("The path " + n.getPath() + " should not contain any indexes", n.getPath() .contains("[")); } catch (RepositoryException e) { // Ignore them, they could be due to deadlocks } } if (index == 0 && j == 0) { // The cache is full, we can launch the others startSignalOthers.countDown(); } session.logout(); sleep(50); } } catch (Throwable e) { errors.add(e); startSignalOthers.countDown(); } finally { doneSignal.countDown(); if (session != null) { session.logout(); session = null; } } } }; thread.start(); } startSignalWriter.countDown(); for (int i = 0; i < reader; i++) { Thread thread = new Thread() { public void run() { Session session = null; try { startSignalOthers.await(); for (int j = 0; j < totalTimes; j++) { session = repository.login(credentials, WS_NAME); Node node = (Node)session.getItem(parentNode.getPath()); for (int i = 0; i < totalElement; i++) { try { Node n = type.getDataNode(node, dataIdPrefix + i); assertFalse("The path " + n.getPath() + " should not contain any indexes", n.getPath() .contains("[")); } catch (PathNotFoundException e) { // ignore me } } session.logout(); sleep(50); } } catch (Throwable e) { errors.add(e); } finally { doneSignal.countDown(); if (session != null) { session.logout(); session = null; } } } }; thread.start(); } for (int i = 0; i < remover; i++) { Thread thread = new Thread() { public void run() { Session session = null; try { startSignalOthers.await(); for (int j = 0; j < totalTimes; j++) { session = repository.login(credentials, WS_NAME); Node node = (Node)session.getItem(parentNode.getPath()); for (int i = 0; i < totalElement; i++) { try { type.removeDataNode(node, dataIdPrefix + i); } catch (RepositoryException e) { // Ignore them, they could be due to deadlocks } } session.logout(); sleep(50); } } catch (Throwable e) { errors.add(e); } finally { doneSignal.countDown(); if (session != null) { session.logout(); session = null; } } } }; thread.start(); } doneSignal.await(); for (int i = 0; i < totalElement; i++) { type.removeDataNode(parentNode, dataIdPrefix + i); } for (int i = 0; i < totalElement; i++) { try { type.getDataNode(parentNode, dataIdPrefix + i); fail("The node should be removed"); } catch (PathNotFoundException e) { // ignore me } } for (int i = 0; i < totalElement; i++) { Node n = type.getOrCreateDataNode(parentNode, dataIdPrefix + i); assertFalse("The path " + n.getPath() + " should not contain any indexes", n.getPath().contains("[")); } if (!errors.isEmpty()) { for (Throwable e : errors) { e.printStackTrace(); } throw errors.get(0); } } public void testMigration() throws Exception { Node rootNode = session.getRootNode().addNode("testRoot"); rootNode.addNode("a").setProperty("a", "a"); rootNode.addNode("bob").setProperty("bob", "bob"); rootNode.addNode("john.smith").setProperty("john.smith", "john.smith"); rootNode.addNode("joiv").setProperty("joiv", "joiv"); rootNode.addNode("bonjov").setProperty("bonjov", "bonjov"); rootNode.addNode("anatoliy.bazko").setProperty("anatoliy.bazko", "anatoliy.bazko"); rootNode.getSession().save(); manager.getDataDistributionType(DataDistributionMode.READABLE).migrate(rootNode); assertTrue(rootNode.hasNode("a")); assertFalse(rootNode.hasNode("bob")); assertFalse(rootNode.hasNode("john.smith")); assertFalse(rootNode.hasNode("joiv")); assertFalse(rootNode.hasNode("bonjov")); assertFalse(rootNode.hasNode("anatoliy.bazko")); assertTrue(rootNode.hasNode("b___/bo___/bob")); assertTrue(rootNode.hasNode("j___/jo___/joh___/john.smith")); assertTrue(rootNode.hasNode("j___/jo___/joi___/joiv")); assertTrue(rootNode.hasNode("b___/bo___/bon___/bonjov")); assertTrue(rootNode.hasNode("a___/an___/ana___/anatoliy.bazko")); assertTrue(rootNode.getNode("a").hasProperty("a")); assertTrue(rootNode.getNode("b___/bo___/bob").hasProperty("bob")); assertTrue(rootNode.getNode("j___/jo___/joh___/john.smith").hasProperty("john.smith")); assertTrue(rootNode.getNode("j___/jo___/joi___/joiv").hasProperty("joiv")); assertTrue(rootNode.getNode("b___/bo___/bon___/bonjov").hasProperty("bonjov")); assertTrue(rootNode.getNode("a___/an___/ana___/anatoliy.bazko").hasProperty("anatoliy.bazko")); // shoud not be any changes manager.getDataDistributionType(DataDistributionMode.READABLE).migrate(rootNode); assertTrue(rootNode.hasNode("a")); assertFalse(rootNode.hasNode("bob")); assertFalse(rootNode.hasNode("john.smith")); assertFalse(rootNode.hasNode("joiv")); assertFalse(rootNode.hasNode("bonjov")); assertFalse(rootNode.hasNode("anatoliy.bazko")); assertTrue(rootNode.hasNode("b___/bo___/bob")); assertTrue(rootNode.hasNode("j___/jo___/joh___/john.smith")); assertTrue(rootNode.hasNode("j___/jo___/joi___/joiv")); assertTrue(rootNode.hasNode("b___/bo___/bon___/bonjov")); assertTrue(rootNode.hasNode("a___/an___/ana___/anatoliy.bazko")); assertTrue(rootNode.getNode("a").hasProperty("a")); assertTrue(rootNode.getNode("b___/bo___/bob").hasProperty("bob")); assertTrue(rootNode.getNode("j___/jo___/joh___/john.smith").hasProperty("john.smith")); assertTrue(rootNode.getNode("j___/jo___/joi___/joiv").hasProperty("joiv")); assertTrue(rootNode.getNode("b___/bo___/bon___/bonjov").hasProperty("bonjov")); assertTrue(rootNode.getNode("a___/an___/ana___/anatoliy.bazko").hasProperty("anatoliy.bazko")); } }