001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.repository.jcr;
017
018import java.util.ArrayList;
019import java.util.List;
020
021import javax.jcr.Node;
022import javax.jcr.NodeIterator;
023import javax.jcr.RepositoryException;
024
025import org.apache.cocoon.util.HashUtil;
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.plugins.repository.AmetysRepositoryException;
029import org.ametys.plugins.repository.collection.AmetysObjectCollectionFactory;
030
031/**
032 * Provides helper methods on nodes.
033 */
034public final class NodeHelper
035{
036    private NodeHelper()
037    {
038        // Hides the default constructor.
039    }
040
041    /**
042     * Rename the given {@link Node} with the given new name
043     * @param node the node to rename
044     * @param newName the new name of the node
045     * @throws AmetysRepositoryException if an error occurs.
046     */
047    public static void rename(Node node, String newName) throws AmetysRepositoryException
048    {
049        try
050        {
051            Node parentNode = node.getParent();
052            boolean order = parentNode.getPrimaryNodeType().hasOrderableChildNodes();
053            Node nextSibling = null;
054            
055            if (order)
056            {
057                // iterate over the siblings to find the following
058                NodeIterator siblings = parentNode.getNodes();
059                boolean iterate = true;
060                
061                while (siblings.hasNext() && iterate)
062                {
063                    Node sibling = siblings.nextNode();
064                    iterate = !sibling.getName().equals(node.getName());
065                }
066                
067                // iterator is currently on the node
068                while (siblings.hasNext() && nextSibling == null)
069                {
070                    Node sibling = siblings.nextNode();
071                    String path = sibling.getPath();
072                    if (node.getSession().itemExists(path))
073                    {
074                        nextSibling = sibling;
075                    }
076                }
077            }
078            
079            node.getSession().move(node.getPath(), node.getParent().getPath() + "/" + newName);
080            
081            if (order)
082            {
083                // nextSibling is either null meaning that the Node must be ordered last or is equals to the following sibling
084                if (nextSibling != null)
085                {
086                    parentNode.orderBefore(newName, nextSibling.getName());
087                }
088                else
089                {
090                    parentNode.orderBefore(newName, null);
091                }
092            }
093        }
094        catch (RepositoryException e)
095        {
096            throw new AmetysRepositoryException(e);
097        }
098    }
099    
100    /**
101     * Computes a hashed path in the JCR tree from the name of the child object.<br>
102     * Subclasses may override this method to provide a more suitable hash function.<br>
103     * This implementation relies on the buzhash algorithm.
104     * This method MUST return an array of the same length for each name.
105     * @param name the name of the child object
106     * @return a hashed path of the name.
107     */
108    public static List<String> hashAsList(String name)
109    {
110        long hash = Math.abs(HashUtil.hash(name));
111        String hashStr = Long.toString(hash, 16);
112        hashStr = StringUtils.leftPad(hashStr, 4, '0');
113        return List.of(hashStr.substring(0, 2), hashStr.substring(2, 4));
114    }
115    
116    /**
117     * Get the path with hashed nodes.
118     * @param name the name of the final node
119     * @return the path with hashed nodes
120     */
121    public static String getFullHashPath(String name)
122    {
123        List<String> pathList = new ArrayList<>(hashAsList(name));
124        pathList.add(name);
125        return StringUtils.join(pathList, "/");
126    }
127
128    /**
129     * Get or create hash nodes for the given name, intermediate nodes are of type {@value AmetysObjectCollectionFactory#COLLECTION_ELEMENT_NODETYPE}.
130     * The final node is not created.
131     * The session is not saved.
132     * @param parentNode the parent node of the hashed nodes
133     * @param name the name of the final node.
134     * @return the last level of hashed nodes
135     * @throws AmetysRepositoryException if an error occurs
136     */
137    public static Node getOrCreateFinalHashNode(Node parentNode, String name) throws AmetysRepositoryException
138    {
139        return getOrCreateFinalHashNode(parentNode, name, AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE);
140    }
141    
142    /**
143     * Get or create hash nodes for the given name and intermediate primary type.
144     * The final node is not created.
145     * The session is not saved.
146     * @param parentNode the parent node of the hashed nodes
147     * @param name the name of the final node.
148     * @param hashType the primary type of hashed nodes
149     * @return the last level of hashed nodes
150     * @throws AmetysRepositoryException if an error occurs
151     */
152    public static Node getOrCreateFinalHashNode(Node parentNode, String name, String hashType) throws AmetysRepositoryException
153    {
154        try
155        {
156            Node finalNode = parentNode;
157            
158            for (String hashPart : hashAsList(name))
159            {
160                finalNode = finalNode.hasNode(hashPart)
161                        ? finalNode.getNode(hashPart)
162                        : finalNode.addNode(hashPart, hashType);
163            }
164            
165            return finalNode;
166        }
167        catch (RepositoryException e)
168        {
169            throw new AmetysRepositoryException(e);
170        }
171    }
172}