jackrabbit-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Vijai Kalyan <vijai.kal...@gmail.com>
Subject Reordering of Child Nodes
Date Wed, 12 Dec 2007 17:37:36 GMT

Hello All,

We think we have discovered a problem in reordering of nodes. I apologize
upfront for the long post, but we are hoping the information will help.

Background

We have been attempting to use JackRabbit for meta-data management. 

We have adopted a model where we have meta-meta artifacts, for example like


Model
Atom
Implementation


so that the node names will actually be "Model" and "Atom" and
"Implementation". One reason for this is that this helps us retrieve "all
models in the repository". The othery way of doing this is of course, to
store nodes with the actual name and then include a type property that is
one of "Model", "Atom" or "Implementation".

Attributes are likewise stored as nodes instead of as properties (so that we
can reorder them or look for all defined atttributes across all models and
so on). 

Problem

Assume we have a node "Parent" as a child of the root node. Let us assume
further that "Parent" has three children "Child", "Child", "Child" with the
name property of each set to "1", "2" and "3". Thus we have


root
  |
  + parent
        |
        + child [name = 1]
        |
        + child [name = 2]
        |
        + child [name = 3]


Now we want to reorder the "child" nodes in reverse order of "name"
property. That is, we want to change the above to the following


root
  |
  + parent
        |
        + child [name = 3]
        |
        + child [name = 2]
        |
        + child [name = 1]


We figured that this is equivalent to an algorithm for doing the following

Given an input list {1, 2, ... , N} and a required output list {a1, a2, ...
, aN}, generate a sequence of operations using only the primitive operation 

orderBefore (y, x): x,y ---> y,x

That is orderBefore will place element y before x. I think this is a good
enough abstraction of using the Node::orderBefore method to achieve this. 

We wrote some tests to test our assumptions. Our first program was:-


public class OrderAttributes 
{
	/**
	 * @param args
	 */
	public static void main(String[] args) 
		throws Exception
	{
		Repository repository = new TransientRepository();
		Session session = repository.login(new SimpleCredentials("username",
"password".toCharArray()));
		
	    try 
	    {
	        Node root = session.getRootNode();
	        
	        Node node1 = root.addNode("node1");
	        Node node2 = root.addNode("node2");
	        Node node3 = root.addNode("node3");
	        
	        System.out.println(node1.getName() + " @ " + node1.getIndex());
	        System.out.println(node2.getName() + " @ " + node2.getIndex());
	        System.out.println(node3.getName() + " @ " + node3.getIndex());
	        
	        printChildren(root, System.out);
	        
	        root.orderBefore("node2", "node1");
	        
	        printChildren(root, System.out);
	        
	        root.orderBefore("node3", "node2");

	        printChildren(root, System.out);
	    }
	    finally
	    {
	    	session.logout();
	    }
	}
	
    /**
     * 
     * @param node
     * @param target
     * @throws RepositoryException
     */
    public static void printChildren (Node node, PrintStream target)
        throws RepositoryException
    {
        if (node == null || target == null) 
        {
            return;
        }
        
        target.println(node.getName());
        
        NodeIterator iter = node.getNodes();
        
        while (iter.hasNext())
        {
            Node child = iter.nextNode();
            
            target.println(child.getName());
        }
    }
}


This worked correctly and produce the following output:-


node1 @ 1
node2 @ 1
node3 @ 1

jcr:system
node1
node2
node3

jcr:system
node2
node1
node3

jcr:system
node3
node2
node1


We then attempted to do what we wanted to do:-


public class ReOrderAttributes 
{
	/**
	 * @param args
	 */
	public static void main(String[] args) 
		throws Exception
	{
	        Repository repository = new TransientRepository();
	        Session session = repository.login(new
SimpleCredentials("username", "password".toCharArray()));
		
	    try 
	    {
	        Node root = session.getRootNode();
	        
	        Node parent = root.addNode("parent");
	        
	        Node node1 = createNode(parent, 1);
	        Node node2 = createNode(parent, 2);
	        Node node3 = createNode(parent, 3);
	        
	        session.save();
	        
                     printChildren(parent, System.out);
	        
	        String path1 = getRelativePath(parent, node1.getPath());
	        String path2 = getRelativePath(parent, node2.getPath());
	        String path3 = getRelativePath(parent, node3.getPath());
	        
	        printChildren(parent, System.out);
	        
	        parent.orderBefore(getRelativePath(parent, node2.getPath()),
getRelativePath(parent, node1.getPath()));
	        
	        System.out.println(node1.getPath());
                    System.out.println(node2.getPath());
                    System.out.println(node3.getPath());
	        
	        printChildren(parent, System.out);
	        
	        parent.orderBefore(getRelativePath(parent, node3.getPath()),
getRelativePath(parent, node2.getPath()));

	        printChildren(parent, System.out);
	    }
	    finally
	    {
	    	session.logout();
	    }
	}
	
