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}