001 /* 002 * JBoss, Home of Professional Open Source. 003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors 004 * as indicated by the @author tags. See the copyright.txt file in the 005 * distribution for a full listing of individual contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 package org.jboss.dna.connector.jbosscache; 023 024 import java.util.ArrayList; 025 import java.util.Collections; 026 import java.util.LinkedList; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 import java.util.UUID; 031 import java.util.concurrent.TimeUnit; 032 import javax.transaction.xa.XAResource; 033 import org.jboss.cache.Cache; 034 import org.jboss.cache.Fqn; 035 import org.jboss.cache.Node; 036 import org.jboss.dna.common.util.StringUtil; 037 import org.jboss.dna.graph.ExecutionContext; 038 import org.jboss.dna.graph.cache.CachePolicy; 039 import org.jboss.dna.graph.commands.CopyBranchCommand; 040 import org.jboss.dna.graph.commands.CopyNodeCommand; 041 import org.jboss.dna.graph.commands.CreateNodeCommand; 042 import org.jboss.dna.graph.commands.DeleteBranchCommand; 043 import org.jboss.dna.graph.commands.GetChildrenCommand; 044 import org.jboss.dna.graph.commands.GetPropertiesCommand; 045 import org.jboss.dna.graph.commands.GraphCommand; 046 import org.jboss.dna.graph.commands.MoveBranchCommand; 047 import org.jboss.dna.graph.commands.RecordBranchCommand; 048 import org.jboss.dna.graph.commands.SetPropertiesCommand; 049 import org.jboss.dna.graph.commands.executor.AbstractCommandExecutor; 050 import org.jboss.dna.graph.commands.executor.CommandExecutor; 051 import org.jboss.dna.graph.connectors.RepositoryConnection; 052 import org.jboss.dna.graph.connectors.RepositorySourceException; 053 import org.jboss.dna.graph.connectors.RepositorySourceListener; 054 import org.jboss.dna.graph.properties.Name; 055 import org.jboss.dna.graph.properties.NameFactory; 056 import org.jboss.dna.graph.properties.Path; 057 import org.jboss.dna.graph.properties.PathFactory; 058 import org.jboss.dna.graph.properties.PathNotFoundException; 059 import org.jboss.dna.graph.properties.Property; 060 import org.jboss.dna.graph.properties.PropertyFactory; 061 import org.jboss.dna.graph.properties.ValueFactory; 062 import org.jboss.dna.graph.properties.Path.Segment; 063 064 /** 065 * The repository connection to a JBoss Cache instance. 066 * 067 * @author Randall Hauch 068 */ 069 public class JBossCacheConnection implements RepositoryConnection { 070 071 protected static final RepositorySourceListener NO_OP_LISTENER = new RepositorySourceListener() { 072 073 /** 074 * {@inheritDoc} 075 */ 076 public void notify( String sourceName, 077 Object... events ) { 078 // do nothing 079 } 080 }; 081 082 private Name uuidPropertyName; 083 private final JBossCacheSource source; 084 private final Cache<Name, Object> cache; 085 private RepositorySourceListener listener = NO_OP_LISTENER; 086 087 JBossCacheConnection( JBossCacheSource source, 088 Cache<Name, Object> cache ) { 089 assert source != null; 090 assert cache != null; 091 this.source = source; 092 this.cache = cache; 093 } 094 095 /** 096 * @return cache 097 */ 098 /*package*/Cache<Name, Object> getCache() { 099 return cache; 100 } 101 102 /** 103 * {@inheritDoc} 104 */ 105 public String getSourceName() { 106 return source.getName(); 107 } 108 109 /** 110 * {@inheritDoc} 111 */ 112 public CachePolicy getDefaultCachePolicy() { 113 return source.getDefaultCachePolicy(); 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 public XAResource getXAResource() { 120 return null; 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 public boolean ping( long time, 127 TimeUnit unit ) { 128 this.cache.getRoot(); 129 return true; 130 } 131 132 /** 133 * {@inheritDoc} 134 */ 135 public void setListener( RepositorySourceListener listener ) { 136 this.listener = listener != null ? listener : NO_OP_LISTENER; 137 } 138 139 /** 140 * {@inheritDoc} 141 */ 142 public void close() { 143 // do nothing 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 public void execute( ExecutionContext context, 150 GraphCommand... commands ) throws RepositorySourceException { 151 // Now execute the commands ... 152 CommandExecutor executor = new Executor(context, this.getSourceName()); 153 for (GraphCommand command : commands) { 154 executor.execute(command); 155 } 156 } 157 158 /** 159 * @return listener 160 */ 161 protected RepositorySourceListener getListener() { 162 return this.listener; 163 } 164 165 /** 166 * Utility method to calculate (if required) and obtain the name that should be used to store the UUID values for each node. 167 * This method may be called without regard to synchronization, since it should return the same value if it happens to be 168 * called concurrently while not yet initialized. 169 * 170 * @param context the execution context 171 * @return the name, or null if the UUID should not be stored 172 */ 173 protected Name getUuidPropertyName( ExecutionContext context ) { 174 if (uuidPropertyName == null) { 175 NameFactory nameFactory = context.getValueFactories().getNameFactory(); 176 uuidPropertyName = nameFactory.create(this.source.getUuidPropertyName()); 177 } 178 return this.uuidPropertyName; 179 } 180 181 protected Fqn<?> getFullyQualifiedName( Path path ) { 182 assert path != null; 183 return Fqn.fromList(path.getSegmentsList()); 184 } 185 186 /** 187 * Get a relative fully-qualified name that consists only of the supplied segment. 188 * 189 * @param pathSegment the segment from which the fully qualified name is to be created 190 * @return the relative fully-qualified name 191 */ 192 protected Fqn<?> getFullyQualifiedName( Path.Segment pathSegment ) { 193 assert pathSegment != null; 194 return Fqn.fromElements(pathSegment); 195 } 196 197 @SuppressWarnings( "unchecked" ) 198 protected Path getPath( PathFactory factory, 199 Fqn<?> fqn ) { 200 List<Path.Segment> segments = (List<Path.Segment>)fqn.peekElements(); 201 return factory.create(factory.createRootPath(), segments); 202 } 203 204 protected Node<Name, Object> getNode( ExecutionContext context, 205 Path path ) { 206 // Look up the node with the supplied path ... 207 Fqn<?> fqn = getFullyQualifiedName(path); 208 Node<Name, Object> node = cache.getNode(fqn); 209 if (node == null) { 210 String nodePath = path.getString(context.getNamespaceRegistry()); 211 Path lowestExisting = null; 212 while (fqn != null) { 213 fqn = fqn.getParent(); 214 node = cache.getNode(fqn); 215 if (node != null) { 216 lowestExisting = getPath(context.getValueFactories().getPathFactory(), fqn); 217 fqn = null; 218 } 219 } 220 throw new PathNotFoundException(path, lowestExisting, JBossCacheConnectorI18n.nodeDoesNotExist.text(nodePath)); 221 } 222 return node; 223 224 } 225 226 protected UUID generateUuid() { 227 return UUID.randomUUID(); 228 } 229 230 protected int copyNode( Node<Name, Object> original, 231 Node<Name, Object> newParent, 232 boolean recursive, 233 Name uuidProperty, 234 ExecutionContext context ) { 235 assert original != null; 236 assert newParent != null; 237 // Get or create the new node ... 238 Segment name = (Segment)original.getFqn().getLastElement(); 239 240 // Update the children to account for same-name siblings. 241 // This not only updates the FQN of the child nodes, but it also sets the property that stores the 242 // the array of Path.Segment for the children (since the cache doesn't maintain order). 243 Path.Segment newSegment = updateChildList(newParent, name.getName(), context, true); 244 Node<Name, Object> copy = newParent.addChild(getFullyQualifiedName(newSegment)); 245 assert checkChildren(newParent); 246 // Copy the properties ... 247 copy.clearData(); 248 copy.putAll(original.getData()); 249 if (uuidProperty != null) { 250 // Generate a new UUID for the new node, overwriting any existing value from the original ... 251 copy.put(uuidProperty, generateUuid()); 252 } 253 int numNodesCopied = 1; 254 if (recursive) { 255 // Loop over each child and call this method ... 256 for (Node<Name, Object> child : original.getChildren()) { 257 numNodesCopied += copyNode(child, copy, true, uuidProperty, context); 258 } 259 } 260 return numNodesCopied; 261 } 262 263 /** 264 * Update (or create) the array of {@link Path.Segment path segments} for the children of the supplied node. This array 265 * maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking this method will 266 * change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied 267 * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}. 268 * 269 * @param parent the parent node; may not be null 270 * @param changedName the name that should be compared to the existing node siblings to determine whether the same-name 271 * sibling indexes should be updated; may not be null 272 * @param context the execution context; may not be null 273 * @param addChildWithName true if a new child with the supplied name is to be added to the children (but which does not yet 274 * exist in the node's children) 275 * @return the path segment for the new child, or null if <code>addChildWithName</code> was false 276 */ 277 protected Path.Segment updateChildList( Node<Name, Object> parent, 278 Name changedName, 279 ExecutionContext context, 280 boolean addChildWithName ) { 281 assert parent != null; 282 assert changedName != null; 283 assert context != null; 284 Set<Node<Name, Object>> children = parent.getChildren(); 285 if (children.isEmpty() && !addChildWithName) return null; 286 287 // Go through the children, looking for any children with the same name as the 'changedName' 288 List<ChildInfo> childrenWithChangedName = new LinkedList<ChildInfo>(); 289 Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 290 int index = 0; 291 if (childNames != null) { 292 for (Path.Segment childName : childNames) { 293 if (childName.getName().equals(changedName)) { 294 ChildInfo info = new ChildInfo(childName, index); 295 childrenWithChangedName.add(info); 296 } 297 index++; 298 } 299 } 300 if (addChildWithName) { 301 // Make room for the new child at the end of the array ... 302 if (childNames == null) { 303 childNames = new Path.Segment[1]; 304 } else { 305 int numExisting = childNames.length; 306 Path.Segment[] newChildNames = new Path.Segment[numExisting + 1]; 307 System.arraycopy(childNames, 0, newChildNames, 0, numExisting); 308 childNames = newChildNames; 309 } 310 311 // And add a child info for the new node ... 312 ChildInfo info = new ChildInfo(null, index); 313 childrenWithChangedName.add(info); 314 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName); 315 childNames[index++] = newSegment; 316 } 317 assert childNames != null; 318 319 // Now process the children with the same name, which may include a child info for the new node ... 320 assert childrenWithChangedName.isEmpty() == false; 321 if (childrenWithChangedName.size() == 1) { 322 // The child should have no indexes ... 323 ChildInfo child = childrenWithChangedName.get(0); 324 if (child.segment != null && child.segment.hasIndex()) { 325 // The existing child needs to have a new index .. 326 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName); 327 // Replace the child with the correct FQN ... 328 changeNodeName(parent, child.segment, newSegment, context); 329 // Change the segment in the child list ... 330 childNames[child.childIndex] = newSegment; 331 } 332 } else { 333 // There is more than one child with the same name ... 334 int i = 0; 335 for (ChildInfo child : childrenWithChangedName) { 336 if (child.segment != null) { 337 // Determine the new name and index ... 338 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1); 339 // Replace the child with the correct FQN ... 340 changeNodeName(parent, child.segment, newSegment, context); 341 // Change the segment in the child list ... 342 childNames[child.childIndex] = newSegment; 343 } else { 344 // Determine the new name and index ... 345 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1); 346 childNames[child.childIndex] = newSegment; 347 } 348 ++i; 349 } 350 } 351 352 // Record the list of children as a property on the parent ... 353 // (Do this last, as it doesn't need to be done if there's an exception in the above logic) 354 context.getLogger(getClass()).trace("Updating child list of {0} to: {1}", 355 parent.getFqn(), 356 StringUtil.readableString(childNames)); 357 parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames); // replaces any existing value 358 359 if (addChildWithName) { 360 // Return the segment for the new node ... 361 return childNames[childNames.length - 1]; 362 } 363 return null; 364 } 365 366 protected boolean checkChildren( Node<Name, Object> parent ) { 367 Path.Segment[] childNamesProperty = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 368 Set<Object> childNames = parent.getChildrenNames(); 369 boolean result = true; 370 if (childNamesProperty.length != childNames.size()) result = false; 371 for (int i = 0; i != childNamesProperty.length; ++i) { 372 if (!childNames.contains(childNamesProperty[i])) result = false; 373 } 374 if (!result) { 375 List<Path.Segment> names = new ArrayList<Path.Segment>(); 376 for (Object name : childNames) { 377 names.add((Path.Segment)name); 378 } 379 Collections.sort(names); 380 // Logger.getLogger(getClass()).trace("Child list on {0} is: {1}", 381 // parent.getFqn(), 382 // StringUtil.readableString(childNamesProperty)); 383 // Logger.getLogger(getClass()).trace("Children of {0} is: {1}", parent.getFqn(), StringUtil.readableString(names)); 384 } 385 return result; 386 } 387 388 /** 389 * Utility class used by the {@link JBossCacheConnection#updateChildList(Node, Name, ExecutionContext, boolean)} method. 390 * 391 * @author Randall Hauch 392 */ 393 private static class ChildInfo { 394 protected final Path.Segment segment; 395 protected final int childIndex; 396 397 protected ChildInfo( Path.Segment childSegment, 398 int childIndex ) { 399 this.segment = childSegment; 400 this.childIndex = childIndex; 401 } 402 403 } 404 405 /** 406 * Changes the name of the node in the cache (but does not update the list of child segments stored on the parent). 407 * 408 * @param parent 409 * @param existing 410 * @param newSegment 411 * @param context 412 */ 413 protected void changeNodeName( Node<Name, Object> parent, 414 Path.Segment existing, 415 Path.Segment newSegment, 416 ExecutionContext context ) { 417 assert parent != null; 418 assert existing != null; 419 assert newSegment != null; 420 assert context != null; 421 422 if (existing.equals(newSegment)) return; 423 context.getLogger(getClass()).trace("Renaming {0} to {1} under {2}", existing, newSegment, parent.getFqn()); 424 Node<Name, Object> existingChild = parent.getChild(existing); 425 assert existingChild != null; 426 427 // JBoss Cache can move a node from one node to another node, but the move doesn't change the name; 428 // since you provide the FQN of the parent location, the name of the node cannot be changed. 429 // Therefore, to compensate, we need to create a new child, copy all of the data, move all of the child 430 // nodes of the old node, then remove the old node. 431 432 // Create the new node ... 433 Node<Name, Object> newChild = parent.addChild(Fqn.fromElements(newSegment)); 434 Fqn<?> newChildFqn = newChild.getFqn(); 435 436 // Copy the data ... 437 newChild.putAll(existingChild.getData()); 438 439 // Move the children ... 440 for (Node<Name, Object> grandChild : existingChild.getChildren()) { 441 cache.move(grandChild.getFqn(), newChildFqn); 442 } 443 444 // Remove the existing ... 445 parent.removeChild(existing); 446 } 447 448 protected class Executor extends AbstractCommandExecutor { 449 450 private final PropertyFactory propertyFactory; 451 private final ValueFactory<UUID> uuidFactory; 452 453 protected Executor( ExecutionContext context, 454 String sourceName ) { 455 super(context, sourceName); 456 this.propertyFactory = context.getPropertyFactory(); 457 this.uuidFactory = context.getValueFactories().getUuidFactory(); 458 } 459 460 @Override 461 public void execute( CreateNodeCommand command ) { 462 Path path = command.getPath(); 463 Path parent = path.getParent(); 464 // Look up the parent node, which must exist ... 465 Node<Name, Object> parentNode = getNode(parent); 466 467 // Update the children to account for same-name siblings. 468 // This not only updates the FQN of the child nodes, but it also sets the property that stores the 469 // the array of Path.Segment for the children (since the cache doesn't maintain order). 470 Path.Segment newSegment = updateChildList(parentNode, path.getLastSegment().getName(), getExecutionContext(), true); 471 Node<Name, Object> node = parentNode.addChild(Fqn.fromElements(newSegment)); 472 assert checkChildren(parentNode); 473 474 // Add the UUID property (if required), which may be overwritten by a supplied property ... 475 Name uuidPropertyName = getUuidPropertyName(getExecutionContext()); 476 if (uuidPropertyName != null) { 477 node.put(uuidPropertyName, generateUuid()); 478 } 479 // Now add the properties to the supplied node ... 480 for (Property property : command.getProperties()) { 481 if (property.size() == 0) continue; 482 Name propName = property.getName(); 483 Object value = null; 484 if (property.size() == 1) { 485 value = property.iterator().next(); 486 } else { 487 value = property.getValuesAsArray(); 488 } 489 node.put(propName, value); 490 } 491 } 492 493 @Override 494 public void execute( GetChildrenCommand command ) { 495 Node<Name, Object> node = getNode(command.getPath()); 496 Name uuidPropertyName = getUuidPropertyName(getExecutionContext()); 497 // Get the names of the children, using the child list ... 498 Path.Segment[] childList = (Path.Segment[])node.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); 499 for (Path.Segment child : childList) { 500 // We have the child segment, but we need the UUID property ... 501 Node<Name, Object> childNode = node.getChild(child); 502 Object uuid = childNode.get(uuidPropertyName); 503 if (uuid == null) { 504 uuid = generateUuid(); 505 childNode.put(uuidPropertyName, uuid); 506 } else { 507 uuid = uuidFactory.create(uuid); 508 } 509 Property uuidProperty = propertyFactory.create(uuidPropertyName, uuid); 510 command.addChild(child, uuidProperty); 511 } 512 } 513 514 @Override 515 public void execute( GetPropertiesCommand command ) { 516 Node<Name, Object> node = getNode(command.getPath()); 517 Map<Name, Object> dataMap = node.getData(); 518 for (Map.Entry<Name, Object> data : dataMap.entrySet()) { 519 Name propertyName = data.getKey(); 520 // Don't allow the child list property to be accessed 521 if (propertyName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue; 522 Object values = data.getValue(); 523 Property property = propertyFactory.create(propertyName, values); 524 command.setProperty(property); 525 } 526 } 527 528 @Override 529 public void execute( SetPropertiesCommand command ) { 530 Node<Name, Object> node = getNode(command.getPath()); 531 // Now set (or remove) the properties to the supplied node ... 532 for (Property property : command.getProperties()) { 533 Name propName = property.getName(); 534 // Don't allow the child list property to be removed or changed 535 if (propName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue; 536 if (property.size() == 0) { 537 node.remove(propName); 538 continue; 539 } 540 Object value = null; 541 if (property.size() == 1) { 542 value = property.iterator().next(); 543 } else { 544 value = property.getValuesAsArray(); 545 } 546 node.put(propName, value); 547 } 548 } 549 550 @Override 551 public void execute( DeleteBranchCommand command ) { 552 Node<Name, Object> node = getNode(command.getPath()); 553 node.getParent().removeChild(node.getFqn().getLastElement()); 554 } 555 556 @Override 557 public void execute( CopyNodeCommand command ) { 558 Node<Name, Object> node = getNode(command.getPath()); 559 // Look up the new parent, which must exist ... 560 Path newPath = command.getNewPath(); 561 Node<Name, Object> newParent = getNode(newPath.getParent()); 562 copyNode(node, newParent, false, null, getExecutionContext()); 563 } 564 565 @Override 566 public void execute( CopyBranchCommand command ) { 567 Node<Name, Object> node = getNode(command.getPath()); 568 // Look up the new parent, which must exist ... 569 Path newPath = command.getNewPath(); 570 Node<Name, Object> newParent = getNode(newPath.getParent()); 571 copyNode(node, newParent, true, null, getExecutionContext()); 572 } 573 574 @Override 575 public void execute( MoveBranchCommand command ) { 576 Node<Name, Object> node = getNode(command.getPath()); 577 boolean recursive = true; 578 Name uuidProperty = getUuidPropertyName(getExecutionContext()); 579 // Look up the new parent, which must exist ... 580 Path newPath = command.getNewPath(); 581 Node<Name, Object> newParent = getNode(newPath.getParent()); 582 copyNode(node, newParent, recursive, uuidProperty, getExecutionContext()); 583 584 // Now delete the old node ... 585 Node<Name, Object> oldParent = node.getParent(); 586 boolean removed = oldParent.removeChild(node.getFqn().getLastElement()); 587 assert removed; 588 } 589 590 @Override 591 public void execute( RecordBranchCommand command ) { 592 Node<Name, Object> node = getNode(command.getPath()); 593 recordNode(command, node); 594 } 595 596 protected void recordNode( RecordBranchCommand command, 597 Node<Name, Object> node ) { 598 // Record the properties ... 599 Map<Name, Object> dataMap = node.getData(); 600 List<Property> properties = new LinkedList<Property>(); 601 for (Map.Entry<Name, Object> data : dataMap.entrySet()) { 602 Name propertyName = data.getKey(); 603 Object values = data.getValue(); 604 Property property = propertyFactory.create(propertyName, values); 605 properties.add(property); 606 } 607 command.record(command.getPath(), properties); 608 // Now record the children ... 609 for (Node<Name, Object> child : node.getChildren()) { 610 recordNode(command, child); 611 } 612 } 613 614 protected Node<Name, Object> getNode( Path path ) { 615 return JBossCacheConnection.this.getNode(getExecutionContext(), path); 616 } 617 618 } 619 620 }