4 Replies Latest reply on Dec 20, 2016 3:31 AM by hchiorean

    Cannot delete workspace after merge.

    mfed

      I have two workspaces, one which has been created from the other using session.getWorkspace.createWorkspace(newWorkspace, originalWorkspace). All of my nodes are versionable and most are referenceable.

       

      I've then updated a piece of content in newWorkspace such that newWorkspace is on version '1.0' while originalWorkspace is on version 'jcr:rootVersion'.

       

      At this point, were I to call session.getWorkspace.deleteWorkspace(newWorkspace), the new workspace is deleted as I would expect and newWorkspace remains in it's original state as I would expect.

      Now, if after creating the new workspace i do

      val versionManager = session.getWorkspace.getVersionManager

      val unexpected = versionManager.merge("/MyRootNode", newWorkspace, false, false)

       

      then the piece of content in originalWorkspace is updated to version '1.0' as I would expect and the iterable 'unexpected' which is returned from merge is empty (meaning there were no merge failures).

      However, if I then attempt to delete the new workspace (because I have merged it into the original and I no longer need it), using the same code as above, I get an exception:

      org.modeshape.jcr.cache.ReferentialIntegrityException: Cannot remove some nodes because they are still strongly referred by existing nodes: .....

       

      I must be missing some fundamental understanding. What is it about merging B into A which is then making B un-deletable when pre-merge it would be deletable?

       

      Thanks

      Michael

        • 1. Re: Cannot delete workspace after merge.
          hchiorean

          When you create the new workspace you're cloning the old workspace, meaning that all node IDs and therefore references are preserved. When you do the merge operation, depending on your data (node types and state) I suspect some reference properties are being changed and this change is reflected both in the old and new workspace.  This particular change is most likely causing the error you're seeing.

           

          However, the merge algorithm is so complex (https://docs.adobe.com/content/docs/en/spec/jcr/2.0/15_Versioning.html) that it's impossible to tell if this is a bug or it's working as designed by the spec. If you can create a simple test case for this (simple being the operative keyword) I can look at it some more. You can use our unit tests as example: modeshape/JcrVersioningTest.java at master · ModeShape/modeshape · GitHub

          • 2. Re: Cannot delete workspace after merge.
            mfed

            Ok i'll see if i can setup a simple test case. FWIW, forcing the versions to the latest after merge like so:

            mergedNode.update(newBranch)
            versionManager.checkout(nodePath)

             

            If done for each versionable node in the tree, allows me to delete the new workspace and the original workspace ends in the state which i'd expect post-merge.

            • 3. Re: Cannot delete workspace after merge.
              mfed

              Ok so I haven't put it in the exact format of your existing test cases but I wasn't taking that as the big deal. This case uses the following CND:

               

              <jcr='http://www.jcp.org/jcr/1.0'>

              <nt='http://www.jcp.org/jcr/nt/1.0'>

              <mix='http://www.jcp.org/jcr/mix/1.0'>

              <sc='http:///www.foo.com/foo/sc/1.0'>

               

              [sc:simpleContent] > nt:hierarchyNode, mix:created, mix:referenceable, mix:lastModified, mix:versionable, mix:lockable

                - sc:foo (string)

                - sc:gooRef (reference)

               

                [sc:simpleReference] > nt:hierarchyNode, mix:created, mix:referenceable, mix:lastModified, mix:versionable, mix:lockable

                - sc:goo (string)

               

              The key part above is the reference. Without it, everything works fine (ie if gooRef was a string, everything merges and deletes just fine but obviously i don't get proper referential integrity). Conceptually this is the case:

              • Create workspace "working".
              • Create a sc:simpleContent and a sc:simpleReference and have the former refer to the latter via the "gooRef" property. Give "foo" and "goo" default values of "bar".
              • Create a workspace "newWorkspaceA" from "working".
              • Set the "foo" property on the sc:simpleContent to "dar".
              • Merge "newWorkspaceA" into "working".
              • The "foo" value is now "dar" as expected.
              • The "goo" property in the "gooRef" reference still results in the original "bar" as expected.
              • Attempt to delete "newWorkspaceA". Expect it to delete just fine, but get

              Cannot remove some nodes because they are still strongly referred by existing nodes: {/{}Test/{}TheGoo=[/{}Test/{}TheFoo]}

              org.modeshape.jcr.cache.ReferentialIntegrityException: Cannot remove some nodes because they are still strongly referred by existing nodes: {/{}Test/{}TheGoo=[/{}Test/{}TheFoo]}

                at org.modeshape.jcr.cache.document.WritableSessionCache.persistChanges(WritableSessionCache.java:1477)

                at org.modeshape.jcr.cache.document.WritableSessionCache.save(WritableSessionCache.java:538)

                at org.modeshape.jcr.cache.document.WritableSessionCache.save(WritableSessionCache.java:500)

                at org.modeshape.jcr.cache.RepositoryCache.lambda$destroyWorkspace$17(RepositoryCache.java:933)

                at org.modeshape.jcr.cache.document.LocalDocumentStore.runInTransaction(LocalDocumentStore.java:297)

                at org.modeshape.jcr.cache.RepositoryCache.destroyWorkspace(RepositoryCache.java:926)

                at org.modeshape.jcr.JcrWorkspace.deleteWorkspace(JcrWorkspace.java:873)

               

               

              ------------------------------------------------------------------------------------------------

              My local modeshape source isn't working right now and I don't have the time just now to figure out why so as to write this case in your unit test format. Below is roughly my code using org.modeshape.bom:modeshape-bom-embedded:5.2.0.Final (it's scala. sorry if that is a problem). Any thoughts about what I might be doing incorrectly or if this is a bug would be greatly appreciated. Thanks.

               

                /*********** just helpers, possibly not relevant to the case but included here in case they are ****************/

                val tools = new JcrTools()

                def put(value: String, path: String, property: String, typ: String)(implicit  session: Session) = {

                  val node = tools.findOrCreateNode(session.getRootNode, path, "nt:folder", typ)

                  val versionManager = session.getWorkspace.getVersionManager

                  val absPath = node.getPath

               

                  if (!versionManager.isCheckedOut(absPath))

                    versionManager.checkout(absPath)

               

                  node.setProperty(property, value)

                  session.save()

                  versionManager.checkin(absPath)

                }

               

                def merge(nodeToMerge: String, mergeFrom: String)(implicit  session: Session) = {

                  val versionManager = session.getWorkspace.getVersionManager

                  versionManager.merge(nodeToMerge, mergeFrom, false, false)

                  println(s"Workspace '$mergeFrom' merged into '$nodeToMerge'.")

                }

               

                def delete(workspaceName: String) (implicit  session: Session) = {

                  session.getWorkspace.deleteWorkspace(workspaceName)

                  if(session.getWorkspace.getAccessibleWorkspaceNames.contains(workspaceName))

                    throw new Exception(s"'$workspaceName' was not actually deleted.")

                  println(s"Workspace '$workspaceName' was deleted.")

                }

               

                /*********** end helpers, begin case ****************/

               

                def runShouldNotThrowButDoes() = {

                  val modeshape = getModeshape()

                  //this "withSession" function is just some simple wrapper which does login, gets the session and logs out at the end. nothing fancy.

                  modeshape.withSession("working")(session => {

                    implicit val s: Session = session

                    put("bar", "/Test/TheFoo", "sc:foo", "sc:simpleContent")

                    put("bar", "/Test/TheGoo", "sc:goo", "sc:simpleReference")

                    val referencedNode = session.getNode("/Test/TheGoo")

                    val result = session.getValueFactory.createValue(referencedNode)

                    put(result.toString, "/Test/TheFoo", "sc:gooRef", "sc:simpleContent")

                    session.getWorkspace.createWorkspace("newWorkspaceA", "working")

                  })

               

                  modeshape.withSession("newWorkspaceA")(session => {

                    implicit val s: Session = session

                    put("dar", "/Test/TheFoo", "sc:foo", "sc:simpleContent")

                  })

               

                  modeshape.withSession("working")(session => {

                    implicit val s: Session = session

                    merge("/Test", "newWorkspaceA")

                  })

               

                  modeshape.withSession("working")(session => {

                    implicit val s: Session = session

                    delete("newWorkspaceA") //This throws the given exception.

                  })

                }

               

                runShouldNotThrowButDoes()

              • 4. Re: Cannot delete workspace after merge.
                hchiorean

                I was able to reproduce your test case locally and I will argue that this is not a bug, but rather the behavior is according to the JCR 2.0 spec:

                 

                15.9 Merge

                 

                The merge test is performed by comparing N with its corresponding node in

                srcWorkspace, call it N'.

                 

                The merge test is done by comparing the base version of N (call it V) and the base

                version of N' (call it V').

                ...

                If N is currently checked-in then:

                • If V' is an eventual successor of V, then the merge result for N is update.

                ...

                doupdate(n, n')

                replace set of properties of n with those of n'.

                 

                So in your test case you're merging your updated workspace ("newWorkspaceA") into the original workspace ("working"). According to the algorithm from the spec, the properties of the sc:simpleContent node from the "working" workspace will be updated to have the same value as the properties of the sc:simpleContent node from the "newWorkspaceA" workspace.  This means that that strong reference property sc:gooRef belonging to this node will point to the sc:goo node from the "newWorkspaceA" workspace (remember that when you cloned the workspace the property sc:gooRef from the newWorkspaceA points to the node from within the same workspace).

                 

                In other words, because you're merging corresponding versionable nodes with strong reference properties you're creating strong references across nodes from different workspaces. This is why you cannot remove the "newWorkspaceA" - removing it would break a strong referenceable constraint, where a node from "working" strongly refers a node from "newWorkspaceA".

                 

                Your options are to either not use strong references (use weak or simple references instead) or manually change the value of the reference property after performing the merge.