/*
 * 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.IsInstanceOf.instanceOf; 
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 static org.mockito.Mockito.when; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.util.ArrayList; 
import java.util.Calendar; 
import java.util.List; 
import java.util.UUID; 
import java.util.concurrent.TimeUnit; 
import javax.jcr.Binary; 
import javax.jcr.Item; 
import javax.jcr.NamespaceException; 
import javax.jcr.Node; 
import javax.jcr.NodeIterator; 
import javax.jcr.PathNotFoundException; 
import javax.jcr.Property; 
import javax.jcr.PropertyIterator; 
import javax.jcr.PropertyType; 
import javax.jcr.ReferentialIntegrityException; 
import javax.jcr.Repository; 
import javax.jcr.RepositoryException; 
import javax.jcr.Session; 
import javax.jcr.UnsupportedRepositoryOperationException; 
import javax.jcr.Value; 
import javax.jcr.ValueFactory; 
import javax.jcr.nodetype.ConstraintViolationException; 
import javax.jcr.nodetype.NodeDefinitionTemplate; 
import javax.jcr.nodetype.NodeType; 
import javax.jcr.nodetype.NodeTypeManager; 
import javax.jcr.nodetype.NodeTypeTemplate; 
import javax.jcr.observation.EventIterator; 
import javax.jcr.observation.EventListener; 
import javax.jcr.query.Query; 
import javax.jcr.query.QueryManager; 
import org.junit.Test; 
import org.mockito.Mockito; 
import org.modeshape.common.FixFor; 
import org.modeshape.common.junit.SkipLongRunning; 
import org.modeshape.common.statistic.Stopwatch; 
import org.modeshape.jcr.api.AnonymousCredentials; 
import org.modeshape.jcr.api.JcrTools; 
import org.modeshape.jcr.api.Namespaced; 
import org.modeshape.jcr.api.observation.Event; 
import org.modeshape.jcr.value.Path; 
 
public class JcrSessionTest extends SingleUseAbstractTest { 
 
    private static final String MULTI_LINE_VALUE = "Line\t1\nLine 2\rLine 3\r\nLine 4"
    private static final String PUBLIC_DECODED_NAME = "a|b]c[d:e/f*g"
    private static final String PUBLIC_ENCODED_NAME = "a" + '\uF07C' + 'b' + '\uF05D' + 'c' + '\uF05B' + 'd' + '\uF03A' + 'e' 
                                                      + '\uF02F' + 'f' + '\uF02A' + 'g'; 
 
    protected void initializeData() throws Exception { 
        Node root = session.getRootNode(); 
        Node a = root.addNode("a"); 
        Node b = a.addNode("b"); 
        Node c = b.addNode("c"); 
        a.addMixin("mix:lockable"); 
        a.setProperty("stringProperty""value"); 
 
        b.addMixin("mix:referenceable"); 
        b.setProperty("booleanProperty"true); 
 
        c.setProperty("stringProperty""value"); 
        c.setProperty("multiLineProperty", MULTI_LINE_VALUE); 
        session.save(); 
    } 
 
    @FixFor( "MODE-2283" ) 
    @Test 
    public void shouldAllowRemovingAndRestoringPersistedReference() throws Exception { 
        Node referenceableNode = session.getRootNode().addNode("referenceable"); 
        referenceableNode.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
        Value strongRefValue = session.getValueFactory().createValue(referenceableNode, false); 
 
        Node node1 = session.getRootNode().addNode("node1"); 
        node1.setProperty("prop1", strongRefValue); 
 
        session.save(); 
 
        // First remove the property ... 
        node1.setProperty("prop1", (Value)null); 
        // And then set the property to the same value that's persisted. Essentially, this session is trying to restore 
        // the reference that we just removed (transitively). The result should be no net changes in this session. 
        node1.setProperty("prop1", strongRefValue); 
 
        // Now save the changes (even though there should be none) ... 
        session.save(); 
 
        PropertyIterator propertyIterator = referenceableNode.getReferences(); 
        assertEquals(1, propertyIterator.getSize()); 
    } 
 
    @Test 
    @FixFor( "MODE-1956" ) 
        assertThat(session.decode(PUBLIC_ENCODED_NAME), is(PUBLIC_DECODED_NAME)); 
    } 
 
    @Test 
    @FixFor( "MODE-1956" ) 
    public void shouldEncodeNameWithIllegalCharacters() { 
        assertThat(session.encode(PUBLIC_DECODED_NAME), is(PUBLIC_ENCODED_NAME)); 
    } 
 
    @Test 
    public void shouldHaveRootNode() throws Exception { 
        JcrRootNode node = session.getRootNode(); 
        assertThat(node, is(notNullValue())); 
        assertThat(node.getPath(), is("/")); 
    } 
 
    @Test 
    public void shouldHaveJcrSystemNodeUnderRoot() throws Exception { 
        JcrRootNode node = session.getRootNode(); 
        Node system = node.getNode("jcr:system"); 
        assertThat(system, is(notNullValue())); 
        assertThat(system.getPath(), is("/jcr:system")); 
    } 
 
    @Test 
    @SkipLongRunning 
    public void shouldAllowCreatingManyUnstructuredNodesWithSameNameSiblings() throws Exception { 
        JcrRootNode node = session.getRootNode(); 
        int count = 10000
        long start1 = System.nanoTime(); 
        for (int i = 0; i != count; ++i) { 
            node.addNode("childNode"); 
        } 
        long millis = TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start1), TimeUnit.NANOSECONDS); 
        printMessage("Time to create " + count + " nodes under root: " + millis + " ms"); 
 
        long start2 = System.nanoTime(); 
        session.save(); 
        millis = TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start2), TimeUnit.NANOSECONDS); 
        printMessage("Time to save " + count + " new nodes: " + millis + " ms"); 
        millis = TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start1), TimeUnit.NANOSECONDS); 
        printMessage("Total time to create " + count + " new nodes and save: " + millis + " ms"); 
 
        NodeIterator iter = node.getNodes("childNode"); 
        assertThat(iter.getSize(), is((long)count)); 
        while (iter.hasNext()) { 
            Node child = iter.nextNode(); 
            assertThat(child.getPrimaryNodeType().getName(), is("nt:unstructured")); 
        } 
 
        // Now add another node ... 
        start1 = System.nanoTime(); 
        node.addNode("oneMore"); 
        session.save(); 
        millis = TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start1), TimeUnit.NANOSECONDS); 
        printMessage("Time to create " + (count + 1) + "th node and save: " + millis + " ms"); 
    } 
 
    @Test 
    public void shouldAllowCreatingNodeUnderUnsavedNode() throws Exception { 
        Node node = session.getRootNode().addNode("testNode"); 
        node.addNode("childNode"); 
        session.save(); 
    } 
 
    @Test 
    public void shouldAllowCreatingManyUnstructuredNodesWithNoSameNameSiblings() throws Exception { 
        Stopwatch sw = new Stopwatch(); 
        for (int i = 0; i != 15; ++i) { 
            // Each iteration adds another node under the root and creates the many nodes under that node ... 
            Node node = session.getRootNode().addNode("testNode"); 
            session.save(); 
 
            int count = 100
            if (i > 2) sw.start(); 
            for (int j = 0; j != count; ++j) { 
                node.addNode("childNode" + j); 
            } 
 
            session.save(); 
            if (i > 2) sw.stop(); 
 
            // Now add another node ... 
            node.addNode("oneMore"); 
            session.save(); 
 
            session.getRootNode().getNode("testNode").remove(); 
            session.save(); 
        } 
        printMessage(sw.getDetailedStatistics().toString()); 
    } 
 
    @Test 
    public void shouldAllowCreatingNodesTwoLevelsBelowRoot() throws Exception { 
        Node node = session.getRootNode().addNode("testNode"); 
        session.save(); 
        node.addNode("childNode"); 
        session.save(); 
    } 
 
    @Test 
    public void shouldAllowDeletingNodeWithNoChildren() throws Exception { 
        Node node = session.getRootNode().addNode("testNode"); 
        session.save(); 
        // session.getRootNode().getNodes(); 
        // System.out.println("Root: " + session.getRootNode().getNodes().getSize() + " children"); 
        node.remove(); 
        session.save(); 
    } 
 
    @Test 
    public void shouldAllowDeletingTransientNodeWithNoChildren() throws Exception { 
        Node node = session.getRootNode().addNode("testNode"); 
        node.remove(); 
        session.save(); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowAddLockToken() throws Exception { 
        session.addLockToken(null); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowCheckPermissionWithNoPath() throws Exception { 
        session.checkPermission((String)null"read"); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowCheckPermissionWithEmptyPath() throws Exception { 
        session.checkPermission("""read"); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowCheckPermissionWithNoActions() throws Exception { 
        session.checkPermission("/"null); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowCheckPermissionWithEmptyActions() throws Exception { 
        session.checkPermission("/"""); 
    } 
 
    @Test 
    public void shouldReturnNullValueForNullAttributeName() throws Exception { 
        assertThat(session.getAttribute(null), nullValue()); 
    } 
 
    @Test 
    public void shouldReturnNullValueForEmptyOrBlankAttributeName() throws Exception { 
        assertThat(session.getAttribute(""), nullValue()); 
        assertThat(session.getAttribute("  "), nullValue()); 
    } 
 
    @Test 
    public void shouldReturnNullValueForNonExistantAttributeName() throws Exception { 
        assertThat(session.getAttribute("something else entirely"), nullValue()); 
    } 
 
    @Test 
    public void shouldReturnPropertyAttributeValueGivenNameOfExistingAttribute() throws Exception { 
        session = repository.login(new AnonymousCredentials("attribute1""value1")); 
        assertThat(session.getAttribute("attribute1"), is((Object)"value1")); 
    } 
 
    @Test 
    public void shouldProvideAttributeNames() throws Exception { 
        session = repository.login(new AnonymousCredentials("attribute1""value1")); 
        String[] names = session.getAttributeNames(); 
        assertThat(names, notNullValue()); 
        assertThat(names.length, is(1)); 
        assertThat(names[0], is("attribute1")); 
    } 
 
    @Test 
    public void shouldProvideEmptyAttributeNames() throws Exception { 
        session = repository.login(new AnonymousCredentials()); 
        // Get get the attribute names (there should be none) ... 
        String[] names = session.getAttributeNames(); 
        assertThat(names, notNullValue()); 
        assertThat(names.length, is(0)); 
    } 
 
    @Test 
    public void shouldProvideAccessToRepository() throws Exception { 
        assertThat(session.getRepository(), is((Repository)repository)); 
    } 
 
    @Test 
    public void shouldProvideAccessToWorkspace() throws Exception { 
        assertThat(session.getWorkspace(), notNullValue()); 
    } 
 
    @Test 
    public void shouldIndicateLiveBeforeLogout() throws Exception { 
        assertThat(session.isLive(), is(true)); 
    } 
 
    @Test 
    public void shouldAllowLogout() throws Exception { 
        session.logout(); 
    } 
 
    @Test 
    public void shouldIndicateNotLiveAfterLogout() throws Exception { 
        session.logout(); 
        assertThat(session.isLive(), is(false)); 
    } 
 
    @Test 
    public void shouldProvideUserId() throws Exception { 
        assertThat(session.getUserID(), notNullValue()); 
        try { 
            assertThat(session.getUserID(), is("<anonymous>")); 
        } finally { 
            session.logout(); 
        } 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    @Test 
    public void shouldProvideRootNode() throws Exception { 
        Node root = session.getRootNode(); 
        assertThat(root, notNullValue()); 
        String uuid = root.getIdentifier(); 
        assertThat(root.isNodeType("mix:referenceable"), is(true)); 
        assertThat(root.getUUID(), is(uuid)); 
        assertThat(uuid, notNullValue()); 
    } 
 
    @Test 
    public void shouldProvideChildrenByPath() throws Exception { 
        initializeData(); 
        Item item = session.getItem("/a"); 
        assertThat(item, instanceOf(Node.class)); 
        item = session.getItem("/a/b"); 
        assertThat(item, instanceOf(Node.class)); 
        item = session.getItem("/a/b/booleanProperty"); 
        assertThat(item, instanceOf(Property.class)); 
    } 
 
    @Test 
    public void shouldGetItemByIdentifierPath() throws Exception { 
        initializeData(); 
        // Look up the node by the identifier path ... 
        Item item = session.getItem(identifierPathFor("/a")); 
        assertThat(item, instanceOf(Node.class)); 
        assertThat(item.getPath(), is("/a")); 
 
        item = session.getItem(identifierPathFor("/a/b")); 
        assertThat(item, instanceOf(Node.class)); 
        assertThat(item.getPath(), is("/a/b")); 
 
        item = session.getItem(identifierPathFor("/")); 
        assertThat(item, instanceOf(Node.class)); 
        assertThat(item.getPath(), is("/")); 
    } 
 
    @Test 
    public void shouldGetNodeByIdentifierPath() throws Exception { 
        initializeData(); 
        // Look up the node by the identifier path ... 
        Node node = session.getNode(identifierPathFor("/a")); 
        assertThat(node.getPath(), is("/a")); 
 
        node = session.getNode(identifierPathFor("/a/b")); 
        assertThat(node.getPath(), is("/a/b")); 
 
        node = session.getNode(identifierPathFor("/")); 
        assertThat(node.getPath(), is("/")); 
    } 
 
    @Test 
    public void shouldCorrectlyDetermineIfItemExistsUsingPath() throws Exception { 
        initializeData(); 
        assertThat(session.itemExists("/"), is(true)); 
        assertThat(session.itemExists("/a"), is(true)); 
        assertThat(session.itemExists("/a/b"), is(true)); 
    } 
 
    @Test 
    public void shouldCorrectlyDetermineIfItemExistsUsingIdentifierPath() throws Exception { 
        initializeData(); 
        assertThat(session.itemExists(identifierPathFor("/")), is(true)); 
        assertThat(session.itemExists(identifierPathFor("/a")), is(true)); 
        assertThat(session.itemExists(identifierPathFor("/a/b")), is(true)); 
    } 
 
    @Test 
    public void shouldProvidePropertiesByPath() throws Exception { 
        initializeData(); 
        Item item = session.getItem("/a/b/booleanProperty"); 
        assertThat(item, instanceOf(Property.class)); 
 
        Property property = session.getProperty("/a/b/booleanProperty"); 
        assertThat(property, instanceOf(Property.class)); 
    } 
 
    @Test 
    public void shouldProvideNodesByPath() throws Exception { 
        initializeData(); 
        Node node = session.getNode("/a"); 
        assertThat(node, instanceOf(Node.class)); 
        node = session.getNode("/a/b"); 
    } 
 
    @Test( expected = PathNotFoundException.class ) 
    public void shouldNotReturnPropertyAsNode() throws Exception { 
        initializeData(); 
        assertThat(session.nodeExists("/a/b/booleanProperty"), is(false)); 
        session.getNode("/a/b/booleanProperty"); 
    } 
 
    @Test( expected = PathNotFoundException.class ) 
    public void shouldNotReturnNonExistantNode() throws Exception { 
        initializeData(); 
        assertThat(session.nodeExists("/a/b/argleBargle"), is(false)); 
        session.getNode("/a/b/argleBargle"); 
    } 
 
    @Test 
    public void shoulReturnPropertyDoesExistAtPathForExistingProperty() throws Exception { 
        initializeData(); 
        assertThat(session.propertyExists("/a/jcr:primaryType"), is(true)); 
        assertThat(session.propertyExists("/a/jcr:mixinTypes"), is(true)); 
        assertThat(session.propertyExists("/a/b/booleanProperty"), is(true)); 
        assertThat(session.getProperty("/a/b/booleanProperty"), is(notNullValue())); 
    } 
 
    @Test 
    public void shoulReturnPropertyDoesNotExistAtPathForNode() throws Exception { 
        initializeData(); 
        assertThat(session.propertyExists("/a/b"), is(false)); 
        try { 
            assertThat(session.getProperty("/a/b"), is(notNullValue())); 
            fail("Expected an exception"); 
        } catch (PathNotFoundException e) { 
            // expected 
        } 
    } 
 
    @Test 
    public void shouldReturnNoPropertyExistsWhenPathIncludesNonExistantNode() throws Exception { 
        initializeData(); 
        assertThat(session.propertyExists("/a/foo/bar/non-existant"), is(false)); 
    } 
 
    @Test( expected = PathNotFoundException.class ) 
    public void shouldNotReturnNonExistantProperty() throws Exception { 
        initializeData(); 
        try { 
            assertThat(session.propertyExists("/a/b/argleBargle"), is(false)); 
        } catch (RepositoryException e) { 
            fail("Unexpected exception"); 
        } 
        // This will throw a PathNotFoundException ... 
        session.getProperty("/a/b/argleBargle"); 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    @Test 
    public void shouldProvideValueFactory() throws Exception { 
        InputStream stream = new ByteArrayInputStream("something".getBytes()); 
        ValueFactory factory = session.getValueFactory(); 
        Binary binary = factory.createBinary(new ByteArrayInputStream("something".getBytes())); 
        assertThat(factory, notNullValue()); 
        assertThat(factory.createValue(false), notNullValue()); 
        assertThat(factory.createValue(Calendar.getInstance()), notNullValue()); 
        assertThat(factory.createValue(0.0), notNullValue()); 
        assertThat(factory.createValue(binary), notNullValue()); 
        assertThat(factory.createValue(stream), notNullValue()); 
        assertThat(factory.createValue(0L), notNullValue()); 
 
        Node node = session.getRootNode().addNode("testNode"); 
        node.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
 
        assertThat(factory.createValue(node), notNullValue()); 
        assertThat(factory.createValue(""), notNullValue()); 
        assertThat(factory.createValue("", PropertyType.BINARY), notNullValue()); 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    @Test( expected = RepositoryException.class ) 
    public void shouldNotCreateValueForNonReferenceableNode() throws Exception { 
        ValueFactory factory = session.getValueFactory(); 
        Node node = Mockito.mock(Node.class); 
        String uuid = UUID.randomUUID().toString(); 
        when(node.getUUID()).thenReturn(uuid); 
        when(node.getIdentifier()).thenReturn(uuid); 
        when(node.isNodeType("mix:referenceable")).thenReturn(false); 
        factory.createValue(node); 
    } 
 
    @Test 
    public void shouldNotHavePendingChanges() throws Exception { 
        assertThat(session.hasPendingChanges(), is(false)); 
    } 
 
    @Test 
    public void shouldProvideItemExists() throws Exception { 
        initializeData(); 
        assertThat(session.itemExists("/a/b"), is(true)); 
        assertThat(session.itemExists("/a/c"), is(false)); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowItemExistsWithNoPath() throws Exception { 
        session.itemExists(null); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowItemExistsWithEmptyPath() throws Exception { 
        session.itemExists(""); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowNoNamespaceUri() throws Exception { 
        session.getNamespacePrefix(null); 
    } 
 
    @Test( expected = NamespaceException.class ) 
    public void shouldNotProvidePrefixForUnknownUri() throws Exception { 
        session.getNamespacePrefix("bogus"); 
    } 
 
    @Test 
    public void shouldProvideNamespacePrefix() throws Exception { 
        assertThat(session.getNamespacePrefix("http://www.modeshape.org/1.0"), is("mode")); 
        assertThat(session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"), is("jcr")); 
        assertThat(session.getNamespacePrefix("http://www.jcp.org/jcr/mix/1.0"), is("mix")); 
        assertThat(session.getNamespacePrefix("http://www.jcp.org/jcr/nt/1.0"), is("nt")); 
        assertThat(session.getNamespacePrefix("http://www.jcp.org/jcr/sv/1.0"), is("sv")); 
        // assertThat(session.getNamespacePrefix("http://www.w3.org/XML/1998/namespace"), is("xml")); 
    } 
 
    @Test 
    public void shouldProvideNamespacePrefixes() throws Exception { 
        String[] prefixes = session.getNamespacePrefixes(); 
        assertThat(prefixes, notNullValue()); 
        assertThat(prefixes.length, is(not(0))); 
    } 
 
    @Test( expected = IllegalArgumentException.class ) 
    public void shouldNotAllowNoNamespacePrefix() throws Exception { 
        session.getNamespaceURI(null); 
    } 
 
    @Test( expected = NamespaceException.class ) 
    public void shouldNotProvideUriForUnknownPrefix() throws Exception { 
        session.getNamespaceURI("bogus"); 
    } 
 
    @Test 
    public void shouldProvideNamespaceUri() throws Exception { 
        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("sv"), is("http://www.jcp.org/jcr/sv/1.0")); 
        // assertThat(session.getNamespaceURI("xml"), is("http://www.w3.org/XML/1998/namespace")); 
    } 
 
    /**
     * ModeShape JCR implementation is supposed to have root type named {@link ModeShapeLexicon#ROOT}. 
     *  
     * @throws Exception if an error occurs during the test 
     */
 
    @Test 
    public void rootNodeShouldHaveProperType() throws Exception { 
        Node rootNode = session.getRootNode(); 
 
        NodeType rootNodePrimaryType = rootNode.getPrimaryNodeType(); 
        NodeType dnaRootType = session.nodeTypeManager().getNodeType(ModeShapeLexicon.ROOT); 
 
        assertThat(rootNodePrimaryType.getName(), is(dnaRootType.getName())); 
 
    } 
 
    /**
     * ModeShape JCR implementation is supposed to have a referenceable root. 
     *  
     * @throws RepositoryException if an error occurs during the test 
     */
 
    @Test 
    public void rootNodeShouldBeReferenceable() throws RepositoryException { 
        Node rootNode = session.getRootNode(); 
 
        assertTrue(rootNode.getPrimaryNodeType().isNodeType(JcrMixLexicon.REFERENCEABLE.getString(session.namespaces()))); 
    } 
 
    @Test 
    public void shouldExportMultiLinePropertiesInSystemView() throws Exception { 
        initializeData(); 
 
        OutputStream os = new ByteArrayOutputStream(); 
        session.exportSystemView("/a/b/c", os, falsetrue); 
 
        String fileContents = os.toString(); 
        assertTrue(fileContents.contains(MULTI_LINE_VALUE)); 
    } 
 
    @Test 
    public void shouldUseJcrCardinalityPerPropertyDefinition() throws Exception { 
        initializeData(); 
 
        // Verify that the node does exist in the source ... 
        Path pathToNode = session.context().getValueFactories().getPathFactory().create("/a/b"); 
        Node carsNode = session.node(pathToNode); 
 
        String mixinTypesName = JcrLexicon.MIXIN_TYPES.getString(session.context().getNamespaceRegistry()); 
        Property mixinTypes = carsNode.getProperty(mixinTypesName); 
 
        // Check that the JCR property is a MultiProperty - this call will throw an exception if the property is not. 
        mixinTypes.getValues(); 
    } 
 
    /*
     * Moved these three tests over from AbstractJcrNode as they require more extensive scaffolding that is already implemented in 
     * this test. 
     */
 
 
    @Test 
    public void shouldProvideIdentifierEvenIfNotReferenceable() throws Exception { 
        initializeData(); 
        // The b node was not set up to be referenceable in this test, but does have a mixin type 
        Node node = session.getRootNode().getNode("a").getNode("b").getNode("c"); 
        assertThat(node.getIdentifier(), is(notNullValue())); 
    } 
 
    @Test 
    public void shouldProvideIdentifierEvenIfNoMixinTypes() throws Exception { 
        initializeData(); 
        // The b node was not set up to be referenceable in this test, but does have a mixin type 
        Node node = session.getRootNode().getNode("a").getNode("b").getNode("c"); 
        assertThat(node.getIdentifier(), is(notNullValue())); 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    @Test( expected = UnsupportedRepositoryOperationException.class ) 
    public void shouldNotProvideUuidIfNotReferenceable() throws Exception { 
        initializeData(); 
        // The b node was not set up to be referenceable in this test, but does have a mixin type 
        Node node = session.getRootNode().getNode("a").getNode("b").getNode("c"); 
        node.getUUID(); 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    @Test( expected = UnsupportedRepositoryOperationException.class ) 
    public void shouldNotProvideUuidIfNoMixinTypes() throws Exception { 
        initializeData(); 
        // The c node was not set up to be referenceable in this test and has no mixin types 
        Node node = session.getRootNode().getNode("a").getNode("b").getNode("c"); 
        node.getUUID(); 
    } 
 
    @Test 
    public void shouldMoveToNewName() throws Exception { 
        initializeData(); 
        session.move("/a/b/c""/a/b/d"); 
 
        session.getRootNode().getNode("a").getNode("b").getNode("d"); 
        try { 
            session.getRootNode().getNode("a").getNode("b").getNode("c"); 
 
            fail("Node still exists at /a/b/c after move"); 
        } catch (PathNotFoundException e) { 
            // Expected 
        } 
    } 
 
    @Test 
    @FixFor( "MODE-1799" ) 
    public void shouldMoveNodesUnderRoot() throws Exception { 
        initializeData(); 
        session.getRootNode().addNode("d"); 
        session.save(); 
 
        session.move("/a""/d"); 
        session.save(); 
 
        assertNotNull(session.getNode("/d")); 
        assertNotNull(session.getNode("/d/b")); 
        assertNotNull(session.getNode("/d/b/c")); 
    } 
 
    @Test 
    @FixFor( "MODE-2206" ) 
    public void shouldMoveOverNodesRemovedInTheSameSession() throws Exception { 
        try { 
            // add 2 nodes under a parent that doesn't allow SNS 
            final Node root = session.getRootNode(); 
            final Node parent = root.addNode("parent""nt:folder"); 
            parent.addNode("name1""nt:folder"); 
            parent.addNode("name2""nt:folder"); 
 
            session.save(); 
 
            // overwrite 2 with 1 
            session.removeItem("/parent/name2"); 
 
            assertFalse("Added node 2 doest not exist after remove", session.nodeExists("/parent/name2")); 
            assertTrue("Added node 1 still exists", session.nodeExists("/parent/name1")); 
 
            session.move("/parent/name1""/parent/name2"); 
            session.save(); 
 
            assertTrue("Added node 2 doest not exist after move", session.nodeExists("/parent/name2")); 
            assertFalse("Added node 1 still exists", session.nodeExists("/parent/name1")); 
        } finally { 
            session.logout(); 
        } 
    } 
 
    @Test 
    @FixFor( "MODE-2511" ) 
    public void shouldAllowReorderNodesWhenSameNameIsDisabled() throws Exception { 
        try { 
            session.getWorkspace().getNodeTypeManager().registerNodeTypes(resourceStream("cnd/magnolia.cnd"), true); 
 
            // add 2 nodes under a parent that doesn't allow SNS and reorder them 
            final Node root = session.getRootNode(); 
            final Node parent = root.addNode("parent""mgnl:content"); 
            parent.addNode("name1""nt:folder"); 
            parent.addNode("name2""nt:folder"); 
            parent.orderBefore("name2""name1"); 
             
            session.save(); 
        } finally { 
            session.logout(); 
        } 
    } 
 
    @Test 
    @FixFor( "MODE-2206" ) 
    public void shouldMoveOverNodesRenamedInTheSameSession() throws Exception { 
        try { 
            // add 2 nodes under a parent that doesn't allow SNS 
            final Node root = session.getRootNode(); 
            final Node parent = root.addNode("parent""nt:folder"); 
            parent.addNode("name1""nt:folder"); 
            parent.addNode("name2""nt:folder"); 
 
            session.save(); 
 
            // rename 1 to 3 
            session.move("/parent/name1""/parent/name3"); 
 
            assertFalse(session.nodeExists("/parent/name1")); 
            assertTrue(session.nodeExists("/parent/name2")); 
            assertTrue(session.nodeExists("/parent/name3")); 
 
            //rename 2 to 1 
            session.move("/parent/name2""/parent/name1"); 
            session.save(); 
 
            assertTrue(session.nodeExists("/parent/name1")); 
            assertFalse(session.nodeExists("/parent/name2")); 
            assertTrue(session.nodeExists("/parent/name3")); 
        } finally { 
            session.logout(); 
        } 
    } 
 
    @SuppressWarnings( "unchecked" ) 
    @FixFor( "MODE-1721" ) 
    @Test 
    public void shouldMoveToNewNameWhenSnsAreNotAllowed() throws Exception { 
        initializeData(); 
        // Define the node type that disallows SNS ... 
        NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager(); 
        NodeDefinitionTemplate childDefn = ntMgr.createNodeDefinitionTemplate(); 
        childDefn.setSameNameSiblings(false); 
        childDefn.setRequiredPrimaryTypeNames(new String[] {"nt:unstructured"}); 
        childDefn.setDefaultPrimaryTypeName("nt:unstructured"); 
        NodeTypeTemplate nodeType = ntMgr.createNodeTypeTemplate(); 
        nodeType.setName("noSnsChildren"); 
        nodeType.getNodeDefinitionTemplates().add(childDefn); 
        NodeType newNodeType = ntMgr.registerNodeType(nodeType, false); 
        assertThat(newNodeType, is(notNullValue())); 
 
        Node parent = null
        try { 
 
            Node c = session.getNode("/a/b/c"); 
            String parentName = "parent"
            parent = c.addNode(parentName, nodeType.getName()); 
            Node childA = parent.addNode("childA"); 
            Node childB = parent.addNode("childB"); 
            Node childC = parent.addNode("childC"); 
            session.save(); 
            assertThat(childA, is(notNullValue())); 
            assertThat(childB, is(notNullValue())); 
            assertThat(childC, is(notNullValue())); 
 
            String oldChildName = childC.getName(); // no SNS, so this is fine! 
            String newChildName = "childX"
            session.move(childC.getPath(), parent.getPath() + "/" + newChildName); 
 
            // A node should exist at the new location ... 
            parent = session.getRootNode().getNode("a").getNode("b").getNode("c").getNode(parentName); 
            parent.getNode(newChildName); 
            try { 
                // But should not exist at the old location ... 
                parent.getNode(oldChildName); 
 
                fail("Node still exists at /a/b/c/parent/childC after move"); 
            } catch (PathNotFoundException e) { 
                // Expected 
            } 
 
        } finally { 
            // Remove the parent (that uses the node type that we're about to remove) ... 
            if (parent != null) { 
                parent.remove(); 
                session.save(); 
            } 
            // Be sure to always unregister the node type ... 
            ntMgr.unregisterNodeType(nodeType.getName()); 
        } 
    } 
 
    @FixFor( {"MODE-694""MODE-1525"} ) 
    @Test 
    public void shouldAddCreatedPropertyForHierarchyNodes() throws Exception { 
        Node folderNode = session.getRootNode().addNode("folderNode""nt:folder"); 
        assertThat(folderNode.hasProperty("jcr:created"), is(false)); 
 
        Node fileNode = folderNode.addNode("fileNode""nt:file"); 
        Node resource = null
        try { 
            resource = fileNode.addNode("jcr:content"); 
            fail("Should not be able to add this child without specifying the primary type, as there is no default"); 
        } catch (ConstraintViolationException e) { 
            resource = fileNode.addNode("jcr:content""nt:resource"); 
        } 
        assertThat(fileNode.hasProperty("jcr:created"), is(false)); 
 
        // Save the changes ... 
        try { 
            session.save(); 
            fail("Should not be able to save this; 'jcr:content' is missing the mandatory 'jcr:data' property"); 
        } catch (ConstraintViolationException e) { 
            Binary binary = session.getValueFactory().createBinary("Some binary value".getBytes()); 
            resource.setProperty("jcr:data", binary); 
            session.save(); 
        } 
 
        assertThat(folderNode.hasProperty("jcr:created"), is(true)); 
        assertThat(fileNode.hasProperty("jcr:created"), is(true)); 
    } 
 
    @Test 
    public void shouldHaveCapabilityToPerformValidAddNode() throws Exception { 
        assertTrue(session.hasCapability("addNode", session.getRootNode(), new String[] {"someNewNode"})); 
        assertTrue(session.hasCapability("addNode", session.getRootNode(), new String[] {"someNewNode""nt:unstructured"})); 
    } 
 
    @Test 
    public void shouldNotHaveCapabilityToPerformInvalidAddNode() throws Exception { 
        assertTrue(!session.hasCapability("addNode", session.getRootNode(), new String[] {"someNewNode[2]"})); 
        assertTrue(!session.hasCapability("addNode", session.getRootNode(), new String[] {"someNewNode""nt:invalidType"})); 
    } 
 
    @Test 
    public void shouldCheckReferentialIntegrityWhenRemovingNodes() throws Exception { 
        Node referenceableNode = session.getRootNode().addNode("referenceable"); 
        referenceableNode.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
 
        Node node1 = session.getRootNode().addNode("node1"); 
        JcrValueFactory valueFactory = session.getValueFactory(); 
        node1.setProperty("ref1", valueFactory.createValue(referenceableNode, false)); 
        node1.setProperty("ref2", valueFactory.createValue(referenceableNode, false)); 
        node1.setProperty("wref1", valueFactory.createValue(referenceableNode, true)); 
        node1.setProperty("wref2", valueFactory.createValue(referenceableNode, true)); 
 
        session.save(); 
 
        // there are 2 strong refs 
        referenceableNode.remove(); 
        expectReferentialIntegrityException(); 
 
        // remove the first strong ref 
        node1.setProperty("ref1", (Node)null); 
        referenceableNode.remove(); 
        expectReferentialIntegrityException(); 
 
        // remove the second strong ref (we should be able to remove the node now) 
        assertEquals(2, referenceableNode.getWeakReferences().getSize()); 
        node1.setProperty("ref1", (Node)null); 
        node1.setProperty("ref2", (Node)null); 
        referenceableNode.remove(); 
        session.save(); 
 
        // check the node was actually deleted 
        assertFalse(session.getRootNode().hasNode("referenceable")); 
    } 
 
    @FixFor( "MODE-1685" ) 
    @Test 
    public void shouldEnforceReferentialIntegrityWhenRemovingNodes() throws Exception { 
        JcrValueFactory valueFactory = session.getValueFactory(); 
 
        Node targetNode = session.getRootNode().addNode("target"); 
        targetNode.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
        Node parentNode = session.getRootNode().addNode("parent"); 
        Node childNode = parentNode.addNode("child"); 
        childNode.setProperty("ref1", valueFactory.createValue(targetNode, false)); 
        session.save(); 
 
        // Delete the target - there are references to this node, so we can't remove ... 
        try { 
            targetNode.remove(); 
            session.save(); 
            fail("Expected a referential integrity exception"); 
        } catch (ReferentialIntegrityException e) { 
            // expected 
        } 
    } 
 
    @FixFor( "MODE-1685" ) 
    @Test 
    public void shouldCheckReferentialIntegrityOfSubgraphWhenRemovingNodes() throws Exception { 
        JcrValueFactory valueFactory = session.getValueFactory(); 
 
        Node targetNode = session.getRootNode().addNode("target"); 
        targetNode.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
        Node parentNode = session.getRootNode().addNode("parent"); 
        Node childNode = parentNode.addNode("child"); 
        childNode.setProperty("ref1", valueFactory.createValue(targetNode, false)); 
        session.save(); 
 
        // Delete the parent (which will delete the child and the reference to the target ... 
        parentNode.remove(); 
        session.save(); 
 
        // Delete the target - there should be no references ... 
        targetNode.remove(); 
        session.save(); 
    } 
 
    @FixFor( "MODE-1685" ) 
    @Test 
    public void shouldNotEnforceReferentialIntegrityOfWeakReferenceWhenRemovingNodes() throws Exception { 
        JcrValueFactory valueFactory = session.getValueFactory(); 
 
        Node targetNode = session.getRootNode().addNode("target"); 
        targetNode.addMixin(JcrMixLexicon.REFERENCEABLE.toString()); 
        Node parentNode = session.getRootNode().addNode("parent"); 
        Node childNode = parentNode.addNode("child"); 
        childNode.setProperty("ref1", valueFactory.createValue(targetNode, true)); 
        session.save(); 
 
        // Delete the target - there should be no strong references, but the weak is okay and won't prevent removal ... 
        targetNode.remove(); 
        session.save(); 
    } 
 
    @Test 
    @FixFor( "MODE-1613" ) 
    public void shouldMoveSNSAndNotCorruptThePathsOfRemainingSiblings() throws Exception { 
        startRepositoryWithConfiguration(getClass().getClassLoader().getResourceAsStream("config/simple-repo-config.json")); 
        // /testRoot/parent0/child 
        // /testRoot/parent0/child[2] 
        // /testRoot/parent0/child[3] 
 
        // /testRoot/parent1/child 
        // /testRoot/parent1/child[2] 
        // /testRoot/parent1/child[3] 
 
        // move /testRoot/parent0/child[1] to /testRoot/parent1. 
 
        createTreeWithSNS(23); 
 
        String srcId = session.getNode("/testRoot/parent0/child[1]").getIdentifier(); 
        String destId = session.getNode("/testRoot/parent1").getIdentifier(); 
        moveSNSWhileCachingPaths(srcId, destId, "child"); 
    } 
 
    @Test 
    @FixFor( "MODE-1623" ) 
    public void shouldAutomaticallySetDefaultValueOnProperties() throws Exception { 
        // Start the repository and register some node types ... 
        ClassLoader cl = getClass().getClassLoader(); 
        startRepositoryWithConfiguration(cl.getResourceAsStream("config/simple-repo-config.json")); 
        session.getWorkspace().getNodeTypeManager().registerNodeTypes(cl.getResource("cnd/notionalTypes.cnd"), true); 
 
        // Create a node using a type with property definitions that have default values ... 
        Node node1 = session.getRootNode().addNode("node1""notion:typed"); 
 
        // Before saving, the auto-created properties should be there ... 
        assertThat(node1.hasProperty("notion:booleanProperty"), is(false)); 
        assertThat(node1.hasProperty("notion:booleanProperty2"), is(false)); 
        assertThat(node1.hasProperty("notion:longProperty"), is(false)); 
        assertThat(node1.hasProperty("notion:stringProperty"), is(false)); 
        assertThat(node1.hasProperty("notion:booleanPropertyWithDefault"), is(false)); 
        assertThat(node1.hasProperty("notion:stringPropertyWithDefault"), is(false)); 
        assertThat(node1.hasProperty("notion:booleanAutoCreatedPropertyWithDefault"), is(true)); 
        assertThat(node1.hasProperty("notion:stringAutoCreatedPropertyWithDefault"), is(true)); 
        assertThat(node1.getProperty("notion:booleanAutoCreatedPropertyWithDefault").getBoolean(), is(true)); 
        assertThat(node1.getProperty("notion:stringAutoCreatedPropertyWithDefault").getString(), is("default string value")); 
 
        // Save, and then check that the properties exist ... 
        session.save(); 
        assertThat(node1.hasProperty("notion:booleanPropertyWithDefault"), is(false)); 
        assertThat(node1.hasProperty("notion:stringPropertyWithDefault"), is(false)); 
        assertThat(node1.hasProperty("notion:booleanAutoCreatedPropertyWithDefault"), is(true)); 
        assertThat(node1.hasProperty("notion:stringAutoCreatedPropertyWithDefault"), is(true)); 
        assertThat(node1.getProperty("notion:booleanAutoCreatedPropertyWithDefault").getBoolean(), is(true)); 
        assertThat(node1.getProperty("notion:stringAutoCreatedPropertyWithDefault").getString(), is("default string value")); 
    } 
 
    @FixFor( "MODE-1767" ) 
    @Test 
    public void shouldAutomaticallyAddMimeTypePropertyToNtResourceUponSave() throws Exception { 
        // Start the repository ... 
        ClassLoader cl = getClass().getClassLoader(); 
        startRepositoryWithConfiguration(cl.getResourceAsStream("config/simple-repo-config.json")); 
 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
 
        // Upload a file 
        JcrTools tools = new JcrTools(); 
        tools.uploadFile(session, "/node1/simple.json", getResourceFile("data/simple.json")); 
        Node fileNode = node1.getNode("simple.json"); 
        Node contentNode = fileNode.getNode("jcr:content"); 
        assertThat(contentNode.getPrimaryNodeType().getName(), is("nt:resource")); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(false)); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
 
        // Save the session, and verify that the "jcr:mimeType" property is added ... 
        session.save(); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(true)); 
        assertThat(contentNode.getProperty("jcr:mimeType").getString(), is("application/json")); 
    } 
 
    @FixFor( "MODE-1767" ) 
    @Test 
    public void shouldAutomaticallyAddMimeTypePropertyToNtResourceSubtypeUponSave() throws Exception { 
        // Start the repository ... 
        ClassLoader cl = getClass().getClassLoader(); 
        startRepositoryWithConfiguration(cl.getResourceAsStream("config/simple-repo-config.json")); 
 
        // Register a new node type that is a subtype of 'nt:resource' 
        NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager(); 
        NodeTypeTemplate template = ntMgr.createNodeTypeTemplate(); 
        template.setDeclaredSuperTypeNames(new String[] {"nt:resource"}); 
        template.setName("customResourceType"); 
        NodeType ntResourceSubtype = ntMgr.registerNodeType(template, false); 
        assertThat(ntResourceSubtype.getDeclaredSupertypes()[0].getName(), is("nt:resource")); 
 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
 
        // Upload a file 
        JcrTools tools = new JcrTools(); 
        tools.uploadFile(session, "/node1/simple.json", getResourceFile("data/simple.json")); 
        Node fileNode = node1.getNode("simple.json"); 
        Node contentNode = fileNode.getNode("jcr:content"); 
        contentNode.setPrimaryType(ntResourceSubtype.getName()); 
        assertThat(contentNode.getPrimaryNodeType().getName(), is(ntResourceSubtype.getName())); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(false)); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
 
        // Save the session, and verify that the "jcr:mimeType" property is added ... 
        session.save(); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(true)); 
        assertThat(contentNode.getProperty("jcr:mimeType").getString(), is("application/json")); 
    } 
 
    @FixFor( "MODE-1767" ) 
    @Test 
    public void shouldNotOverrideManuallyAddedMimeTypePropertyToNtResourceUponSave() throws Exception { 
        // Start the repository ... 
        ClassLoader cl = getClass().getClassLoader(); 
        startRepositoryWithConfiguration(cl.getResourceAsStream("config/simple-repo-config.json")); 
 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
 
        // Upload a file 
        JcrTools tools = new JcrTools(); 
        tools.uploadFile(session, "/node1/simple.json", getResourceFile("data/simple.json")); 
        Node fileNode = node1.getNode("simple.json"); 
        Node contentNode = fileNode.getNode("jcr:content"); 
        contentNode.setProperty("jcr:mimeType""bogus"); 
        assertThat(contentNode.getPrimaryNodeType().getName(), is("nt:resource")); 
        assertThat(contentNode.getProperty("jcr:mimeType").getString(), is("bogus")); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
 
        // Save the session, and verify that the "jcr:mimeType" property is added ... 
        session.save(); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(true)); 
        assertThat(contentNode.getProperty("jcr:mimeType").getString(), is("bogus")); 
    } 
 
    @FixFor( "MODE-1767" ) 
    @Test 
    public void shouldNotAddMimeTypePropertyUnderNtFileIfContentNodeIsNotNtResource() throws Exception { 
        // Start the repository ... 
        ClassLoader cl = getClass().getClassLoader(); 
        startRepositoryWithConfiguration(cl.getResourceAsStream("config/simple-repo-config.json")); 
 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
 
        // Upload a file 
        JcrTools tools = new JcrTools(); 
        tools.uploadFile(session, "/node1/simple.json", getResourceFile("data/simple.json")); 
        Node fileNode = node1.getNode("simple.json"); 
        Node contentNode = fileNode.getNode("jcr:content"); 
        assertThat(contentNode.getPrimaryNodeType().getName(), is("nt:resource")); 
        contentNode.setPrimaryType("nt:unstructured"); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(false)); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
 
        // Save the session, and verify that the "jcr:mimeType" property is added ... 
        session.save(); 
        assertThat(contentNode.hasProperty("jcr:mimeType"), is(false)); 
        assertThat(contentNode.hasProperty("jcr:data"), is(true)); 
    } 
 
    @FixFor( "MODE-1855" ) 
    @Test 
    public void shouldGetLocalNameAndNamespaceUriFromRootNode() throws Exception { 
        assertLocalNameAndNamespace(session.getRootNode(), """"); 
    } 
 
    @FixFor( "MODE-1855" ) 
    @Test 
    public void shouldGetLocalNameAndNamespaceUriFromNodeAndPropertyObjects() throws Exception { 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
        assertLocalNameAndNamespace(node1, "node1"""); 
 
        // Add another SNS node under which we'll do our work ... 
        Node node1a = session.getRootNode().addNode("node1"); 
        session.save(); 
        assertThat(node1a.getIndex(), is(2)); 
        assertLocalNameAndNamespace(node1a, "node1"""); // no SNS index in local name! 
 
        // Upload a file 
        JcrTools tools = new JcrTools(); 
        tools.uploadFile(session, "/node1/simple.json", getResourceFile("data/simple.json")); 
        Node fileNode = node1.getNode("simple.json"); 
        Node contentNode = fileNode.getNode("jcr:content"); 
 
        assertLocalNameAndNamespace(fileNode, "simple.json"""); 
        assertLocalNameAndNamespace(contentNode, "content""jcr"); 
    } 
 
    @FixFor( "MODE-1856" ) 
    @Test 
    public void shouldNotIndexNoOpChanges() throws Exception { 
        // Add a node under which we'll do our work ... 
        Node node1 = session.getRootNode().addNode("node1"); 
        session.save(); 
 
        // Register the listener 
        PropertyListener listener = new PropertyListener(); 
        session.getWorkspace().getObservationManager() 
               .addEventListener(listener, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED, null// node1.getPath(), 
                                 truenullnullfalse); 
 
        // Now, add a property and remove the property, then save ... 
        node1.setProperty("unchanged""new value"); 
        node1.getProperty("unchanged").remove(); 
        session.save(); 
 
        Thread.sleep(500L); 
        assertThat(listener.adds, is(0)); 
        assertThat(listener.removes, is(0)); 
        assertThat(listener.changes, is(0)); 
    } 
 
    @Test 
    @FixFor( "MODE-1894" ) 
    public void shouldReplaceOldPropertyValuesInIndexesWhenUpdating() throws Exception { 
        initializeData(); 
 
        Node a = session.getNode("/a"); 
        a.setProperty("stringProperty1""value"); 
        session.save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("select * from [nt:unstructured] as node where node.stringProperty='value'"2); 
        queryAndExpectResults("select * from [nt:unstructured] as node where node.stringProperty1='value'"1); 
 
        a.setProperty("stringProperty""value1"); 
        session.save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("select * from [nt:unstructured] as node where node.stringProperty='value'"1); 
        queryAndExpectResults("select * from [nt:unstructured] as node where node.stringProperty1='value'"1); 
    } 
 
    @Test 
    @FixFor( "MODE-1940" ) 
    public void shouldIndexRenamedNodes() throws Exception { 
        initializeData(); 
 
        // rename /a/b to /a/d 
        session.getWorkspace().move("/a/b""/a/d"); 
        Thread.sleep(200); 
 
        queryForAbsentLocalName("b"); 
        queryForExistingLocaLName("d""/a/d"); 
        queryForExistingLocaLName("c""/a/d/c"); 
    } 
 
    @Test 
    @FixFor( "MODE-1940" ) 
    public void shouldIndexMovedNodes() throws Exception { 
        initializeData(); 
 
        // create /w/q 
        session.getNode("/").addNode("w").addNode("q"); 
        session.save(); 
        // move /a to /w/q 
        session.getWorkspace().move("/a""/w/x"); 
        Thread.sleep(200); 
        queryForAbsentLocalName("a"); 
        queryForExistingLocaLName("x""/w/x"); 
        queryForExistingLocaLName("b""/w/x/b"); 
        queryForExistingLocaLName("c""/w/x/b/c"); 
    } 
 
    @Test 
    @FixFor( "MODE-2030" ) 
    public void shouldIndexChildrenOfRenamedNode() throws Exception { 
        Node rootNode = session().getRootNode(); 
        Node folder = rootNode.addNode("folder""nt:folder"); 
        Node file = folder.addNode("file""nt:file"); 
        Node contentNode = file.addNode("jcr:content""nt:resource"); 
        contentNode.setProperty("jcr:data", session().getValueFactory().createBinary("test".getBytes())); 
 
        session().save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folder/%'"1); 
 
        folder.getSession().move(folder.getPath(), "/folder_1"); 
        folder.getSession().save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folder_1/%'"1); 
    } 
 
    @Test 
    @FixFor( "MODE-2030" ) 
    public void shouldIndexChildrenOfMovedNode() throws Exception { 
        Node rootNode = session().getRootNode(); 
        Node folder = rootNode.addNode("folder""nt:folder"); 
        Node file = folder.addNode("file""nt:file"); 
        Node contentNode = file.addNode("jcr:content""nt:resource"); 
        contentNode.setProperty("jcr:data", session().getValueFactory().createBinary("test".getBytes())); 
        String lastModifiedBy = "testCode"
        contentNode.setProperty("jcr:lastModifiedBy", lastModifiedBy); 
        rootNode.addNode("folderA"); 
 
        session().save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folder/%'"1); 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folderA/%'"0); 
        queryAndExpectResults("SELECT * FROM [nt:resource] as r WHERE r.[jcr:lastModifiedBy]='" + lastModifiedBy + "'"1); 
 
        folder.getSession().move(folder.getPath(), "/folderA/folderB"); 
        folder.getSession().save(); 
        Thread.sleep(200); 
 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folder/%'"0); 
        queryAndExpectResults("SELECT * FROM [nt:file] WHERE [jcr:path] LIKE '/folderA/folderB/%'"1); 
        queryAndExpectResults("SELECT * FROM [nt:resource] as r WHERE r.[jcr:lastModifiedBy]='" + lastModifiedBy + "'"1); 
    } 
 
    @Test 
    @FixFor( "MODE-2029" ) 
    public void shouldRetrieveNodesUsingIsChildNodeAfterMove() throws Exception { 
        Node rootNode = session().getRootNode(); 
        rootNode.addNode("a").addNode("b").addNode("c"); 
        rootNode.addNode("tmp"); 
        session.save(); 
        Thread.sleep(200); 
        queryAndExpectResults("SELECT * FROM [nt:unstructured] as node WHERE ISCHILDNODE (node, '/a/b')"1); 
 
        session.getWorkspace().move("/a/b""/tmp/b"); 
        Thread.sleep(200); 
 
        queryAndExpectResults("SELECT * FROM [nt:unstructured] as node WHERE ISCHILDNODE (node, '/tmp/b')"1); 
        queryAndExpectResults("SELECT * FROM [nt:unstructured] as node WHERE ISCHILDNODE (node, '/a/b')"0); 
    } 
 
    @Test 
    @FixFor( "MODE-2281" ) 
    public void shouldAllowUsingSystemNodeTypesInNonSystemArea() throws Exception { 
        try { 
            Node rootNode = session().getRootNode(); 
            Node myVersionNode = rootNode.addNode("myVersion""nt:version"); 
            myVersionNode.addNode("frozen""nt:frozenNode"); 
        } catch (ConstraintViolationException e) { 
            if (!e.getMessage().contains("is protected")) { 
                // It's not the exception we expect ... 
                throw e; 
            } 
        } 
    } 
 
    private List<Node> queryAndExpectResults( String queryString, 
                                              int howMany ) throws RepositoryException { 
        QueryManager queryManager = session.getWorkspace().getQueryManager(); 
        Query query = queryManager.createQuery(queryString, Query.JCR_SQL2); 
 
        NodeIterator nodes = query.execute().getNodes(); 
        List<Node> result = new ArrayList<Node>(); 
        while (nodes.hasNext()) { 
            result.add(nodes.nextNode()); 
        } 
        assertEquals("Invalid nodes retrieved from query: " + result, howMany, result.size()); 
        return result; 
    } 
 
    private void queryForAbsentLocalName( String localNodeName ) throws RepositoryException { 
        queryAndExpectResults("select n from [nt:unstructured] as n where localname(n)='" + localNodeName + "'"0); 
    } 
 
    private void queryForExistingLocaLName( String localNodeName, 
                                            String expectedPath ) throws RepositoryException { 
        List<Node> nodes = queryAndExpectResults("select n from [nt:unstructured] as n where localname(n)='" + localNodeName 
                                                 + "'"1); 
        Node node = nodes.get(0); 
        assertEquals(localNodeName, node.getName()); 
        assertEquals(expectedPath, node.getPath()); 
    } 
 
    protected static class PropertyListener implements EventListener { 
        protected int adds, removes, changes; 
 
        @Override 
        public void onEvent( EventIterator events ) { 
            System.out.println("CALLED"); 
            while (events.hasNext()) { 
                javax.jcr.observation.Event event = events.nextEvent(); 
                switch (event.getType()) { 
                    case Event.PROPERTY_ADDED: 
                        ++adds; 
                        break
                    case Event.PROPERTY_CHANGED: 
                        ++changes; 
                        break
                    case Event.PROPERTY_REMOVED: 
                        ++removes; 
                        break
                } 
            } 
        } 
    } 
 
    protected void assertLocalNameAndNamespace( Item item, 
                                                String expectedLocalName, 
                                                String namespacePrefix ) throws RepositoryException { 
        Namespaced nsed = (Namespaced)item; 
        assertThat(nsed.getLocalName(), is(expectedLocalName)); 
        assertThat(nsed.getNamespaceURI(), is(session.getNamespaceURI(namespacePrefix))); 
    } 
 
    protected InputStream getResourceFile( String path ) { 
        return getClass().getClassLoader().getResourceAsStream(path); 
    } 
 
    /**
     * Create a tree of nodes that have different name siblings at the leaves. 
     *  
     * @param parentsCount the number of nodes created under the root node 
     * @param snsCount the number of nodes created under each parent 
     * @throws Exception 
     */
 
    private void createTreeWithSNSint parentsCount, 
                                    int snsCount ) throws Exception { 
        session = repository.login(); 
        List<String> nodeIds = new ArrayList<String>(); 
        Node testRoot = session.getRootNode().addNode("testRoot"); 
 
        for (int i = 0; i < parentsCount; i++) { 
            nodeIds.add(testRoot.addNode("parent" + i).getIdentifier()); 
        } 
 
        for (String parentId : nodeIds) { 
            Node parent = session.getNodeByIdentifier(parentId); 
            for (int c = 0; c < snsCount; c++) { 
                parent.addNode("child"); 
            } 
        } 
 
        session.save(); 
    } 
 
    private void moveSNSWhileCachingPaths( String srcId, 
                                           String destId, 
                                           String destNodeName ) throws Exception { 
        Node targetNode = session.getNodeByIdentifier(destId); 
        String targetNodePath = targetNode.getPath(); 
        targetNode = session.getNode(targetNodePath); 
 
        Node sourceNode = session.getNodeByIdentifier(srcId); 
        String sourceNodePath = sourceNode.getPath(); 
        sourceNode = session.getNode(sourceNodePath); 
        Node sourceParent = sourceNode.getParent(); 
 
        // the next calls will load all the children (sns) and store them in the ws cache 
        loadChildrenByPaths(session, sourceParent); 
        loadChildrenByPaths(session, targetNode); 
 
        session.move(sourceNodePath, targetNodePath + "/" + destNodeName); 
        session.save(); 
 
        // this will expose the problem of the cached paths 
        loadChildrenByPaths(session, sourceParent); 
        loadChildrenByPaths(session, targetNode); 
    } 
 
    private void loadChildrenByPaths( Session session, 
                                      Node parentNode ) throws Exception { 
        NodeIterator nodeIterator = parentNode.getNodes(); 
        while (nodeIterator.hasNext()) { 
            Node childNode = nodeIterator.nextNode(); 
            // this should load &cache the nodes (and their paths) 
            session.getNode(childNode.getPath()); 
        } 
    } 
 
    private void expectReferentialIntegrityException() throws RepositoryException { 
        try { 
            session.save(); 
            fail("Expected a referential integrity exception"); 
        } catch (ReferentialIntegrityException e) { 
            // expected 
            session.refresh(false); 
        } 
    } 
 
    @SuppressWarnings( "deprecation" ) 
    protected String identifierPathFor( String pathToNode ) throws Exception { 
        AbstractJcrNode node = session.getNode(pathToNode); 
        if (node.isNodeType("mix:referenceable")) { 
            // Make sure that the identifier matches the UUID ... 
            assertThat(node.getUUID(), is(node.getIdentifier())); 
        } else { 
            try { 
                node.getUUID(); 
                fail("Should have thrown an UnsupportedRepositoryOperationException if the node " + pathToNode 
                     + " is not referenceable"); 
            } catch (UnsupportedRepositoryOperationException e) { 
                // expected 
            } 
        } 
        return node.identifierPath(); 
    } 
}