View Javadoc

1   /*
2    * JBoss DNA (http://www.jboss.org/dna)
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    * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
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   * JBoss DNA 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.connector.jcr;
25  
26  import java.util.Calendar;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.UUID;
33  import javax.jcr.Credentials;
34  import javax.jcr.ItemNotFoundException;
35  import javax.jcr.NoSuchWorkspaceException;
36  import javax.jcr.Node;
37  import javax.jcr.NodeIterator;
38  import javax.jcr.PropertyIterator;
39  import javax.jcr.Repository;
40  import javax.jcr.RepositoryException;
41  import javax.jcr.Session;
42  import javax.jcr.Value;
43  import net.jcip.annotations.NotThreadSafe;
44  import org.modeshape.graph.ExecutionContext;
45  import org.modeshape.graph.JcrLexicon;
46  import org.modeshape.graph.Location;
47  import org.modeshape.graph.ModeShapeIntLexicon;
48  import org.modeshape.graph.ModeShapeLexicon;
49  import org.modeshape.graph.cache.CachePolicy;
50  import org.modeshape.graph.observe.Observer;
51  import org.modeshape.graph.property.Binary;
52  import org.modeshape.graph.property.Name;
53  import org.modeshape.graph.property.NameFactory;
54  import org.modeshape.graph.property.NamespaceRegistry;
55  import org.modeshape.graph.property.Path;
56  import org.modeshape.graph.property.PathNotFoundException;
57  import org.modeshape.graph.property.Property;
58  import org.modeshape.graph.property.PropertyFactory;
59  import org.modeshape.graph.property.PropertyType;
60  import org.modeshape.graph.property.ValueFactories;
61  import org.modeshape.graph.property.ValueFactory;
62  import org.modeshape.graph.property.ValueFormatException;
63  import org.modeshape.graph.request.CloneBranchRequest;
64  import org.modeshape.graph.request.CloneWorkspaceRequest;
65  import org.modeshape.graph.request.CopyBranchRequest;
66  import org.modeshape.graph.request.CreateNodeRequest;
67  import org.modeshape.graph.request.CreateWorkspaceRequest;
68  import org.modeshape.graph.request.DeleteBranchRequest;
69  import org.modeshape.graph.request.DestroyWorkspaceRequest;
70  import org.modeshape.graph.request.GetWorkspacesRequest;
71  import org.modeshape.graph.request.InvalidRequestException;
72  import org.modeshape.graph.request.InvalidWorkspaceException;
73  import org.modeshape.graph.request.MoveBranchRequest;
74  import org.modeshape.graph.request.ReadAllChildrenRequest;
75  import org.modeshape.graph.request.ReadAllPropertiesRequest;
76  import org.modeshape.graph.request.ReadNodeRequest;
77  import org.modeshape.graph.request.Request;
78  import org.modeshape.graph.request.UnsupportedRequestException;
79  import org.modeshape.graph.request.UpdatePropertiesRequest;
80  import org.modeshape.graph.request.VerifyWorkspaceRequest;
81  import org.modeshape.graph.request.processor.RequestProcessor;
82  
83  /**
84   * A {@link RequestProcessor} that processes {@link Request}s by operating against the JCR {@link Repository}.
85   */
86  @NotThreadSafe
87  public class JcrRequestProcessor extends RequestProcessor {
88  
89      private final Map<String, Workspace> workspaces = new HashMap<String, Workspace>();
90  
91      private final Repository repository;
92      private final Credentials credentials;
93  
94      /**
95       * @param sourceName
96       * @param context
97       * @param repository
98       * @param observer
99       * @param credentials
100      * @param defaultCachePolicy
101      */
102     public JcrRequestProcessor( String sourceName,
103                                 ExecutionContext context,
104                                 Repository repository,
105                                 Observer observer,
106                                 Credentials credentials,
107                                 CachePolicy defaultCachePolicy ) {
108         super(sourceName, context, observer, null, defaultCachePolicy);
109         this.repository = repository;
110         this.credentials = credentials;
111     }
112 
113     /**
114      * Obtain the JCR session for the named workspace in the current repository using the credentials given to this processor
115      * during instantiation. If no workspace name is supplied, a session for the default workspace is obtained.
116      * <p>
117      * This method caches a single {@link Session} object for each named (or default) workspace, so multiple calls with the same
118      * workspace name will always result in the same {@link Session} object. These cached Session objects are released only when
119      * this processor is {@link #close() closed}.
120      * </p>
121      * 
122      * @param workspaceName the name of the workspace for which a Session is to be found, or null if a session to the default
123      *        workspace should be returned
124      * @return the Session to the workspace, or null if there was an error
125      * @throws NoSuchWorkspaceException if the named workspace does not exist
126      * @throws RepositoryException if there is an error creating a Session
127      */
128     protected Workspace workspaceFor( String workspaceName ) throws RepositoryException {
129         Workspace workspace = workspaces.get(workspaceName);
130         if (workspace == null) {
131             Session session = null;
132             try {
133                 // A workspace was specified, so use it to obtain the session ...
134                 if (credentials != null) {
135                     // Try to create the session using the credentials ...
136                     session = repository.login(credentials, workspaceName);
137                 } else {
138                     // No credentials ...
139                     session = repository.login(workspaceName);
140                 }
141             } catch (NoSuchWorkspaceException e) {
142                 throw new InvalidWorkspaceException(e.getLocalizedMessage());
143             }
144             assert session != null;
145             workspace = new Workspace(session);
146             workspaces.put(workspaceName, workspace);
147             if (workspaceName == null) {
148                 // This is the default workspace, so record the session for the null workspace name, too...
149                 workspaces.put(null, workspace);
150             }
151         }
152         return workspace;
153     }
154 
155     /**
156      * Use the first workspace that's available, or if none is available establish one for the default workspace.
157      * 
158      * @return the workspace, or null if there was an error
159      * @throws RepositoryException if there is an error creating a Session
160      */
161     protected Workspace workspace() throws RepositoryException {
162         if (workspaces.isEmpty()) {
163             // No sessions yet, so create a default one ...
164             return workspaceFor(null);
165         }
166         // Grab any of the sessions ...
167         return workspaces.values().iterator().next();
168     }
169 
170     /**
171      * Determine if there is an existing workspace with the supplied name.
172      * 
173      * @param workspaceName the name of the workspace
174      * @return true if the workspace with the supplied name does exist, or false if it does not
175      * @throws RepositoryException if there is an error working with the session
176      */
177     protected boolean workspaceExistsNamed( String workspaceName ) throws RepositoryException {
178         if (workspaces.containsKey(workspaceName)) return true;
179         for (String actualName : workspace().session().getWorkspace().getAccessibleWorkspaceNames()) {
180             if (actualName == null) continue;
181             if (actualName.equals(workspaceName)) return true;
182         }
183         return false;
184     }
185 
186     /**
187      * Commit all changes to any sessions.
188      * 
189      * @throws RepositoryException if there is a problem committing any changes
190      */
191     public void commit() throws RepositoryException {
192         for (Workspace workspace : workspaces.values()) {
193             // Save the changes in this workspace, or throw an error.
194             workspace.session().save();
195             // Note that this does not really behave like a distributed transaction, because
196             // the first may succeed while the second fails (and all subsequent will not be saved).
197         }
198     }
199 
200     /**
201      * Rollback all changes made to any sessions.
202      * 
203      * @throws RepositoryException if there is a problem committing any changes
204      */
205     public void rollback() throws RepositoryException {
206         // don't do anything ...
207     }
208 
209     /**
210      * {@inheritDoc}
211      * 
212      * @see org.modeshape.graph.request.processor.RequestProcessor#close()
213      */
214     @Override
215     public void close() {
216         try {
217             RuntimeException problem = null;
218             for (Workspace workspace : workspaces.values()) {
219                 try {
220                     workspace.session().logout();
221                 } catch (RuntimeException e) {
222                     if (problem == null) problem = e;
223                 }
224             }
225             if (problem != null) throw problem;
226         } finally {
227             try {
228                 workspaces.clear();
229             } finally {
230                 super.close();
231             }
232         }
233     }
234 
235     /**
236      * {@inheritDoc}
237      * 
238      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.VerifyWorkspaceRequest)
239      */
240     @Override
241     public void process( VerifyWorkspaceRequest request ) {
242         if (request == null) return;
243         try {
244             Workspace workspace = workspaceFor(request.workspaceName());
245             request.setActualWorkspaceName(workspace.name());
246             request.setActualRootLocation(workspace.locationForRootNode());
247         } catch (Throwable e) {
248             request.setError(e);
249         }
250     }
251 
252     /**
253      * {@inheritDoc}
254      * 
255      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.GetWorkspacesRequest)
256      */
257     @Override
258     public void process( GetWorkspacesRequest request ) {
259         if (request == null) return;
260         try {
261             Set<String> workspaceNames = new HashSet<String>();
262             for (String workspaceName : workspace().session().getWorkspace().getAccessibleWorkspaceNames()) {
263                 workspaceNames.add(workspaceName);
264             }
265             request.setAvailableWorkspaceNames(workspaceNames);
266             setCacheableInfo(request);
267         } catch (Throwable e) {
268             request.setError(e);
269         }
270     }
271 
272     /**
273      * {@inheritDoc}
274      * 
275      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateWorkspaceRequest)
276      */
277     @Override
278     public void process( CreateWorkspaceRequest request ) {
279         if (request == null) return;
280         try {
281             // See if the workspace exists, so we record the right error ...
282             String desiredName = request.desiredNameOfNewWorkspace();
283             if (workspaceExistsNamed(desiredName)) {
284                 String msg = JcrConnectorI18n.workspaceAlreadyExistsInRepository.text(desiredName, getSourceName());
285                 request.setError(new InvalidWorkspaceException(msg));
286             } else {
287                 // The workspace does not yet exist, but JCR doesn't let us create it ...
288                 String msg = JcrConnectorI18n.unableToCreateWorkspaceInRepository.text(desiredName, getSourceName());
289                 request.setError(new InvalidRequestException(msg));
290             }
291         } catch (Throwable e) {
292             request.setError(e);
293         }
294         assert request.hasError();
295     }
296 
297     /**
298      * {@inheritDoc}
299      * 
300      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneWorkspaceRequest)
301      */
302     @Override
303     public void process( CloneWorkspaceRequest request ) {
304         if (request == null) return;
305         try {
306             // JCR requires that the target workspace already exist, so check it ...
307             String desiredName = request.desiredNameOfTargetWorkspace();
308             if (workspaceExistsNamed(desiredName)) {
309                 switch (request.cloneConflictBehavior()) {
310                     case DO_NOT_CLONE:
311                         String msg = JcrConnectorI18n.workspaceAlreadyExistsInRepository.text(desiredName, getSourceName());
312                         request.setError(new InvalidWorkspaceException(msg));
313                         break;
314                     case SKIP_CLONE:
315                         // Perform the clone ...
316                         Workspace workspace = workspaceFor(desiredName);
317                         workspace.session().getWorkspace().clone(request.nameOfWorkspaceToBeCloned(), "/", "/", true);
318                         Location actualLocation = workspace.locationForRootNode();
319                         request.setActualWorkspaceName(workspace.name());
320                         request.setActualRootLocation(actualLocation);
321                         break;
322                 }
323                 return;
324             }
325             // Otherwise, the workspace doesn't exist and JCR doesn't let us create one ...
326             String msg = JcrConnectorI18n.unableToCreateWorkspaceInRepository.text(desiredName, getSourceName());
327             request.setError(new InvalidRequestException(msg));
328         } catch (Throwable e) {
329             request.setError(e);
330         }
331     }
332 
333     /**
334      * {@inheritDoc}
335      * 
336      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DestroyWorkspaceRequest)
337      */
338     @Override
339     public void process( DestroyWorkspaceRequest request ) {
340         if (request == null) return;
341         String msg = JcrConnectorI18n.unableToDestroyWorkspaceInRepository.text(request.workspaceName(), getSourceName());
342         request.setError(new UnsupportedRequestException(msg));
343     }
344 
345     /**
346      * {@inheritDoc}
347      * 
348      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadNodeRequest)
349      */
350     @Override
351     public void process( ReadNodeRequest request ) {
352         if (request == null) return;
353         try {
354             Workspace workspace = workspaceFor(request.inWorkspace());
355             Node node = workspace.node(request.at());
356             Location actualLocation = workspace.locationFor(node);
357             request.setActualLocationOfNode(actualLocation);
358             // Read the children ...
359             for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
360                 request.addChild(workspace.locationFor(iter.nextNode()));
361             }
362             // Read the properties ...
363             for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
364                 request.addProperty(workspace.propertyFor(iter.nextProperty()));
365             }
366             // Add in the 'jcr:uuid' property ...
367             if (actualLocation.hasIdProperties()) {
368                 request.addProperty(workspace.propertyFor(ModeShapeLexicon.UUID, actualLocation.getUuid()));
369             }
370             setCacheableInfo(request);
371         } catch (Throwable e) {
372             request.setError(e);
373         }
374     }
375 
376     /**
377      * {@inheritDoc}
378      * 
379      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadAllChildrenRequest)
380      */
381     @Override
382     public void process( ReadAllChildrenRequest request ) {
383         if (request == null) return;
384         try {
385             Workspace workspace = workspaceFor(request.inWorkspace());
386             Node parent = workspace.node(request.of());
387             request.setActualLocationOfNode(workspace.locationFor(parent));
388             for (NodeIterator iter = parent.getNodes(); iter.hasNext();) {
389                 request.addChild(workspace.locationFor(iter.nextNode()));
390             }
391             setCacheableInfo(request);
392         } catch (Throwable e) {
393             request.setError(e);
394         }
395     }
396 
397     /**
398      * {@inheritDoc}
399      * 
400      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.ReadAllPropertiesRequest)
401      */
402     @Override
403     public void process( ReadAllPropertiesRequest request ) {
404         if (request == null) return;
405         try {
406             Workspace workspace = workspaceFor(request.inWorkspace());
407             Node node = workspace.node(request.at());
408             Location actualLocation = workspace.locationFor(node);
409             request.setActualLocationOfNode(actualLocation);
410             // Read the properties ...
411             for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
412                 request.addProperty(workspace.propertyFor(iter.nextProperty()));
413             }
414             // Add in the 'jcr:uuid' property ...
415             if (actualLocation.hasIdProperties()) {
416                 request.addProperty(workspace.propertyFor(ModeShapeLexicon.UUID, actualLocation.getUuid()));
417             }
418             // Get the number of children ...
419             NodeIterator childIter = node.getNodes();
420             int numChildren = (int)childIter.getSize();
421             if (numChildren == -1) {
422                 numChildren = 0;
423                 while (childIter.hasNext()) {
424                     childIter.nextNode();
425                     ++numChildren;
426                 }
427             }
428             request.setNumberOfChildren(numChildren);
429             setCacheableInfo(request);
430         } catch (Throwable e) {
431             request.setError(e);
432         }
433     }
434 
435     /**
436      * {@inheritDoc}
437      * 
438      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CreateNodeRequest)
439      */
440     @Override
441     public void process( CreateNodeRequest request ) {
442         if (request == null) return;
443         try {
444             Workspace workspace = workspaceFor(request.inWorkspace());
445             Node parent = workspace.node(request.under());
446             String childName = workspace.stringFor(request.named());
447 
448             // Look for the primary type, if it was set on the request ...
449             String primaryTypeName = null;
450             for (Property property : request.properties()) {
451                 if (property.getName().equals(JcrLexicon.PRIMARY_TYPE)) {
452                     primaryTypeName = workspace.stringFor(property.getFirstValue());
453                     break;
454                 }
455             }
456 
457             // Create the child node ...
458             Node child = null;
459             if (primaryTypeName != null) {
460                 child = parent.addNode(childName, primaryTypeName);
461             } else {
462                 child = parent.addNode(childName);
463             }
464             assert child != null;
465 
466             // And set all of the properties on the new node ...
467             workspace.setProperties(child, request);
468 
469             // Set up the actual results on the request ...
470             request.setActualLocationOfNode(workspace.locationFor(child));
471         } catch (Throwable e) {
472             request.setError(e);
473         }
474     }
475 
476     /**
477      * {@inheritDoc}
478      * 
479      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CloneBranchRequest)
480      */
481     @Override
482     public void process( CloneBranchRequest request ) {
483         if (request == null) return;
484         try {
485             String fromWorkspaceName = request.fromWorkspace();
486             String intoWorkspaceName = request.intoWorkspace();
487             boolean sameWorkspace = fromWorkspaceName.equals(intoWorkspaceName);
488             Workspace fromWorkspace = workspaceFor(fromWorkspaceName);
489             Workspace intoWorkspace = sameWorkspace ? fromWorkspace : workspaceFor(intoWorkspaceName);
490             Node sourceNode = fromWorkspace.node(request.from());
491             Node targetNode = fromWorkspace.node(request.into());
492             Location fromLocation = fromWorkspace.locationFor(sourceNode);
493 
494             // Calculate the source and destination paths ...
495             String srcAbsPath = sourceNode.getPath();
496             String destAbsPath = targetNode.getPath();
497             String copyName = request.desiredName() != null ? intoWorkspace.stringFor(request.desiredName()) : sourceNode.getName();
498             destAbsPath += '/' + copyName;
499 
500             // Perform the clone ...
501             javax.jcr.Workspace workspace = intoWorkspace.session().getWorkspace();
502             workspace.clone(fromWorkspaceName, srcAbsPath, destAbsPath, request.removeExisting());
503 
504             // Find the actual location of the result of the node ...
505             Node last = null;
506             for (NodeIterator iter = targetNode.getNodes(copyName); iter.hasNext();) {
507                 last = iter.nextNode();
508             }
509             Location intoLocation = intoWorkspace.locationFor(last);
510             request.setActualLocations(fromLocation, intoLocation);
511         } catch (Throwable e) {
512             request.setError(e);
513         }
514     }
515 
516     /**
517      * {@inheritDoc}
518      * 
519      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.CopyBranchRequest)
520      */
521     @Override
522     public void process( CopyBranchRequest request ) {
523         if (request == null) return;
524         try {
525             String fromWorkspaceName = request.fromWorkspace();
526             String intoWorkspaceName = request.intoWorkspace();
527             boolean sameWorkspace = fromWorkspaceName.equals(intoWorkspaceName);
528             Workspace fromWorkspace = workspaceFor(fromWorkspaceName);
529             Workspace intoWorkspace = sameWorkspace ? fromWorkspace : workspaceFor(intoWorkspaceName);
530             Node sourceNode = fromWorkspace.node(request.from());
531             Node targetNode = fromWorkspace.node(request.into());
532             Location fromLocation = fromWorkspace.locationFor(sourceNode);
533 
534             // Calculate the source and destination paths ...
535             String srcAbsPath = sourceNode.getPath();
536             String destAbsPath = targetNode.getPath();
537             String copyName = request.desiredName() != null ? intoWorkspace.stringFor(request.desiredName()) : sourceNode.getName();
538             destAbsPath += '/' + copyName;
539 
540             // Perform the copy ...
541             javax.jcr.Workspace workspace = intoWorkspace.session().getWorkspace();
542             if (sameWorkspace) {
543                 workspace.copy(srcAbsPath, destAbsPath);
544             } else {
545                 workspace.copy(fromWorkspaceName, srcAbsPath, destAbsPath);
546             }
547 
548             // Find the actual location of the result of the node ...
549             Node last = null;
550             for (NodeIterator iter = targetNode.getNodes(copyName); iter.hasNext();) {
551                 last = iter.nextNode();
552             }
553             Location intoLocation = intoWorkspace.locationFor(last);
554             request.setActualLocations(fromLocation, intoLocation);
555         } catch (Throwable e) {
556             request.setError(e);
557         }
558     }
559 
560     /**
561      * {@inheritDoc}
562      * 
563      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.DeleteBranchRequest)
564      */
565     @Override
566     public void process( DeleteBranchRequest request ) {
567         if (request == null) return;
568         try {
569             Workspace workspace = workspaceFor(request.inWorkspace());
570             Node node = workspace.node(request.at());
571             Location actual = workspace.locationFor(node);
572             node.remove();
573             request.setActualLocationOfNode(actual);
574         } catch (Throwable e) {
575             request.setError(e);
576         }
577     }
578 
579     /**
580      * {@inheritDoc}
581      * 
582      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.MoveBranchRequest)
583      */
584     @Override
585     public void process( MoveBranchRequest request ) {
586         if (request == null) return;
587         try {
588             Workspace workspace = workspaceFor(request.inWorkspace());
589             Node orig = workspace.node(request.from());
590             Node into = request.into() != null ? workspace.node(request.into()) : null;
591             Node before = request.before() != null ? workspace.node(request.before()) : null;
592             Location originalLocation = workspace.locationFor(orig);
593             Location newLocation = workspace.move(orig, into, request.desiredName(), before);
594             request.setActualLocations(originalLocation, newLocation);
595         } catch (Throwable e) {
596             request.setError(e);
597         }
598     }
599 
600     /**
601      * {@inheritDoc}
602      * 
603      * @see org.modeshape.graph.request.processor.RequestProcessor#process(org.modeshape.graph.request.UpdatePropertiesRequest)
604      */
605     @Override
606     public void process( UpdatePropertiesRequest request ) {
607         if (request == null) return;
608         try {
609             Workspace workspace = workspaceFor(request.inWorkspace());
610             Node node = workspace.node(request.on());
611             Set<Name> newProperties = new HashSet<Name>();
612             for (Map.Entry<Name, Property> entry : request.properties().entrySet()) {
613                 Name propertyName = entry.getKey();
614                 String name = workspace.stringFor(propertyName);
615                 Property property = entry.getValue();
616 
617                 // We need to remove the existing property (if there is one) in case the
618                 // old property has a different cardinality than the new property ...
619                 javax.jcr.Property existing = node.hasProperty(name) ? node.getProperty(name) : null;
620                 if (existing != null && (property == null || existing.getDefinition().isMultiple() == property.isSingle())) {
621                     // Remove the property ...
622                     if (!existing.getDefinition().isProtected()) {
623                         existing.remove();
624                     }
625                 }
626                 if (property != null) {
627                     if (property.size() == 1) {
628                         // Try setting as a single-valued property ...
629                         try {
630                             workspace.setProperty(node, property, false);
631                         } catch (ValueFormatException e) {
632                             workspace.setProperty(node, property, true);
633                         }
634                     } else {
635                         // Set as a multi-valued property ...
636                         workspace.setProperty(node, property, true);
637                     }
638                     if (existing == null) newProperties.add(propertyName);
639                 }
640             }
641 
642             if (request.removeOtherProperties()) {
643                 Set<String> stringNames = new HashSet<String>();
644                 for (Name name : request.properties().keySet()) {
645                     stringNames.add(workspace.stringFor(name));
646                 }
647                 stringNames.add(workspace.stringFor(JcrLexicon.PRIMARY_TYPE));
648                 stringNames.add(workspace.stringFor(JcrLexicon.MIXIN_TYPES));
649                 PropertyIterator propertyIter = node.getProperties();
650                 while (propertyIter.hasNext()) {
651                     javax.jcr.Property property = propertyIter.nextProperty();
652                     if (!stringNames.contains(property.getName()) && !property.getDefinition().isProtected()) {
653                         property.remove();
654                     }
655                 }
656             }
657 
658             // Set up the actual results on the request ...
659             request.setActualLocationOfNode(workspace.locationFor(node));
660             request.setNewProperties(newProperties);
661         } catch (Throwable e) {
662             request.setError(e);
663         }
664     }
665 
666     /**
667      * An encapsulation of a remote JCR Session, with an ExecutionContext that contains a namespace registry which mirrors the
668      * session's registry, and with factories that convert the JCR-specific values, paths, and names into their graph-equivalents.
669      */
670     protected class Workspace {
671         private final Session session;
672         /** The context used to transform the JCR session values into graph values */
673         private final ExecutionContext context;
674         /** The factories for creating graph property values from JCR values */
675         private final ValueFactories factories;
676         /** The factory for creating graph properties from JCR values */
677         private final PropertyFactory propertyFactory;
678         private final NameFactory nameFactory;
679         private final ValueFactory<String> stringFactory;
680         private final String name;
681         private final javax.jcr.ValueFactory jcrValueFactory;
682 
683         protected Workspace( Session jcrSession ) throws RepositoryException {
684             this.session = jcrSession;
685             this.name = this.session.getWorkspace().getName();
686             ExecutionContext connectorContext = getExecutionContext();
687             NamespaceRegistry connectorRegistry = connectorContext.getNamespaceRegistry();
688             this.context = connectorContext.with(new JcrNamespaceRegistry(getSourceName(), this.session, connectorRegistry));
689             this.factories = context.getValueFactories();
690             this.propertyFactory = context.getPropertyFactory();
691             this.nameFactory = this.factories.getNameFactory();
692             this.stringFactory = this.factories.getStringFactory();
693             this.jcrValueFactory = this.session.getValueFactory();
694         }
695 
696         public Location move( Node original,
697                               Node newParent,
698                               Name newName,
699                               Node beforeSibling ) throws RepositoryException {
700             // Determine whether the node needs to move ...
701             if (newParent == null && beforeSibling != null) {
702                 newParent = beforeSibling.getParent();
703             }
704 
705             if (newName != null || (newParent != null && !original.getParent().equals(newParent))) {
706                 // This is not just a reorder, so we definitely have to move first ...
707                 String destAbsPath = newParent != null ? newParent.getPath() : original.getParent().getPath();
708                 assert !destAbsPath.endsWith("/");
709                 String newNameStr = newName != null ? stringFor(newName) : original.getName();
710                 destAbsPath += '/' + newNameStr;
711                 session.move(original.getPath(), destAbsPath);
712             }
713 
714             if (beforeSibling != null) {
715                 // Even if moved, the 'orginal' node should still point to the node we just moved ...
716                 String siblingName = nameFor(beforeSibling);
717                 String originalName = nameFor(original);
718                 original.getParent().orderBefore(originalName, siblingName);
719             }
720             return locationFor(original);
721         }
722 
723         public String name() {
724             return name;
725         }
726 
727         public Session session() {
728             return session;
729         }
730 
731         protected String stringFor( Object value ) {
732             return stringFactory.create(value);
733         }
734 
735         protected String nameFor( Node node ) throws RepositoryException {
736             String name = node.getName();
737             int snsIndex = node.getIndex();
738             return snsIndex == 1 ? name : name + '[' + snsIndex + ']';
739         }
740 
741         public Property propertyFor( Name name,
742                                      Object... values ) {
743             return propertyFactory.create(name, values);
744         }
745 
746         /**
747          * Get the graph {@link Property property} for the supplied JCR property representation.
748          * 
749          * @param jcrProperty the JCR property; may not be null
750          * @return the graph property
751          * @throws RepositoryException if there is an error working with the session
752          */
753         public Property propertyFor( javax.jcr.Property jcrProperty ) throws RepositoryException {
754             // Get the values ...
755             Object[] values = null;
756             if (jcrProperty.getDefinition().isMultiple()) {
757                 Value[] jcrValues = jcrProperty.getValues();
758                 values = new Object[jcrValues.length];
759                 for (int i = 0; i < jcrValues.length; i++) {
760                     values[i] = convert(jcrValues[i], jcrProperty);
761                 }
762             } else {
763                 values = new Object[] {convert(jcrProperty.getValue(), jcrProperty)};
764             }
765             // Get the name, and then create the property ...
766             Name name = nameFactory.create(jcrProperty.getName());
767             return propertyFactory.create(name, values);
768         }
769 
770         /**
771          * Utility method used to convert a JCR {@link Value} object into a valid graph property value.
772          * 
773          * @param value the JCR value; may be null
774          * @param jcrProperty the JCR property, used to access the session (if needed)
775          * @return the graph representation of the value
776          * @throws RepositoryException if there is an error working with the session
777          */
778         public Object convert( Value value,
779                                javax.jcr.Property jcrProperty ) throws RepositoryException {
780             if (value == null) return null;
781             try {
782                 switch (value.getType()) {
783                     case javax.jcr.PropertyType.BINARY:
784                         return factories.getBinaryFactory().create(value.getStream(), 0);
785                     case javax.jcr.PropertyType.BOOLEAN:
786                         return factories.getBooleanFactory().create(value.getBoolean());
787                     case javax.jcr.PropertyType.DATE:
788                         return factories.getDateFactory().create(value.getDate());
789                     case javax.jcr.PropertyType.DOUBLE:
790                         return factories.getDoubleFactory().create(value.getDouble());
791                     case javax.jcr.PropertyType.LONG:
792                         return factories.getLongFactory().create(value.getLong());
793                     case javax.jcr.PropertyType.NAME:
794                         return factories.getNameFactory().create(value.getString());
795                     case javax.jcr.PropertyType.PATH:
796                         return factories.getPathFactory().create(value.getString());
797                     case javax.jcr.PropertyType.REFERENCE:
798                         return factories.getReferenceFactory().create(value.getString());
799                     case javax.jcr.PropertyType.STRING:
800                         return factories.getStringFactory().create(value.getString());
801                     case javax.jcr.PropertyType.UNDEFINED:
802                         // We don't know what type of values these are, but we need to convert any value
803                         // that depends on namespaces (e.g., names and paths) into Graph objects.
804                         try {
805                             // So first try a name ...
806                             return factories.getNameFactory().create(value.getString());
807                         } catch (ValueFormatException e) {
808                             // Wasn't a name, so try a path ...
809                             try {
810                                 return factories.getPathFactory().create(value.getString());
811                             } catch (ValueFormatException e2) {
812                                 // Wasn't a path, so finally treat as a string ...
813                                 return factories.getStringFactory().create(value.getString());
814                             }
815                         }
816                 }
817             } catch (ValueFormatException e) {
818                 // There was an error converting the JCR value into the appropriate graph value
819                 String typeName = javax.jcr.PropertyType.nameFromValue(value.getType());
820                 throw new RepositoryException(JcrConnectorI18n.errorConvertingJcrValueOfType.text(value.getString(), typeName));
821             }
822             return null;
823         }
824 
825         /**
826          * Obtain the actual location for the supplied node.
827          * 
828          * @param node the existing node; may not be null
829          * @return the actual location, with UUID if the node is "mix:referenceable"
830          * @throws RepositoryException if there is an error
831          */
832         public Location locationFor( Node node ) throws RepositoryException {
833             // Get the path to the node ...
834             String pathStr = node.getPath();
835             Path path = factories.getPathFactory().create(pathStr);
836 
837             // Does the node have a UUID ...
838             if (node.isNodeType("mix:referenceable")) {
839                 String uuidStr = node.getUUID();
840                 if (uuidStr != null) {
841                     UUID uuid = UUID.fromString(uuidStr);
842                     return Location.create(path, uuid);
843                 }
844             }
845             return Location.create(path);
846         }
847 
848         public Location locationForRootNode() throws RepositoryException {
849             return locationFor(session.getRootNode());
850         }
851 
852         /**
853          * Find the existing node given the location.
854          * 
855          * @param location the location of the node, which must have a {@link Location#getPath() path} and/or
856          *        {@link Location#getUuid() UUID}
857          * @return the existing node; never null
858          * @throws RepositoryException if there is an error working with the session
859          * @throws PathNotFoundException if the node could not be found by its path
860          */
861         public Node node( Location location ) throws RepositoryException {
862             Node root = session.getRootNode();
863             UUID uuid = location.getUuid();
864             if (uuid != null) {
865                 try {
866                     return session.getNodeByUUID(uuid.toString());
867                 } catch (ItemNotFoundException e) {
868                     if (!location.hasPath()) {
869                         String msg = JcrConnectorI18n.unableToFindNodeWithUuid.text(getSourceName(), uuid);
870                         throw new PathNotFoundException(location, factories.getPathFactory().createRootPath(), msg);
871                     }
872                     // Otherwise, try to find it by its path ...
873                 }
874             }
875             if (location.hasPath()) {
876                 Path relativePath = location.getPath().relativeToRoot();
877                 ValueFactory<String> stringFactory = factories.getStringFactory();
878                 String relativePathStr = stringFactory.create(relativePath);
879                 try {
880                     return root.getNode(relativePathStr);
881                 } catch (javax.jcr.PathNotFoundException e) {
882                     // Figure out the lowest existing path ...
883                     Node node = root;
884                     for (Path.Segment segment : relativePath) {
885                         try {
886                             node = node.getNode(stringFactory.create(segment));
887                         } catch (javax.jcr.PathNotFoundException e2) {
888                             String pathStr = stringFactory.create(location.getPath());
889                             Path lowestPath = factories.getPathFactory().create(node.getPath());
890                             throw new PathNotFoundException(location, lowestPath,
891                                                             JcrConnectorI18n.nodeDoesNotExist.text(getSourceName(),
892                                                                                                    session.getWorkspace()
893                                                                                                           .getName(),
894                                                                                                    pathStr));
895                         }
896                     }
897                 }
898             }
899             // Otherwise, we can't find the node ...
900             String msg = JcrConnectorI18n.unableToFindNodeWithoutPathOrUuid.text(getSourceName(), location);
901             throw new IllegalArgumentException(msg);
902         }
903 
904         public void setProperties( Node node,
905                                    Iterable<Property> properties ) throws RepositoryException {
906             // Look for the internal property that ModeShape uses to track which properties are multi-valued w/r/t JCR ...
907             Set<Name> multiValued = null;
908             for (Property property : properties) {
909                 if (property.getName().equals(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES)) {
910                     // Go through the properties and see which properties are multi-valued ...
911                     multiValued = getMultiValuedProperties(property);
912                     break;
913                 }
914             }
915             if (multiValued == null) multiValued = Collections.emptySet();
916 
917             // Now set each of the properties ...
918             for (Property property : properties) {
919                 setProperty(node, property, multiValued.contains(property.getName()));
920             }
921         }
922 
923         protected Set<Name> getMultiValuedProperties( Property multiValuedProperty ) {
924             // Go through the properties and see which properties are multi-valued ...
925             Set<Name> multiValued = new HashSet<Name>();
926             for (Object value : multiValuedProperty) {
927                 Name multiValuedPropertyName = nameFactory.create(value);
928                 if (multiValuedPropertyName != null) {
929                     multiValued.add(multiValuedPropertyName);
930                 }
931             }
932             return multiValued;
933         }
934 
935         protected void setProperty( Node node,
936                                     Property property,
937                                     boolean isMultiValued ) throws RepositoryException {
938             Name name = property.getName();
939             if (name.equals(JcrLexicon.PRIMARY_TYPE)) return;
940             if (name.equals(ModeShapeIntLexicon.NODE_DEFINITON)) return;
941             if (name.equals(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES)) return;
942             if (name.equals(JcrLexicon.MIXIN_TYPES)) {
943                 for (Object mixinVvalue : property.getValuesAsArray()) {
944                     String mixinTypeName = stringFor(mixinVvalue);
945                     node.addMixin(mixinTypeName);
946                 }
947                 return;
948             }
949 
950             // Otherwise, just set the normal property. First determine the expected type ...
951             String propertyName = stringFor(name);
952             if (isMultiValued) {
953                 Value[] values = new Value[property.size()];
954                 int index = 0;
955                 PropertyType propertyType = null;
956                 for (Object value : property) {
957                     if (value == null) continue;
958                     if (propertyType == null) propertyType = PropertyType.discoverType(value);
959                     values[index] = convertToJcrValue(propertyType, value);
960                     ++index;
961                 }
962                 node.setProperty(propertyName, values);
963             } else {
964                 Object firstValue = property.getFirstValue();
965                 PropertyType propertyType = PropertyType.discoverType(firstValue);
966                 Value value = convertToJcrValue(propertyType, firstValue);
967                 node.setProperty(propertyName, value);
968             }
969         }
970 
971         protected Value convertToJcrValue( PropertyType graphType,
972                                            Object graphValue ) {
973             if (graphValue == null) return null;
974             switch (graphType) {
975                 case DECIMAL:
976                 case NAME:
977                 case PATH:
978                 case REFERENCE:
979                 case UUID:
980                 case URI:
981                 case STRING:
982                 case OBJECT:
983                     String stringValue = factories.getStringFactory().create(graphValue);
984                     return jcrValueFactory.createValue(stringValue);
985                 case BOOLEAN:
986                     Boolean booleanValue = factories.getBooleanFactory().create(graphValue);
987                     return jcrValueFactory.createValue(booleanValue.booleanValue());
988                 case DOUBLE:
989                     Double doubleValue = factories.getDoubleFactory().create(graphValue);
990                     return jcrValueFactory.createValue(doubleValue);
991                 case LONG:
992                     Long longValue = factories.getLongFactory().create(graphValue);
993                     return jcrValueFactory.createValue(longValue);
994                 case DATE:
995                     Calendar calValue = factories.getDateFactory().create(graphValue).toCalendar();
996                     return jcrValueFactory.createValue(calValue);
997                 case BINARY:
998                     Binary binary = factories.getBinaryFactory().create(graphValue);
999                     try {
1000                         binary.acquire();
1001                         return jcrValueFactory.createValue(binary.getStream());
1002                     } finally {
1003                         binary.release();
1004                     }
1005             }
1006             assert false;
1007             return null;
1008         }
1009     }
1010 }