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.map; 25 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.Set; 29 import java.util.UUID; 30 import net.jcip.annotations.GuardedBy; 31 import org.modeshape.common.util.CheckArg; 32 import org.modeshape.graph.ExecutionContext; 33 import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; 34 35 /** 36 * A default implementation of a map-based repository. 37 * <p> 38 * {@code MapRepository} provides means for creating, retrieving, and destroying {@link MapWorkspace map-based workspaces}, 39 * including providing the locking primitives required for safe control of the workspaces. 40 * </p> 41 */ 42 public abstract class MapRepository { 43 44 protected final UUID rootNodeUuid; 45 private final String sourceName; 46 private final String defaultWorkspaceName; 47 private final Map<String, MapWorkspace> workspaces = new HashMap<String, MapWorkspace>(); 48 49 /** 50 * Creates a {@code MapRepository} with the given repository source name, root node UUID, and a default workspace named 51 * {@code ""} (the empty string). 52 * 53 * @param sourceName the name of the repository source for use in error and informational messages; may not be null or empty 54 * @param rootNodeUuid the UUID that will be used as the root node UUID for each workspace in the repository; may not be null 55 * or empty 56 */ 57 protected MapRepository( String sourceName, 58 UUID rootNodeUuid ) { 59 this(sourceName, rootNodeUuid, null); 60 } 61 62 /** 63 * Creates a {@code MapRepository} with the given repository source name, root node UUID, and a default workspace with the 64 * given name. 65 * 66 * @param sourceName the name of the repository source for use in error and informational messages; may not be null or empty 67 * @param rootNodeUuid the UUID that will be used as the root node UUID for each workspace in the repository; may not be null 68 * or empty 69 * @param defaultWorkspaceName the name of the default, auto-created workspace 70 */ 71 protected MapRepository( String sourceName, 72 UUID rootNodeUuid, 73 String defaultWorkspaceName ) { 74 CheckArg.isNotEmpty(sourceName, "sourceName"); 75 CheckArg.isNotNull(rootNodeUuid, "rootNodeUUID"); 76 this.rootNodeUuid = rootNodeUuid; 77 this.sourceName = sourceName; 78 this.defaultWorkspaceName = defaultWorkspaceName != null ? defaultWorkspaceName : ""; 79 } 80 81 /** 82 * Initializes the repository by creating the default workspace. 83 * <p> 84 * Due to the ordering restrictions on constructor chaining, this method cannot be called until the repository is fully 85 * initialized. <b>This method MUST be called at the end of the constructor by any class that implements {@code MapRepository} 86 * .</b> 87 */ 88 protected void initialize() { 89 // Create the default workspace ... 90 workspaces.put(this.defaultWorkspaceName, createWorkspace(null, 91 this.defaultWorkspaceName, 92 CreateConflictBehavior.DO_NOT_CREATE)); 93 94 } 95 96 protected String getDefaultWorkspaceName() { 97 return defaultWorkspaceName; 98 } 99 100 /** 101 * Returns the UUID used by the root nodes in each workspace. 102 * <p> 103 * Note that the root nodes themselves are distinct objects in each workspace and a change to the root node of one workspace 104 * does not imply a change to the root nodes of any other workspaces. However, the JCR specification mandates that all 105 * referenceable root nodes in a repository use a common UUID (in support of node correspondence); therefore this must be 106 * supported by ModeShape. 107 * 108 * @return the root node UUID 109 */ 110 public final UUID getRootNodeUuid() { 111 return rootNodeUuid; 112 } 113 114 /** 115 * Returns the logical name (as opposed to the class name) of the repository source that defined this instance of the 116 * repository for use in error, informational, and other contextual messages. 117 * 118 * @return sourceName the logical name for the repository source name 119 */ 120 public String getSourceName() { 121 return sourceName; 122 } 123 124 /** 125 * Returns a list of the names of the currently created workspaces 126 * 127 * @return a list of the names of the currently created workspaces 128 */ 129 @GuardedBy( "getLock()" ) 130 public Set<String> getWorkspaceNames() { 131 return workspaces.keySet(); 132 } 133 134 /** 135 * Returns the workspace with the given name 136 * 137 * @param name the name of the workspace to return 138 * @return the workspace with the given name; may be null if no workspace with the given name exists 139 */ 140 @GuardedBy( "getLock()" ) 141 public MapWorkspace getWorkspace( String name ) { 142 if (name == null) name = defaultWorkspaceName; 143 return workspaces.get(name); 144 } 145 146 /** 147 * Creates a new workspace with the given name containing only a root node. 148 * <p> 149 * <b>This method does NOT automatically add the newly created workspace to the {@link #workspaces workspace map} or check to 150 * see if a workspace already exists in this repository with the same name.</b> 151 * </p> 152 * 153 * @param context the context in which the workspace is to be created 154 * @param name the name of the workspace 155 * @return the newly created workspace; may not be null 156 */ 157 @GuardedBy( "getLock()" ) 158 protected abstract MapWorkspace createWorkspace( ExecutionContext context, 159 String name ); 160 161 /** 162 * Attempts to create a workspace with the given name with name-collision behavior determined by the behavior parameter. 163 * <p> 164 * This method will first check to see if a workspace already exists with the given name. If no such workspace exists, the 165 * method will create a new workspace with the given name, add it to the {@code #workspaces workspaces map}, and return it. If 166 * a workspace with the requested name already exists and the {@code behavior} is {@link CreateConflictBehavior#DO_NOT_CREATE} 167 * , this method will return {@code null} without modifying the state of the repository. If a workspace with the requested 168 * name already exists and the {@code behavior} is {@link CreateConflictBehavior#CREATE_WITH_ADJUSTED_NAME}, this method will 169 * generate a unique new name for the workspace, create a new workspace with the given name, added it to the {@code 170 * #workspaces workspaces map}, and return it. 171 * 172 * @param context the context in which the workspace is to be created; may not be null 173 * @param name the requested name of the workspace. The name of the workspace that is returned from this method may not be the 174 * same as the requested name; may not be null 175 * @param behavior the behavior to use in case a workspace with the requested name already exists in the repository 176 * @return the newly created workspace or {@code null} if a workspace with the requested name already exists in the repository 177 * and {@code behavior == CreateConflictBehavior#DO_NOT_CREATE}. 178 */ 179 @GuardedBy( "getLock()" ) 180 public MapWorkspace createWorkspace( ExecutionContext context, 181 String name, 182 CreateConflictBehavior behavior ) { 183 String newName = name; 184 boolean conflictingName = workspaces.containsKey(newName); 185 if (conflictingName) { 186 switch (behavior) { 187 case DO_NOT_CREATE: 188 return null; 189 case CREATE_WITH_ADJUSTED_NAME: 190 int counter = 0; 191 do { 192 newName = name + (++counter); 193 } while (workspaces.containsKey(newName)); 194 break; 195 } 196 } 197 assert workspaces.containsKey(newName) == false; 198 199 MapWorkspace workspace = createWorkspace(context, name); 200 workspaces.put(name, workspace); 201 return workspace; 202 } 203 204 /** 205 * Attempts to create a workspace with the requested name as in the 206 * {@link #createWorkspace(ExecutionContext, String, CreateConflictBehavior)} method and then clones the content from the 207 * given source workspace into the new workspace if the creation was successful. 208 * <p> 209 * If no workspace with the name {@code nameOfWorkspaceToClone} exists, the method will return an empty workspace. 210 * </p> 211 * 212 * @param context the context in which the workspace is to be created; may not be null 213 * @param name the requested name of the workspace. The name of the workspace that is returned from this method may not be the 214 * same as the requested name; may not be null 215 * @param existingWorkspaceBehavior the behavior to use in case a workspace with the requested name already exists in the 216 * repository 217 * @param nameOfWorkspaceToClone the name of the workspace from which the content should be cloned; may not be null 218 * @return the newly created workspace with an exact copy of the contents from the workspace named {@code 219 * nameOfWorkspaceToClone} or {@code null} if a workspace with the requested name already exists in the repository and 220 * {@code behavior == CreateConflictBehavior#DO_NOT_CREATE}. 221 */ 222 @GuardedBy( "getLock()" ) 223 public MapWorkspace createWorkspace( ExecutionContext context, 224 String name, 225 CreateConflictBehavior existingWorkspaceBehavior, 226 String nameOfWorkspaceToClone ) { 227 MapWorkspace workspace = createWorkspace(context, name, existingWorkspaceBehavior); 228 if (workspace == null) { 229 // Unable to create because of a duplicate name ... 230 return null; 231 } 232 MapWorkspace original = getWorkspace(nameOfWorkspaceToClone); 233 if (original != null) { 234 // Copy the properties of the root node ... 235 MapNode root = workspace.getRoot(); 236 MapNode origRoot = original.getRoot(); 237 root.getProperties().clear(); 238 root.getProperties().putAll(origRoot.getProperties()); 239 240 // Loop over each child and call this method to copy the immediate children (and below). 241 // Note that this makes the copy have the same UUID as the original. 242 for (MapNode originalNode : origRoot.getChildren()) { 243 original.cloneNode(context, originalNode, workspace, root, originalNode.getName().getName(), null, true, null); 244 } 245 } 246 247 workspaces.put(name, workspace); 248 return workspace; 249 } 250 251 /** 252 * Removes the named workspace from the {@code #workspaces workspaces map}. 253 * 254 * @param name the name of the workspace to remove 255 * @return {@code true} if a workspace with that name previously existed in the map 256 */ 257 @GuardedBy( "getLock()" ) 258 public boolean destroyWorkspace( String name ) { 259 return workspaces.remove(name) != null; 260 } 261 262 /** 263 * Begin a transaction, hinting whether the transaction will be used only to read the content. If this is called, then the 264 * transaction must be either {@link MapRepositoryTransaction#commit() committed} or 265 * {@link MapRepositoryTransaction#rollback() rolled back}. 266 * 267 * @param readonly true if the transaction will not modify any content, or false if changes are to be made 268 * @return the transaction; never null 269 * @see MapRepositoryTransaction#commit() 270 * @see MapRepositoryTransaction#rollback() 271 */ 272 public abstract MapRepositoryTransaction startTransaction( boolean readonly ); 273 }