View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * Unless otherwise indicated, all code in ModeShape is licensed
10   * to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.request;
25  
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.LinkedList;
29  import java.util.Map;
30  import net.jcip.annotations.NotThreadSafe;
31  import org.modeshape.common.util.CheckArg;
32  import org.modeshape.graph.Location;
33  import org.modeshape.graph.NodeConflictBehavior;
34  import org.modeshape.graph.connector.UuidAlreadyExistsException;
35  import org.modeshape.graph.property.Name;
36  import org.modeshape.graph.property.Path;
37  import org.modeshape.graph.property.Property;
38  import org.modeshape.graph.request.CloneWorkspaceRequest.CloneConflictBehavior;
39  import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
40  
41  /**
42   * A component that can be used to build up a list of requests. This implementation does perform some simple optimizations, such
43   * as combining adjacent compatible requests.
44   * <p>
45   * This builder can be used to add multiple requests. When the enqueued requests are to be processed, calling {@link #pop()} will
46   * remove and return the enqueued requests (as a {@link CompositeRequest} if there is more than one enqueued request).
47   * </p>
48   */
49  @NotThreadSafe
50  public class BatchRequestBuilder {
51  
52      private LinkedList<Request> requests;
53      private NodeChange pendingRequest;
54  
55      public BatchRequestBuilder() {
56          this.requests = new LinkedList<Request>();
57      }
58  
59      public BatchRequestBuilder( LinkedList<Request> requests ) {
60          this.requests = requests != null ? requests : new LinkedList<Request>();
61      }
62  
63      /**
64       * Determine whether this builder has built any requests.
65       * 
66       * @return true if there are requests (i.e., {@link #pop()} will return a non-null request), or false if there are no requests
67       */
68      public boolean hasRequests() {
69          return pendingRequest != null || !requests.isEmpty();
70      }
71  
72      /**
73       * Finish any pending request
74       */
75      public void finishPendingRequest() {
76          if (pendingRequest != null) {
77              // There's a pending request, we need to build it ...
78              add(pendingRequest.toRequest());
79              pendingRequest = null;
80          }
81      }
82  
83      /**
84       * Remove and return any requests that have been built by this builder since the last call to this method. This method will
85       * return null if no requests have been built. If only one request was built, then it will be returned. If multiple requests
86       * have been built, then this method will return a {@link CompositeRequest} containing them.
87       * 
88       * @return the request (or {@link CompositeRequest}) representing those requests that this builder has created since the last
89       *         call to this method, or null if there are no requests to return
90       */
91      public Request pop() {
92          int number = requests.size();
93          if (pendingRequest != null) {
94              // There's a pending request, we need to build it ...
95              Request newRequest = pendingRequest.toRequest();
96              if (number == 0) {
97                  // There's no other request ...
98                  return newRequest;
99              }
100             // We have at least one other request, so add the pending request ...
101             addPending();
102             ++number;
103         } else {
104             // There is no pending request ...
105             if (number == 0) {
106                 // And no enqueued request ...
107                 return null;
108             }
109             if (number == 1) {
110                 // There's only one request, so return just the one ...
111                 Request result = requests.getFirst();
112                 requests.clear();
113                 return result;
114             }
115         }
116         assert number >= 2;
117         // Build a composite request (reusing the existing list), and then replace the list
118         Request result = CompositeRequest.with(requests);
119         requests = new LinkedList<Request>();
120         return result;
121     }
122 
123     protected final BatchRequestBuilder add( Request request ) {
124         addPending();
125         requests.add(request);
126         return this;
127     }
128 
129     protected final BatchRequestBuilder addPending() {
130         if (pendingRequest != null) {
131             requests.add(pendingRequest.toRequest());
132             pendingRequest = null;
133         }
134         return this;
135     }
136 
137     /**
138      * Add a request to obtain the information about the available workspaces.
139      * 
140      * @return this builder for method chaining; never null
141      */
142     public BatchRequestBuilder getWorkspaces() {
143         return add(new GetWorkspacesRequest());
144     }
145 
146     /**
147      * Add a request to verify the existance of the named workspace.
148      * 
149      * @param workspaceName the desired name of the workspace, or null if the source's default workspace should be used
150      * @return this builder for method chaining; never null
151      */
152     public BatchRequestBuilder verifyWorkspace( String workspaceName ) {
153         return add(new VerifyWorkspaceRequest(workspaceName));
154     }
155 
156     /**
157      * Add a request to create a new workspace, and specify the behavior should a workspace already exists with a name that
158      * matches the desired name for the new workspace.
159      * 
160      * @param desiredNameOfNewWorkspace the desired name of the new workspace
161      * @param createConflictBehavior the behavior if a workspace already exists with the same name, or null if the default
162      *        behavior should be used
163      * @return this builder for method chaining; never null
164      */
165     public BatchRequestBuilder createWorkspace( String desiredNameOfNewWorkspace,
166                                                 CreateConflictBehavior createConflictBehavior ) {
167         return add(new CreateWorkspaceRequest(desiredNameOfNewWorkspace, createConflictBehavior));
168     }
169 
170     /**
171      * Add a request to clone an existing workspace to create a new workspace, and specify the behavior should a workspace already
172      * exists with a name that matches the desired name for the new workspace.
173      * 
174      * @param nameOfWorkspaceToBeCloned the name of the existing workspace that is to be cloned
175      * @param desiredNameOfTargetWorkspace the desired name of the target workspace
176      * @param createConflictBehavior the behavior if a workspace already exists with the same name
177      * @param cloneConflictBehavior the behavior if the workspace to be cloned does not exist
178      * @return this builder for method chaining; never null
179      * @throws IllegalArgumentException if the either workspace name is null
180      */
181     public BatchRequestBuilder cloneWorkspace( String nameOfWorkspaceToBeCloned,
182                                                String desiredNameOfTargetWorkspace,
183                                                CreateConflictBehavior createConflictBehavior,
184                                                CloneConflictBehavior cloneConflictBehavior ) {
185         return add(new CloneWorkspaceRequest(nameOfWorkspaceToBeCloned, desiredNameOfTargetWorkspace, createConflictBehavior,
186                                              cloneConflictBehavior));
187     }
188 
189     /**
190      * Add a request to destroy an existing workspace.
191      * 
192      * @param workspaceName the name of the workspace that is to be destroyed
193      * @return this builder for method chaining; never null
194      * @throws IllegalArgumentException if the workspace name is null
195      */
196     public BatchRequestBuilder destroyWorkspace( String workspaceName ) {
197         return add(new DestroyWorkspaceRequest(workspaceName));
198     }
199 
200     /**
201      * Add a request to verify the existance and location of a node at the supplied location.
202      * 
203      * @param at the location of the node to be verified
204      * @param workspaceName the name of the workspace containing the node
205      * @return this builder for method chaining; never null
206      * @throws IllegalArgumentException if the location or workspace name is null
207      */
208     public BatchRequestBuilder verifyNodeExists( Location at,
209                                                  String workspaceName ) {
210         return add(new VerifyNodeExistsRequest(at, workspaceName));
211     }
212 
213     /**
214      * Add a request to read the properties and number of children of a node at the supplied location.
215      * 
216      * @param at the location of the node to be read
217      * @param workspaceName the name of the workspace containing the node
218      * @return this builder for method chaining; never null
219      * @throws IllegalArgumentException if the location or workspace name is null
220      */
221     public BatchRequestBuilder readNode( Location at,
222                                          String workspaceName ) {
223         return add(new ReadNodeRequest(at, workspaceName));
224     }
225 
226     /**
227      * Add a request to read the children of a node at the supplied location in the designated workspace.
228      * 
229      * @param of the location of the node whose children are to be read
230      * @param workspaceName the name of the workspace
231      * @return this builder for method chaining; never null
232      * @throws IllegalArgumentException if the location or workspace name is null
233      */
234     public BatchRequestBuilder readAllChildren( Location of,
235                                                 String workspaceName ) {
236         return add(new ReadAllChildrenRequest(of, workspaceName));
237     }
238 
239     /**
240      * Add a request to read the properties and number of children of a node at the supplied location.
241      * 
242      * @param of the location of the node whose children are to be read
243      * @param workspaceName the name of the workspace
244      * @return this builder for method chaining; never null
245      * @throws IllegalArgumentException if the location or workspace name is null
246      */
247     public BatchRequestBuilder readAllProperties( Location of,
248                                                   String workspaceName ) {
249         return add(new ReadAllPropertiesRequest(of, workspaceName));
250     }
251 
252     /**
253      * Add a request to read the properties and number of children of a node at the supplied location.
254      * 
255      * @param of the location of the node whose children are to be read
256      * @param workspaceName the name of the workspace
257      * @param propertyName the name of the property to read
258      * @return this builder for method chaining; never null
259      * @throws IllegalArgumentException if the location or workspace name is null
260      */
261     public BatchRequestBuilder readProperty( Location of,
262                                              String workspaceName,
263                                              Name propertyName ) {
264         return add(new ReadPropertyRequest(of, workspaceName, propertyName));
265     }
266 
267     /**
268      * Add a request to read the branch at the supplied location, to a maximum depth of 2.
269      * 
270      * @param at the location of the branch
271      * @param workspaceName the name of the workspace containing the branch
272      * @return this builder for method chaining; never null
273      * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
274      */
275     public BatchRequestBuilder readBranch( Location at,
276                                            String workspaceName ) {
277         return add(new ReadBranchRequest(at, workspaceName));
278     }
279 
280     /**
281      * Add a request to read the branch (of given depth) at the supplied location.
282      * 
283      * @param at the location of the branch
284      * @param workspaceName the name of the workspace containing the branch
285      * @param maxDepth the maximum depth to read
286      * @return this builder for method chaining; never null
287      * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
288      */
289     public BatchRequestBuilder readBranch( Location at,
290                                            String workspaceName,
291                                            int maxDepth ) {
292         return add(new ReadBranchRequest(at, workspaceName, maxDepth));
293     }
294 
295     /**
296      * Add a request to read a block of the children of a node at the supplied location. The block is defined by the starting
297      * index of the first child and the number of children to include. Note that this index is <i>not</i> the
298      * {@link Path.Segment#getIndex() same-name-sibiling index}, but rather is the index of the child as if the children were in
299      * an array.
300      * 
301      * @param of the location of the node whose children are to be read
302      * @param workspaceName the name of the workspace containing the parent
303      * @param startingIndex the zero-based index of the first child to be included in the block
304      * @param count the maximum number of children that should be included in the block
305      * @return this builder for method chaining; never null
306      * @throws IllegalArgumentException if the location or workspace name is null, if <code>startingIndex</code> is negative, or
307      *         if <code>count</count> is less than 1.
308      */
309     public BatchRequestBuilder readBlockOfChildren( Location of,
310                                                     String workspaceName,
311                                                     int startingIndex,
312                                                     int count ) {
313         return add(new ReadBlockOfChildrenRequest(of, workspaceName, startingIndex, count));
314     }
315 
316     /**
317      * Add a request to read those children of a node that are immediately after a supplied sibling node.
318      * 
319      * @param startingAfter the location of the previous sibling that was the last child of the previous block of children read
320      * @param workspaceName the name of the workspace containing the node
321      * @param count the maximum number of children that should be included in the block
322      * @return this builder for method chaining; never null
323      * @throws IllegalArgumentException if the workspace name or <code>startingAfter</code> location is null, or if
324      *         <code>count</count> is less than 1.
325      */
326     public BatchRequestBuilder readNextBlockOfChildren( Location startingAfter,
327                                                         String workspaceName,
328                                                         int count ) {
329         return add(new ReadNextBlockOfChildrenRequest(startingAfter, workspaceName, count));
330     }
331 
332     /**
333      * Add a request to create a node with the given properties under the supplied location.
334      * 
335      * @param parentLocation the location of the existing parent node, under which the new child should be created
336      * @param workspaceName the name of the workspace containing the parent
337      * @param childName the name of the new child to create under the existing parent
338      * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
339      *        properties} for the new node
340      * @return this builder for method chaining; never null
341      * @throws IllegalArgumentException if the location, workspace name, or child name is null
342      */
343     public BatchRequestBuilder createNode( Location parentLocation,
344                                            String workspaceName,
345                                            Name childName,
346                                            Iterator<Property> properties ) {
347         return add(new CreateNodeRequest(parentLocation, workspaceName, childName, CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR,
348                                          properties));
349     }
350 
351     /**
352      * Add a request to create a node with the given properties under the supplied location.
353      * 
354      * @param parentLocation the location of the existing parent node, under which the new child should be created
355      * @param workspaceName the name of the workspace containing the parent
356      * @param childName the name of the new child to create under the existing parent
357      * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
358      *        properties} for the new node
359      * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
360      *        location
361      * @return this builder for method chaining; never null
362      * @throws IllegalArgumentException if the location, workspace name, or child name is null
363      */
364     public BatchRequestBuilder createNode( Location parentLocation,
365                                            String workspaceName,
366                                            Name childName,
367                                            Iterator<Property> properties,
368                                            NodeConflictBehavior conflictBehavior ) {
369         if (conflictBehavior == null) conflictBehavior = CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR;
370         return add(new CreateNodeRequest(parentLocation, workspaceName, childName, conflictBehavior, properties));
371     }
372 
373     /**
374      * Add a request to create a node with the given properties under the supplied location.
375      * 
376      * @param parentLocation the location of the existing parent node, under which the new child should be created
377      * @param workspaceName the name of the workspace containing the parent
378      * @param childName the name of the new child to create under the existing parent
379      * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
380      *        properties} for the new node
381      * @return this builder for method chaining; never null
382      * @throws IllegalArgumentException if the location, workspace name, or child name is null
383      */
384     public BatchRequestBuilder createNode( Location parentLocation,
385                                            String workspaceName,
386                                            Name childName,
387                                            Property[] properties ) {
388         return add(new CreateNodeRequest(parentLocation, workspaceName, childName, CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR,
389                                          properties));
390     }
391 
392     /**
393      * Add a request to create a node with the given properties under the supplied location.
394      * 
395      * @param parentLocation the location of the existing parent node, under which the new child should be created
396      * @param workspaceName the name of the workspace containing the parent
397      * @param childName the name of the new child to create under the existing parent
398      * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
399      *        properties} for the new node
400      * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
401      *        location
402      * @return this builder for method chaining; never null
403      * @throws IllegalArgumentException if the location, workspace name, or child name is null
404      */
405     public BatchRequestBuilder createNode( Location parentLocation,
406                                            String workspaceName,
407                                            Name childName,
408                                            Property[] properties,
409                                            NodeConflictBehavior conflictBehavior ) {
410         if (conflictBehavior == null) conflictBehavior = CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR;
411         return add(new CreateNodeRequest(parentLocation, workspaceName, childName, conflictBehavior, properties));
412     }
413 
414     /**
415      * Add a request to update the property on the node at the supplied location. This request will create the property if it does
416      * not yet exist.
417      * 
418      * @param on the location of the node to be read
419      * @param workspaceName the name of the workspace containing the node
420      * @param property the new property on the node
421      * @return this builder for method chaining; never null
422      * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update
423      */
424     public BatchRequestBuilder setProperty( Location on,
425                                             String workspaceName,
426                                             Property property ) {
427         // If there's a pending request ...
428         if (pendingRequest != null) {
429             // Compare the supplied location with that of the pending request
430             if (pendingRequest.location.isSame(on)) {
431                 // They are the same location, so we can add the properties to the pending request ...
432                 pendingRequest.pendingProperties.put(property.getName(), property);
433                 return this;
434             }
435             // Not the exact same location, so push the existing pending request ...
436             addPending();
437         }
438 
439         // Record this operation as a pending change ...
440         pendingRequest = new NodeChange(on, workspaceName);
441         pendingRequest.pendingProperties.put(property.getName(), property);
442         return this;
443     }
444 
445     /**
446      * Add a request to update the properties on the node at the supplied location.
447      * 
448      * @param on the location of the node to be read
449      * @param workspaceName the name of the workspace containing the node
450      * @param properties the new properties on the node
451      * @return this builder for method chaining; never null
452      * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update
453      */
454     public BatchRequestBuilder setProperties( Location on,
455                                               String workspaceName,
456                                               Property... properties ) {
457         // If there's a pending request ...
458         if (pendingRequest != null) {
459             // Compare the supplied location with that of the pending request
460             if (pendingRequest.location.isSame(on)) {
461                 // They are the same location, so we can add the properties to the pending request ...
462                 for (Property property : properties) {
463                     pendingRequest.pendingProperties.put(property.getName(), property);
464                 }
465                 return this;
466             }
467             // Not the exact same location, so push the existing pending request ...
468             addPending();
469         }
470 
471         // Record this operation as a pending change ...
472         pendingRequest = new NodeChange(on, workspaceName);
473         for (Property property : properties) {
474             if (property == null) continue;
475             pendingRequest.pendingProperties.put(property.getName(), property);
476         }
477         return this;
478     }
479 
480     /**
481      * Add a request to remove the property with the supplied name from the given node. Supplying a name for a property that does
482      * not exist will not cause an error.
483      * 
484      * @param on the location of the node to be read
485      * @param workspaceName the name of the workspace containing the node
486      * @param propertyName the name of the property that is to be removed
487      * @return this builder for method chaining; never null
488      * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
489      */
490     public BatchRequestBuilder removeProperty( Location on,
491                                                String workspaceName,
492                                                Name propertyName ) {
493         // If there's a pending request ...
494         if (pendingRequest != null) {
495             // Compare the supplied location with that of the pending request
496             if (pendingRequest.location.isSame(on)) {
497                 // They are the same location, so we can add the properties to the pending request ...
498                 pendingRequest.pendingProperties.put(propertyName, null);
499                 return this;
500             }
501             // Not the exact same location, so push the existing pending request ...
502             addPending();
503         }
504 
505         // Record this operation as a pending change ...
506         pendingRequest = new NodeChange(on, workspaceName);
507         pendingRequest.pendingProperties.put(propertyName, null);
508         return this;
509     }
510 
511     /**
512      * Add a request to remove from the node the properties with the supplied names. Supplying a name for a property that does not
513      * exist will not cause an error.
514      * 
515      * @param on the location of the node to be read
516      * @param workspaceName the name of the workspace containing the node
517      * @param propertyNames the names of the properties that are to be removed
518      * @return this builder for method chaining; never null
519      * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
520      */
521     public BatchRequestBuilder removeProperties( Location on,
522                                                  String workspaceName,
523                                                  Name... propertyNames ) {
524         // If there's a pending request ...
525         if (pendingRequest != null) {
526             // Compare the supplied location with that of the pending request
527             if (pendingRequest.location.isSame(on)) {
528                 // They are the same location, so we can add the properties to the pending request ...
529                 for (Name propertyName : propertyNames) {
530                     pendingRequest.pendingProperties.put(propertyName, null);
531                 }
532                 return this;
533             }
534             // Not the exact same location, so push the existing pending request ...
535             addPending();
536         }
537 
538         // Record this operation as a pending change ...
539         pendingRequest = new NodeChange(on, workspaceName);
540         for (Name propertyName : propertyNames) {
541             pendingRequest.pendingProperties.put(propertyName, null);
542         }
543         return this;
544     }
545 
546     /**
547      * Add a request to rename the node at the supplied location.
548      * 
549      * @param at the location of the node to be read
550      * @param workspaceName the name of the workspace containing the node
551      * @param newName the new name for the node
552      * @return this builder for method chaining; never null
553      * @throws IllegalArgumentException if the location or workspace name is null
554      */
555     public BatchRequestBuilder renameNode( Location at,
556                                            String workspaceName,
557                                            Name newName ) {
558         return add(new RenameNodeRequest(at, workspaceName, newName));
559     }
560 
561     /**
562      * Add a request to copy a branch to another.
563      * 
564      * @param from the location of the top node in the existing branch that is to be copied
565      * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
566      * @param into the location of the existing node into which the copy should be placed
567      * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
568      * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
569      *        used
570      * @return this builder for method chaining; never null
571      * @throws IllegalArgumentException if either of the locations or workspace names are null
572      */
573     public BatchRequestBuilder copyBranch( Location from,
574                                            String fromWorkspace,
575                                            Location into,
576                                            String intoWorkspace,
577                                            Name nameForCopy ) {
578         return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy,
579                                          CopyBranchRequest.DEFAULT_NODE_CONFLICT_BEHAVIOR));
580     }
581 
582     /**
583      * Add a request to copy a branch to another.
584      * 
585      * @param from the location of the top node in the existing branch that is to be copied
586      * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
587      * @param into the location of the existing node into which the copy should be placed
588      * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
589      * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
590      *        used
591      * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
592      *        location, or null if the default node conflict behavior should be used
593      * @return this builder for method chaining; never null
594      * @throws IllegalArgumentException if either of the locations or workspace names are null
595      */
596     public BatchRequestBuilder copyBranch( Location from,
597                                            String fromWorkspace,
598                                            Location into,
599                                            String intoWorkspace,
600                                            Name nameForCopy,
601                                            NodeConflictBehavior conflictBehavior ) {
602         if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_NODE_CONFLICT_BEHAVIOR;
603         return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior));
604     }
605 
606     /**
607      * Add a request to clone a branch to another.
608      * 
609      * @param from the location of the top node in the existing branch that is to be cloned
610      * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
611      * @param into the location of the existing node into which the clone should be placed
612      * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be cloned
613      * @param nameForClone the desired name for the node that results from the clone, or null if the name of the original should
614      *        be used
615      * @param exactSegmentForClone the exact {@link Path.Segment segment} at which the cloned tree should be rooted.
616      * @param removeExisting whether any nodes in the intoWorkspace with the same UUIDs as a node in the source branch should be
617      *        removed (if true) or a {@link UuidAlreadyExistsException} should be thrown.
618      * @return this builder for method chaining; never null
619      * @throws IllegalArgumentException if any of the parameters are null except for {@code nameForClone} or {@code
620      *         exactSegmentForClone}. Exactly one of {@code nameForClone} and {@code exactSegmentForClone} must be null.
621      */
622     public BatchRequestBuilder cloneBranch( Location from,
623                                             String fromWorkspace,
624                                             Location into,
625                                             String intoWorkspace,
626                                             Name nameForClone,
627                                             Path.Segment exactSegmentForClone,
628                                             boolean removeExisting ) {
629         return add(new CloneBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForClone, exactSegmentForClone,
630                                           removeExisting));
631     }
632 
633     /**
634      * Create a request to move a branch from one location into another.
635      * 
636      * @param from the location of the top node in the existing branch that is to be moved
637      * @param into the location of the existing node into which the branch should be moved
638      * @param workspaceName the name of the workspace
639      * @return this builder for method chaining; never null
640      * @throws IllegalArgumentException if any of the parameters are null
641      */
642     public BatchRequestBuilder moveBranch( Location from,
643                                            Location into,
644                                            String workspaceName ) {
645         return add(new MoveBranchRequest(from, into, workspaceName, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
646     }
647 
648     /**
649      * Create a request to move a branch from one location into another.
650      * 
651      * @param from the location of the top node in the existing branch that is to be moved
652      * @param into the location of the existing node into which the branch should be moved
653      * @param workspaceName the name of the workspace
654      * @param newNameForNode the new name for the node being moved, or null if the name of the original should be used
655      * @return this builder for method chaining; never null
656      * @throws IllegalArgumentException if any of the parameters are null
657      */
658     public BatchRequestBuilder moveBranch( Location from,
659                                            Location into,
660                                            String workspaceName,
661                                            Name newNameForNode ) {
662         return add(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode,
663                                          MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
664     }
665 
666     /**
667      * Create a request to move a branch from one location into another.
668      * 
669      * @param from the location of the top node in the existing branch that is to be moved
670      * @param into the location of the existing node into which the branch should be moved
671      * @param before the location of the node before which the branch should be moved; may be null
672      * @param workspaceName the name of the workspace
673      * @param newNameForNode the new name for the node being moved, or null if the name of the original should be used
674      * @return this builder for method chaining; never null
675      * @throws IllegalArgumentException if any of the parameters are null
676      */
677     public BatchRequestBuilder moveBranch( Location from,
678                                            Location into,
679                                            Location before,
680                                            String workspaceName,
681                                            Name newNameForNode ) {
682         return add(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode,
683                                          MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
684     }
685 
686     /**
687      * Create a request to move a branch from one location into another.
688      * 
689      * @param from the location of the top node in the existing branch that is to be moved
690      * @param into the location of the existing node into which the branch should be moved
691      * @param workspaceName the name of the workspace
692      * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
693      *        location
694      * @return this builder for method chaining; never null
695      * @throws IllegalArgumentException if any of the parameters are null
696      */
697     public BatchRequestBuilder moveBranch( Location from,
698                                            Location into,
699                                            String workspaceName,
700                                            NodeConflictBehavior conflictBehavior ) {
701         if (conflictBehavior == null) conflictBehavior = MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR;
702         return add(new MoveBranchRequest(from, into, workspaceName, conflictBehavior));
703     }
704 
705     /**
706      * Add a request to delete a branch.
707      * 
708      * @param at the location of the top node in the existing branch that is to be deleted
709      * @param workspaceName the name of the workspace containing the parent
710      * @return this builder for method chaining; never null
711      * @throws IllegalArgumentException if the location or workspace name is null
712      */
713     public BatchRequestBuilder deleteBranch( Location at,
714                                              String workspaceName ) {
715         return add(new DeleteBranchRequest(at, workspaceName));
716     }
717 
718     /**
719      * Submit any request to this batch.
720      * 
721      * @param request the request to be batched; may not be null
722      * @return this builder for method chaining; never null
723      * @throws IllegalArgumentException if the request is null
724      */
725     public BatchRequestBuilder submit( Request request ) {
726         CheckArg.isNotNull(request, "request");
727         return add(request);
728     }
729 
730     /**
731      * {@inheritDoc}
732      * 
733      * @see java.lang.Object#toString()
734      */
735     @Override
736     public String toString() {
737         StringBuilder sb = new StringBuilder();
738         for (Request request : requests) {
739             sb.append(request.toString());
740             sb.append("\n");
741         }
742         return sb.toString();
743     }
744 
745     protected class NodeChange {
746         protected final Location location;
747         protected final String workspaceName;
748         protected final Map<Name, Property> pendingProperties = new HashMap<Name, Property>();
749 
750         protected NodeChange( Location location,
751                               String workspaceName ) {
752             this.location = location;
753             this.workspaceName = workspaceName;
754         }
755 
756         protected Request toRequest() {
757             if (pendingProperties.size() == 1) {
758                 Map.Entry<Name, Property> entry = pendingProperties.entrySet().iterator().next();
759                 Property property = entry.getValue();
760                 if (property == null) {
761                     return new RemovePropertyRequest(location, workspaceName, entry.getKey());
762                 }
763                 return new SetPropertyRequest(location, workspaceName, property);
764             }
765             return new UpdatePropertiesRequest(location, workspaceName, pendingProperties);
766         }
767     }
768 
769 }