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 */ 355 public void moveNode( ExecutionContext context, 356 InMemoryNode node, 357 Name desiredNewName, 358 Workspace newWorkspace, 359 InMemoryNode newParent ) { 360 assert context != null; 361 assert newParent != null; 362 assert node != null; 363 assert newWorkspace.getRoot().equals(newParent) != true; 364 assert this.getRoot().equals(node) != true; 365 InMemoryNode oldParent = node.getParent(); 366 Name oldName = node.getName().getName(); 367 if (oldParent != null) { 368 if (oldParent.equals(newParent)) return; 369 boolean removed = oldParent.getChildren().remove(node); 370 assert removed == true; 371 node.setParent(null); 372 correctSameNameSiblingIndexes(context, oldParent, oldName); 373 } 374 node.setParent(newParent); 375 Name newName = oldName; 376 if (desiredNewName != null) { 377 newName = desiredNewName; 378 node.setName(context.getValueFactories().getPathFactory().createSegment(desiredNewName, 1)); 379 } 380 newParent.getChildren().add(node); 381 correctSameNameSiblingIndexes(context, newParent, newName); 382 383 // If the node was moved to a new workspace... 384 if (!this.equals(newWorkspace)) { 385 // We need to remove the node from this workspace's map of nodes ... 386 this.moveNodeToWorkspace(node, newWorkspace); 387 } 388 } 389 390 protected void moveNodeToWorkspace( InMemoryNode node, 391 Workspace newWorkspace ) { 392 assert this.nodesByUuid.containsKey(node.getUuid()); 393 assert !newWorkspace.nodesByUuid.containsKey(node.getUuid()); 394 395 this.nodesByUuid.remove(node.getUuid()); 396 newWorkspace.nodesByUuid.put(node.getUuid(), node); 397 for (InMemoryNode child : node.getChildren()) { 398 moveNodeToWorkspace(child, newWorkspace); 399 } 400 } 401 402 /** 403 * This should copy the subgraph given by the original node and place the new copy under the supplied new parent. Note 404 * that internal references between nodes within the original subgraph must be reflected as internal nodes within the new 405 * subgraph. 406 * 407 * @param context the context; may not be null 408 * @param original the node to be copied; may not be null 409 * @param newWorkspace the workspace containing the new parent node; may not be null 410 * @param newParent the parent where the copy is to be placed; may not be null 411 * @param desiredName the desired name for the node; if null, the name will be obtained from the original node 412 * @param recursive true if the copy should be recursive 413 * @param oldToNewUuids the map of UUIDs of nodes in the new subgraph keyed by the UUIDs of nodes in the original; may be 414 * null if the UUIDs are to be maintained 415 * @return the new node, which is the top of the new subgraph 416 */ 417 public InMemoryNode copyNode( ExecutionContext context, 418 InMemoryNode original, 419 Workspace newWorkspace, 420 InMemoryNode newParent, 421 Name desiredName, 422 boolean recursive, 423 Map<UUID, UUID> oldToNewUuids ) { 424 assert context != null; 425 assert original != null; 426 assert newParent != null; 427 assert newWorkspace != null; 428 if (this.equals(newWorkspace) && oldToNewUuids == null) oldToNewUuids = new HashMap<UUID, UUID>(); 429 boolean reuseUuids = oldToNewUuids == null; 430 431 // Get or create the new node ... 432 Name childName = desiredName != null ? desiredName : original.getName().getName(); 433 UUID uuidForCopy = reuseUuids ? original.getUuid() : UUID.randomUUID(); 434 InMemoryNode copy = newWorkspace.createNode(context, newParent, childName, uuidForCopy); 435 if (oldToNewUuids != null) { 436 oldToNewUuids.put(original.getUuid(), copy.getUuid()); 437 } 438 439 // Copy the properties ... 440 copy.getProperties().clear(); 441 copy.getProperties().putAll(original.getProperties()); 442 if (recursive) { 443 // Loop over each child and call this method ... 444 for (InMemoryNode child : original.getChildren()) { 445 copyNode(context, child, newWorkspace, copy, null, true, oldToNewUuids); 446 } 447 } 448 449 if (oldToNewUuids != null) { 450 // Now, adjust any references in the new subgraph to objects in the original subgraph 451 // (because they were internal references, and need to be internal to the new subgraph) 452 PropertyFactory propertyFactory = context.getPropertyFactory(); 453 UuidFactory uuidFactory = context.getValueFactories().getUuidFactory(); 454 ValueFactory<Reference> referenceFactory = context.getValueFactories().getReferenceFactory(); 455 for (Map.Entry<UUID, UUID> oldToNew : oldToNewUuids.entrySet()) { 456 InMemoryNode oldNode = this.getNode(oldToNew.getKey()); 457 InMemoryNode newNode = newWorkspace.getNode(oldToNew.getValue()); 458 assert oldNode != null; 459 assert newNode != null; 460 // Iterate over the properties of the new ... 461 for (Map.Entry<Name, Property> entry : newNode.getProperties().entrySet()) { 462 Property property = entry.getValue(); 463 // Now see if any of the property values are references ... 464 List<Object> newValues = new ArrayList<Object>(); 465 boolean foundReference = false; 466 for (Iterator<?> iter = property.getValues(); iter.hasNext();) { 467 Object value = iter.next(); 468 PropertyType type = PropertyType.discoverType(value); 469 if (type == PropertyType.REFERENCE) { 470 UUID oldReferencedUuid = uuidFactory.create(value); 471 UUID newReferencedUuid = oldToNewUuids.get(oldReferencedUuid); 472 if (newReferencedUuid != null) { 473 newValues.add(referenceFactory.create(newReferencedUuid)); 474 foundReference = true; 475 } 476 } else { 477 newValues.add(value); 478 } 479 } 480 // If we found at least one reference, we have to build a new Property object ... 481 if (foundReference) { 482 Property newProperty = propertyFactory.create(property.getName(), newValues); 483 entry.setValue(newProperty); 484 } 485 } 486 } 487 } 488 489 return copy; 490 } 491 492 protected void correctSameNameSiblingIndexes( ExecutionContext context, 493 InMemoryNode parentNode, 494 Name name ) { 495 if (parentNode == null) return; 496 // Look for the highest existing index ... 497 List<InMemoryNode> childrenWithSameNames = new LinkedList<InMemoryNode>(); 498 for (InMemoryNode child : parentNode.getChildren()) { 499 if (child.getName().getName().equals(name)) childrenWithSameNames.add(child); 500 } 501 if (childrenWithSameNames.size() == 0) return; 502 if (childrenWithSameNames.size() == 1) { 503 InMemoryNode childWithSameName = childrenWithSameNames.get(0); 504 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, Path.DEFAULT_INDEX); 505 childWithSameName.setName(newName); 506 return; 507 } 508 int index = 1; 509 for (InMemoryNode childWithSameName : childrenWithSameNames) { 510 Path.Segment segment = childWithSameName.getName(); 511 if (segment.getIndex() != index) { 512 Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, index); 513 childWithSameName.setName(newName); 514 } 515 ++index; 516 } 517 } 518 519 /** 520 * {@inheritDoc} 521 * 522 * @see java.lang.Object#equals(java.lang.Object) 523 */ 524 @Override 525 public boolean equals( Object obj ) { 526 if (obj == this) return true; 527 if (obj instanceof Workspace) { 528 Workspace that = (Workspace)obj; 529 // Assume the workspaces are in the same repository ... 530 if (!this.name.equals(that.name)) return false; 531 return true; 532 } 533 return false; 534 } 535 536 /** 537 * {@inheritDoc} 538 * 539 * @see java.lang.Object#toString() 540 */ 541 @Override 542 public String toString() { 543 return InMemoryRepository.this.getSourceName() + "/" + this.getName(); 544 } 545 546 } 547 548 }