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