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