	/**
	 * 
	 * @param index
	 * @return
	 * @throws RepositoryException
	 */
	private static Node createNode (Node parent, int index)
	    throws RepositoryException
	{
	    Node node = parent.addNode("ChildNode");
	    
	    node.setProperty("name", String.valueOf(index));
	    
	    return node;
	}
	
    /**
     * 
     * @param parent
     * @param target
     * @throws RepositoryException
     */
    private static void printChildren (Node parent, PrintStream target)
        throws RepositoryException
    {
        if (parent == null || target == null) 
        {
            return;
        }
        
        target.println(parent.getName());
        
        NodeIterator iter = parent.getNodes();
        
        while (iter.hasNext())
        {
            Node child = iter.nextNode();
            
            target.print(child.getName());
            
            try
            {
                target.println("[name = " +
child.getProperty("name").getString() + "]");
            }
            catch (Exception ex)
            {
                ex.printStackTrace(System.out);
            }
        }
    }
    
    /**
     * Given an absolute path and a node, this function computes the
relative path
     * from that node to the node pointed to by the given absolute path.
Note that
     * this function does not handle namespace resolution. Hence namespace
qualified
     * paths will not be properly handled by calls to this function.
     * 
     * @param node A {@link javax.jcr.Node} instance relative to which we
want the
     *             node pointed to by the given absolute path.
     * @param absolutePath The absolute path to convert into a relative
path.
     * @return A relative path string.
     * @throws RepositoryException
     * @throws IllegalArgumentException
     * @throws MalformedPathException
     * @throws NoPrefixDeclaredException
     */
    public static String getRelativePath (Node node, String absolutePath)
        throws RepositoryException, MalformedPathException,
NoPrefixDeclaredException
    {
        if (absolutePath.length() <= 0)
        {
            throw new IllegalArgumentException("Invalid absolute path
argument.");
        }
        
        Path mainNodePath = PathFormat.parse(node.getPath(),
DummyNamespaceResolver.getInstance());
        Path relNodePath = PathFormat.parse(absolutePath,
DummyNamespaceResolver.getInstance());
        
        Path relPath = mainNodePath.computeRelativePath(relNodePath);
        
        return PathFormat.format(relPath,
DummyNamespaceResolver.getInstance());
    }
    
    /**
     * @author Vijai Kalyan
     */
    private static class DummyNamespaceResolver extends
AbstractNamespaceResolver
    {
        /** Required by PROS coding standards. Do not remove. */
        public static final String SOURCE_FILE_REVISION = "$ Revision: $";
        
        static final NamespaceResolver instance = new
DummyNamespaceResolver();
        
        static final NamespaceResolver getInstance()
        {
            return instance;
        }

        /**
         * 
         */
        private DummyNamespaceResolver()
        {
            super(false);
        }

        /* (non-Javadoc)
         * @see
org.apache.jackrabbit.name.NamespaceResolver#getPrefix(java.lang.String)
         */
        public String getPrefix(String uri) throws NamespaceException
        {
            return "";
        }

        /* (non-Javadoc)
         * @see
org.apache.jackrabbit.name.NamespaceResolver#getURI(java.lang.String)
         */
        public String getURI(String prefix) throws NamespaceException
        {
            return "";
        }
    }
}


This produced the following output:-


parent
ChildNode[name = 1]
ChildNode[name = 2]
ChildNode[name = 3]
parent
ChildNode[name = 1]
ChildNode[name = 2]
ChildNode[name = 3]
/parent/ChildNode
/parent/ChildNode[2]
/parent/ChildNode[3]
parent
ChildNode[name = 2]
ChildNode[name = 1]
ChildNode[name = 3]
parent
ChildNode[name = 2]
ChildNode[name = 3]
ChildNode[name = 1]


When we debugged, we found that in the following code
(NodeImpl::orderBefore):-


