001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * Unless otherwise indicated, all code in JBoss DNA is licensed 010 * to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.connector.jbosscache; 025 026 import java.util.ArrayList; 027 import java.util.Arrays; 028 import java.util.Collections; 029 import java.util.LinkedList; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Set; 033 import java.util.UUID; 034 import java.util.concurrent.atomic.AtomicInteger; 035 import org.jboss.cache.Cache; 036 import org.jboss.cache.Fqn; 037 import org.jboss.cache.Node; 038 import org.jboss.dna.common.util.Logger; 039 import org.jboss.dna.graph.DnaLexicon; 040 import org.jboss.dna.graph.ExecutionContext; 041 import org.jboss.dna.graph.Location; 042 import org.jboss.dna.graph.connector.RepositorySourceException; 043 import org.jboss.dna.graph.observe.Observer; 044 import org.jboss.dna.graph.property.Name; 045 import org.jboss.dna.graph.property.Path; 046 import org.jboss.dna.graph.property.PathFactory; 047 import org.jboss.dna.graph.property.PathNotFoundException; 048 import org.jboss.dna.graph.property.Property; 049 import org.jboss.dna.graph.property.PropertyFactory; 050 import org.jboss.dna.graph.property.UuidFactory; 051 import org.jboss.dna.graph.request.CloneWorkspaceRequest; 052 import org.jboss.dna.graph.request.CopyBranchRequest; 053 import org.jboss.dna.graph.request.CreateNodeRequest; 054 import org.jboss.dna.graph.request.CreateWorkspaceRequest; 055 import org.jboss.dna.graph.request.DeleteBranchRequest; 056 import org.jboss.dna.graph.request.DestroyWorkspaceRequest; 057 import org.jboss.dna.graph.request.GetWorkspacesRequest; 058 import org.jboss.dna.graph.request.InvalidRequestException; 059 import org.jboss.dna.graph.request.InvalidWorkspaceException; 060 import org.jboss.dna.graph.request.MoveBranchRequest; 061 import org.jboss.dna.graph.request.ReadAllChildrenRequest; 062 import org.jboss.dna.graph.request.ReadAllPropertiesRequest; 063 import org.jboss.dna.graph.request.Request; 064 import org.jboss.dna.graph.request.UpdatePropertiesRequest; 065 import org.jboss.dna.graph.request.VerifyWorkspaceRequest; 066 import org.jboss.dna.graph.request.processor.RequestProcessor; 067 068 /** 069 * A {@link RequestProcessor} implementation that operates upon a {@link Cache JBoss Cache} instance for each workspace in the 070 * {@link JBossCacheSource source}. 071 * <p> 072 * This processor only uses {@link Location} objects with {@link Location#getPath() paths}. Even though every node in the cache is 073 * automatically assigned a UUID (and all operations properly handle UUIDs), these UUIDs are not included in the {@link Location} 074 * objects because the processor is unable to search the cache to find nodes by UUID. 075 * </p> 076 */ 077 public class JBossCacheRequestProcessor extends RequestProcessor { 078 079 private final JBossCacheWorkspaces workspaces; 080 private final boolean creatingWorkspacesAllowed; 081 private final String defaultWorkspaceName; 082 private final PathFactory pathFactory; 083 private final PropertyFactory propertyFactory; 084 private final UuidFactory uuidFactory; 085 086 /** 087 * @param sourceName the name of the source in which this processor is operating 088 * @param context the execution context in which this processor operates 089 * @param observer the observer to which events should be published; may be null if the events are not be published 090 * @param workspaces the manager for the workspaces 091 * @param defaultWorkspaceName the name of the default workspace; never null 092 * @param creatingWorkspacesAllowed true if clients can create new workspaces, or false otherwise 093 */ 094 JBossCacheRequestProcessor( String sourceName, 095 ExecutionContext context, 096 Observer observer, 097 JBossCacheWorkspaces workspaces, 098 String defaultWorkspaceName, 099 boolean creatingWorkspacesAllowed ) { 100 super(sourceName, context, observer); 101 assert workspaces != null; 102 assert defaultWorkspaceName != null; 103 this.workspaces = workspaces; 104 this.creatingWorkspacesAllowed = creatingWorkspacesAllowed; 105 this.defaultWorkspaceName = defaultWorkspaceName; 106 this.pathFactory = context.getValueFactories().getPathFactory(); 107 this.propertyFactory = context.getPropertyFactory(); 108 this.uuidFactory = context.getValueFactories().getUuidFactory(); 109 } 110 111 @Override 112 public void process( ReadAllChildrenRequest request ) { 113 // Look up the cache and the node ... 114 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 115 if (cache == null) return; 116 Path nodePath = request.of().getPath(); 117 Node<Name, Object> node = getNode(request, cache, nodePath); 118 if (node == null) return; 119 120 // Get the names of the children, using the child list ... 121 Path.Segment[] childList = (Path.Segment[])node.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 122 if (childList != null) { 123 for (Path.Segment child : childList) { 124 request.addChild(Location.create(pathFactory.create(nodePath, child))); 125 } 126 } 127 request.setActualLocationOfNode(Location.create(nodePath)); 128 setCacheableInfo(request); 129 } 130 131 @Override 132 public void process( ReadAllPropertiesRequest request ) { 133 // Look up the cache and the node ... 134 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 135 if (cache == null) return; 136 Path nodePath = request.at().getPath(); 137 Node<Name, Object> node = getNode(request, cache, nodePath); 138 if (node == null) return; 139 140 // Get the properties on the node ... 141 Map<Name, Object> dataMap = node.getData(); 142 for (Map.Entry<Name, Object> data : dataMap.entrySet()) { 143 Name propertyName = data.getKey(); 144 // Don't allow the child list property to be accessed 145 if (propertyName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue; 146 Object values = data.getValue(); 147 Property property = propertyFactory.create(propertyName, values); 148 request.addProperty(property); 149 } 150 request.setActualLocationOfNode(Location.create(nodePath)); 151 setCacheableInfo(request); 152 } 153 154 @Override 155 public void process( CreateNodeRequest request ) { 156 // Look up the cache and the node ... 157 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 158 if (cache == null) return; 159 Path parent = request.under().getPath(); 160 Node<Name, Object> parentNode = getNode(request, cache, parent); 161 if (parentNode == null) return; 162 163 // Update the children to account for same-name siblings. 164 // This not only updates the FQN of the child nodes, but it also sets the property that stores the 165 // the array of Path.Segment for the children (since the cache doesn't maintain order). 166 Path.Segment newSegment = updateChildList(cache, parentNode, request.named(), null, getExecutionContext(), true); 167 Node<Name, Object> node = parentNode.addChild(Fqn.fromElements(newSegment)); 168 assert checkChildren(parentNode); 169 170 // Add the UUID property (if required), which may be overwritten by a supplied property ... 171 node.put(DnaLexicon.UUID, uuidFactory.create()); 172 // Now add the properties to the supplied node ... 173 for (Property property : request.properties()) { 174 if (property.size() == 0) continue; 175 Name propName = property.getName(); 176 Object value = null; 177 if (property.size() == 1) { 178 value = property.iterator().next(); 179 } else { 180 value = property.getValuesAsArray(); 181 } 182 node.put(propName, value); 183 } 184 Path nodePath = pathFactory.create(parent, newSegment); 185 request.setActualLocationOfNode(Location.create(nodePath)); 186 recordChange(request); 187 } 188 189 @Override 190 public void process( UpdatePropertiesRequest request ) { 191 // Look up the cache and the node ... 192 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 193 if (cache == null) return; 194 Path nodePath = request.on().getPath(); 195 Node<Name, Object> node = getNode(request, cache, nodePath); 196 if (node == null) return; 197 198 // Now set (or remove) the properties to the supplied node ... 199 for (Map.Entry<Name, Property> entry : request.properties().entrySet()) { 200 Name propName = entry.getKey(); 201 // Don't allow the child list property to be removed or changed 202 if (propName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue; 203 204 Property property = entry.getValue(); 205 if (property == null) { 206 node.remove(propName); 207 continue; 208 } 209 Object value = null; 210 if (property.isSingle()) { 211 value = property.iterator().next(); 212 } else { 213 value = property.getValuesAsArray(); 214 } 215 node.put(propName, value); 216 } 217 request.setActualLocationOfNode(Location.create(nodePath)); 218 recordChange(request); 219 } 220 221 @Override 222 public void process( CopyBranchRequest request ) { 223 // Look up the caches ... 224 Cache<Name, Object> fromCache = getCache(request, request.fromWorkspace()); 225 if (fromCache == null) return; 226 Cache<Name, Object> intoCache = getCache(request, request.intoWorkspace()); 227 if (intoCache == null) return; 228 229 // Look up the current node and the new parent (both of which must exist) ... 230 Path nodePath = request.from().getPath(); 231 Node<Name, Object> node = getNode(request, fromCache, nodePath); 232 if (node == null) return; 233 Path newParentPath = request.into().getPath(); 234 Node<Name, Object> newParent = getNode(request, intoCache, newParentPath); 235 if (newParent == null) return; 236 237 boolean useSameUuids = fromCache != intoCache; 238 UUID uuid = uuidFactory.create(node.get(DnaLexicon.UUID)); 239 UUID newNodeUuid = useSameUuids ? uuid : uuidFactory.create(); 240 241 // Copy the branch ... 242 Name desiredName = request.desiredName(); 243 Path.Segment newSegment = copyNode(intoCache, 244 node, 245 newParent, 246 desiredName, 247 null, 248 true, 249 useSameUuids, 250 newNodeUuid, 251 null, 252 getExecutionContext()); 253 254 Path newPath = pathFactory.create(newParentPath, newSegment); 255 request.setActualLocations(Location.create(nodePath), Location.create(newPath)); 256 recordChange(request); 257 } 258 259 @Override 260 public void process( DeleteBranchRequest request ) { 261 // Look up the cache and the node ... 262 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 263 if (cache == null) return; 264 Path nodePath = request.at().getPath(); 265 Node<Name, Object> node = getNode(request, cache, nodePath); 266 if (node == null) return; 267 268 Path.Segment nameOfRemovedNode = nodePath.getLastSegment(); 269 Node<Name, Object> parent = node.getParent(); 270 if (cache.removeNode(node.getFqn())) { 271 removeFromChildList(cache, parent, nameOfRemovedNode, getExecutionContext()); 272 request.setActualLocationOfNode(Location.create(nodePath)); 273 recordChange(request); 274 } else { 275 String msg = JBossCacheConnectorI18n.unableToDeleteBranch.text(getSourceName(), request.inWorkspace(), nodePath); 276 request.setError(new RepositorySourceException(msg)); 277 } 278 } 279 280 @Override 281 public void process( MoveBranchRequest request ) { 282 // Look up the caches ... 283 Cache<Name, Object> cache = getCache(request, request.inWorkspace()); 284 if (cache == null) return; 285 286 // Look up the current node and the new parent (both of which must exist) ... 287 Path nodePath = request.from().getPath(); 288 Node<Name, Object> node = getNode(request, cache, nodePath); 289 if (node == null) return; 290 Path newParentPath; 291 292 if (request.into() != null) { 293 newParentPath = request.into().getPath(); 294 } else { 295 // into() and before() can't both be null 296 assert request.before() != null; 297 newParentPath = request.before().getPath().getParent(); 298 } 299 300 Path.Segment beforeNodeName = request.before() != null ? request.before().getPath().getLastSegment() : null; 301 Node<Name, Object> newParent = getNode(request, cache, newParentPath); 302 if (newParent == null) return; 303 304 // Copy the branch and use the same UUIDs ... 305 Name desiredName = request.desiredName(); 306 Path.Segment newSegment = copyNode(cache, 307 node, 308 newParent, 309 desiredName, 310 beforeNodeName, 311 true, 312 true, 313 null, 314 null, 315 getExecutionContext()); 316 317 // Now delete the old node ... 318 Node<Name, Object> oldParent = node.getParent(); 319 boolean removed = oldParent.removeChild(node.getFqn().getLastElement()); 320 assert removed; 321 Path.Segment nameOfRemovedNode = nodePath.getLastSegment(); 322 removeFromChildList(cache, oldParent, nameOfRemovedNode, getExecutionContext()); 323 324 Path newPath = pathFactory.create(newParentPath, newSegment); 325 request.setActualLocations(Location.create(nodePath), Location.create(newPath)); 326 recordChange(request); 327 } 328 329 /** 330 * {@inheritDoc} 331 * 332 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest) 333 */ 334 @Override 335 public void process( VerifyWorkspaceRequest request ) { 336 String workspaceName = request.workspaceName(); 337 if (workspaceName == null) workspaceName = defaultWorkspaceName; 338 339 Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, false); 340 if (cache == null) { 341 String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName); 342 request.setError(new InvalidWorkspaceException(msg)); 343 } else { 344 Fqn<?> rootName = Fqn.root(); 345 UUID uuid = uuidFactory.create(cache.get(rootName, DnaLexicon.UUID)); 346 if (uuid == null) { 347 uuid = uuidFactory.create(); 348 cache.put(rootName, DnaLexicon.UUID, uuid); 349 } 350 request.setActualRootLocation(Location.create(pathFactory.createRootPath())); 351 request.setActualWorkspaceName(workspaceName); 352 } 353 } 354 355 /** 356 * {@inheritDoc} 357 * 358 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest) 359 */ 360 @Override 361 public void process( GetWorkspacesRequest request ) { 362 request.setAvailableWorkspaceNames(workspaces.getWorkspaceNames()); 363 } 364 365 /** 366 * {@inheritDoc} 367 * 368 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest) 369 */ 370 @Override 371 public void process( CreateWorkspaceRequest request ) { 372 String workspaceName = request.desiredNameOfNewWorkspace(); 373 if (!creatingWorkspacesAllowed) { 374 String msg = JBossCacheConnectorI18n.unableToCreateWorkspaces.text(getSourceName(), workspaceName); 375 request.setError(new InvalidRequestException(msg)); 376 return; 377 } 378 // Try to create the workspace ... 379 Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, creatingWorkspacesAllowed); 380 if (cache == null) { 381 String msg = JBossCacheConnectorI18n.unableToCreateWorkspace.text(getSourceName(), workspaceName); 382 request.setError(new InvalidWorkspaceException(msg)); 383 return; 384 } 385 // Make sure the root node has a UUID ... 386 Fqn<?> rootName = Fqn.root(); 387 UUID uuid = uuidFactory.create(cache.get(rootName, DnaLexicon.UUID)); 388 if (uuid == null) { 389 uuid = uuidFactory.create(); 390 cache.put(rootName, DnaLexicon.UUID, uuid); 391 } 392 request.setActualRootLocation(Location.create(pathFactory.createRootPath())); 393 request.setActualWorkspaceName(workspaceName); 394 recordChange(request); 395 } 396 397 /** 398 * {@inheritDoc} 399 * 400 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest) 401 */ 402 @Override 403 public void process( CloneWorkspaceRequest request ) { 404 String fromWorkspaceName = request.nameOfWorkspaceToBeCloned(); 405 String toWorkspaceName = request.desiredNameOfTargetWorkspace(); 406 if (!creatingWorkspacesAllowed) { 407 String msg = JBossCacheConnectorI18n.unableToCloneWorkspaces.text(getSourceName(), fromWorkspaceName, toWorkspaceName); 408 request.setError(new InvalidRequestException(msg)); 409 return; 410 } 411 // Make sure there is already a workspace that we're cloning ... 412 Cache<Name, Object> fromCache = workspaces.getWorkspace(fromWorkspaceName, false); 413 if (fromCache == null) { 414 String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), fromWorkspaceName); 415 request.setError(new InvalidWorkspaceException(msg)); 416 return; 417 } 418 419 // Try to create a new workspace with the target name ... 420 Cache<Name, Object> intoCache = workspaces.createWorkspace(toWorkspaceName); 421 if (intoCache == null) { 422 // Couldn't create it because one already exists ... 423 String msg = JBossCacheConnectorI18n.workspaceAlreadyExists.text(getSourceName(), toWorkspaceName); 424 request.setError(new InvalidWorkspaceException(msg)); 425 return; 426 } 427 428 // And finally copy the contents ... 429 Fqn<?> rootName = Fqn.root(); 430 Node<Name, Object> fromRoot = fromCache.getNode(rootName); 431 Node<Name, Object> intoRoot = intoCache.getNode(rootName); 432 intoRoot.clearData(); 433 intoRoot.putAll(fromRoot.getData()); 434 ExecutionContext context = getExecutionContext(); 435 436 // Loop over each child and copy it ... 437 for (Node<Name, Object> child : fromRoot.getChildren()) { 438 copyNode(intoCache, child, intoRoot, null, null, true, true, null, null, context); 439 } 440 441 // Copy the list of child segments in the root (this maintains the order of the children) ... 442 Path.Segment[] childNames = (Path.Segment[])fromRoot.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 443 intoRoot.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames); 444 recordChange(request); 445 } 446 447 /** 448 * {@inheritDoc} 449 * 450 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest) 451 */ 452 @Override 453 public void process( DestroyWorkspaceRequest request ) { 454 Cache<Name, Object> fromCache = workspaces.getWorkspace(request.workspaceName(), false); 455 if (fromCache == null) { 456 String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), request.workspaceName()); 457 request.setError(new InvalidWorkspaceException(msg)); 458 return; 459 } 460 request.setActualRootLocation(Location.create(pathFactory.createRootPath())); 461 recordChange(request); 462 } 463 464 // ---------------------------------------------------------------------------------------------------------------- 465 // Utility methods 466 // ---------------------------------------------------------------------------------------------------------------- 467 468 /** 469 * Obtain the appropriate cache for the supplied workspace name, or set an error on the request if the workspace does not 470 * exist (and could not or should not be created). 471 * 472 * @param request the request 473 * @param workspaceName the workspace name 474 * @return the cache, or null if there is no such workspace 475 */ 476 protected Cache<Name, Object> getCache( Request request, 477 String workspaceName ) { 478 if (workspaceName == null) workspaceName = defaultWorkspaceName; 479 Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, creatingWorkspacesAllowed); 480 if (cache == null) { 481 String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName); 482 request.setError(new InvalidWorkspaceException(msg)); 483 } 484 return cache; 485 } 486 487 protected Fqn<?> getFullyQualifiedName( Path path ) { 488 assert path != null; 489 return Fqn.fromList(path.getSegmentsList()); 490 } 491 492 /** 493 * Get a relative fully-qualified name that consists only of the supplied segment. 494 * 495 * @param pathSegment the segment from which the fully qualified name is to be created 496 * @return the relative fully-qualified name 497 */ 498 protected Fqn<?> getFullyQualifiedName( Path.Segment pathSegment ) { 499 assert pathSegment != null; 500 return Fqn.fromElements(pathSegment); 501 } 502 503 @SuppressWarnings( "unchecked" ) 504 protected Path getPath( PathFactory factory, 505 Fqn<?> fqn ) { 506 List<Path.Segment> segments = (List<Path.Segment>)fqn.peekElements(); 507 return factory.create(factory.createRootPath(), segments); 508 } 509 510 protected Node<Name, Object> getNode( Request request, 511 Cache<Name, Object> cache, 512 Path path ) { 513 ExecutionContext context = getExecutionContext(); 514 if (path == null) { 515 String msg = JBossCacheConnectorI18n.locationsMustHavePath.text(getSourceName(), request); 516 request.setError(new InvalidRequestException(msg)); 517 return null; 518 } 519 // Look up the node with the supplied path ... 520 Fqn<?> fqn = getFullyQualifiedName(path); 521 Node<Name, Object> node = cache.getNode(fqn); 522 if (node == null) { 523 String nodePath = path.getString(context.getNamespaceRegistry()); 524 Path lowestExisting = null; 525 while (fqn != null) { 526 fqn = fqn.getParent(); 527 node = cache.getNode(fqn); 528 if (node != null) { 529 lowestExisting = getPath(context.getValueFactories().getPathFactory(), fqn); 530 fqn = null; 531 } 532 } 533 request.setError(new PathNotFoundException(Location.create(path), lowestExisting, 534 JBossCacheConnectorI18n.nodeDoesNotExist.text(nodePath))); 535 node = null; 536 } 537 return node; 538 539 } 540 541 /** 542 * @param newCache the cache into which the node is to be copied 543 * @param original the node to be copied 544 * @param newParent the new parent of the node to be copied 545 * @param desiredName the desired name of the node in the new location 546 * @param beforeNodeName the node before which the new node should be placed 547 * @param recursive if this is a deep copy 548 * @param reuseOriginalUuids indicates whether the original UUIDs should be used for the copies or new UUIDs should be used 549 * @param uuidForCopyOfOriginal pre-determined UUID for copy of node; ignored if reuseOriginalUuids is true 550 * @param count the count of nodes affected by the operation 551 * @param context the execution context that provides the path factory to be used to create the new path name 552 * @return the path segment that identifies the new node under its new parent 553 */ 554 protected Path.Segment copyNode( Cache<Name, Object> newCache, 555 Node<Name, Object> original, 556 Node<Name, Object> newParent, 557 Name desiredName, 558 Path.Segment beforeNodeName, 559 boolean recursive, 560 boolean reuseOriginalUuids, 561 UUID uuidForCopyOfOriginal, 562 AtomicInteger count, 563 ExecutionContext context ) { 564 assert original != null; 565 assert newParent != null; 566 // Get or create the new node ... 567 Path.Segment name = desiredName != null ? context.getValueFactories().getPathFactory().createSegment(desiredName) : (Path.Segment)original.getFqn() 568 .getLastElement(); 569 570 // Update the children to account for same-name siblings. 571 // This not only updates the FQN of the child nodes, but it also sets the property that stores the 572 // the array of Path.Segment for the children (since the cache doesn't maintain order). 573 Path.Segment newSegment = updateChildList(newCache, newParent, name.getName(), beforeNodeName, context, true); 574 Node<Name, Object> copy = newParent.addChild(getFullyQualifiedName(newSegment)); 575 assert checkChildren(newParent); 576 // Copy the properties ... 577 copy.clearData(); 578 copy.putAll(original.getData()); 579 copy.remove(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); // will be reset later ... 580 581 // Generate a new UUID for the new node, overwriting any existing value from the original ... 582 if (reuseOriginalUuids) uuidForCopyOfOriginal = uuidFactory.create(original.get(DnaLexicon.UUID)); 583 if (uuidForCopyOfOriginal == null) uuidForCopyOfOriginal = uuidFactory.create(); 584 copy.put(DnaLexicon.UUID, uuidForCopyOfOriginal); 585 586 if (count != null) count.incrementAndGet(); 587 if (recursive) { 588 // Loop over each child and call this method ... 589 for (Node<Name, Object> child : original.getChildren()) { 590 copyNode(newCache, child, copy, null, null, true, reuseOriginalUuids, null, count, context); 591 } 592 } 593 return newSegment; 594 } 595 596 /** 597 * Update (or create) the array of {@link Path.Segment path segments} for the children of the supplied node. This array 598 * maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking this method will 599 * change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied 600 * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}. 601 * 602 * @param cache the cache in which the parent exists ... 603 * @param parent the parent node; may not be null 604 * @param changedName the name that should be compared to the existing node siblings to determine whether the same-name 605 * sibling indexes should be updated; may not be null 606 * @param beforeNodeName the name of the node before which this node should be placed; null indicates that this node should be 607 * added as the last child under the node 608 * @param context the execution context; may not be null 609 * @param addChildWithName true if a new child with the supplied name is to be added to the children (but which does not yet 610 * exist in the node's children) 611 * @return the path segment for the new child, or null if <code>addChildWithName</code> was false 612 */ 613 protected Path.Segment updateChildList( Cache<Name, Object> cache, 614 Node<Name, Object> parent, 615 Name changedName, 616 Path.Segment beforeNodeName, 617 ExecutionContext context, 618 boolean addChildWithName ) { 619 assert parent != null; 620 assert changedName != null; 621 assert context != null; 622 Set<Node<Name, Object>> children = parent.getChildren(); 623 if (children.isEmpty() && !addChildWithName) return null; 624 625 // Go through the children, looking for any children with the same name as the 'changedName' 626 List<ChildInfo> childrenWithChangedName = new LinkedList<ChildInfo>(); 627 Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 628 int index = 0; 629 int snsIndex = 0; 630 boolean foundBeforeNode = false; 631 if (childNames != null) { 632 for (Path.Segment childName : childNames) { 633 if (childName.equals(beforeNodeName)) { 634 foundBeforeNode = true; 635 // And add a child info for the new node ... 636 ChildInfo info = new ChildInfo(null, snsIndex++); 637 childrenWithChangedName.add(info); 638 } 639 if (childName.getName().equals(changedName)) { 640 ChildInfo info = new ChildInfo(childName, snsIndex); 641 childrenWithChangedName.add(info); 642 } 643 644 snsIndex++; 645 if (!foundBeforeNode) index++; 646 } 647 648 } 649 if (addChildWithName) { 650 // Make room for the new child at the end of the array ... 651 if (childNames == null) { 652 childNames = new Path.Segment[1]; 653 } else { 654 int numExisting = childNames.length; 655 Path.Segment[] newChildNames = new Path.Segment[numExisting + 1]; 656 System.arraycopy(childNames, 0, newChildNames, 0, index); 657 658 if (index != numExisting) { 659 System.arraycopy(childNames, index, newChildNames, index + 1, numExisting - index); 660 } 661 childNames = newChildNames; 662 } 663 664 if (!foundBeforeNode) { 665 // Make sure that we add a record for the new node if it hasn't previously been added 666 ChildInfo info = new ChildInfo(null, index); 667 childrenWithChangedName.add(info); 668 669 } 670 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName); 671 childNames[index] = newSegment; 672 } 673 assert childNames != null; 674 675 // Now process the children with the same name, which may include a child info for the new node ... 676 assert childrenWithChangedName.isEmpty() == false; 677 if (childrenWithChangedName.size() == 1) { 678 // The child should have no indexes ... 679 ChildInfo child = childrenWithChangedName.get(0); 680 if (child.segment != null && child.segment.hasIndex()) { 681 // The existing child needs to have a new index .. 682 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName); 683 // Replace the child with the correct FQN ... 684 changeNodeName(cache, parent, child.segment, newSegment, context); 685 // Change the segment in the child list ... 686 childNames[child.childIndex] = newSegment; 687 } 688 } else { 689 // There is more than one child with the same name ... 690 int i = 0; 691 for (ChildInfo child : childrenWithChangedName) { 692 if (child.segment != null) { 693 // Determine the new name and index ... 694 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1); 695 // Replace the child with the correct FQN ... 696 changeNodeName(cache, parent, child.segment, newSegment, context); 697 // Change the segment in the child list ... 698 childNames[child.childIndex] = newSegment; 699 } else { 700 // Determine the new name and index ... 701 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1); 702 childNames[child.childIndex] = newSegment; 703 } 704 ++i; 705 } 706 } 707 708 // Record the list of children as a property on the parent ... 709 // (Do this last, as it doesn't need to be done if there's an exception in the above logic) 710 context.getLogger(getClass()).trace("Updating child list of {0} to: {1}", parent.getFqn(), Arrays.asList(childNames)); 711 parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames); // replaces any existing value 712 713 if (addChildWithName) { 714 // Return the segment for the new node ... 715 return childNames[index]; 716 } 717 return null; 718 } 719 720 /** 721 * Update the array of {@link Path.Segment path segments} for the children of the supplied node, based upon a node being 722 * removed. This array maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking 723 * this method will change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied 724 * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}. 725 * 726 * @param cache the cache in which the parent exists ... 727 * @param parent the parent node; may not be null 728 * @param removedNode the segment of the node that was removed, which signals to look for node with the same name; may not be 729 * null 730 * @param context the execution context; may not be null 731 */ 732 protected void removeFromChildList( Cache<Name, Object> cache, 733 Node<Name, Object> parent, 734 Path.Segment removedNode, 735 ExecutionContext context ) { 736 assert parent != null; 737 assert context != null; 738 Set<Node<Name, Object>> children = parent.getChildren(); 739 if (children.isEmpty()) { 740 parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, null); // replaces any existing value 741 return; 742 } 743 744 // Go through the children, looking for any children with the same name as the 'changedName' 745 Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 746 assert childNames != null; 747 int snsIndex = removedNode.getIndex(); 748 int index = 0; 749 Path.Segment[] newChildNames = new Path.Segment[childNames.length - 1]; 750 for (Path.Segment childName : childNames) { 751 if (!childName.getName().equals(removedNode.getName())) { 752 newChildNames[index] = childName; 753 index++; 754 } else { 755 // The name matches ... 756 if (childName.getIndex() < snsIndex) { 757 // Just copy ... 758 newChildNames[index] = childName; 759 index++; 760 } else if (childName.getIndex() == snsIndex) { 761 // don't copy ... 762 } else { 763 // Append an updated segment ... 764 Path.Segment newSegment = context.getValueFactories() 765 .getPathFactory() 766 .createSegment(childName.getName(), childName.getIndex() - 1); 767 newChildNames[index] = newSegment; 768 // Replace the child with the correct FQN ... 769 changeNodeName(cache, parent, childName, newSegment, context); 770 index++; 771 } 772 } 773 } 774 parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, newChildNames); // replaces any existing value 775 } 776 777 protected boolean checkChildren( Node<Name, Object> parent ) { 778 Path.Segment[] childNamesProperty = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 779 Set<Object> childNames = parent.getChildrenNames(); 780 boolean result = true; 781 if (childNamesProperty.length != childNames.size()) result = false; 782 for (int i = 0; i != childNamesProperty.length; ++i) { 783 if (!childNames.contains(childNamesProperty[i])) result = false; 784 } 785 if (!result) { 786 List<Path.Segment> names = new ArrayList<Path.Segment>(); 787 for (Object name : childNames) { 788 names.add((Path.Segment)name); 789 } 790 Collections.sort(names); 791 Logger.getLogger(getClass()).trace("Child list on {0} is: {1}", parent.getFqn(), childNamesProperty); 792 Logger.getLogger(getClass()).trace("Children of {0} is: {1}", parent.getFqn(), names); 793 } 794 return result; 795 } 796 797 /** 798 * Utility class used by the 799 * {@link JBossCacheRequestProcessor#updateChildList(Cache, Node, Name, org.jboss.dna.graph.property.Path.Segment, ExecutionContext, boolean)} 800 * method. 801 * 802 * @author Randall Hauch 803 */ 804 private static class ChildInfo { 805 protected final Path.Segment segment; 806 protected final int childIndex; 807 808 protected ChildInfo( Path.Segment childSegment, 809 int childIndex ) { 810 this.segment = childSegment; 811 this.childIndex = childIndex; 812 } 813 814 @Override 815 public String toString() { 816 return (segment != null ? segment.getString() : "null") + "@" + childIndex; 817 } 818 819 } 820 821 /** 822 * Changes the name of the node in the cache (but does not update the list of child segments stored on the parent). 823 * 824 * @param cache 825 * @param parent 826 * @param existing 827 * @param newSegment 828 * @param context 829 */ 830 protected void changeNodeName( Cache<Name, Object> cache, 831 Node<Name, Object> parent, 832 Path.Segment existing, 833 Path.Segment newSegment, 834 ExecutionContext context ) { 835 assert parent != null; 836 assert existing != null; 837 assert newSegment != null; 838 assert context != null; 839 840 if (existing.equals(newSegment)) return; 841 context.getLogger(getClass()).trace("Renaming {0} to {1} under {2}", existing, newSegment, parent.getFqn()); 842 Node<Name, Object> existingChild = parent.getChild(existing); 843 assert existingChild != null; 844 845 // JBoss Cache can move a node from one node to another node, but the move doesn't change the name; 846 // since you provide the FQN of the parent location, the name of the node cannot be changed. 847 // Therefore, to compensate, we need to create a new child, copy all of the data, move all of the child 848 // nodes of the old node, then remove the old node. 849 850 // Create the new node ... 851 Node<Name, Object> newChild = parent.addChild(Fqn.fromElements(newSegment)); 852 Fqn<?> newChildFqn = newChild.getFqn(); 853 854 // Copy the data ... 855 newChild.putAll(existingChild.getData()); 856 857 // Move the children ... 858 for (Node<Name, Object> grandChild : existingChild.getChildren()) { 859 cache.move(grandChild.getFqn(), newChildFqn); 860 } 861 862 // Remove the existing ... 863 parent.removeChild(existing); 864 } 865 }