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 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed 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.graph.connector.inmemory; 025 026 import java.util.ArrayList; 027 import java.util.HashMap; 028 import java.util.Iterator; 029 import java.util.LinkedList; 030 import java.util.List; 031 import java.util.ListIterator; 032 import java.util.Map; 033 import java.util.Set; 034 import java.util.UUID; 035 import java.util.concurrent.locks.ReadWriteLock; 036 import java.util.concurrent.locks.ReentrantReadWriteLock; 037 import net.jcip.annotations.GuardedBy; 038 import net.jcip.annotations.NotThreadSafe; 039 import org.jboss.dna.common.util.CheckArg; 040 import org.jboss.dna.graph.ExecutionContext; 041 import org.jboss.dna.graph.property.Name; 042 import org.jboss.dna.graph.property.Path; 043 import org.jboss.dna.graph.property.Property; 044 import org.jboss.dna.graph.property.PropertyFactory; 045 import org.jboss.dna.graph.property.PropertyType; 046 import org.jboss.dna.graph.property.Reference; 047 import org.jboss.dna.graph.property.UuidFactory; 048 import org.jboss.dna.graph.property.ValueFactory; 049 import org.jboss.dna.graph.property.basic.RootPath; 050 import org.jboss.dna.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; 051 052 /** 053 * @author Randall Hauch 054 */ 055 @NotThreadSafe 056 public class InMemoryRepository { 057 058 protected final ReadWriteLock lock = new ReentrantReadWriteLock(); 059 protected final UUID rootNodeUuid; 060 private final String sourceName; 061 private final String defaultWorkspaceName; 062 private final Map<String, Workspace> workspaces = new HashMap<String, Workspace>(); 063 064 public InMemoryRepository( String sourceName, 065 UUID rootNodeUuid ) { 066 this(sourceName, rootNodeUuid, null); 067 } 068 069 public InMemoryRepository( String sourceName, 070 UUID rootNodeUuid, 071 String defaultWorkspaceName ) { 072 CheckArg.isNotEmpty(sourceName, "sourceName"); 073 CheckArg.isNotNull(rootNodeUuid, "rootNodeUUID"); 074 this.rootNodeUuid = rootNodeUuid; 075 this.sourceName = sourceName; 076 this.defaultWorkspaceName = defaultWorkspaceName != null ? defaultWorkspaceName : ""; 077 // Create the default workspace ... 078 workspaces.put(this.defaultWorkspaceName, new Workspace(this.defaultWorkspaceName)); 079 } 080 081 /** 082 * @return sourceName 083 */ 084 public String getSourceName() { 085 return sourceName; 086 } 087 088 /** 089 * @return lock 090 */ 091 public ReadWriteLock getLock() { 092 return lock; 093 } 094 095 @GuardedBy( "getLock()" ) 096 public Set<String> getWorkspaceNames() { 097 return workspaces.keySet(); 098 } 099 100 @GuardedBy( "getLock()" ) 101 public Workspace getWorkspace( ExecutionContext context, 102 String name ) { 103 if (name == null) name = defaultWorkspaceName; 104 return workspaces.get(name); 105 } 106 107 @GuardedBy( "getLock()" ) 108 public Workspace createWorkspace( ExecutionContext context, 109 String name, 110 CreateConflictBehavior behavior ) { 111 String newName = name; 112 boolean conflictingName = workspaces.containsKey(newName); 113 if (conflictingName) { 114 switch (behavior) { 115 case DO_NOT_CREATE: 116 return null; 117 case CREATE_WITH_ADJUSTED_NAME: 118 int counter = 0; 119 do { 120 newName = name + (++counter); 121 } while (workspaces.containsKey(newName)); 122 break; 123 } 124 } 125 assert workspaces.containsKey(newName) == false; 126 Workspace workspace = new Workspace(newName); 127 workspaces.put(newName, workspace); 128 return workspace; 129 } 130 131 @GuardedBy( "getLock()" ) 132 public Workspace createWorkspace( ExecutionContext context, 133 String name, 134 CreateConflictBehavior existingWorkspaceBehavior, 135 String nameOfWorkspaceToClone ) { 136 Workspace workspace = createWorkspace(context, name, existingWorkspaceBehavior); 137 if (workspace == null) { 138 // Unable to create because of a duplicate name ... 139 return null; 140 } 141 Workspace original = getWorkspace(context, nameOfWorkspaceToClone); 142 if (original != null) { 143 // Copy the properties of the root node ... 144 InMemoryNode root = workspace.getRoot(); 145 InMemoryNode origRoot = original.getRoot(); 146 root.getProperties().clear(); 147 root.getProperties().putAll(origRoot.getProperties()); 148 149 // Loop over each child and call this method to copy the immediate children (and below). 150 // Note that this makes the copy have the same UUID as the original. 151 for (InMemoryNode originalNode : origRoot.getChildren()) { 152 original.copyNode(context, originalNode, workspace, root, originalNode.getName().getName(), true, null); 153 } 154 } 155 return workspace; 156 } 157 158 @GuardedBy( "getLock()" ) 159 public boolean destroyWorkspace( String name ) { 160 return workspaces.remove(name) != null; 161 } 162 163 protected class Workspace { 164 private final Map<UUID, InMemoryNode> nodesByUuid = new HashMap<UUID, InMemoryNode>(); 165 private final String name; 166 167 protected Workspace( String name ) { 168 assert name != null; 169 this.name = name; 170 // Create the root node ... 171 InMemoryNode root = new InMemoryNode(rootNodeUuid); 172 nodesByUuid.put(root.getUuid(), root); 173 } 174 175 /** 176 * @return name 177 */ 178 public String getName() { 179 return name; 180 } 181 182 public InMemoryNode getRoot() { 183 return nodesByUuid.get(rootNodeUuid); 184 } 185 186 public InMemoryNode getNode( UUID uuid ) { 187 assert uuid != null; 188 return nodesByUuid.get(uuid); 189 } 190 191 protected Map<UUID, InMemoryNode> getNodesByUuid() { 192 return nodesByUuid; 193 } 194 195 public InMemoryNode getNode( ExecutionContext context, 196 String path ) { 197 assert context != null; 198 assert path != null; 199 return getNode(context.getValueFactories().getPathFactory().create(path)); 200 } 201 202 /** 203 * Find a node with the given path. 204 * 205 * @param path the path to the node; may not be null 206 * @return the node with the path, or null if the node does not exist 207 */ 208 public InMemoryNode getNode( Path path ) { 209 assert path != null; 210 InMemoryNode node = getRoot(); 211 for (Path.Segment segment : path) { 212 InMemoryNode desiredChild = null; 213 for (InMemoryNode child : node.getChildren()) { 214 if (child == null) continue; 215 Path.Segment childName = child.getName(); 216 if (childName == null) continue; 217 if (childName.equals(segment)) { 218 desiredChild = child; 219 break; 220 } 221 } 222 if (desiredChild != null) { 223 node = desiredChild; 224 } else { 225 return null; 226 } 227 } 228 return node; 229 } 230 231 /** 232 * Find the lowest existing node along the path. 233 * 234 * @param path the path to the node; may not be null 235 * @return the lowest existing node along the path, or the root node if no node exists on the path 236 */ 237 public Path getLowestExistingPath( Path path ) { 238 assert path != null; 239 InMemoryNode node = getRoot(); 240 int segmentNumber = 0; 241 for (Path.Segment segment : path) { 242 InMemoryNode desiredChild = null; 243 for (InMemoryNode child : node.getChildren()) { 244 if (child == null) continue; 245 Path.Segment childName = child.getName(); 246 if (childName == null) continue; 247 if (childName.equals(segment)) { 248 desiredChild = child; 249 break; 250 } 251 } 252 if (desiredChild != null) { 253 node = desiredChild; 254 } else { 255 return path.subpath(0, segmentNumber); 256 } 257 ++segmentNumber; 258 } 259 return RootPath.INSTANCE; 260 } 261 262 public void removeNode( ExecutionContext context, 263 InMemoryNode node ) { 264 assert context != null; 265 assert node != null; 266 if (getRoot().equals(node)) { 267 nodesByUuid.clear(); 268 // Create the root node ... 269 InMemoryNode root = new InMemoryNode(rootNodeUuid); 270 nodesByUuid.put(root.getUuid(), root); 271 return; 272 } 273 InMemoryNode parent = node.getParent(); 274 assert parent != null; 275 parent.getChildren().remove(node); 276 correctSameNameSiblingIndexes(context, parent, node.getName().getName()); 277 removeUuidReference(node); 278 } 279 280 protected void removeUuidReference( InMemoryNode node ) { 281 nodesByUuid.remove(node.getUuid()); 282 for (InMemoryNode child : node.getChildren()) { 283 removeUuidReference(child); 284 } 285 } 286 287 /** 288 * Create a node at the supplied path. The parent of the new node must already exist. 289 * 290 * @param context the environment; may not be null 291 * @param pathToNewNode the path to the new node; may not be null 292 * @return the new node (or root if the path specified the root) 293 */ 294 public InMemoryNode createNode( ExecutionContext context, 295 String pathToNewNode ) { 296 assert context != null; 297 assert pathToNewNode != null; 298 Path path = context.getValueFactories().getPathFactory().create(pathToNewNode); 299 if (path.isRoot()) return getRoot(); 300 Path parentPath = path.getParent(); 301 InMemoryNode parentNode = getNode(parentPath); 302 Name name = path.getLastSegment().getName(); 303 return createNode(context, parentNode, name, null); 304 } 305 306 /** 307 * Create a new node with the supplied name, as a child of the supplied parent. 308 * 309 * @param context the execution context 310 * @param parentNode the parent node; may not be null 311 * @param name the name; may not be null 312 * @param uuid the UUID of the node, or null if the UUID is to be generated 313 * @return the new node 314 */ 315 public InMemoryNode createNode( ExecutionContext context, 316 InMemoryNode parentNode, 317 Name name, 318 UUID uuid ) { 319 assert context != null; 320 assert name != null; 321 if (parentNode == null) parentNode = getRoot(); 322 if (uuid == null) uuid = UUID.randomUUID(); 323 InMemoryNode node = new InMemoryNode(uuid); 324 nodesByUuid.put(node.getUuid(), node); 325 node.setParent(parentNode); 326 // Find the last node with this same name ... 327 int nextIndex = 1; 328 if (parentNode.existingNames.contains(name)) { 329 ListIterator<InMemoryNode> iter = parentNode.getChildren().listIterator(parentNode.getChildren().size()); 330 while (iter.hasPrevious()) { 331 InMemoryNode prev = iter.previous(); 332 if (prev.getName().getName().equals(name)) { 333 nextIndex = prev.getName().getIndex() + 1; 334 break; 335 } 336 } 337 } 338 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, nextIndex); 339 node.setName(newName); 340 parentNode.getChildren().add(node); 341 parentNode.existingNames.add(name); 342 return node; 343 } 344 345 /** 346 * Move the supplied node to the new parent. This method automatically removes the node from its existing parent, and also 347 * correctly adjusts the {@link Path.Segment#getIndex() index} to be correct in the new parent. 348 * 349 * @param context 350 * @param node the node to be moved; may not be the {@link Workspace#getRoot() root} 351 * @param desiredNewName the new name for the node, if it is to be changed; may be null 352 * @param newWorkspace the workspace containing the new parent node 353 * @param newParent the new parent; may not be the {@link Workspace#getRoot() root} 354 * @param beforeNode the node before which this new node should be placed 355 */ 356 public void moveNode( ExecutionContext context, 357 InMemoryNode node, 358 Name desiredNewName, 359 Workspace newWorkspace, 360 InMemoryNode newParent, 361 InMemoryNode beforeNode) { 362 assert context != null; 363 assert newParent != null; 364 assert node != null; 365 // Why was this restriction here? -- BRC 366 // assert newWorkspace.getRoot().equals(newParent) != true; 367 assert this.getRoot().equals(node) != true; 368 InMemoryNode oldParent = node.getParent(); 369 Name oldName = node.getName().getName(); 370 if (oldParent != null) { 371 boolean removed = oldParent.getChildren().remove(node); 372 assert removed == true; 373 node.setParent(null); 374 correctSameNameSiblingIndexes(context, oldParent, oldName); 375 } 376 node.setParent(newParent); 377 Name newName = oldName; 378 if (desiredNewName != null) { 379 newName = desiredNewName; 380 node.setName(context.getValueFactories().getPathFactory().createSegment(desiredNewName, 1)); 381 } 382 383 if (beforeNode == null) { 384 newParent.getChildren().add(node); 385 } 386 else { 387 int index = newParent.getChildren().indexOf(beforeNode); 388 newParent.getChildren().add(index, node); 389 } 390 correctSameNameSiblingIndexes(context, newParent, newName); 391 392 // If the node was moved to a new workspace... 393 if (!this.equals(newWorkspace)) { 394 // We need to remove the node from this workspace's map of nodes ... 395 this.moveNodeToWorkspace(node, newWorkspace); 396 } 397 } 398 399 protected void moveNodeToWorkspace( InMemoryNode node, 400 Workspace newWorkspace ) { 401 assert this.nodesByUuid.containsKey(node.getUuid()); 402 assert !newWorkspace.nodesByUuid.containsKey(node.getUuid()); 403 404 this.nodesByUuid.remove(node.getUuid()); 405 newWorkspace.nodesByUuid.put(node.getUuid(), node); 406 for (InMemoryNode child : node.getChildren()) { 407 moveNodeToWorkspace(child, newWorkspace); 408 } 409 } 410 411 /** 412 * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note 413 * that internal references between nodes within the original subgraph must be reflected as internal nodes within the new 414 * subgraph. 415 * 416 * @param context the context; may not be null 417 * @param original the node to be copied; may not be null 418 * @param newWorkspace the workspace containing the new parent node; may not be null 419 * @param newParent the parent where the copy is to be placed; may not be null 420 * @param desiredName the desired name for the node; if null, the name will be obtained from the original node 421 * @param recursive true if the copy should be recursive 422 * @param oldToNewUuids the map of UUIDs of nodes in the new subgraph keyed by the UUIDs of nodes in the original; may be 423 * null if the UUIDs are to be maintained 424 * @return the new node, which is the top of the new subgraph 425 */ 426 public InMemoryNode copyNode( ExecutionContext context, 427 InMemoryNode original, 428 Workspace newWorkspace, 429 InMemoryNode newParent, 430 Name desiredName, 431 boolean recursive, 432 Map<UUID, UUID> oldToNewUuids ) { 433 assert context != null; 434 assert original != null; 435 assert newParent != null; 436 assert newWorkspace != null; 437 if (this.equals(newWorkspace) && oldToNewUuids == null) oldToNewUuids = new HashMap<UUID, UUID>(); 438 boolean reuseUuids = oldToNewUuids == null; 439 440 // Get or create the new node ... 441 Name childName = desiredName != null ? desiredName : original.getName().getName(); 442 UUID uuidForCopy = reuseUuids ? original.getUuid() : UUID.randomUUID(); 443 InMemoryNode copy = newWorkspace.createNode(context, newParent, childName, uuidForCopy); 444 if (oldToNewUuids != null) { 445 oldToNewUuids.put(original.getUuid(), copy.getUuid()); 446 } 447 448 // Copy the properties ... 449 copy.getProperties().clear(); 450 copy.getProperties().putAll(original.getProperties()); 451 if (recursive) { 452 // Loop over each child and call this method ... 453 for (InMemoryNode child : original.getChildren()) { 454 copyNode(context, child, newWorkspace, copy, null, true, oldToNewUuids); 455 } 456 } 457 458 if (oldToNewUuids != null) { 459 // Now, adjust any references in the new subgraph to objects in the original subgraph 460 // (because they were internal references, and need to be internal to the new subgraph) 461 PropertyFactory propertyFactory = context.getPropertyFactory(); 462 UuidFactory uuidFactory = context.getValueFactories().getUuidFactory(); 463 ValueFactory<Reference> referenceFactory = context.getValueFactories().getReferenceFactory(); 464 for (Map.Entry<UUID, UUID> oldToNew : oldToNewUuids.entrySet()) { 465 InMemoryNode oldNode = this.getNode(oldToNew.getKey()); 466 InMemoryNode newNode = newWorkspace.getNode(oldToNew.getValue()); 467 assert oldNode != null; 468 assert newNode != null; 469 // Iterate over the properties of the new ... 470 for (Map.Entry<Name, Property> entry : newNode.getProperties().entrySet()) { 471 Property property = entry.getValue(); 472 // Now see if any of the property values are references ... 473 List<Object> newValues = new ArrayList<Object>(); 474 boolean foundReference = false; 475 for (Iterator<?> iter = property.getValues(); iter.hasNext();) { 476 Object value = iter.next(); 477 PropertyType type = PropertyType.discoverType(value); 478 if (type == PropertyType.REFERENCE) { 479 UUID oldReferencedUuid = uuidFactory.create(value); 480 UUID newReferencedUuid = oldToNewUuids.get(oldReferencedUuid); 481 if (newReferencedUuid != null) { 482 newValues.add(referenceFactory.create(newReferencedUuid)); 483 foundReference = true; 484 } 485 } else { 486 newValues.add(value); 487 } 488 } 489 // If we found at least one reference, we have to build a new Property object ... 490 if (foundReference) { 491 Property newProperty = propertyFactory.create(property.getName(), newValues); 492 entry.setValue(newProperty); 493 } 494 } 495 } 496 } 497 498 return copy; 499 } 500 501 protected void correctSameNameSiblingIndexes( ExecutionContext context, 502 InMemoryNode parentNode, 503 Name name ) { 504 if (parentNode == null) return; 505 // Look for the highest existing index ... 506 List<InMemoryNode> childrenWithSameNames = new LinkedList<InMemoryNode>(); 507 for (InMemoryNode child : parentNode.getChildren()) { 508 if (child.getName().getName().equals(name)) childrenWithSameNames.add(child); 509 } 510 if (childrenWithSameNames.size() == 0) return; 511 if (childrenWithSameNames.size() == 1) { 512 InMemoryNode childWithSameName = childrenWithSameNames.get(0); 513 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, Path.DEFAULT_INDEX); 514 childWithSameName.setName(newName); 515 return; 516 } 517 int index = 1; 518 for (InMemoryNode childWithSameName : childrenWithSameNames) { 519 Path.Segment segment = childWithSameName.getName(); 520 if (segment.getIndex() != index) { 521 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, index); 522 childWithSameName.setName(newName); 523 } 524 ++index; 525 } 526 } 527 528 /** 529 * {@inheritDoc} 530 * 531 * @see java.lang.Object#equals(java.lang.Object) 532 */ 533 @Override 534 public boolean equals( Object obj ) { 535 if (obj == this) return true; 536 if (obj instanceof Workspace) { 537 Workspace that = (Workspace)obj; 538 // Assume the workspaces are in the same repository ... 539 if (!this.name.equals(that.name)) return false; 540 return true; 541 } 542 return false; 543 } 544 545 /** 546 * {@inheritDoc} 547 * 548 * @see java.lang.Object#toString() 549 */ 550 @Override 551 public String toString() { 552 return InMemoryRepository.this.getSourceName() + "/" + this.getName(); 553 } 554 555 } 556 557 }