1 /* 2 * ModeShape (http://www.modeshape.org) 3 * See the COPYRIGHT.txt file distributed with this work for information 4 * regarding copyright ownership. Some portions may be licensed 5 * to Red Hat, Inc. under one or more contributor license agreements. 6 * See the AUTHORS.txt file in the distribution for a full listing of 7 * individual contributors. 8 * 9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape 10 * is licensed to you under the terms of the GNU Lesser General Public License as 11 * published by the Free Software Foundation; either version 2.1 of 12 * the License, or (at your option) any later version. 13 * 14 * ModeShape is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this software; if not, write to the Free 21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 23 */ 24 package org.modeshape.graph.connector.base; 25 26 import java.util.List; 27 import java.util.UUID; 28 import java.util.concurrent.locks.ReadWriteLock; 29 import net.jcip.annotations.NotThreadSafe; 30 import org.modeshape.graph.Location; 31 import org.modeshape.graph.property.Path; 32 33 /** 34 * The {@link Workspace} implementation that represents all nodes as {@link PathNode} objects and stores them in an internal data 35 * structure that allows for nodes to be accessed via a {@link Path}. 36 * <p> 37 * Subclasses are required to provide thread-safe access and modification of the state within the encapsulated data structure, 38 * since multiple {@link Transaction} implementations may be {@link Transaction#commit() committing} changes to the data structure 39 * at the same time. However, this class does not provide any thread-safety, since the nature of the thread-safety will almost 40 * certainly depend on the actual implementation. For example, a subclass may use a {@link ReadWriteLock lock}, or it may use a 41 * map implementation that provides the thread-safety. 42 * </p> 43 * 44 * @param <NodeType> the type of node 45 */ 46 @NotThreadSafe 47 public abstract class PathWorkspace<NodeType extends PathNode> implements Workspace { 48 49 private final String name; 50 private final UUID rootNodeUuid; 51 52 /** 53 * Create a new instance of the workspace. 54 * 55 * @param name the workspace name; may not be null 56 * @param rootNodeUuid the root node that is expected to already exist in the map 57 */ 58 public PathWorkspace( String name, 59 UUID rootNodeUuid ) { 60 this.name = name; 61 this.rootNodeUuid = rootNodeUuid; 62 assert this.name != null; 63 assert this.rootNodeUuid != null; 64 } 65 66 /** 67 * Create a new instance of the workspace. 68 * 69 * @param name the workspace name; may not be null 70 * @param originalToClone the workspace that is to be cloned; may not be null 71 */ 72 public PathWorkspace( String name, 73 PathWorkspace<NodeType> originalToClone ) { 74 this.name = name; 75 this.rootNodeUuid = originalToClone.getRootNode().getUuid(); 76 assert this.name != null; 77 assert this.rootNodeUuid != null; 78 throw new UnsupportedOperationException("Need to implement the ability to clone a workspace"); 79 } 80 81 /** 82 * {@inheritDoc} 83 * 84 * @see org.modeshape.graph.connector.base.Workspace#getName() 85 */ 86 public String getName() { 87 return name; 88 } 89 90 protected UUID getRootNodeUuid() { 91 return rootNodeUuid; 92 } 93 94 /** 95 * Get the root node in this workspace. 96 * 97 * @return the root node; never null 98 */ 99 public abstract NodeType getRootNode(); 100 101 /** 102 * Get the node with the supplied path. 103 * 104 * @param path the path to the node 105 * @return the node state as known by this workspace, or null if no such node exists in this workspace 106 */ 107 public abstract NodeType getNode( Path path ); 108 109 /** 110 * Verify that the supplied node exists. 111 * 112 * @param path the path of the node; may not be null 113 * @return the location of the node if it exists, or null if it does not 114 */ 115 public Location verifyNodeExists( Path path ) { 116 NodeType node = getNode(path); 117 return node != null ? Location.create(path) : null; 118 } 119 120 /** 121 * Save this node into the workspace, overwriting any previous record of the node. This method should be overridden by 122 * writable path workspace implementations that use the default {@link ChangeCommand} implementations. 123 * 124 * @param node the new node; may not be null 125 * @return the previous node state, or null if the node is new to this workspace 126 * @throws UnsupportedOperationException by default, subclasses should override this method so that this exception is not 127 * thrown 128 * @see #createMoveCommand(PathNode, PathNode) 129 * @see #createPutCommand(PathNode, PathNode) 130 * @see #createRemoveCommand(Path) 131 */ 132 public NodeType putNode( NodeType node ) { 133 throw new UnsupportedOperationException(); 134 } 135 136 /** 137 * Move the node from it's previous location to the new location, overwriting any previous node at that location. This method 138 * should be overridden by writable path workspace implementations that use the default {@link ChangeCommand} implementations. 139 * <p> 140 * The move operation is intended to reflect changes to the node's {@link PathNode#getName() name} or 141 * {@link PathNode#getParent() parent} only. Changes to the children or properties of the node should be reflected separately 142 * in a {@link #putNode(PathNode) put command} of some sort. The details of the put command are implementation-specific. 143 * </p> 144 * 145 * @param source the original version of the node to be moved; may not be null 146 * @param target the new version (implying a change to the name or parent) of the node to be moved; may not be null 147 * @return the new node state;may not be null 148 * @throws UnsupportedOperationException by default, subclasses should override this method so that this exception is not 149 * thrown 150 * @see #createMoveCommand(PathNode, PathNode) 151 * @see #createPutCommand(PathNode, PathNode) 152 * @see #createRemoveCommand(Path) 153 */ 154 public NodeType moveNode( NodeType source, 155 NodeType target ) { 156 throw new UnsupportedOperationException(); 157 } 158 159 /** 160 * Remove this node and its descendants from the workspace. This method should be overridden by writable path workspace 161 * implementations that use the default {@link ChangeCommand} implementations. 162 * 163 * @param path the path to the node to be removed; may not be null 164 * @return the previous node state, or null if the node does not exist in this workspace 165 * @throws UnsupportedOperationException by default, subclasses should override this method so that this exception is not 166 * thrown 167 * @see #createMoveCommand(PathNode, PathNode) 168 * @see #createPutCommand(PathNode, PathNode) 169 * @see #createRemoveCommand(Path) 170 */ 171 public NodeType removeNode( Path path ) { 172 throw new UnsupportedOperationException(); 173 } 174 175 /** 176 * Remove all of the nodes in this workspace, and make sure there is a single root node with no properties and no children. 177 */ 178 public void removeAll() { 179 throw new UnsupportedOperationException(); 180 181 } 182 183 /** 184 * {@inheritDoc} 185 * 186 * @see java.lang.Object#toString() 187 */ 188 @Override 189 public String toString() { 190 return name; 191 } 192 193 /** 194 * Successively (and in order) apply the changes from the list of pending commands 195 * <p> 196 * All validation for each of the objects (including validation of resource availability in the underlying persistent store) 197 * should be performed prior to invoking this method. 198 * </p> 199 * 200 * @param commands the list of commands to apply 201 */ 202 public void commit( List<ChangeCommand<NodeType>> commands ) { 203 for (ChangeCommand<NodeType> command : commands) { 204 command.apply(); 205 } 206 } 207 208 /** 209 * Create a change command for the required update to the given node 210 * 211 * @param oldNode the prior version of the node; may be null if this is a new node 212 * @param node the new version of the node; may not be null 213 * @return a {@link ChangeCommand} instance that reflects the changes to the node 214 * @see #commit(List) 215 */ 216 public ChangeCommand<NodeType> createPutCommand( NodeType oldNode, 217 NodeType node ) { 218 return new PutCommand(node); 219 } 220 221 /** 222 * Create a change command for the removal of the given node and its descendants 223 * 224 * @param path the path to the node at the root of the branch to be removed; may not be null 225 * @return a {@link ChangeCommand} instance that reflects the changes to the node 226 * @see #createPutCommand(PathNode, PathNode) 227 * @see #commit(List) 228 */ 229 public ChangeCommand<NodeType> createRemoveCommand( Path path ) { 230 return new RemoveCommand(path); 231 } 232 233 /** 234 * Create a change command that represents the movement of a node. The movement record will only reflect the changes to the 235 * node's name and/or parent. Changes to the node's properties or children should be ignored. A separate 236 * {@link #createPutCommand(PathNode, PathNode) put command} should be used to reflect these changes. 237 * 238 * @param source the original version of the node; may not be null 239 * @param target the new version of the node; may not be null 240 * @return a {@link ChangeCommand} instance that reflects the changes to the node 241 * @see #createMoveCommand(PathNode, PathNode) 242 * @see #commit(List) 243 */ 244 public ChangeCommand<NodeType> createMoveCommand( NodeType source, 245 NodeType target ) { 246 return new MoveCommand(source, target); 247 } 248 249 /** 250 * A specific operation that mutates the underlying persistent repository. 251 * 252 * @param <NodeType> the type of node against which this change should apply 253 */ 254 public interface ChangeCommand<NodeType extends PathNode> { 255 /** 256 * Make the change represented by this command permanent. 257 */ 258 void apply(); 259 } 260 261 private class PutCommand implements ChangeCommand<NodeType> { 262 private NodeType node; 263 264 protected PutCommand( NodeType node ) { 265 super(); 266 this.node = node; 267 } 268 269 public void apply() { 270 PathWorkspace.this.putNode(node); 271 } 272 273 @Override 274 public String toString() { 275 return "Put: { " + node + "}"; 276 } 277 } 278 279 private class RemoveCommand implements ChangeCommand<NodeType> { 280 private Path path; 281 282 protected RemoveCommand( Path path ) { 283 super(); 284 this.path = path; 285 } 286 287 public void apply() { 288 PathWorkspace.this.removeNode(path); 289 } 290 291 @Override 292 public String toString() { 293 return "Remove: { " + path.getString() + "}"; 294 } 295 } 296 297 private class MoveCommand implements ChangeCommand<NodeType> { 298 private NodeType node; 299 private NodeType newNode; 300 301 protected MoveCommand( NodeType node, 302 NodeType newNode ) { 303 super(); 304 this.node = node; 305 this.newNode = newNode; 306 } 307 308 public void apply() { 309 PathWorkspace.this.moveNode(node, newNode); 310 } 311 312 @Override 313 public String toString() { 314 return "Move: { " + node + " to " + newNode + "}"; 315 } 316 } 317 318 }