    /**
     * Same as {@link Node#orderBefore(String, String)} except that
     * this method takes a Path.PathElement arguments instead of
     * Strings.
     *
     * @param srcName
     * @param dstName
     * @throws UnsupportedRepositoryOperationException
     * @throws VersionException
     * @throws ConstraintViolationException
     * @throws ItemNotFoundException
     * @throws LockException
     * @throws RepositoryException
     */
    public synchronized void orderBefore(Path.PathElement srcName,
                                         Path.PathElement dstName)
            throws UnsupportedRepositoryOperationException,
VersionException,
            ConstraintViolationException, ItemNotFoundException,
LockException,
            RepositoryException {

        // check state of this instance
        sanityCheck();

        if (!getPrimaryNodeType().hasOrderableChildNodes()) {
            throw new UnsupportedRepositoryOperationException(
                    "child node ordering not supported on node "
                    + safeGetJCRPath());
        }

        // check arguments
        if (srcName.equals(dstName)) {
            // there's nothing to do
            return;
        }

        // check existence
        if (!hasNode(srcName.getName(), srcName.getIndex())) {
            String name;
            try {
                Path.PathElement[] path = new Path.PathElement[] { srcName
};
                name = session.getJCRPath(new
Path.PathBuilder(path).getPath());
            } catch (NameException e) {
                name = srcName.toString();
            } catch (NamespaceException e) {
                name = srcName.toString();
            }
            throw new ItemNotFoundException(safeGetJCRPath()
                    + " has no child node with name " + name);
        }
        if (dstName != null && !hasNode(dstName.getName(),
dstName.getIndex())) {
            String name;
            try {
                Path.PathElement[] path = new Path.PathElement[] { dstName
};
                name = session.getJCRPath(new
Path.PathBuilder(path).getPath());
            } catch (NameException e) {
                name = dstName.toString();
            } catch (NamespaceException e) {
                name = dstName.toString();
            }
            throw new ItemNotFoundException(safeGetJCRPath()
                    + " has no child node with name " + name);
        }

        // make sure this node is checked-out
        if (!internalIsCheckedOut()) {
            String msg = safeGetJCRPath()
                    + ": cannot change child node ordering of a checked-in
node";
            log.debug(msg);
            throw new VersionException(msg);
        }

        // check protected flag
        if (definition.isProtected()) {
            String msg = safeGetJCRPath()
                    + ": cannot change child node ordering of a protected
node";
            log.debug(msg);
            throw new ConstraintViolationException(msg);
        }

        // check lock status
        checkLock();

        ArrayList list = new ArrayList(((NodeState)
state).getChildNodeEntries());
        int srcInd = -1, destInd = -1;
        for (int i = 0; i < list.size(); i++) {
            NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry)
list.get(i);
            if (srcInd == -1) {
                if (entry.getName().equals(srcName.getName())
                        && (entry.getIndex() == srcName.getIndex()
                        || srcName.getIndex() == 0 && entry.getIndex() ==
1)) {
                    srcInd = i;
                }
            }
            if (destInd == -1 && dstName != null) {
                if (entry.getName().equals(dstName.getName())
                        && (entry.getIndex() == dstName.getIndex()
                        || dstName.getIndex() == 0 && entry.getIndex() ==
1)) {
                    destInd = i;
                    if (srcInd != -1) {
                        break;
                    }
                }
            } else {
                if (srcInd != -1) {
                    break;
                }
            }
        }

        // check if resulting order would be different to current order
        if (destInd == -1) {
            if (srcInd == list.size() - 1) {
                // no change, we're done
                return;
            }
        } else {
            if ((destInd - srcInd) == 1) {
                // no change, we're done
                return;
            }
        }

        // reorder list
        if (destInd == -1) {
            list.add(list.remove(srcInd));
        } else {
            if (srcInd < destInd) {
                list.add(destInd, list.get(srcInd));
                list.remove(srcInd);
            } else {
                list.add(destInd, list.remove(srcInd));
            }
        }

        // modify the state of 'this', i.e. the parent node
        NodeState thisState = (NodeState) getOrCreateTransientItemState();
        thisState.setChildNodeEntries(list);
    }


The value of "destInd" and "srcInd" are such that it looks like the original
order of the child nodes is being used. That is, when the second
"orderBefore" call is done, destInd should be 0 and srcInd should be 1.
However, we found that in the above, destInd was 1 and srcInd was 2 (which
is what it would be if the original order of child node entries was being
used).

So, although it looks like the state change is being changed at the end of
the "orderBefore" method, it doesn't seem to be reflected in the next
iteration.

Looking at the documentation, it indicates that calls to "orderBefore" may
not be persisted until a "save" method call is done. So, changing the
program slighlty, we introduced a line "session.save()" just after the first
call to "orderBefore". 

That ended up with the following output:-


parent
ChildNode[name = 1]
ChildNode[name = 2]
ChildNode[name = 3]
parent
ChildNode[name = 1]
ChildNode[name = 2]
ChildNode[name = 3]
/parent[2]/ChildNode[2]
/ChildNode[2]
/parent[2]/ChildNode[3]
parent
ChildNode[name = 2]
ChildNode[name = 1]
ChildNode[name = 3]
Exception in thread "main" javax.jcr.RepositoryException: invalid name:
../ChildNode[2]
	at org.apache.jackrabbit.core.NodeImpl.orderBefore(NodeImpl.java:2044)
	at ReOrderAttributes.main(ReOrderAttributes.java:69)


Note in the above that one of the "child" nodes has lost its parent.

Can someone point out to us what we may be doing wrong?

Thanks,

Vijai.
-- 
View this message in context: http://www.nabble.com/Reordering-of-Child-Nodes-tp14300056p14300056.html
Sent from the Jackrabbit - Users mailing list archive at Nabble.com.

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message