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