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.jcr;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.security.AccessControlException;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.HashSet;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.UUID;
37  import javax.jcr.AccessDeniedException;
38  import javax.jcr.InvalidSerializedDataException;
39  import javax.jcr.ItemExistsException;
40  import javax.jcr.ItemNotFoundException;
41  import javax.jcr.NoSuchWorkspaceException;
42  import javax.jcr.NodeIterator;
43  import javax.jcr.PathNotFoundException;
44  import javax.jcr.RepositoryException;
45  import javax.jcr.Session;
46  import javax.jcr.UnsupportedRepositoryOperationException;
47  import javax.jcr.Workspace;
48  import javax.jcr.lock.Lock;
49  import javax.jcr.lock.LockException;
50  import javax.jcr.lock.LockManager;
51  import javax.jcr.nodetype.ConstraintViolationException;
52  import javax.jcr.nodetype.NodeTypeManager;
53  import javax.jcr.observation.ObservationManager;
54  import javax.jcr.query.QueryManager;
55  import javax.jcr.version.Version;
56  import javax.jcr.version.VersionException;
57  import javax.jcr.version.VersionManager;
58  import net.jcip.annotations.NotThreadSafe;
59  import org.modeshape.common.util.CheckArg;
60  import org.modeshape.graph.ExecutionContext;
61  import org.modeshape.graph.Location;
62  import org.modeshape.graph.Subgraph;
63  import org.modeshape.graph.SubgraphNode;
64  import org.modeshape.graph.connector.RepositoryConnectionFactory;
65  import org.modeshape.graph.connector.RepositorySource;
66  import org.modeshape.graph.connector.RepositorySourceException;
67  import org.modeshape.graph.connector.UuidAlreadyExistsException;
68  import org.modeshape.graph.property.Name;
69  import org.modeshape.graph.property.NameFactory;
70  import org.modeshape.graph.property.NamespaceRegistry;
71  import org.modeshape.graph.property.Path;
72  import org.modeshape.graph.property.PathFactory;
73  import org.modeshape.graph.property.Property;
74  import org.modeshape.graph.property.PropertyFactory;
75  import org.modeshape.graph.property.ValueFormatException;
76  import org.modeshape.graph.property.basic.LocalNamespaceRegistry;
77  import org.modeshape.graph.request.InvalidWorkspaceException;
78  import org.modeshape.graph.request.ReadBranchRequest;
79  import org.modeshape.graph.session.GraphSession;
80  import org.modeshape.graph.session.GraphSession.Node;
81  import org.modeshape.jcr.JcrContentHandler.EnclosingSAXException;
82  import org.modeshape.jcr.JcrContentHandler.SaveMode;
83  import org.modeshape.jcr.SessionCache.JcrNodePayload;
84  import org.modeshape.jcr.SessionCache.JcrPropertyPayload;
85  import org.modeshape.jcr.WorkspaceLockManager.ModeShapeLock;
86  import org.xml.sax.ContentHandler;
87  import org.xml.sax.InputSource;
88  import org.xml.sax.SAXException;
89  import org.xml.sax.SAXParseException;
90  import org.xml.sax.XMLReader;
91  import org.xml.sax.helpers.XMLReaderFactory;
92  
93  /**
94   * ModeShape implementation of a {@link Workspace JCR Workspace}.
95   */
96  @NotThreadSafe
97  class JcrWorkspace implements Workspace {
98  
99      /**
100      * The name of this workspace. This name is used as the name of the source when
101      * {@link RepositoryConnectionFactory#createConnection(String) creating connections} to the underlying
102      * {@link RepositorySource} that stores the content for this workspace.
103      */
104     private final String name;
105 
106     /**
107      * The context in which this workspace is executing/operating. This context already has been authenticated.
108      */
109     private final ExecutionContext context;
110 
111     /**
112      * The reference to the {@link JcrRepository} instance that owns this {@link Workspace} instance. Very few methods on this
113      * repository object are used; mainly {@link JcrRepository#createWorkspaceGraph(String,ExecutionContext)},
114      * {@link JcrRepository#getPersistentRegistry()} and {@link JcrRepository#getRepositorySourceName()}.
115      */
116     private final JcrRepository repository;
117 
118     /**
119      * The graph used by this workspace to access persistent content. This graph is not thread-safe, but since this workspace is
120      * not thread-safe, it is okay for any method in this workspace to use the same graph. It is also okay for other objects that
121      * have the same thread context as this workspace (e.g., the session, namespace registry, etc.) to also reuse this same graph
122      * instance (though it's not very expensive at all for each to have their own instance, too).
123      */
124     private final JcrGraph graph;
125 
126     /**
127      * Reference to the namespace registry for this workspace. Per the JCR specification, this registry instance is persistent
128      * (unlike the namespace-related methods in the {@link Session}, like {@link Session#getNamespacePrefix(String)},
129      * {@link Session#setNamespacePrefix(String, String)}, etc.).
130      */
131     private final JcrNamespaceRegistry workspaceRegistry;
132 
133     /**
134      * Reference to the JCR type manager for this workspace.
135      */
136     private final JcrNodeTypeManager nodeTypeManager;
137 
138     /**
139      * Reference to the version manager for this workspace.
140      */
141     private final JcrVersionManager versionManager;
142 
143     /**
144      * Reference to the JCR query manager for this workspace.
145      */
146     private final JcrQueryManager queryManager;
147 
148     /**
149      * Reference to the JCR observation manager for this workspace.
150      */
151     private final JcrObservationManager observationManager;
152 
153     private final JcrLockManager lockManager;
154 
155     /**
156      * The {@link Session} instance that this corresponds with this workspace.
157      */
158     private final JcrSession session;
159 
160     JcrWorkspace( JcrRepository repository,
161                   String workspaceName,
162                   ExecutionContext context,
163                   Map<String, Object> sessionAttributes ) {
164 
165         assert workspaceName != null;
166         assert context != null;
167         assert context.getSecurityContext() != null;
168         assert repository != null;
169         this.name = workspaceName;
170         this.repository = repository;
171 
172         // Create an execution context for this session, which should use the local namespace registry ...
173         NamespaceRegistry globalRegistry = context.getNamespaceRegistry();
174         LocalNamespaceRegistry localRegistry = new LocalNamespaceRegistry(globalRegistry);
175         this.context = context.with(localRegistry);
176 
177         // Pre-cache all of the namespaces to be a snapshot of what's in the global registry at this time.
178         // This behavior is specified in Section 3.5.2 of the JCR 2.0 specification.
179         localRegistry.getNamespaces();
180 
181         // Now create a graph for the session ...
182         this.graph = this.repository.createWorkspaceGraph(this.name, this.context);
183 
184         // Set up the session for this workspace ...
185         this.session = new JcrSession(this.repository, this, this.context, globalRegistry, sessionAttributes);
186 
187         // This must be initialized after the session
188         this.nodeTypeManager = new JcrNodeTypeManager(session, this.repository.getRepositoryTypeManager());
189         this.versionManager = new JcrVersionManager(this.session);
190         this.queryManager = new JcrQueryManager(this.session);
191         this.observationManager = new JcrObservationManager(this.session, this.repository.getRepositoryObservable());
192 
193         // if (Boolean.valueOf(repository.getOptions().get(Option.PROJECT_NODE_TYPES))) {
194         // Path parentOfTypeNodes = context.getValueFactories().getPathFactory().create(systemPath, JcrLexicon.NODE_TYPES);
195         // repoTypeManager.projectOnto(this.graph, parentOfTypeNodes);
196         // }
197         //
198         // Set up and initialize the persistent JCR namespace registry ...
199         this.workspaceRegistry = new JcrNamespaceRegistry(this.repository.getPersistentRegistry(), this.session);
200         this.lockManager = new JcrLockManager(session, repository.getRepositoryLockManager().getLockManager(workspaceName));
201 
202     }
203 
204     final JcrGraph graph() {
205         return this.graph;
206     }
207 
208     final String getSourceName() {
209         return this.repository.getRepositorySourceName();
210     }
211 
212     final JcrNodeTypeManager nodeTypeManager() {
213         return this.nodeTypeManager;
214     }
215 
216     final ExecutionContext context() {
217         return this.context;
218     }
219 
220     final JcrLockManager lockManager() {
221         return this.lockManager;
222     }
223 
224     final JcrObservationManager observationManager() {
225         return this.observationManager;
226     }
227 
228     final JcrQueryManager queryManager() {
229         return this.queryManager;
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     public final String getName() {
236         return name;
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     public final Session getSession() {
243         return this.session;
244     }
245 
246     /**
247      * {@inheritDoc}
248      * 
249      * @see javax.jcr.Workspace#getNamespaceRegistry()
250      */
251     public final javax.jcr.NamespaceRegistry getNamespaceRegistry() {
252         return workspaceRegistry;
253     }
254 
255     /**
256      * {@inheritDoc}
257      */
258     public String[] getAccessibleWorkspaceNames() throws RepositoryException {
259         session.checkLive();
260         try {
261             Set<String> workspaceNamesFromGraph = graph.getWorkspaces();
262             Set<String> workspaceNames = new HashSet<String>(workspaceNamesFromGraph.size());
263 
264             for (String workspaceName : workspaceNamesFromGraph) {
265                 try {
266                     session.checkPermission(workspaceName, null, ModeShapePermissions.READ);
267                     workspaceNames.add(workspaceName);
268                 } catch (AccessControlException ace) {
269                     // Can happen if user doesn't have the privileges to read from the workspace
270                 }
271             }
272 
273             return workspaceNames.toArray(new String[workspaceNames.size()]);
274         } catch (RepositorySourceException e) {
275             throw new RepositoryException(JcrI18n.errorObtainingWorkspaceNames.text(getSourceName(), e.getMessage()), e);
276         }
277     }
278 
279     /**
280      * {@inheritDoc}
281      */
282     public final NodeTypeManager getNodeTypeManager() {
283         return nodeTypeManager;
284     }
285 
286     /**
287      * {@inheritDoc}
288      * 
289      * @see javax.jcr.Workspace#getObservationManager()
290      */
291     public final ObservationManager getObservationManager() {
292         return this.observationManager;
293     }
294 
295     /**
296      * @return the lock manager for this workspace and session
297      */
298     public LockManager getLockManager() {
299         return lockManager;
300     }
301 
302     /**
303      * {@inheritDoc}
304      */
305     public final QueryManager getQueryManager() {
306         return queryManager;
307     }
308 
309     final JcrVersionManager versionManager() {
310         return versionManager;
311     }
312 
313     /**
314      * {@inheritDoc}
315      * 
316      * @see javax.jcr.Workspace#clone(java.lang.String, java.lang.String, java.lang.String, boolean)
317      */
318     public void clone( String srcWorkspace,
319                        String srcAbsPath,
320                        String destAbsPath,
321                        boolean removeExisting )
322         throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException,
323         LockException, RepositoryException {
324         CheckArg.isNotNull(srcWorkspace, "source workspace name");
325         CheckArg.isNotNull(srcAbsPath, "source path");
326         CheckArg.isNotNull(destAbsPath, "destination path");
327 
328         final boolean sameWorkspace = getName().equals(srcWorkspace);
329         if (sameWorkspace && removeExisting) {
330             // This is a special case of cloning within the same workspace but removing the original, which equates to a move ...
331             move(srcAbsPath, destAbsPath);
332             return;
333         }
334 
335         session.checkLive();
336         if (!sameWorkspace && !graph.getWorkspaces().contains(srcWorkspace)) {
337             throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(graph.getSourceName(), this.name));
338         }
339 
340         // Create the paths ...
341         PathFactory factory = context.getValueFactories().getPathFactory();
342         Path srcPath = null;
343         Path destPath = null;
344         try {
345             srcPath = factory.create(srcAbsPath);
346         } catch (ValueFormatException e) {
347             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(srcAbsPath, "srcAbsPath"), e);
348         }
349         try {
350             destPath = factory.create(destAbsPath);
351         } catch (ValueFormatException e) {
352             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(destAbsPath, "destAbsPath"), e);
353         }
354 
355         // Doing a literal test here because the path factory will canonicalize "/node[1]" to "/node"
356         if (!sameWorkspace && !destPath.isIdentifier() && destAbsPath.endsWith("]")) {
357             throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(destAbsPath));
358         }
359 
360         try {
361             // Use the session to verify that the node location has a definition and is valid with the new cloned child.
362             // This also performs the check permission for reading the parent ...
363             SessionCache cache = this.session.cache();
364             AbstractJcrNode parentNode = null;
365             Name newNodeName = null;
366             if (destPath.isIdentifier()) {
367                 AbstractJcrNode existingDestNode = cache.findJcrNode(Location.create(destPath));
368                 parentNode = existingDestNode.getParent();
369                 newNodeName = existingDestNode.segment().getName();
370                 destPath = factory.create(parentNode.path(), newNodeName);
371             } else {
372                 parentNode = cache.findJcrNode(null, destPath.getParent());
373                 newNodeName = destPath.getLastSegment().getName();
374             }
375 
376             /*
377              * Find the UUID for the source node.  Have to go directly against the graph.
378              */
379             org.modeshape.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace, context).getNodeAt(srcPath);
380             Property uuidProp = sourceNode.getProperty(ModeShapeLexicon.UUID);
381             UUID sourceUuid = null;
382 
383             if (uuidProp != null) {
384                 sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
385 
386                 ModeShapeLock sourceLock = lockManager().lockFor(sourceUuid);
387                 if (sourceLock != null && sourceLock.getLockToken() == null) {
388                     throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
389                 }
390             }
391 
392             if (parentNode.isLocked()) {
393                 Lock newParentLock = parentNode.getLock();
394                 if (newParentLock != null && newParentLock.getLockToken() == null) {
395                     throw new LockException(destAbsPath);
396                 }
397             }
398 
399             if (!parentNode.isCheckedOut()) {
400                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(parentNode.getPath()));
401             }
402 
403             // Verify that this node accepts a child of the supplied name (given any existing SNS nodes) ...
404             Node<JcrNodePayload, JcrPropertyPayload> parent = cache.findNode(parentNode.nodeId, parentNode.path());
405             JcrNodeDefinition childDefn = null;
406             try {
407                 childDefn = cache.findBestNodeDefinition(parent, newNodeName, parent.getPayload().getPrimaryTypeName());
408             } catch (RepositoryException e) {
409                 if (sameWorkspace) {
410                     // We're creating a shared node, so get the child node defn for the original's primary type
411                     // under the parent of the new shared (proxy) node ...
412                     AbstractJcrNode originalShareable = cache.findJcrNode(Location.create(sourceUuid));
413                     Name originalShareablePrimaryType = originalShareable.getPrimaryTypeName();
414                     childDefn = cache.findBestNodeDefinition(parent, newNodeName, originalShareablePrimaryType);
415                 } else {
416                     throw e;
417                 }
418             }
419 
420             if (sameWorkspace) {
421                 assert !removeExisting;
422 
423                 // This method is also used to create a shared node, so first check that the source is shareable ...
424                 Property primaryType = sourceNode.getProperty(JcrLexicon.PRIMARY_TYPE);
425                 NameFactory nameFactory = context.getValueFactories().getNameFactory();
426                 boolean shareable = false;
427                 if (primaryType != null) {
428                     Name primaryTypeName = nameFactory.create(primaryType.getFirstValue());
429                     JcrNodeType nodeType = nodeTypeManager().getNodeType(primaryTypeName);
430                     if (nodeType != null && nodeType.isNodeType(JcrMixLexicon.SHAREABLE)) shareable = true;
431                 }
432                 if (!shareable) {
433                     Property mixinTypes = sourceNode.getProperty(JcrLexicon.MIXIN_TYPES);
434                     if (mixinTypes != null) {
435                         for (Object value : mixinTypes) {
436                             Name mixinTypeName = nameFactory.create(value);
437                             JcrNodeType nodeType = nodeTypeManager().getNodeType(mixinTypeName);
438                             if (nodeType != null && nodeType.isNodeType(JcrMixLexicon.SHAREABLE)) {
439                                 shareable = true;
440                                 break;
441                             }
442                         }
443                     }
444                 }
445                 if (shareable) {
446 
447                     // All is okay so far, so all we need to do here is create a "mode:share" node with a primary type ...
448                     // and "mode:sharedUuid" property ...
449                     assert sourceUuid != null;
450                     assert childDefn != null;
451                     PropertyFactory propertyFactory = context.getPropertyFactory();
452                     Collection<Property> properties = new ArrayList<Property>(2);
453                     properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.SHARE));
454                     properties.add(propertyFactory.create(ModeShapeLexicon.SHARED_UUID, sourceUuid));
455                     properties.add(propertyFactory.create(ModeShapeIntLexicon.NODE_DEFINITON, childDefn.getId().getString()));
456                     cache.graphSession().immediateCreateOrReplace(destPath, properties);
457                     return;
458                 }
459                 // Otherwise, just fall through and attempt to create a clone without removing the existing nodes;
460                 // this may fail or it may succeed, but it is the prescribed behavior...
461             }
462             if (removeExisting) {
463                 // This will remove any existing nodes in this (the "target") workspace that have the same UUIDs
464                 // as nodes that will be put into this workspace with the clone operation. Thus, any such
465                 // existing nodes will be removed; but if they're mandatory they cannot be removed, resulting
466                 // in a ConstraintViolationException. Therefore, we have to do a little homework here ...
467                 Set<UUID> uuidsInCloneBranch = getUuidsInBranch(srcPath, srcWorkspace);
468                 if (!uuidsInCloneBranch.isEmpty()) {
469                     // See if any of these exist in the current workspace, and if so whether they can be removed ...
470                     // This is NOT very efficient, since it may result in a batch read for each node ...
471                     GraphSession<JcrNodePayload, JcrPropertyPayload> graphSession = cache.graphSession();
472                     Node<JcrNodePayload, JcrPropertyPayload> node = null;
473                     for (UUID uuid : uuidsInCloneBranch) {
474                         Location location = Location.create(uuid);
475                         try {
476                             node = graphSession.findNodeWith(location);
477                         } catch (org.modeshape.graph.property.PathNotFoundException e) {
478                             // okay, it's not found in the current workspace, so nothing to check ...
479                             continue;
480                         }
481                         // Get the node type that owns the child node definition ...
482                         NodeDefinitionId childDefnId = node.getPayload().getDefinitionId();
483                         JcrNodeType nodeType = nodeTypeManager().getNodeType(childDefnId.getNodeTypeName());
484                         childDefn = nodeType.childNodeDefinition(childDefnId);
485                         if (childDefn.isMandatory()) {
486                             // We can't just remove a mandatory node... unless its parent will be removed too!
487                             String path = node.getPath().getString(context.getNamespaceRegistry());
488                             throw new ConstraintViolationException(JcrI18n.cannotRemoveNodeFromClone.text(path, uuid));
489                         }
490                         // Check whether the node has any local changes ...
491                         if (node.isChanged(true)) {
492                             // This session has changes on nodes that will be removed as a result of the clone ...
493                             String path = node.getPath().getString(context.getNamespaceRegistry());
494                             throw new RepositoryException(JcrI18n.cannotRemoveNodeFromCloneDueToChangesInSession.text(path, uuid));
495                         }
496                     }
497                 }
498             }
499 
500             // Now perform the clone, using the direct (non-session) method ...
501             cache.graphSession().immediateClone(srcPath, srcWorkspace, destPath, removeExisting, false);
502         } catch (ItemNotFoundException e) {
503             // The destination path was not found ...
504             throw new PathNotFoundException(e.getLocalizedMessage(), e);
505         } catch (org.modeshape.graph.property.PathNotFoundException e) {
506             throw new PathNotFoundException(e.getLocalizedMessage(), e);
507         } catch (UuidAlreadyExistsException e) {
508             throw new ItemExistsException(e.getLocalizedMessage(), e);
509         } catch (InvalidWorkspaceException e) {
510             throw new NoSuchWorkspaceException(e.getLocalizedMessage(), e);
511         } catch (RepositorySourceException e) {
512             throw new RepositoryException(e.getLocalizedMessage(), e);
513         } catch (AccessControlException ace) {
514             throw new AccessDeniedException(ace);
515         }
516     }
517 
518     protected Set<UUID> getUuidsInBranch( Path sourcePath,
519                                           String workspace ) {
520         String existingWorkspace = graph.getCurrentWorkspaceName();
521         try {
522             graph.useWorkspace(workspace);
523             Location location = Location.create(sourcePath);
524             Subgraph subgraph = graph.getSubgraphOfDepth(ReadBranchRequest.NO_MAXIMUM_DEPTH).at(location);
525             // Collect up the UUIDs; we use UUID here because that's what JCR requires ...
526             Set<UUID> uuids = new HashSet<UUID>();
527             for (SubgraphNode nodeInSubgraph : subgraph) {
528                 UUID uuid = nodeInSubgraph.getLocation().getUuid();
529                 if (uuid != null) uuids.add(uuid);
530             }
531             return uuids;
532         } finally {
533             graph.useWorkspace(existingWorkspace);
534         }
535     }
536 
537     /**
538      * {@inheritDoc}
539      * 
540      * @see javax.jcr.Workspace#copy(java.lang.String, java.lang.String)
541      */
542     public void copy( String srcAbsPath,
543                       String destAbsPath )
544         throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException,
545         LockException, RepositoryException {
546         this.copy(this.name, srcAbsPath, destAbsPath);
547     }
548 
549     /**
550      * {@inheritDoc}
551      */
552     public void copy( String srcWorkspace,
553                       String srcAbsPath,
554                       String destAbsPath )
555         throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException,
556         LockException, RepositoryException {
557         CheckArg.isNotNull(srcWorkspace, "source workspace name");
558         CheckArg.isNotNull(srcAbsPath, "source path");
559         CheckArg.isNotNull(destAbsPath, "destination path");
560         session.checkLive();
561 
562         if (!graph.getWorkspaces().contains(srcWorkspace)) {
563             throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(graph.getSourceName(), this.name));
564         }
565 
566         // Create the paths ...
567         PathFactory factory = context.getValueFactories().getPathFactory();
568         Path srcPath = null;
569         Path destPath = null;
570         try {
571             srcPath = factory.create(srcAbsPath);
572         } catch (ValueFormatException e) {
573             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(srcAbsPath, "srcAbsPath"), e);
574         }
575         try {
576             destPath = factory.create(destAbsPath);
577         } catch (ValueFormatException e) {
578             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(destAbsPath, "destAbsPath"), e);
579         }
580 
581         // Doing a literal test here because the path factory will canonicalize "/node[1]" to "/node"
582         if (!destPath.isIdentifier() && destAbsPath.endsWith("]")) {
583             throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(destAbsPath));
584         }
585 
586         try {
587             // Use the session to verify that the node location has a definition and is valid with the new cloned child.
588             // This also performs the check permission for reading the parent ...
589             SessionCache cache = this.session.cache();
590             AbstractJcrNode parentNode = null;
591             Name newNodeName = null;
592             if (destPath.isIdentifier()) {
593                 AbstractJcrNode existingDestNode = cache.findJcrNode(Location.create(destPath));
594                 parentNode = existingDestNode.getParent();
595                 newNodeName = existingDestNode.segment().getName();
596                 destPath = factory.create(parentNode.path(), newNodeName);
597             } else {
598                 parentNode = cache.findJcrNode(null, destPath.getParent());
599                 newNodeName = destPath.getLastSegment().getName();
600             }
601 
602             /*
603              * Find the UUID for the source node.  Have to go directly against the graph.
604              */
605             org.modeshape.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace, context).getNodeAt(srcPath);
606             Property uuidProp = sourceNode.getProperty(ModeShapeLexicon.UUID);
607 
608             if (uuidProp != null) {
609                 UUID sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
610 
611                 ModeShapeLock sourceLock = lockManager().lockFor(sourceUuid);
612                 if (sourceLock != null && sourceLock.getLockToken() == null) {
613                     throw new LockException(srcAbsPath);
614                 }
615             }
616 
617             /*
618              * Next, find the primary type for the source node.  
619              */
620             Property primaryTypeProp = sourceNode.getProperty(JcrLexicon.PRIMARY_TYPE);
621             Name primaryTypeName = this.context.getValueFactories().getNameFactory().create(primaryTypeProp.getFirstValue());
622 
623             if (parentNode.isLocked()) {
624                 Lock newParentLock = parentNode.getLock();
625                 if (newParentLock != null && newParentLock.getLockToken() == null) {
626                     throw new LockException(destAbsPath);
627                 }
628             }
629 
630             if (!parentNode.isCheckedOut()) {
631                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(parentNode.getPath()));
632             }
633 
634             cache.findBestNodeDefinition(parentNode.nodeInfo(), newNodeName, primaryTypeName);
635 
636             // Now perform the clone, using the direct (non-session) method ...
637             cache.graphSession().immediateCopy(srcPath, srcWorkspace, destPath);
638 
639             List<AbstractJcrNode> nodesToCheck = new LinkedList<AbstractJcrNode>();
640             nodesToCheck.add(cache.findJcrNode(Location.create(destPath)));
641 
642             while (!nodesToCheck.isEmpty()) {
643                 AbstractJcrNode node = nodesToCheck.remove(0);
644 
645                 if (node.isNodeType(JcrMixLexicon.VERSIONABLE)) {
646                     // Find the node that this was copied from
647                     Path nodeDestPath = node.path().relativeTo(destPath);
648                     Path nodeSourcePath = nodeDestPath.resolveAgainst(srcPath);
649 
650                     AbstractJcrNode fromNode = cache.findJcrNode(Location.create(nodeSourcePath));
651                     if (!(fromNode instanceof JcrSharedNode)) {
652                         UUID originalVersion = fromNode.getBaseVersion().uuid();
653                         versionManager.initializeVersionHistoryFor(node, originalVersion);
654                     }
655                 }
656 
657                 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
658                     nodesToCheck.add((AbstractJcrNode)iter.nextNode());
659                 }
660             }
661 
662         } catch (ItemNotFoundException e) {
663             // The destination path was not found ...
664             throw new PathNotFoundException(e.getLocalizedMessage(), e);
665         } catch (org.modeshape.graph.property.PathNotFoundException e) {
666             throw new PathNotFoundException(e.getLocalizedMessage(), e);
667         } catch (UuidAlreadyExistsException e) {
668             throw new ItemExistsException(e.getLocalizedMessage(), e);
669         } catch (InvalidWorkspaceException e) {
670             throw new NoSuchWorkspaceException(e.getLocalizedMessage(), e);
671         } catch (RepositorySourceException e) {
672             throw new RepositoryException(e.getLocalizedMessage(), e);
673         } catch (AccessControlException ace) {
674             throw new AccessDeniedException(ace);
675         }
676     }
677 
678     /**
679      * {@inheritDoc}
680      * 
681      * @see javax.jcr.Workspace#getImportContentHandler(java.lang.String, int)
682      */
683     public ContentHandler getImportContentHandler( String parentAbsPath,
684                                                    int uuidBehavior )
685         throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, AccessDeniedException,
686         RepositoryException {
687 
688         CheckArg.isNotNull(parentAbsPath, "parentAbsPath");
689         session.checkLive();
690         Path parentPath = this.context.getValueFactories().getPathFactory().create(parentAbsPath);
691         return new JcrContentHandler(this.session, parentPath, uuidBehavior, SaveMode.WORKSPACE);
692     }
693 
694     /**
695      * {@inheritDoc}
696      * 
697      * @see javax.jcr.Workspace#importXML(java.lang.String, java.io.InputStream, int)
698      */
699     public void importXML( String parentAbsPath,
700                            InputStream in,
701                            int uuidBehavior )
702         throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException,
703         InvalidSerializedDataException, LockException, AccessDeniedException, RepositoryException {
704 
705         CheckArg.isNotNull(parentAbsPath, "parentAbsPath");
706         CheckArg.isNotNull(in, "in");
707         session.checkLive();
708 
709         try {
710             XMLReader parser = XMLReaderFactory.createXMLReader();
711             parser.setContentHandler(getImportContentHandler(parentAbsPath, uuidBehavior));
712             parser.parse(new InputSource(in));
713         } catch (EnclosingSAXException ese) {
714             Exception cause = ese.getException();
715             if (cause instanceof ItemExistsException) {
716                 throw (ItemExistsException)cause;
717             } else if (cause instanceof ConstraintViolationException) {
718                 throw (ConstraintViolationException)cause;
719             }
720             throw new RepositoryException(cause);
721         } catch (SAXParseException se) {
722             throw new InvalidSerializedDataException(se);
723         } catch (SAXException se) {
724             throw new RepositoryException(se);
725         }
726 
727     }
728 
729     /**
730      * {@inheritDoc}
731      * 
732      * @see javax.jcr.Workspace#move(java.lang.String, java.lang.String)
733      */
734     public void move( String srcAbsPath,
735                       String destAbsPath ) throws PathNotFoundException, RepositoryException {
736         CheckArg.isNotEmpty(srcAbsPath, "srcAbsPath");
737         CheckArg.isNotEmpty(destAbsPath, "destAbsPath");
738         session.checkLive();
739 
740         // Create the paths ...
741         PathFactory factory = context.getValueFactories().getPathFactory();
742         Path srcPath = null;
743         Path destPath = null;
744         try {
745             srcPath = factory.create(srcAbsPath);
746         } catch (ValueFormatException e) {
747             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(srcAbsPath, "srcAbsPath"), e);
748         }
749         try {
750             destPath = factory.create(destAbsPath);
751         } catch (ValueFormatException e) {
752             throw new PathNotFoundException(JcrI18n.invalidPathParameter.text(destAbsPath, "destAbsPath"), e);
753         }
754 
755         // Doing a literal test here because the path factory will canonicalize "/node[1]" to "/node"
756         if (!destPath.isIdentifier() && destAbsPath.endsWith("]")) {
757             throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(destAbsPath));
758         }
759 
760         try {
761             // Use the session to verify that the node location has a definition and is valid with the new cloned child.
762             // This also performs the check permission for reading the parent ...
763             SessionCache cache = this.session.cache();
764             Node<JcrNodePayload, JcrPropertyPayload> newParent = null;
765             Name newNodeName = null;
766             if (destPath.isIdentifier()) {
767                 Node<JcrNodePayload, JcrPropertyPayload> existingDestNode = cache.findNodeWith(Location.create(destPath));
768                 newParent = existingDestNode.getParent();
769                 newNodeName = existingDestNode.getName();
770                 destPath = factory.create(newParent.getPath(), newNodeName);
771             } else {
772                 newParent = cache.findNode(null, destPath.getParent());
773                 newNodeName = destPath.getLastSegment().getName();
774             }
775 
776             cache.findBestNodeDefinition(newParent, newNodeName, newParent.getPayload().getPrimaryTypeName());
777 
778             AbstractJcrNode sourceNode = cache.findJcrNode(Location.create(srcPath));
779 
780             if (sourceNode.isLocked()) {
781                 Lock sourceLock = sourceNode.getLock();
782                 if (sourceLock != null && sourceLock.getLockToken() == null) {
783                     throw new LockException(srcAbsPath);
784                 }
785             }
786 
787             AbstractJcrNode parentNode = cache.findJcrNode(newParent.getNodeId(), newParent.getPath());
788 
789             if (parentNode.isLocked()) {
790                 Lock newParentLock = parentNode.getLock();
791                 if (newParentLock != null && newParentLock.getLockToken() == null) {
792                     throw new LockException(destAbsPath);
793                 }
794             }
795 
796             if (!sourceNode.isCheckedOut()) {
797                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(sourceNode.getPath()));
798             }
799 
800             if (!parentNode.isCheckedOut()) {
801                 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(parentNode.getPath()));
802             }
803 
804             // Now perform the clone, using the direct (non-session) method ...
805             cache.graphSession().immediateMove(srcPath, destPath);
806         } catch (AccessControlException ace) {
807             throw new AccessDeniedException(ace);
808         } catch (ItemNotFoundException infe) {
809             throw new PathNotFoundException(infe);
810         } catch (org.modeshape.graph.property.PathNotFoundException pnfe) {
811             throw new PathNotFoundException(pnfe);
812         }
813     }
814 
815     /**
816      * {@inheritDoc}
817      * 
818      * @see Workspace#restore(Version[], boolean)
819      */
820     public void restore( Version[] versions,
821                          boolean removeExisting ) throws RepositoryException {
822         versionManager().restore(versions, removeExisting);
823     }
824 
825     /**
826      * {@inheritDoc}
827      * <p>
828      * The caller must have {@link ModeShapePermissions#CREATE_WORKSPACE permission to create workspaces}, and must have
829      * {@link ModeShapePermissions#READ read permission} on the source workspace.
830      * </p>
831      * 
832      * @see javax.jcr.Workspace#createWorkspace(java.lang.String, java.lang.String)
833      */
834     @Override
835     public void createWorkspace( String name,
836                                  String srcWorkspace )
837         throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
838         CheckArg.isNotNull(name, "name");
839         CheckArg.isNotNull(srcWorkspace, "srcWorkspace");
840         session.checkLive();
841         try {
842             session.checkPermission(srcWorkspace, null, ModeShapePermissions.READ);
843             session.checkPermission(name, null, ModeShapePermissions.CREATE_WORKSPACE);
844             repository.createWorkspace(name, srcWorkspace);
845         } catch (AccessControlException e) {
846             throw new AccessDeniedException(e);
847         } catch (InvalidWorkspaceException e) {
848             throw new NoSuchWorkspaceException(e);
849         } catch (RepositorySourceException e) {
850             throw new RepositoryException(e);
851         }
852     }
853 
854     /**
855      * {@inheritDoc}
856      * <p>
857      * The caller must have {@link ModeShapePermissions#CREATE_WORKSPACE permission to create workspaces}.
858      * </p>
859      * 
860      * @see javax.jcr.Workspace#createWorkspace(java.lang.String)
861      */
862     @Override
863     public void createWorkspace( String name )
864         throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
865         CheckArg.isNotNull(name, "name");
866         session.checkLive();
867         try {
868             session.checkPermission(name, null, ModeShapePermissions.CREATE_WORKSPACE);
869             repository.createWorkspace(name, null);
870         } catch (AccessControlException e) {
871             throw new AccessDeniedException(e);
872         } catch (InvalidWorkspaceException e) {
873             throw new RepositoryException(e);
874         } catch (RepositorySourceException e) {
875             throw new RepositoryException(e);
876         }
877     }
878 
879     /**
880      * {@inheritDoc}
881      * <p>
882      * It is not possible to delete the current workspace. Also, the caller must have
883      * {@link ModeShapePermissions#DELETE_WORKSPACE permission to delete workspaces}.
884      * </p>
885      * 
886      * @see javax.jcr.Workspace#deleteWorkspace(java.lang.String)
887      */
888     @Override
889     public void deleteWorkspace( String name )
890         throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
891         CheckArg.isNotNull(name, "name");
892         session.checkLive();
893         try {
894             session.checkPermission(name, null, ModeShapePermissions.DELETE_WORKSPACE);
895             repository.destroyWorkspace(name, this);
896         } catch (AccessControlException e) {
897             throw new AccessDeniedException(e);
898         } catch (InvalidWorkspaceException e) {
899             throw new RepositoryException(e);
900         } catch (RepositorySourceException e) {
901             throw new RepositoryException(e);
902         }
903     }
904 
905     /**
906      * {@inheritDoc}
907      * 
908      * @see javax.jcr.Workspace#getVersionManager()
909      */
910     @Override
911     public VersionManager getVersionManager() {
912         return versionManager;
913     }
914 
915 }