View Javadoc

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 }