001/* 002 * Copyright 2011 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.cms.repository; 017 018import javax.jcr.ItemExistsException; 019import javax.jcr.ItemNotFoundException; 020import javax.jcr.Node; 021import javax.jcr.NodeIterator; 022import javax.jcr.PathNotFoundException; 023import javax.jcr.Property; 024import javax.jcr.PropertyIterator; 025import javax.jcr.PropertyType; 026import javax.jcr.RepositoryException; 027import javax.jcr.Session; 028import javax.jcr.Value; 029import javax.jcr.version.VersionHistory; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.logger.AbstractLogEnabled; 033import org.apache.avalon.framework.thread.ThreadSafe; 034import org.apache.commons.collections.Predicate; 035import org.apache.commons.collections.PredicateUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.jackrabbit.JcrConstants; 038import org.apache.jackrabbit.core.NodeImpl; 039 040import org.ametys.cms.support.AmetysPredicateUtils; 041import org.ametys.plugins.repository.RepositoryConstants; 042import org.ametys.plugins.repository.collection.AmetysObjectCollectionFactory; 043import org.ametys.plugins.repository.jcr.NameHelper; 044import org.ametys.plugins.repository.jcr.NodeHelper; 045 046/** 047 * Helper for common processing used while synchronizing. 048 */ 049public class CloneComponent extends AbstractLogEnabled implements Component, ThreadSafe 050{ 051 /** Avalon Role */ 052 public static final String ROLE = CloneComponent.class.getName(); 053 054 /** 055 * Adds a node to a parent node using a source node for name, type 056 * and potential UUID. 057 * @param srcNode the source node. 058 * @param parentNode the parent node for the newly created node. 059 * @param nodeName the node name to use. 060 * @return the created node. 061 * @throws RepositoryException if an error occurs. 062 */ 063 public Node addNodeWithUUID(Node srcNode, Node parentNode, String nodeName) throws RepositoryException 064 { 065 if (srcNode.isNodeType(JcrConstants.NT_FROZENNODE)) 066 { 067 String nodeTypeName = srcNode.getProperty(JcrConstants.JCR_FROZENPRIMARYTYPE).getString(); 068 String uuid = null; 069 070 if (srcNode.hasProperty(JcrConstants.JCR_FROZENUUID)) 071 { 072 uuid = srcNode.getProperty(JcrConstants.JCR_FROZENUUID).getString(); 073 } 074 075 if (uuid == null) 076 { 077 return parentNode.addNode(nodeName, nodeTypeName); 078 } 079 else 080 { 081 return ((NodeImpl) parentNode).addNodeWithUuid(nodeName, nodeTypeName, uuid); 082 } 083 } 084 else 085 { 086 if (!srcNode.isNodeType(JcrConstants.MIX_REFERENCEABLE)) 087 { 088 return parentNode.addNode(nodeName, srcNode.getPrimaryNodeType().getName()); 089 } 090 else 091 { 092 return ((NodeImpl) parentNode).addNodeWithUuid(nodeName, srcNode.getPrimaryNodeType().getName(), srcNode.getIdentifier()); 093 } 094 } 095 } 096 097 /** 098 * Clones all the properties of a node. 099 * @param srcNode the source node. 100 * @param clonedNode the cloned node. 101 * @param propertyPredicate the property selector. 102 * @throws RepositoryException if an error occurs. 103 */ 104 public void cloneAllProperties(Node srcNode, Node clonedNode, Predicate propertyPredicate) throws RepositoryException 105 { 106 // First remove existing matching properties from cloned Node 107 PropertyIterator clonedProperties = clonedNode.getProperties(); 108 while (clonedProperties.hasNext()) 109 { 110 Property property = clonedProperties.nextProperty(); 111 if (AmetysPredicateUtils.ignoreProtectedProperties(propertyPredicate).evaluate(property)) 112 { 113 property.remove(); 114 } 115 } 116 117 // Then copy properties 118 PropertyIterator itProperties = srcNode.getProperties(); 119 120 while (itProperties.hasNext()) 121 { 122 Property property = itProperties.nextProperty(); 123 124 // Ignore protected properties 125 if (AmetysPredicateUtils.ignoreProtectedProperties(propertyPredicate).evaluate(property)) 126 { 127 cloneProperty(clonedNode, property); 128 } 129 } 130 } 131 132 /** 133 * Clone a property. 134 * @param clonedNode the node to copy the property to. 135 * @param property the property to clone. 136 * @throws RepositoryException if an error occurs. 137 */ 138 protected void cloneProperty(Node clonedNode, Property property) throws RepositoryException 139 { 140 Session session = clonedNode.getSession(); 141 142 if (property.getType() == PropertyType.REFERENCE) 143 { 144 try 145 { 146 // Clone the referenced nodes. 147 if (property.isMultiple()) 148 { 149 Value[] sourceValues = property.getValues(); 150 Value[] clonedValues = new Value[sourceValues.length]; 151 152 for (int i = 0; i < sourceValues.length; i++) 153 { 154 Node referencedNode = session.getNodeByIdentifier(sourceValues[i].getString()); 155 156 Node clonedReferencedNode = null; 157 try 158 { 159 clonedReferencedNode = clonedNode.getSession().getNodeByIdentifier(referencedNode.getIdentifier()); 160 } 161 catch (ItemNotFoundException e) 162 { 163 clonedReferencedNode = getOrCloneNode(clonedNode.getSession(), referencedNode); 164 } 165 166 clonedValues[i] = session.getValueFactory().createValue(clonedReferencedNode); 167 } 168 169 clonedNode.setProperty(property.getName(), clonedValues); 170 } 171 else 172 { 173 Node referencedNode = property.getNode(); 174 175 Node clonedReferencedNode = null; 176 try 177 { 178 clonedReferencedNode = clonedNode.getSession().getNodeByIdentifier(referencedNode.getIdentifier()); 179 } 180 catch (ItemNotFoundException e) 181 { 182 clonedReferencedNode = getOrCloneNode(clonedNode.getSession(), referencedNode); 183 } 184 185 clonedNode.setProperty(property.getName(), clonedReferencedNode); 186 } 187 } 188 catch (ItemNotFoundException e) 189 { 190 // the target node does not exist anymore, this could be due to workflow having been deleted 191 } 192 } 193 else 194 { 195 if (property.getDefinition().isMultiple()) 196 { 197 clonedNode.setProperty(property.getName(), property.getValues(), property.getType()); 198 } 199 else 200 { 201 clonedNode.setProperty(property.getName(), property.getValue(), property.getType()); 202 } 203 } 204 } 205 206 /** 207 * Clones a node by preserving the source node UUID. 208 * @param srcNode the source node. 209 * @param clonedNode the cloned node. 210 * @param propertyPredicate the property selector. 211 * @param nodePredicate the node selector. 212 * @throws RepositoryException if an error occurs. 213 */ 214 public void cloneNodeAndPreserveUUID(Node srcNode, Node clonedNode, Predicate propertyPredicate, Predicate nodePredicate) throws RepositoryException 215 { 216 // Clone properties 217 cloneAllProperties(srcNode, clonedNode, propertyPredicate); 218 219 // Remove all matching subNodes before cloning, for better handling of same name siblings 220 NodeIterator subNodes = clonedNode.getNodes(); 221 222 while (subNodes.hasNext()) 223 { 224 Node subNode = subNodes.nextNode(); 225 226 if (nodePredicate.evaluate(subNode)) 227 { 228 subNode.remove(); 229 } 230 } 231 232 // Then copy sub nodes 233 NodeIterator itNodes = srcNode.getNodes(); 234 235 while (itNodes.hasNext()) 236 { 237 Node subNode = itNodes.nextNode(); 238 239 if (nodePredicate.evaluate(subNode)) 240 { 241 Node clonedSubNode = addNodeWithUUID(subNode, clonedNode, subNode.getName()); 242 cloneNodeAndPreserveUUID(subNode, clonedSubNode, propertyPredicate, nodePredicate); 243 } 244 } 245 } 246 247 /** 248 * Get or clone a node. 249 * @param destSession the destination session. 250 * @param node the node in the source workspace. 251 * @return the cloned Node in the destination session. 252 * @throws RepositoryException if an error occurs. 253 */ 254 public Node getOrCloneNode(Session destSession, Node node) throws RepositoryException 255 { 256 return getOrCloneNode(destSession, node, null); 257 } 258 259 /** 260 * Get or clone a node in a specified version. 261 * @param destSession the destination session. 262 * @param node the node in the source workspace. 263 * @param version the node version to clone, null to get the current version. 264 * @return the cloned Node in the destination session. 265 * @throws RepositoryException if an error occurs. 266 */ 267 public Node getOrCloneNode(Session destSession, Node node, String version) throws RepositoryException 268 { 269 Node clonedParentNode = cloneAncestorsAndPreserveUUID(node, destSession); 270 String nodeName = node.getName(); 271 272 if (clonedParentNode.hasNode(nodeName)) 273 { 274 return clonedParentNode.getNode(nodeName); 275 } 276 else 277 { 278 Node clonedNode = null; 279 280 if (StringUtils.isNotEmpty(version)) 281 { 282 VersionHistory versionHistory = node.getSession().getWorkspace().getVersionManager().getVersionHistory(node.getPath()); 283 Node validatedNode = versionHistory.getVersionByLabel(version).getFrozenNode(); 284 clonedNode = addNodeWithUUID(validatedNode, clonedParentNode, nodeName); 285 286 cloneNodeAndPreserveUUID(validatedNode, clonedNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 287 } 288 else 289 { 290 clonedNode = addNodeWithUUID(node, clonedParentNode, nodeName); 291 292 cloneNodeAndPreserveUUID(node, clonedNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 293 } 294 295 return clonedNode; 296 } 297 } 298 299 /** 300 * Clones ancestors of a node by preserving the source node UUID. 301 * @param srcNode the source node. 302 * @param destSession the destination session. 303 * @return the parent node the destination workspace. 304 * @throws RepositoryException if an error occurs. 305 */ 306 public Node cloneAncestorsAndPreserveUUID(Node srcNode, Session destSession) throws RepositoryException 307 { 308 if (srcNode.getName().length() == 0) 309 { 310 // We are on the root node which already exists 311 return destSession.getRootNode(); 312 } 313 else 314 { 315 Node destRootNode = destSession.getRootNode(); 316 Node parentNode = srcNode.getParent(); 317 String parentNodePath = parentNode.getPath().substring(1); 318 319 if (parentNodePath.length() == 0) 320 { 321 return destSession.getRootNode(); 322 } 323 else if (destRootNode.hasNode(parentNodePath)) 324 { 325 // Found existing parent 326 return destRootNode.getNode(parentNodePath); 327 } 328 else 329 { 330 Node clonedAncestorNode = cloneAncestorsAndPreserveUUID(parentNode, destSession); 331 Node clonedParentNode = null; 332 333 if (clonedAncestorNode.hasNode(parentNode.getName())) 334 { 335 // Possible with autocreated children 336 clonedParentNode = clonedAncestorNode.getNode(parentNode.getName()); 337 } 338 else 339 { 340 clonedParentNode = addNodeWithUUID(parentNode, clonedAncestorNode, parentNode.getName()); 341 } 342 343 // Copy only properties 344 cloneNodeAndPreserveUUID(parentNode, clonedParentNode, PredicateUtils.truePredicate(), PredicateUtils.falsePredicate()); 345 346 return clonedParentNode; 347 } 348 } 349 } 350 351 /** 352 * Clone a content node, cloning its workflow along. 353 * @param destSession the destination session. 354 * @param node the node to clone. 355 * @return the cloned node. 356 * @throws RepositoryException if an error occurs. 357 */ 358 public Node cloneContentNodeWithWorkflow(Session destSession, Node node) throws RepositoryException 359 { 360 return cloneContentNodeWithWorkflow(destSession, node, PredicateUtils.truePredicate(), PredicateUtils.truePredicate(), null); 361 } 362 363 /** 364 * Clone a content node, cloning its workflow along. 365 * @param destSession the destination session. 366 * @param node the node to clone. 367 * @param propertyPredicate a test on the properties. 368 * @param nodePredicate a test on the nodes. 369 * @return the cloned node. 370 * @throws RepositoryException if an error occurs. 371 */ 372 public Node cloneContentNodeWithWorkflow(Session destSession, Node node, Predicate propertyPredicate, Predicate nodePredicate) throws RepositoryException 373 { 374 return cloneContentNodeWithWorkflow(destSession, node, propertyPredicate, nodePredicate, null); 375 } 376 377 /** 378 * Clone a content node, cloning its workflow along. 379 * @param destSession the destination session. 380 * @param node the node to clone. 381 * @param propertyPredicate a test on the properties. 382 * @param nodePredicate a test on the nodes. 383 * @param version the version of the node to clone. 384 * @return the cloned node. 385 * @throws RepositoryException if an error occurs. 386 */ 387 public Node cloneContentNodeWithWorkflow(Session destSession, Node node, Predicate propertyPredicate, Predicate nodePredicate, String version) throws RepositoryException 388 { 389 Node clonedContentParentNode = null; 390 391 String nodeName = node.getName(); 392 393 if (_inCollection(node)) 394 { 395 // Clone the ancestors until the collection (hashed nodes will be processed separatel). 396 Node destCollectionNode = cloneAncestorsAndPreserveUUID(node.getParent().getParent(), destSession); 397 // Search for an available node name. 398 nodeName = _getAvailableNodeName(destCollectionNode, node); 399 400 // Create the two hashed nodes, the content will be created in the second one. 401 clonedContentParentNode = NodeHelper.getOrCreateFinalHashNode(destCollectionNode, nodeName); 402 } 403 else 404 { 405 clonedContentParentNode = cloneAncestorsAndPreserveUUID(node, destSession); 406 } 407 408 Node clonedNode = null; 409 try 410 { 411 clonedNode = destSession.getNodeByIdentifier(node.getIdentifier()); 412 } 413 catch (ItemNotFoundException e) 414 { 415 clonedNode = createNodeClone(node, clonedContentParentNode, nodeName); 416 } 417 418 cloneNodeAndPreserveUUID(node, clonedNode, propertyPredicate, nodePredicate); 419 420 // Clone unversioned metadata 421 String unversionedNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":unversioned"; 422 Node unversionedNode = null; 423 424 if (clonedNode.hasNode(unversionedNodeName)) 425 { 426 unversionedNode = clonedNode.getNode(unversionedNodeName); 427 } 428 else 429 { 430 unversionedNode = clonedNode.addNode(unversionedNodeName, "ametys:compositeMetadata"); 431 } 432 433 cloneNodeAndPreserveUUID(node.getNode(unversionedNodeName), unversionedNode, propertyPredicate, nodePredicate); 434 435 return clonedNode; 436 } 437 438 private boolean _inCollection(Node node) throws RepositoryException 439 { 440 boolean inCollection = false; 441 442 try 443 { 444 inCollection = node.getParent().isNodeType(AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE) 445 && node.getParent().getParent().isNodeType(AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE) 446 && node.getParent().getParent().getParent().isNodeType(AmetysObjectCollectionFactory.COLLECTION_NODETYPE); 447 } 448 catch (ItemNotFoundException e) 449 { 450 // Ignore, one parent does not exist, just return false. 451 } 452 453 return inCollection; 454 } 455 456 private String _getAvailableNodeName(Node destCollectionNode, Node srcNode) throws RepositoryException 457 { 458 String baseName = srcNode.getName(); 459 String nodeName = NameHelper.filterName(baseName); 460 461 int index = 2; 462 while (_nodeExistsInCollection(destCollectionNode, nodeName)) 463 { 464 baseName = srcNode.getName() + "-" + index; 465 nodeName = NameHelper.filterName(baseName); 466 index++; 467 } 468 469 return nodeName; 470 } 471 472 private boolean _nodeExistsInCollection(Node collectionNode, String nodeName) throws RepositoryException 473 { 474 try 475 { 476 return collectionNode.hasNode(NodeHelper.getFullHashPath(nodeName)); 477 } 478 catch (PathNotFoundException e) 479 { 480 // If a path element is not found, the node doesn't exist. 481 return false; 482 } 483 } 484 485 /** 486 * Create a clone of the specified node. 487 * @param srcNode the node to clone. 488 * @param parentNode the node under which to create the clonde. 489 * @param desiredNodeName the wanted node name. 490 * @return the cloned node. 491 * @throws RepositoryException if an error occurs. 492 */ 493 protected Node createNodeClone(Node srcNode, Node parentNode, String desiredNodeName) throws RepositoryException 494 { 495 Node clonedNode = null; 496 497 String nodeName = NameHelper.filterName(desiredNodeName); 498 499 int errorCount = 0; 500 do 501 { 502 try 503 { 504 clonedNode = addNodeWithUUID(srcNode, parentNode, nodeName); 505 } 506 catch (ItemExistsException e) 507 { 508 // Node name is already used. 509 errorCount++; 510 511 nodeName = NameHelper.filterName(desiredNodeName + " " + (errorCount + 1)); 512 } 513 } 514 while (clonedNode == null); 515 516 return clonedNode; 517 } 518 519 /** 520 * Reorder a node, mirroring the order in the default workspace. 521 * @param parentNode the parent of the source Node in the default workspace. 522 * @param nodeName the node name. 523 * @param node the node in the destination workspace to be reordered. 524 * @throws RepositoryException if an error occurs. 525 */ 526 public void orderNode(Node parentNode, String nodeName, Node node) throws RepositoryException 527 { 528 Session destSession = node.getSession(); 529 530 // iterate over the siblings to find the following 531 NodeIterator siblings = parentNode.getNodes(); 532 boolean iterate = true; 533 534 while (siblings.hasNext() && iterate) 535 { 536 Node sibling = siblings.nextNode(); 537 iterate = !sibling.getName().equals(nodeName); 538 } 539 540 // iterator is currently on the pageNode 541 542 Node nextSibling = null; 543 while (siblings.hasNext() && nextSibling == null) 544 { 545 Node sibling = siblings.nextNode(); 546 String name = sibling.getName(); 547 String path = sibling.getPath(); 548 if (!name.startsWith(RepositoryConstants.NAMESPACE_PREFIX + ":") && !name.startsWith(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":") && destSession.itemExists(path)) 549 { 550 nextSibling = sibling; 551 } 552 } 553 554 // nextSibling is either null meaning that the Node must be ordered last or is equals to the following sibling 555 if (nextSibling != null) 556 { 557 node.getParent().orderBefore(nodeName, nextSibling.getName()); 558 } 559 else 560 { 561 node.getParent().orderBefore(nodeName, null); 562 } 563 } 564 565}