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.Property; 024import javax.jcr.PropertyIterator; 025import javax.jcr.RepositoryException; 026 027import org.apache.cocoon.util.HashUtil; 028import org.apache.commons.lang3.StringUtils; 029import org.apache.jackrabbit.JcrConstants; 030import org.apache.jackrabbit.core.NodeImpl; 031 032import org.ametys.plugins.repository.AmetysRepositoryException; 033import org.ametys.plugins.repository.collection.AmetysObjectCollectionFactory; 034 035/** 036 * Provides helper methods on nodes. 037 */ 038public final class NodeHelper 039{ 040 private NodeHelper() 041 { 042 // Hides the default constructor. 043 } 044 045 /** 046 * Rename the given {@link Node} with the given new name 047 * @param node the node to rename 048 * @param newName the new name of the node 049 * @throws AmetysRepositoryException if an error occurs. 050 */ 051 public static void rename(Node node, String newName) throws AmetysRepositoryException 052 { 053 try 054 { 055 Node parentNode = node.getParent(); 056 boolean order = parentNode.getPrimaryNodeType().hasOrderableChildNodes(); 057 Node nextSibling = null; 058 059 if (order) 060 { 061 // iterate over the siblings to find the following 062 NodeIterator siblings = parentNode.getNodes(); 063 boolean iterate = true; 064 065 while (siblings.hasNext() && iterate) 066 { 067 Node sibling = siblings.nextNode(); 068 iterate = !sibling.getName().equals(node.getName()); 069 } 070 071 // iterator is currently on the node 072 while (siblings.hasNext() && nextSibling == null) 073 { 074 Node sibling = siblings.nextNode(); 075 String path = sibling.getPath(); 076 if (node.getSession().itemExists(path)) 077 { 078 nextSibling = sibling; 079 } 080 } 081 } 082 083 node.getSession().move(node.getPath(), node.getParent().getPath() + "/" + newName); 084 085 if (order) 086 { 087 // nextSibling is either null meaning that the Node must be ordered last or is equals to the following sibling 088 if (nextSibling != null) 089 { 090 parentNode.orderBefore(newName, nextSibling.getName()); 091 } 092 else 093 { 094 parentNode.orderBefore(newName, null); 095 } 096 } 097 } 098 catch (RepositoryException e) 099 { 100 throw new AmetysRepositoryException(e); 101 } 102 } 103 104 /** 105 * Clone the node to the given parent node keeping the identifiers. 106 * To be able to do that, parent node should be in another workspace than the node to copy. 107 * The destination node should not already contains a node with the same name as the node to copy except if same name siblings is allowed. 108 * This method does not save the parent node. 109 * @param nodeToCopy The node to copy. 110 * @param parentNode The parent node of the destination node. 111 * @return the create node 112 * @throws AmetysRepositoryException if an error occurs 113 */ 114 public static Node cloneNode(Node nodeToCopy, Node parentNode) throws AmetysRepositoryException 115 { 116 try 117 { 118 return cloneNode(nodeToCopy, parentNode, nodeToCopy.getName()); 119 } 120 catch (RepositoryException e) 121 { 122 throw new AmetysRepositoryException(e); 123 } 124 } 125 126 /** 127 * Clone the node to the given parent node keeping the identifiers. 128 * To be able to do that, parent node should be in another workspace than the node to copy. 129 * The destination node should not already contains a node with the given node name except if same name siblings is allowed. 130 * This method does not save the parent node. 131 * @param nodeToCopy The node to copy. 132 * @param parentNode The parent node of the destination node. 133 * @param nodeName The destination node name. 134 * @return the create node 135 * @throws AmetysRepositoryException if an error occurs 136 */ 137 public static Node cloneNode(Node nodeToCopy, Node parentNode, String nodeName) throws AmetysRepositoryException 138 { 139 try 140 { 141 // Create the base node with the identifier if the node is referenceable 142 Node destNode = nodeToCopy.isNodeType(JcrConstants.MIX_REFERENCEABLE) 143 ? ((NodeImpl) parentNode).addNodeWithUuid(nodeName, nodeToCopy.getPrimaryNodeType().getName(), nodeToCopy.getIdentifier()) 144 : parentNode.addNode(nodeName, nodeToCopy.getPrimaryNodeType().getName()); 145 146 // Copy properties 147 PropertyIterator properties = nodeToCopy.getProperties(); 148 while (properties.hasNext()) 149 { 150 Property property = properties.nextProperty(); 151 152 if (!property.getDefinition().isProtected()) 153 { 154 if (property.isMultiple()) 155 { 156 destNode.setProperty(property.getName(), property.getValues(), property.getType()); 157 } 158 else 159 { 160 destNode.setProperty(property.getName(), property.getValue(), property.getType()); 161 } 162 } 163 } 164 165 // Copy sub-nodes 166 NodeIterator subNodes = nodeToCopy.getNodes(); 167 while (subNodes.hasNext()) 168 { 169 Node subNode = subNodes.nextNode(); 170 if (subNode.getDefinition().isAutoCreated()) 171 { 172 destNode.getNode(subNode.getName()).remove(); 173 } 174 cloneNode(subNode, destNode); 175 } 176 177 return destNode; 178 } 179 catch (RepositoryException e) 180 { 181 throw new AmetysRepositoryException(e); 182 } 183 } 184 185 /** 186 * Computes a hashed path in the JCR tree from the name of the child object.<br> 187 * Subclasses may override this method to provide a more suitable hash function.<br> 188 * This implementation relies on the buzhash algorithm. 189 * This method MUST return an array of the same length for each name. 190 * @param name the name of the child object 191 * @return a hashed path of the name. 192 */ 193 public static List<String> hashAsList(String name) 194 { 195 long hash = Math.abs(HashUtil.hash(name)); 196 String hashStr = Long.toString(hash, 16); 197 hashStr = StringUtils.leftPad(hashStr, 4, '0'); 198 return List.of(hashStr.substring(0, 2), hashStr.substring(2, 4)); 199 } 200 201 /** 202 * Get the path with hashed nodes. 203 * @param name the name of the final node 204 * @return the path with hashed nodes 205 */ 206 public static String getFullHashPath(String name) 207 { 208 List<String> pathList = new ArrayList<>(hashAsList(name)); 209 pathList.add(name); 210 return StringUtils.join(pathList, "/"); 211 } 212 213 /** 214 * Get or create hash nodes for the given name, intermediate nodes are of type {@value AmetysObjectCollectionFactory#COLLECTION_ELEMENT_NODETYPE}. 215 * The final node is not created. 216 * The session is not saved. 217 * @param parentNode the parent node of the hashed nodes 218 * @param name the name of the final node. 219 * @return the last level of hashed nodes 220 * @throws AmetysRepositoryException if an error occurs 221 */ 222 public static Node getOrCreateFinalHashNode(Node parentNode, String name) throws AmetysRepositoryException 223 { 224 return getOrCreateFinalHashNode(parentNode, name, AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE); 225 } 226 227 /** 228 * Get or create hash nodes for the given name and intermediate primary type. 229 * The final node is not created. 230 * The session is not saved. 231 * @param parentNode the parent node of the hashed nodes 232 * @param name the name of the final node. 233 * @param hashType the primary type of hashed nodes 234 * @return the last level of hashed nodes 235 * @throws AmetysRepositoryException if an error occurs 236 */ 237 public static Node getOrCreateFinalHashNode(Node parentNode, String name, String hashType) throws AmetysRepositoryException 238 { 239 try 240 { 241 Node finalNode = parentNode; 242 243 for (String hashPart : hashAsList(name)) 244 { 245 finalNode = finalNode.hasNode(hashPart) 246 ? finalNode.getNode(hashPart) 247 : finalNode.addNode(hashPart, hashType); 248 } 249 250 return finalNode; 251 } 252 catch (RepositoryException e) 253 { 254 throw new AmetysRepositoryException(e); 255 } 256 } 257}