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.inmemory; 023 024 import java.util.HashMap; 025 import java.util.LinkedList; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.UUID; 029 import java.util.concurrent.locks.ReadWriteLock; 030 import java.util.concurrent.locks.ReentrantReadWriteLock; 031 import net.jcip.annotations.NotThreadSafe; 032 import org.jboss.dna.common.CommonI18n; 033 import org.jboss.dna.common.util.CheckArg; 034 import org.jboss.dna.graph.DnaLexicon; 035 import org.jboss.dna.graph.ExecutionContext; 036 import org.jboss.dna.graph.Location; 037 import org.jboss.dna.graph.properties.Name; 038 import org.jboss.dna.graph.properties.Path; 039 import org.jboss.dna.graph.properties.PathFactory; 040 import org.jboss.dna.graph.properties.PathNotFoundException; 041 import org.jboss.dna.graph.properties.Property; 042 import org.jboss.dna.graph.properties.PropertyFactory; 043 import org.jboss.dna.graph.properties.Path.Segment; 044 import org.jboss.dna.graph.properties.basic.BasicPath; 045 import org.jboss.dna.graph.requests.CopyBranchRequest; 046 import org.jboss.dna.graph.requests.CreateNodeRequest; 047 import org.jboss.dna.graph.requests.DeleteBranchRequest; 048 import org.jboss.dna.graph.requests.MoveBranchRequest; 049 import org.jboss.dna.graph.requests.ReadAllChildrenRequest; 050 import org.jboss.dna.graph.requests.ReadAllPropertiesRequest; 051 import org.jboss.dna.graph.requests.Request; 052 import org.jboss.dna.graph.requests.UpdatePropertiesRequest; 053 import org.jboss.dna.graph.requests.processor.RequestProcessor; 054 055 /** 056 * @author Randall Hauch 057 */ 058 @NotThreadSafe 059 public class InMemoryRepository { 060 061 protected final ReadWriteLock lock = new ReentrantReadWriteLock(); 062 private final String name; 063 private final UUID rootNodeUuid; 064 private final Map<UUID, Node> nodesByUuid = new HashMap<UUID, Node>(); 065 066 public InMemoryRepository( String name, 067 UUID rootNodeUUID ) { 068 CheckArg.isNotNull(rootNodeUUID, "rootNodeUUID"); 069 CheckArg.isNotEmpty(name, "name"); 070 this.name = name; 071 this.rootNodeUuid = rootNodeUUID; 072 // Create the root node ... 073 Node root = new Node(rootNodeUUID); 074 nodesByUuid.put(root.getUuid(), root); 075 } 076 077 /** 078 * @return lock 079 */ 080 public ReadWriteLock getLock() { 081 return lock; 082 } 083 084 /** 085 * @return name 086 */ 087 public String getName() { 088 return name; 089 } 090 091 public Node getRoot() { 092 return nodesByUuid.get(this.rootNodeUuid); 093 } 094 095 public Node getNode( UUID uuid ) { 096 assert uuid != null; 097 return nodesByUuid.get(uuid); 098 } 099 100 protected Map<UUID, Node> getNodesByUuid() { 101 return nodesByUuid; 102 } 103 104 public Node getNode( ExecutionContext context, 105 String path ) { 106 assert context != null; 107 assert path != null; 108 return getNode(context.getValueFactories().getPathFactory().create(path)); 109 } 110 111 /** 112 * Find a node with the given path. 113 * 114 * @param path the path to the node; may not be null 115 * @return the node with the path, or null if the node does not exist 116 */ 117 public Node getNode( Path path ) { 118 assert path != null; 119 Node node = getRoot(); 120 for (Path.Segment segment : path) { 121 Node desiredChild = null; 122 for (Node child : node.getChildren()) { 123 if (child == null) continue; 124 Path.Segment childName = child.getName(); 125 if (childName == null) continue; 126 if (childName.equals(segment)) { 127 desiredChild = child; 128 break; 129 } 130 } 131 if (desiredChild != null) { 132 node = desiredChild; 133 } else { 134 return null; 135 } 136 } 137 return node; 138 } 139 140 /** 141 * Find the lowest existing node along the path. 142 * 143 * @param path the path to the node; may not be null 144 * @return the lowest existing node along the path, or the root node if no node exists on the path 145 */ 146 public Path getLowestExistingPath( Path path ) { 147 assert path != null; 148 Node node = getRoot(); 149 int segmentNumber = 0; 150 for (Path.Segment segment : path) { 151 Node desiredChild = null; 152 for (Node child : node.getChildren()) { 153 if (child == null) continue; 154 Path.Segment childName = child.getName(); 155 if (childName == null) continue; 156 if (childName.equals(segment)) { 157 desiredChild = child; 158 break; 159 } 160 } 161 if (desiredChild != null) { 162 node = desiredChild; 163 } else { 164 return path.subpath(0, segmentNumber); 165 } 166 ++segmentNumber; 167 } 168 return BasicPath.ROOT; 169 } 170 171 protected UUID generateUuid() { 172 return UUID.randomUUID(); 173 } 174 175 public void removeNode( ExecutionContext context, 176 Node node ) { 177 assert context != null; 178 assert node != null; 179 assert getRoot().equals(node) != true; 180 Node parent = node.getParent(); 181 assert parent != null; 182 parent.getChildren().remove(node); 183 correctSameNameSiblingIndexes(context, parent, node.getName().getName()); 184 removeUuidReference(node); 185 } 186 187 protected void removeUuidReference( Node node ) { 188 nodesByUuid.remove(node.getUuid()); 189 for (Node child : node.getChildren()) { 190 removeUuidReference(child); 191 } 192 } 193 194 /** 195 * Create a node at the supplied path. The parent of the new node must already exist. 196 * 197 * @param context the environment; may not be null 198 * @param pathToNewNode the path to the new node; may not be null 199 * @return the new node (or root if the path specified the root) 200 */ 201 public Node createNode( ExecutionContext context, 202 String pathToNewNode ) { 203 assert context != null; 204 assert pathToNewNode != null; 205 Path path = context.getValueFactories().getPathFactory().create(pathToNewNode); 206 if (path.isRoot()) return getRoot(); 207 Path parentPath = path.getParent(); 208 Node parentNode = getNode(parentPath); 209 Name name = path.getLastSegment().getName(); 210 return createNode(context, parentNode, name, null); 211 } 212 213 /** 214 * Create a new node with the supplied name, as a child of the supplied parent. 215 * 216 * @param context the execution context 217 * @param parentNode the parent node; may not be null 218 * @param name the name; may not be null 219 * @param uuid the UUID of the node, or null if the UUID is to be generated 220 * @return the new node 221 */ 222 public Node createNode( ExecutionContext context, 223 Node parentNode, 224 Name name, 225 UUID uuid ) { 226 assert context != null; 227 assert name != null; 228 if (parentNode == null) parentNode = getRoot(); 229 if (uuid == null) uuid = generateUuid(); 230 Node node = new Node(uuid); 231 nodesByUuid.put(node.getUuid(), node); 232 node.setParent(parentNode); 233 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name); 234 node.setName(newName); 235 parentNode.getChildren().add(node); 236 correctSameNameSiblingIndexes(context, parentNode, name); 237 return node; 238 } 239 240 protected void correctSameNameSiblingIndexes( ExecutionContext context, 241 Node parentNode, 242 Name name ) { 243 if (parentNode == null) return; 244 // Look for the highest existing index ... 245 List<Node> childrenWithSameNames = new LinkedList<Node>(); 246 for (Node child : parentNode.getChildren()) { 247 if (child.getName().getName().equals(name)) childrenWithSameNames.add(child); 248 } 249 if (childrenWithSameNames.size() == 0) return; 250 if (childrenWithSameNames.size() == 1) { 251 Node childWithSameName = childrenWithSameNames.get(0); 252 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, Path.NO_INDEX); 253 childWithSameName.setName(newName); 254 return; 255 } 256 int index = 1; 257 for (Node childWithSameName : childrenWithSameNames) { 258 Path.Segment segment = childWithSameName.getName(); 259 if (segment.getIndex() != index) { 260 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, index); 261 childWithSameName.setName(newName); 262 } 263 ++index; 264 } 265 } 266 267 /** 268 * Move the supplied node to the new parent. This method automatically removes the node from its existing parent, and also 269 * correctly adjusts the {@link Path.Segment#getIndex() index} to be correct in the new parent. 270 * 271 * @param context 272 * @param node the node to be moved; may not be the {@link #getRoot() root} 273 * @param newParent the new parent; may not be the {@link #getRoot() root} 274 */ 275 public void moveNode( ExecutionContext context, 276 Node node, 277 Node newParent ) { 278 assert context != null; 279 assert newParent != null; 280 assert node != null; 281 assert getRoot().equals(newParent) != true; 282 assert getRoot().equals(node) != true; 283 Node oldParent = node.getParent(); 284 if (oldParent != null) { 285 if (oldParent.equals(newParent)) return; 286 boolean removed = oldParent.getChildren().remove(node); 287 assert removed == true; 288 node.setParent(null); 289 correctSameNameSiblingIndexes(context, oldParent, node.getName().getName()); 290 } 291 node.setParent(newParent); 292 newParent.getChildren().add(node); 293 correctSameNameSiblingIndexes(context, newParent, node.getName().getName()); 294 } 295 296 public Node copyNode( ExecutionContext context, 297 Node original, 298 Node newParent, 299 boolean recursive ) { 300 assert context != null; 301 assert original != null; 302 assert newParent != null; 303 // Get or create the new node ... 304 Node copy = createNode(context, newParent, original.getName().getName(), null); 305 306 // Copy the properties ... 307 copy.getProperties().clear(); 308 copy.getProperties().putAll(original.getProperties()); 309 if (recursive) { 310 // Loop over each child and call this method ... 311 for (Node child : original.getChildren()) { 312 copyNode(context, child, copy, true); 313 } 314 } 315 return copy; 316 } 317 318 /** 319 * Get a request processor given the supplied environment and source name. 320 * 321 * @param context the environment in which the commands are to be executed 322 * @param sourceName the name of the repository source 323 * @return the request processor; never null 324 */ 325 /*package*/RequestProcessor getRequestProcessor( ExecutionContext context, 326 String sourceName ) { 327 return new Processor(context, sourceName); 328 } 329 330 protected class Processor extends RequestProcessor { 331 private final PathFactory pathFactory; 332 private final PropertyFactory propertyFactory; 333 334 protected Processor( ExecutionContext context, 335 String sourceName ) { 336 super(sourceName, context); 337 pathFactory = context.getValueFactories().getPathFactory(); 338 propertyFactory = context.getPropertyFactory(); 339 } 340 341 @Override 342 public void process( ReadAllChildrenRequest request ) { 343 Node node = getTargetNode(request, request.of()); 344 if (node == null) return; 345 Path path = request.of().getPath(); 346 // Get the names of the children ... 347 List<Node> children = node.getChildren(); 348 for (Node child : children) { 349 Segment childName = child.getName(); 350 Path childPath = pathFactory.create(path, childName); 351 request.addChild(childPath, propertyFactory.create(DnaLexicon.UUID, child.getUuid())); 352 } 353 request.setActualLocationOfNode(new Location(path, node.getUuid())); 354 } 355 356 @Override 357 public void process( ReadAllPropertiesRequest request ) { 358 Node node = getTargetNode(request, request.at()); 359 if (node == null) return; 360 // Get the properties of the node ... 361 request.addProperty(propertyFactory.create(DnaLexicon.UUID, node.getUuid())); 362 for (Property property : node.getProperties().values()) { 363 request.addProperty(property); 364 } 365 request.setActualLocationOfNode(new Location(request.at().getPath(), node.getUuid())); 366 } 367 368 @Override 369 public void process( CopyBranchRequest request ) { 370 Node node = getTargetNode(request, request.from()); 371 if (node == null) return; 372 // Look up the new parent, which must exist ... 373 Path newPath = request.into().getPath(); 374 Path newParentPath = newPath.getParent(); 375 Node newParent = getNode(newParentPath); 376 Node newNode = copyNode(getExecutionContext(), node, newParent, true); 377 newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, newNode.getName()); 378 Location oldLocation = new Location(request.from().getPath(), node.getUuid()); 379 Location newLocation = new Location(newPath, newNode.getUuid()); 380 request.setActualLocations(oldLocation, newLocation); 381 } 382 383 @Override 384 public void process( CreateNodeRequest request ) { 385 Path path = request.at().getPath(); 386 CheckArg.isNotNull(path, "request.at().getPath()"); 387 Node node = null; 388 if (!path.isRoot()) { 389 Path parent = path.getParent(); 390 // Look up the parent node, which must exist ... 391 Node parentNode = getNode(parent); 392 if (parentNode == null) { 393 Path lowestExisting = getLowestExistingPath(parent); 394 throw new PathNotFoundException(request.at(), lowestExisting, 395 InMemoryConnectorI18n.nodeDoesNotExist.text(parent)); 396 } 397 UUID uuid = null; 398 for (Property property : request.properties()) { 399 if (property.getName().equals(DnaLexicon.UUID)) { 400 uuid = getExecutionContext().getValueFactories().getUuidFactory().create(property.getValues().next()); 401 break; 402 } 403 } 404 node = createNode(getExecutionContext(), parentNode, path.getLastSegment().getName(), uuid); 405 path = getExecutionContext().getValueFactories().getPathFactory().create(parent, node.getName()); 406 } else { 407 node = getRoot(); 408 } 409 // Now add the properties to the supplied node ... 410 for (Property property : request.properties()) { 411 Name propName = property.getName(); 412 if (property.size() == 0) { 413 node.getProperties().remove(propName); 414 continue; 415 } 416 if (!propName.equals(DnaLexicon.UUID)) { 417 node.getProperties().put(propName, property); 418 } 419 } 420 assert node != null; 421 request.setActualLocationOfNode(new Location(path, node.getUuid())); 422 } 423 424 @Override 425 public void process( DeleteBranchRequest request ) { 426 Node node = getTargetNode(request, request.at()); 427 if (node == null) return; 428 removeNode(getExecutionContext(), node); 429 request.setActualLocationOfNode(new Location(request.at().getPath(), node.getUuid())); 430 } 431 432 @Override 433 public void process( MoveBranchRequest request ) { 434 Node node = getTargetNode(request, request.from()); 435 if (node == null) return; 436 // Look up the new parent, which must exist ... 437 Path newPath = request.into().getPath(); 438 Path newParentPath = newPath.getParent(); 439 Node newParent = getNode(newParentPath); 440 node.setParent(newParent); 441 newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, node.getName()); 442 Location oldLocation = new Location(request.from().getPath(), node.getUuid()); 443 Location newLocation = new Location(newPath, node.getUuid()); 444 request.setActualLocations(oldLocation, newLocation); 445 } 446 447 @Override 448 public void process( UpdatePropertiesRequest request ) { 449 Node node = getTargetNode(request, request.on()); 450 if (node == null) return; 451 // Now set (or remove) the properties to the supplied node ... 452 for (Property property : request.properties()) { 453 Name propName = property.getName(); 454 if (property.size() == 0) { 455 node.getProperties().remove(propName); 456 continue; 457 } 458 if (!propName.equals(DnaLexicon.UUID)) { 459 node.getProperties().put(propName, property); 460 } 461 } 462 request.setActualLocationOfNode(new Location(request.on().getPath(), node.getUuid())); 463 } 464 465 protected Node getTargetNode( Request request, 466 Location location ) { 467 Path path = location.getPath(); 468 if (path == null) { 469 request.setError(new IllegalArgumentException(CommonI18n.argumentMayNotBeNull.text("location.getPath()"))); 470 return null; 471 } 472 // Look up the node with the supplied path ... 473 Node node = InMemoryRepository.this.getNode(path); 474 if (node == null) { 475 Path lowestExisting = getLowestExistingPath(path); 476 request.setError(new PathNotFoundException(location, lowestExisting, 477 InMemoryConnectorI18n.nodeDoesNotExist.text(path))); 478 return null; 479 } 480 return node; 481 } 482 } 483 }