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 }