/* * 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.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.HashSet; import java.util.Set; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinitionTemplate; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.version.Version; import org.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; /** * A series of tests of the shareable nodes feature. */ public class ShareableNodesTest extends SingleUseAbstractTest { private static final String CAR_CARRIER_TYPENAME = "car:Carrier"; private static final String CAR_TYPENAME = "car:Car"; private static final String MIX_SHAREABLE = JcrMixLexicon.SHAREABLE.toString(); private static final String MIX_VERSIONABLE = JcrMixLexicon.VERSIONABLE.toString(); private static final String JCR_BASEVERSION = JcrLexicon.BASE_VERSION.toString(); private Workspace workspace; private Session session2; @Before @Override public void beforeEach() throws Exception { startRepositoryWithConfigurationFrom("config/simple-repo-config.json"); // Import the node types and the data ... registerNodeTypes("cars.cnd"); importContent("/", "io/cars-system-view-with-uuids.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); session.getRootNode().addNode("NewArea"); session.getRootNode().addNode("NewSecondArea"); session.save(); workspace = session.getWorkspace(); session2 = repository.login("otherWorkspace"); } /** * Verify that it is possible to create a new shareable node and then clone it to create a shared node and a shared set of * exactly one node. * * @throws RepositoryException */ @Test public void shouldCreateOneSharedNode() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIs(original, originalPath, sharedPath); assertSharedSetIs(sharedNode, originalPath, sharedPath); } @Test @FixFor( "MODE-1984" ) public void shouldRemoveShareableNode() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIs(original, originalPath, sharedPath); assertSharedSetIs(sharedNode, originalPath, sharedPath); // Make sure the shared node can be found in both parents ... Node parent1 = session.getNode("/Cars"); Node parent2 = session.getNode("/NewArea"); assertChildrenContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); // Now remove the share by ID ... String id = sharedNode.getIdentifier(); AbstractJcrNode node = session.getNodeByIdentifier(id); assertThat(node.isShared(), is(true)); node.remove(); // Verify that the shareable node is not consider a child of the original parent, // but is still considered a child of the second (ModeShape implements 'Node.remove()' as 'Node.removeShare()') ... assertChildrenDoNotContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); AbstractJcrNode node2 = session.getNodeByIdentifier(id); assertThat(node2.isShared(), is(false)); // Save, then try again ... session.save(); // Verify that the shareable node is not consider a child of either parent ... assertChildrenDoNotContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); node2 = session.getNodeByIdentifier(id); assertThat(node2.isShared(), is(false)); // Now remove the other share ... parent2.remove(); session.save(); } @SuppressWarnings( "deprecation" ) @Test @FixFor( "MODE-1984" ) public void shouldRemoveShareableNodeUsingDeprecatedNodeSave() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIs(original, originalPath, sharedPath); assertSharedSetIs(sharedNode, originalPath, sharedPath); // Make sure the shared node can be found in both parents ... Node parent1 = session.getNode("/Cars"); Node parent2 = session.getNode("/NewArea"); assertChildrenContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); // Now remove the share by ID ... String id = sharedNode.getIdentifier(); AbstractJcrNode node = session.getNodeByIdentifier(id); assertThat(node.isShared(), is(true)); node.remove(); // Verify that the shareable node is not consider a child of the original parent, // but is still considered a child of the second (ModeShape implements 'Node.remove()' as 'Node.removeShare()') ... assertChildrenDoNotContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); AbstractJcrNode node2 = session.getNodeByIdentifier(id); assertThat(node2.isShared(), is(false)); // Save the parent of the share that was just removed. This should still work, even though the shared node // is no longer under the node being saved... parent1.save(); // Verify that the shareable node is not consider a child of either parent ... assertChildrenDoNotContain(parent1, sharedNode, "Utility"); assertChildrenContain(parent2, sharedNode, "SharedUtility"); node2 = session.getNodeByIdentifier(id); assertThat(node2.isShared(), is(false)); // Now remove the other share ... parent2.remove(); session.save(); } @Test public void shouldNotAllowMultipleSharesUnderTheSameParent() throws Exception { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now a share ... Node sharedNode1 = makeShare(originalPath, sharedPath); assertSharedSetIs(original, originalPath, sharedPath); assertSharedSetIs(sharedNode1, originalPath, sharedPath); // Try to create another share under the same parent try { makeShare(originalPath, "/NewArea/SharedUtility[2]"); fail("Should not be allowed multiple shares under the same parent"); } catch (RepositoryException e) { // expected } } /** * Verify that it is possible to move a (proxy) node in a shared set. * * @throws RepositoryException */ @Test public void shouldAllowingMovingSharedNode() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIncludes(original, originalPath, sharedPath); assertSharedSetIncludes(sharedNode, originalPath, sharedPath); // Now move the new shared node ... String newPath = "/NewSecondArea/SharedUtility"; // no index session.move(sharedPath, newPath); session.save(); // Verify ... Node newSharedNode = session.getNode(newPath); assertSharedSetIs(original, originalPath, newPath); assertSharedSetIs(newSharedNode, originalPath, newPath); verifyShare(original, newSharedNode); } /** * Verify that it is possible to copy a (proxy) node in a shared set. * * @throws RepositoryException */ @Test public void shouldAllowingCopyingSharedNode() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIncludes(original, originalPath, sharedPath); assertSharedSetIncludes(sharedNode, originalPath, sharedPath); // Now move the new shared node ... workspace.copy("/NewArea", "/NewSecondArea/NewArea"); // Verify ... String copiedSharedPath = "/NewSecondArea" + sharedPath; session.refresh(false); Node node = session.getNode(copiedSharedPath); assertSharedSetIncludes(original, originalPath, sharedPath, copiedSharedPath); assertSharedSetIncludes(sharedNode, originalPath, sharedPath, copiedSharedPath); assertSharedSetIncludes(node, originalPath, sharedPath, copiedSharedPath); verifyShare(original, node); } /** * This test attempts to create a share underneath a node, A, that has a node type without a child definition for the * "mode:share" node type. This means that normally, a child of type "mode:share" cannot be placed under the node A. However, * because that node is only there as a proxy, ModeShape should transparently allow this. * * @throws RepositoryException */ @Test public void shouldAllowCreatingShareUnderNodeWithTypeThatDoesNotAllowProxyNodeButAllowsPrimaryTypeOfOriginal() throws RepositoryException { // Register the "car:Carrier" node type ... registerCarCarrierNodeType(session); // And create a new node of this type ... Node myCarrier = session.getNode("/NewArea").addNode("MyCarrier", CAR_CARRIER_TYPENAME); // Make one of the cars shareable ... Node prius = session.getNode("/Cars/Hybrid/Toyota Prius"); prius.addMixin(MIX_SHAREABLE); session.save(); // Now create a share under the carrier ... String sharedPath = myCarrier.getPath() + "/The Prius"; String originalPath = prius.getPath(); Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIncludes(prius, originalPath, sharedPath); assertSharedSetIncludes(sharedNode, originalPath, sharedPath); // Now, try refreshing the session so we have to re-materialize the node ... session.refresh(false); prius = session.getNode(prius.getPath()); sharedNode = session.getNode(sharedNode.getPath()); assertSharedSetIncludes(prius, originalPath, sharedPath); assertSharedSetIncludes(sharedNode, originalPath, sharedPath); } @SuppressWarnings( "unchecked" ) @FixFor( "MODE-883" ) @Test public void shouldAllowCreatingShareableNodeUnderParentThatDoesNotAllowSameNameSiblings() throws RepositoryException { // Now create a node type that allows only one car ... NodeTypeManager ntManager = session.getWorkspace().getNodeTypeManager(); NodeTypeTemplate template = ntManager.createNodeTypeTemplate(); template.setName("car:Owner"); NodeDefinitionTemplate childDefn = ntManager.createNodeDefinitionTemplate(); childDefn.setSameNameSiblings(false); childDefn.setName("*"); childDefn.setRequiredPrimaryTypeNames(new String[] {"car:Car"}); template.getNodeDefinitionTemplates().add(childDefn); // Register the node type ... ntManager.registerNodeType(template, false); // Create two nodes with this node type ... Node joe = session.getNode("/NewSecondArea").addNode("Joe", "car:Owner"); Node sally = session.getNode("/NewSecondArea").addNode("Sally", "car:Owner"); session.save(); // Create a node under Joe, since he will be the owner ... Node minibus = joe.addNode("Type 2", "car:Car"); minibus.setProperty("car:maker", "Volkswagen"); minibus.setProperty("car:year", "1952"); minibus.addMixin("mix:shareable"); session.save(); // Share the minibus under sally ... String originalPath = minibus.getPath(); String sharedPath = sally.getPath() + "/Our Bus"; Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIs(minibus, originalPath, sharedPath); assertSharedSetIs(sharedNode, originalPath, sharedPath); // Remove the node from Joe .. minibus = session.getNode("/NewSecondArea/Joe/Type 2"); minibus.remove(); session.save(); } /** * This test attempts to verify that a user cannot explicitly use the "mode:share" node type as the primary type for a new * manually-created node. * * @throws RepositoryException */ @Test( expected = ConstraintViolationException.class ) public void shouldNotBeAbleToCreateNodeWithProxyNodeTypeAsPrimaryType() throws RepositoryException { session.getRootNode().addNode("ShouldNotBePossible", string(ModeShapeLexicon.SHARE)); } /** * This test attempts to verify that 'canAddNode()' returns false if using the "mode:share" node type as the primary type for * a new manually-created node. * * @throws RepositoryException */ @Test public void shouldReturnFalseFromCanAddNodeIfUsingProxyNodeTypeAsPrimaryType() throws RepositoryException { boolean can = ((AbstractJcrNode)session.getRootNode()).canAddNode("ShouldNotBePossible", string(ModeShapeLexicon.SHARE)); assertThat(can, is(false)); } @Test public void shouldBeAbleToRegisterCarCarrierNodeType() throws RepositoryException { registerCarCarrierNodeType(session); } @Test public void shouldExportSharedNodesAsSystemViewXml() throws RepositoryException, IOException { createExportableContent(); // Export the content ... ByteArrayOutputStream baos = new ByteArrayOutputStream(); session.exportSystemView("/", baos, false, false); // System.out.println(baos); // Now import the content ... session2.importXML("/", new ByteArrayInputStream(baos.toByteArray()), ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); session2.save(); checkImportedContent(session2); } @Test public void shouldExportSharedNodesAsDocumentViewXml() throws RepositoryException, IOException { createExportableContent(); // Export the content ... ByteArrayOutputStream baos = new ByteArrayOutputStream(); session.exportDocumentView("/", baos, false, false); // System.out.println(baos); // Now import the content ... session2.importXML("/", new ByteArrayInputStream(baos.toByteArray()), ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); session2.save(); checkImportedContent(session2); } @Test public void shouldAllowingCreatingShareOfVersionedNode() throws RepositoryException { String originalPath = "/Cars/Utility"; // Make the original versionable ... Node original = makeVersionable(originalPath); assertNotNull(original); session.save(); Version version1 = checkin(originalPath); assertThat(version1, is(notNullValue())); Node baseVersion = findBaseVersion(originalPath); assertNotNull(baseVersion); // System.out.println("original => " + original); // System.out.println("baseVersion => " + baseVersion); // Make the original a shareable node ... checkout(originalPath); String sharedPath = "/NewArea/SharedUtility"; Node original2 = makeShareable(originalPath); session.save(); Version version2 = checkin(originalPath); assertThat(version2, is(notNullValue())); Node baseVersion2 = findBaseVersion(original2); assertNotNull(baseVersion2); // System.out.println("original2 => " + original2); // System.out.println("baseVersion2 => " + baseVersion2); // Now create the share ... Node sharedNode = makeShare(originalPath, sharedPath); assertSharedSetIs(original2, originalPath, sharedPath); assertSharedSetIs(sharedNode, originalPath, sharedPath); // System.out.println("sharedNode => " + sharedNode); // Now copy a subgraph that contains the shared area ... session.getWorkspace().copy("/NewArea", "/OtherNewArea"); Node baseVersion3 = findBaseVersion(originalPath); assertNotNull(baseVersion3); // System.out.println("baseVersion3 => " + baseVersion3); } protected void createExportableContent() throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; // Make the original a shareable node ... Node original = makeShareable(originalPath); session.save(); // Now a share ... Node sharedNode1 = makeShare(originalPath, sharedPath); assertSharedSetIs(original, originalPath, sharedPath); assertSharedSetIs(sharedNode1, originalPath, sharedPath); // Create another share ... String sharedPath2 = "/NewSecondArea/SharedUtility"; Node sharedNode2 = makeShare(originalPath, sharedPath2); assertSharedSetIs(original, originalPath, sharedPath, sharedPath2, sharedPath2); assertSharedSetIs(sharedNode1, originalPath, sharedPath, sharedPath2, sharedPath2); assertSharedSetIs(sharedNode2, originalPath, sharedPath, sharedPath2, sharedPath2); } protected void checkImportedContent( Session session ) throws RepositoryException { String originalPath = "/Cars/Utility"; String sharedPath = "/NewArea/SharedUtility"; String sharedPath2 = "/NewSecondArea/SharedUtility"; Node original = session.getNode(originalPath); Node sharedNode1 = session.getNode(sharedPath); Node sharedNode2 = session.getNode(sharedPath2); assertSharedSetIs(original, originalPath, sharedPath, sharedPath2, sharedPath2); assertSharedSetIs(sharedNode1, originalPath, sharedPath, sharedPath2, sharedPath2); assertSharedSetIs(sharedNode2, originalPath, sharedPath, sharedPath2, sharedPath2); } protected String string( Object object ) { return session.context().getValueFactories().getStringFactory().create(object); } protected Node makeShareable( String absPath ) throws RepositoryException { Node node = session.getNode(absPath); if (!node.isNodeType(MIX_SHAREABLE)) { node.addMixin(MIX_SHAREABLE); } return node; } protected Node makeShare( String sourcePath, String newSharePath ) throws RepositoryException { // Make sure the source node exists ... Node original = session.getNode(sourcePath); // It is expected that a node does not exist at the supplied path, so verify this... boolean exists = session.nodeExists(newSharePath); // Then this call will create a shared node at this exact path ... workspace.clone(workspace.getName(), sourcePath, newSharePath, false); // If we've succeeded in the creation of the share, we can make sure that a node did // not already exist (otherwise the call should have then failed) ... assertThat(exists, is(false)); // Now look up the new share node ... Node node = session.getNode(newSharePath); // And verify that this node has the same path and name ... assertThat(node.getPath(), is(newSharePath)); assertThat(node.getName(), is(string(path(newSharePath).getLastSegment().getName()))); assertThat(node.getIndex(), is(path(newSharePath).getLastSegment().getIndex())); // But that the identity, properties and children match the original node ... verifyShare(original, node); return node; } protected Node makeVersionable( String absPath ) throws RepositoryException { Node node = session.getNode(absPath); if (!node.isNodeType(MIX_VERSIONABLE)) { node.addMixin(MIX_VERSIONABLE); } return node; } protected Version checkin( String absPath ) throws RepositoryException { return session.getWorkspace().getVersionManager().checkin(absPath); } protected void checkout( String absPath ) throws RepositoryException { session.getWorkspace().getVersionManager().checkout(absPath); } protected void verifyShare( Node original, Node sharedNode ) throws RepositoryException { // The identity, properties and children match the original node ... assertThat(sharedNode.getIdentifier(), is(original.getIdentifier())); assertThat(sharedNode.isSame(original), is(true)); assertSameProperties(sharedNode, original); assertSameChildren(sharedNode, original); // Verify the shared attributes ... assertThat(sharedNode.isNodeType(MIX_SHAREABLE), is(true)); assertSharedSetIncludes(original, original.getPath(), sharedNode.getPath()); assertSharedSetIncludes(sharedNode, original.getPath(), sharedNode.getPath()); } protected Node findBaseVersion( Node node ) throws RepositoryException { String baseVersionUuid = node.getProperty(JCR_BASEVERSION).getString(); return session.getNodeByIdentifier(baseVersionUuid); } protected Node findBaseVersion( String path ) throws RepositoryException { Node node = session.getNode(path); return findBaseVersion(node); } protected void assertSharedSetIs( Node node, String... paths ) throws RepositoryException { Set<String> pathsInShare = sharedSetPathsFor(node); for (String path : paths) { pathsInShare.remove(path); } assertThat(pathsInShare.isEmpty(), is(true)); } protected void assertSharedSetIncludes( Node node, String... paths ) throws RepositoryException { Set<String> pathsInShare = sharedSetPathsFor(node); for (String path : paths) { pathsInShare.remove(path); } } protected Set<String> sharedSetPathsFor( Node node ) throws RepositoryException { Set<String> paths = new HashSet<String>(); for (NodeIterator iter = node.getSharedSet(); iter.hasNext();) { Node nodeInShare = iter.nextNode(); paths.add(nodeInShare.getPath()); } return paths; } protected void assertSameProperties( Node share, Node original ) throws RepositoryException { Set<String> originalPropertyNames = new HashSet<String>(); for (PropertyIterator iter = original.getProperties(); iter.hasNext();) { Property property = iter.nextProperty(); originalPropertyNames.add(property.getName()); } for (PropertyIterator iter = share.getProperties(); iter.hasNext();) { Property property = iter.nextProperty(); Property originalProperty = original.getProperty(property.getName()); originalPropertyNames.remove(property.getName()); assertThat(property.isModified(), is(originalProperty.isModified())); assertThat(property.isMultiple(), is(originalProperty.isMultiple())); assertThat(property.isNew(), is(originalProperty.isNew())); assertThat(property.isNode(), is(originalProperty.isNode())); assertThat(property.isSame(originalProperty), is(true)); // not the same property owner instance, but isSame() if (property.isMultiple()) { Value[] values = property.getValues(); Value[] originalValues = originalProperty.getValues(); assertThat(values.length, is(originalValues.length)); for (int i = 0; i != values.length; ++i) { assertThat(values[i].equals(originalValues[i]), is(true)); } } else { assertThat(property.getValue(), is(originalProperty.getValue())); } } assertThat("Extra properties in original: " + originalPropertyNames, originalPropertyNames.isEmpty(), is(true)); } protected void assertSameChildren( Node share, Node original ) throws RepositoryException { Set<String> originalChildNames = new HashSet<String>(); for (NodeIterator iter = original.getNodes(); iter.hasNext();) { Node node = iter.nextNode(); originalChildNames.add(node.getName()); } for (NodeIterator iter = share.getNodes(); iter.hasNext();) { Node child = iter.nextNode(); Node originalChild = original.getNode(child.getName()); originalChildNames.remove(child.getName()); assertThat(child.isSame(originalChild), is(true)); // Should be the exact same child nodes } assertThat("Extra children in original: " + originalChildNames, originalChildNames.isEmpty(), is(true)); } protected void assertChildrenContain( Node parent, Node expectedChild, String actualName ) throws RepositoryException { if (actualName == null) actualName = expectedChild.getName(); NodeIterator iter = parent.getNodes(); boolean found = false; while (iter.hasNext()) { Node child = iter.nextNode(); if (child.isSame(expectedChild)) found = true; } assertThat("Did not find the child named '" + actualName + "' as a child of the '" + parent.getPath() + "' parent node", found, is(true)); iter = parent.getNodes(actualName); found = false; while (iter.hasNext()) { Node child = iter.nextNode(); if (child.isSame(expectedChild)) found = true; } assertThat("Did not find the '" + actualName + "' as a child of the '" + parent.getPath() + "' parent node", found, is(true)); } protected void assertChildrenDoNotContain( Node parent, Node expectedNonChild, String actualName ) throws RepositoryException { if (actualName == null) actualName = expectedNonChild.getName(); NodeIterator iter = parent.getNodes(); while (iter.hasNext()) { Node child = iter.nextNode(); boolean found = child.isSame(expectedNonChild); assertThat("Unexpectedly found the '" + actualName + "' as a child of the '" + parent.getPath() + "' node", found, is(false)); } iter = parent.getNodes(actualName); while (iter.hasNext()) { Node child = iter.nextNode(); boolean found = child.isSame(expectedNonChild); assertThat("Unexpectedly found the '" + actualName + "' as a child of the '" + parent.getPath() + "' node", found, is(false)); } } @SuppressWarnings( "unchecked" ) protected void registerCarCarrierNodeType( Session session ) throws RepositoryException { NodeTypeManager ntManager = session.getWorkspace().getNodeTypeManager(); try { ntManager.getNodeType(CAR_CARRIER_TYPENAME); } catch (NoSuchNodeTypeException e) { NodeTypeTemplate nt = ntManager.createNodeTypeTemplate(); nt.setName(CAR_CARRIER_TYPENAME); // Children ... NodeDefinitionTemplate carChildType = ntManager.createNodeDefinitionTemplate(); carChildType.setRequiredPrimaryTypeNames(new String[] {CAR_TYPENAME}); nt.getNodeDefinitionTemplates().add(carChildType); ntManager.registerNodeType(nt, true); } // Verify it was registered ... ntManager.getNodeType(CAR_CARRIER_TYPENAME); } protected static URI resourceUri( String name ) throws URISyntaxException { return resourceUrl(name).toURI(); } protected static URL resourceUrl( String name ) { return ShareableNodesTest.class.getClassLoader().getResource(name); } }