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