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