001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * Unless otherwise indicated, all code in JBoss DNA is licensed
010     * to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.request;
025    
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.LinkedList;
029    import java.util.Map;
030    import org.jboss.dna.graph.Location;
031    import org.jboss.dna.graph.NodeConflictBehavior;
032    import org.jboss.dna.graph.property.Name;
033    import org.jboss.dna.graph.property.Path;
034    import org.jboss.dna.graph.property.Property;
035    import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior;
036    import org.jboss.dna.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
037    
038    /**
039     * A component that can be used to build up a list of requests. This implementation does perform some simple optimizations, such
040     * as combining adjacent compatible requests.
041     * <p>
042     * This builder can be used to add multiple requests. When the enqueued requests are to be processed, calling {@link #pop()} will
043     * remove and return the enqueued requests (as a {@link CompositeRequest} if there is more than one enqueued request).
044     * </p>
045     */
046    public class BatchRequestBuilder {
047    
048        private LinkedList<Request> requests;
049        private NodeChange pendingRequest;
050    
051        public BatchRequestBuilder() {
052            this.requests = new LinkedList<Request>();
053        }
054    
055        public BatchRequestBuilder( LinkedList<Request> requests ) {
056            this.requests = requests != null ? requests : new LinkedList<Request>();
057        }
058    
059        /**
060         * Determine whether this builder has built any requests.
061         * 
062         * @return true if there are requests (i.e., {@link #pop()} will return a non-null request), or false if there are no requests
063         */
064        public boolean hasRequests() {
065            return pendingRequest != null || !requests.isEmpty();
066        }
067    
068        /**
069         * Finish any pending request
070         */
071        public void finishPendingRequest() {
072            if (pendingRequest != null) {
073                // There's a pending request, we need to build it ...
074                add(pendingRequest.toRequest());
075                pendingRequest = null;
076            }
077        }
078    
079        /**
080         * Remove and return any requests that have been built by this builder since the last call to this method. This method will
081         * return null if no requests have been built. If only one request was built, then it will be returned. If multiple requests
082         * have been built, then this method will return a {@link CompositeRequest} containing them.
083         * 
084         * @return the request (or {@link CompositeRequest}) representing those requests that this builder has created since the last
085         *         call to this method, or null if there are no requests to return
086         */
087        public Request pop() {
088            int number = requests.size();
089            if (pendingRequest != null) {
090                // There's a pending request, we need to build it ...
091                Request newRequest = pendingRequest.toRequest();
092                if (number == 0) {
093                    // There's no other request ...
094                    return newRequest;
095                }
096                // We have at least one other request, so add the pending request ...
097                addPending();
098                ++number;
099            } else {
100                // There is no pending request ...
101                if (number == 0) {
102                    // And no enqueued request ...
103                    return null;
104                }
105                if (number == 1) {
106                    // There's only one request, so return just the one ...
107                    Request result = requests.getFirst();
108                    requests.clear();
109                    return result;
110                }
111            }
112            assert number >= 2;
113            // Build a composite request (reusing the existing list), and then replace the list
114            Request result = CompositeRequest.with(requests);
115            requests = new LinkedList<Request>();
116            return result;
117        }
118    
119        protected final BatchRequestBuilder add( Request request ) {
120            addPending();
121            requests.add(request);
122            return this;
123        }
124    
125        protected final BatchRequestBuilder addPending() {
126            if (pendingRequest != null) {
127                requests.add(pendingRequest.toRequest());
128                pendingRequest = null;
129            }
130            return this;
131        }
132    
133        /**
134         * Add a request to obtain the information about the available workspaces.
135         * 
136         * @return this builder for method chaining; never null
137         */
138        public BatchRequestBuilder getWorkspaces() {
139            return add(new GetWorkspacesRequest());
140        }
141    
142        /**
143         * Add a request to verify the existance of the named workspace.
144         * 
145         * @param workspaceName the desired name of the workspace, or null if the source's default workspace should be used
146         * @return this builder for method chaining; never null
147         */
148        public BatchRequestBuilder verifyWorkspace( String workspaceName ) {
149            return add(new VerifyWorkspaceRequest(workspaceName));
150        }
151    
152        /**
153         * Add a request to create a new workspace, and specify the behavior should a workspace already exists with a name that
154         * matches the desired name for the new workspace.
155         * 
156         * @param desiredNameOfNewWorkspace the desired name of the new workspace
157         * @param createConflictBehavior the behavior if a workspace already exists with the same name, or null if the default
158         *        behavior should be used
159         * @return this builder for method chaining; never null
160         */
161        public BatchRequestBuilder createWorkspace( String desiredNameOfNewWorkspace,
162                                                    CreateConflictBehavior createConflictBehavior ) {
163            return add(new CreateWorkspaceRequest(desiredNameOfNewWorkspace, createConflictBehavior));
164        }
165    
166        /**
167         * Add a request to clone an existing workspace to create a new workspace, and specify the behavior should a workspace already
168         * exists with a name that matches the desired name for the new workspace.
169         * 
170         * @param nameOfWorkspaceToBeCloned the name of the existing workspace that is to be cloned
171         * @param desiredNameOfTargetWorkspace the desired name of the target workspace
172         * @param createConflictBehavior the behavior if a workspace already exists with the same name
173         * @param cloneConflictBehavior the behavior if the workspace to be cloned does not exist
174         * @return this builder for method chaining; never null
175         * @throws IllegalArgumentException if the either workspace name is null
176         */
177        public BatchRequestBuilder cloneWorkspace( String nameOfWorkspaceToBeCloned,
178                                                   String desiredNameOfTargetWorkspace,
179                                                   CreateConflictBehavior createConflictBehavior,
180                                                   CloneConflictBehavior cloneConflictBehavior ) {
181            return add(new CloneWorkspaceRequest(nameOfWorkspaceToBeCloned, desiredNameOfTargetWorkspace, createConflictBehavior,
182                                                 cloneConflictBehavior));
183        }
184    
185        /**
186         * Add a request to destroy an existing workspace.
187         * 
188         * @param workspaceName the name of the workspace that is to be destroyed
189         * @return this builder for method chaining; never null
190         * @throws IllegalArgumentException if the workspace name is null
191         */
192        public BatchRequestBuilder destroyWorkspace( String workspaceName ) {
193            return add(new DestroyWorkspaceRequest(workspaceName));
194        }
195    
196        /**
197         * Add a request to verify the existance and location of a node at the supplied location.
198         * 
199         * @param at the location of the node to be verified
200         * @param workspaceName the name of the workspace containing the node
201         * @return this builder for method chaining; never null
202         * @throws IllegalArgumentException if the location or workspace name is null
203         */
204        public BatchRequestBuilder verifyNodeExists( Location at,
205                                                     String workspaceName ) {
206            return add(new VerifyNodeExistsRequest(at, workspaceName));
207        }
208    
209        /**
210         * Add a request to read the properties and number of children of a node at the supplied location.
211         * 
212         * @param at the location of the node to be read
213         * @param workspaceName the name of the workspace containing the node
214         * @return this builder for method chaining; never null
215         * @throws IllegalArgumentException if the location or workspace name is null
216         */
217        public BatchRequestBuilder readNode( Location at,
218                                             String workspaceName ) {
219            return add(new ReadNodeRequest(at, workspaceName));
220        }
221    
222        /**
223         * Add a request to read the children of a node at the supplied location in the designated workspace.
224         * 
225         * @param of the location of the node whose children are to be read
226         * @param workspaceName the name of the workspace
227         * @return this builder for method chaining; never null
228         * @throws IllegalArgumentException if the location or workspace name is null
229         */
230        public BatchRequestBuilder readAllChildren( Location of,
231                                                    String workspaceName ) {
232            return add(new ReadAllChildrenRequest(of, workspaceName));
233        }
234    
235        /**
236         * Add a request to read the properties and number of children of a node at the supplied location.
237         * 
238         * @param of the location of the node whose children are to be read
239         * @param workspaceName the name of the workspace
240         * @return this builder for method chaining; never null
241         * @throws IllegalArgumentException if the location or workspace name is null
242         */
243        public BatchRequestBuilder readAllProperties( Location of,
244                                                      String workspaceName ) {
245            return add(new ReadAllPropertiesRequest(of, workspaceName));
246        }
247    
248        /**
249         * Add a request to read the properties and number of children of a node at the supplied location.
250         * 
251         * @param of the location of the node whose children are to be read
252         * @param workspaceName the name of the workspace
253         * @param propertyName the name of the property to read
254         * @return this builder for method chaining; never null
255         * @throws IllegalArgumentException if the location or workspace name is null
256         */
257        public BatchRequestBuilder readProperty( Location of,
258                                                 String workspaceName,
259                                                 Name propertyName ) {
260            return add(new ReadPropertyRequest(of, workspaceName, propertyName));
261        }
262    
263        /**
264         * Add a request to read the branch at the supplied location, to a maximum depth of 2.
265         * 
266         * @param at the location of the branch
267         * @param workspaceName the name of the workspace containing the branch
268         * @return this builder for method chaining; never null
269         * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
270         */
271        public BatchRequestBuilder readBranch( Location at,
272                                               String workspaceName ) {
273            return add(new ReadBranchRequest(at, workspaceName));
274        }
275    
276        /**
277         * Add a request to read the branch (of given depth) at the supplied location.
278         * 
279         * @param at the location of the branch
280         * @param workspaceName the name of the workspace containing the branch
281         * @param maxDepth the maximum depth to read
282         * @return this builder for method chaining; never null
283         * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
284         */
285        public BatchRequestBuilder readBranch( Location at,
286                                               String workspaceName,
287                                               int maxDepth ) {
288            return add(new ReadBranchRequest(at, workspaceName, maxDepth));
289        }
290    
291        /**
292         * Add a request to read a block of the children of a node at the supplied location. The block is defined by the starting
293         * index of the first child and the number of children to include. Note that this index is <i>not</i> the
294         * {@link Path.Segment#getIndex() same-name-sibiling index}, but rather is the index of the child as if the children were in
295         * an array.
296         * 
297         * @param of the location of the node whose children are to be read
298         * @param workspaceName the name of the workspace containing the parent
299         * @param startingIndex the zero-based index of the first child to be included in the block
300         * @param count the maximum number of children that should be included in the block
301         * @return this builder for method chaining; never null
302         * @throws IllegalArgumentException if the location or workspace name is null, if <code>startingIndex</code> is negative, or
303         *         if <code>count</count> is less than 1.
304         */
305        public BatchRequestBuilder readBlockOfChildren( Location of,
306                                                        String workspaceName,
307                                                        int startingIndex,
308                                                        int count ) {
309            return add(new ReadBlockOfChildrenRequest(of, workspaceName, startingIndex, count));
310        }
311    
312        /**
313         * Add a request to read those children of a node that are immediately after a supplied sibling node.
314         * 
315         * @param startingAfter the location of the previous sibling that was the last child of the previous block of children read
316         * @param workspaceName the name of the workspace containing the node
317         * @param count the maximum number of children that should be included in the block
318         * @return this builder for method chaining; never null
319         * @throws IllegalArgumentException if the workspace name or <code>startingAfter</code> location is null, or if
320         *         <code>count</count> is less than 1.
321         */
322        public BatchRequestBuilder readNextBlockOfChildren( Location startingAfter,
323                                                            String workspaceName,
324                                                            int count ) {
325            return add(new ReadNextBlockOfChildrenRequest(startingAfter, workspaceName, count));
326        }
327    
328        /**
329         * Add a request to create a node with the given properties under the supplied location.
330         * 
331         * @param parentLocation the location of the existing parent node, under which the new child should be created
332         * @param workspaceName the name of the workspace containing the parent
333         * @param childName the name of the new child to create under the existing parent
334         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
335         *        properties} for the new node
336         * @return this builder for method chaining; never null
337         * @throws IllegalArgumentException if the location, workspace name, or child name is null
338         */
339        public BatchRequestBuilder createNode( Location parentLocation,
340                                               String workspaceName,
341                                               Name childName,
342                                               Iterator<Property> properties ) {
343            return add(new CreateNodeRequest(parentLocation, workspaceName, childName, CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR,
344                                             properties));
345        }
346    
347        /**
348         * Add a request to create a node with the given properties under the supplied location.
349         * 
350         * @param parentLocation the location of the existing parent node, under which the new child should be created
351         * @param workspaceName the name of the workspace containing the parent
352         * @param childName the name of the new child to create under the existing parent
353         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
354         *        properties} for the new node
355         * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
356         *        location
357         * @return this builder for method chaining; never null
358         * @throws IllegalArgumentException if the location, workspace name, or child name is null
359         */
360        public BatchRequestBuilder createNode( Location parentLocation,
361                                               String workspaceName,
362                                               Name childName,
363                                               Iterator<Property> properties,
364                                               NodeConflictBehavior conflictBehavior ) {
365            if (conflictBehavior == null) conflictBehavior = CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR;
366            return add(new CreateNodeRequest(parentLocation, workspaceName, childName, conflictBehavior, properties));
367        }
368    
369        /**
370         * Add a request to create a node with the given properties under the supplied location.
371         * 
372         * @param parentLocation the location of the existing parent node, under which the new child should be created
373         * @param workspaceName the name of the workspace containing the parent
374         * @param childName the name of the new child to create under the existing parent
375         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
376         *        properties} for the new node
377         * @return this builder for method chaining; never null
378         * @throws IllegalArgumentException if the location, workspace name, or child name is null
379         */
380        public BatchRequestBuilder createNode( Location parentLocation,
381                                               String workspaceName,
382                                               Name childName,
383                                               Property[] properties ) {
384            return add(new CreateNodeRequest(parentLocation, workspaceName, childName, CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR,
385                                             properties));
386        }
387    
388        /**
389         * Add a request to create a node with the given properties under the supplied location.
390         * 
391         * @param parentLocation the location of the existing parent node, under which the new child should be created
392         * @param workspaceName the name of the workspace containing the parent
393         * @param childName the name of the new child to create under the existing parent
394         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
395         *        properties} for the new node
396         * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
397         *        location
398         * @return this builder for method chaining; never null
399         * @throws IllegalArgumentException if the location, workspace name, or child name is null
400         */
401        public BatchRequestBuilder createNode( Location parentLocation,
402                                               String workspaceName,
403                                               Name childName,
404                                               Property[] properties,
405                                               NodeConflictBehavior conflictBehavior ) {
406            if (conflictBehavior == null) conflictBehavior = CreateNodeRequest.DEFAULT_CONFLICT_BEHAVIOR;
407            return add(new CreateNodeRequest(parentLocation, workspaceName, childName, conflictBehavior, properties));
408        }
409    
410        /**
411         * Add a request to update the property on the node at the supplied location. This request will create the property if it does
412         * not yet exist.
413         * 
414         * @param on the location of the node to be read
415         * @param workspaceName the name of the workspace containing the node
416         * @param property the new property on the node
417         * @return this builder for method chaining; never null
418         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update
419         */
420        public BatchRequestBuilder setProperty( Location on,
421                                                String workspaceName,
422                                                Property property ) {
423            // If there's a pending request ...
424            if (pendingRequest != null) {
425                // Compare the supplied location with that of the pending request
426                if (pendingRequest.location.equals(on)) {
427                    // They are the same location, so we can add the properties to the pending request ...
428                    pendingRequest.pendingProperties.put(property.getName(), property);
429                    return this;
430                }
431                // Not the exact same location, so push the existing pending request ...
432                addPending();
433            }
434    
435            // Record this operation as a pending change ...
436            pendingRequest = new NodeChange(on, workspaceName);
437            pendingRequest.pendingProperties.put(property.getName(), property);
438            return this;
439        }
440    
441        /**
442         * Add a request to update the properties on the node at the supplied location.
443         * 
444         * @param on the location of the node to be read
445         * @param workspaceName the name of the workspace containing the node
446         * @param properties the new properties on the node
447         * @return this builder for method chaining; never null
448         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update
449         */
450        public BatchRequestBuilder setProperties( Location on,
451                                                  String workspaceName,
452                                                  Property... properties ) {
453            // If there's a pending request ...
454            if (pendingRequest != null) {
455                // Compare the supplied location with that of the pending request
456                if (pendingRequest.location.equals(on)) {
457                    // They are the same location, so we can add the properties to the pending request ...
458                    for (Property property : properties) {
459                        pendingRequest.pendingProperties.put(property.getName(), property);
460                    }
461                    return this;
462                }
463                // Not the exact same location, so push the existing pending request ...
464                addPending();
465            }
466    
467            // Record this operation as a pending change ...
468            pendingRequest = new NodeChange(on, workspaceName);
469            for (Property property : properties) {
470                pendingRequest.pendingProperties.put(property.getName(), property);
471            }
472            return this;
473        }
474    
475        /**
476         * Add a request to remove the property with the supplied name from the given node. Supplying a name for a property that does
477         * not exist will not cause an error.
478         * 
479         * @param on the location of the node to be read
480         * @param workspaceName the name of the workspace containing the node
481         * @param propertyName the name of the property that is to be removed
482         * @return this builder for method chaining; never null
483         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
484         */
485        public BatchRequestBuilder removeProperty( Location on,
486                                                   String workspaceName,
487                                                   Name propertyName ) {
488            // If there's a pending request ...
489            if (pendingRequest != null) {
490                // Compare the supplied location with that of the pending request
491                if (pendingRequest.location.equals(on)) {
492                    // They are the same location, so we can add the properties to the pending request ...
493                    pendingRequest.pendingProperties.put(propertyName, null);
494                    return this;
495                }
496                // Not the exact same location, so push the existing pending request ...
497                addPending();
498            }
499    
500            // Record this operation as a pending change ...
501            pendingRequest = new NodeChange(on, workspaceName);
502            pendingRequest.pendingProperties.put(propertyName, null);
503            return this;
504        }
505    
506        /**
507         * Add a request to remove from the node the properties with the supplied names. Supplying a name for a property that does not
508         * exist will not cause an error.
509         * 
510         * @param on the location of the node to be read
511         * @param workspaceName the name of the workspace containing the node
512         * @param propertyNames the names of the properties that are to be removed
513         * @return this builder for method chaining; never null
514         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
515         */
516        public BatchRequestBuilder removeProperties( Location on,
517                                                     String workspaceName,
518                                                     Name... propertyNames ) {
519            // If there's a pending request ...
520            if (pendingRequest != null) {
521                // Compare the supplied location with that of the pending request
522                if (pendingRequest.location.equals(on)) {
523                    // They are the same location, so we can add the properties to the pending request ...
524                    for (Name propertyName : propertyNames) {
525                        pendingRequest.pendingProperties.put(propertyName, null);
526                    }
527                    return this;
528                }
529                // Not the exact same location, so push the existing pending request ...
530                addPending();
531            }
532    
533            // Record this operation as a pending change ...
534            pendingRequest = new NodeChange(on, workspaceName);
535            for (Name propertyName : propertyNames) {
536                pendingRequest.pendingProperties.put(propertyName, null);
537            }
538            return this;
539        }
540    
541        /**
542         * Add a request to rename the node at the supplied location.
543         * 
544         * @param at the location of the node to be read
545         * @param workspaceName the name of the workspace containing the node
546         * @param newName the new name for the node
547         * @return this builder for method chaining; never null
548         * @throws IllegalArgumentException if the location or workspace name is null
549         */
550        public BatchRequestBuilder renameNode( Location at,
551                                               String workspaceName,
552                                               Name newName ) {
553            return add(new RenameNodeRequest(at, workspaceName, newName));
554        }
555    
556        /**
557         * Add a request to copy a branch to another.
558         * 
559         * @param from the location of the top node in the existing branch that is to be copied
560         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
561         * @param into the location of the existing node into which the copy should be placed
562         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
563         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
564         *        used
565         * @return this builder for method chaining; never null
566         * @throws IllegalArgumentException if either of the locations or workspace names are null
567         */
568        public BatchRequestBuilder copyBranch( Location from,
569                                               String fromWorkspace,
570                                               Location into,
571                                               String intoWorkspace,
572                                               Name nameForCopy ) {
573            return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy,
574                                             CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
575        }
576    
577        /**
578         * Add a request to copy a branch to another.
579         * 
580         * @param from the location of the top node in the existing branch that is to be copied
581         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
582         * @param into the location of the existing node into which the copy should be placed
583         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
584         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
585         *        used
586         * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
587         *        location, or null if the default conflict behavior should be used
588         * @return this builder for method chaining; never null
589         * @throws IllegalArgumentException if either of the locations or workspace names are null
590         */
591        public BatchRequestBuilder copyBranch( Location from,
592                                               String fromWorkspace,
593                                               Location into,
594                                               String intoWorkspace,
595                                               Name nameForCopy,
596                                               NodeConflictBehavior conflictBehavior ) {
597            if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR;
598            return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior));
599        }
600    
601        /**
602         * Create a request to move a branch from one location into another.
603         * 
604         * @param from the location of the top node in the existing branch that is to be moved
605         * @param into the location of the existing node into which the branch should be moved
606         * @param workspaceName the name of the workspace
607         * @return this builder for method chaining; never null
608         * @throws IllegalArgumentException if any of the parameters are null
609         */
610        public BatchRequestBuilder moveBranch( Location from,
611                                               Location into,
612                                               String workspaceName ) {
613            return add(new MoveBranchRequest(from, into, workspaceName, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
614        }
615    
616        /**
617         * Create a request to move a branch from one location into another.
618         * 
619         * @param from the location of the top node in the existing branch that is to be moved
620         * @param into the location of the existing node into which the branch should be moved
621         * @param workspaceName the name of the workspace
622         * @param newNameForNode the new name for the node being moved, or null if the name of the original should be used
623         * @return this builder for method chaining; never null
624         * @throws IllegalArgumentException if any of the parameters are null
625         */
626        public BatchRequestBuilder moveBranch( Location from,
627                                               Location into,
628                                               String workspaceName,
629                                               Name newNameForNode ) {
630            return add(new MoveBranchRequest(from, into, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
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         * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
640         *        location
641         * @return this builder for method chaining; never null
642         * @throws IllegalArgumentException if any of the parameters are null
643         */
644        public BatchRequestBuilder moveBranch( Location from,
645                                               Location into,
646                                               String workspaceName,
647                                               NodeConflictBehavior conflictBehavior ) {
648            if (conflictBehavior == null) conflictBehavior = MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR;
649            return add(new MoveBranchRequest(from, into, workspaceName, conflictBehavior));
650        }
651    
652        /**
653         * Add a request to delete a branch.
654         * 
655         * @param at the location of the top node in the existing branch that is to be deleted
656         * @param workspaceName the name of the workspace containing the parent
657         * @return this builder for method chaining; never null
658         * @throws IllegalArgumentException if the location or workspace name is null
659         */
660        public BatchRequestBuilder deleteBranch( Location at,
661                                                 String workspaceName ) {
662            return add(new DeleteBranchRequest(at, workspaceName));
663        }
664    
665        protected class NodeChange {
666            protected final Location location;
667            protected final String workspaceName;
668            protected final Map<Name, Property> pendingProperties = new HashMap<Name, Property>();
669    
670            protected NodeChange( Location location,
671                                  String workspaceName ) {
672                this.location = location;
673                this.workspaceName = workspaceName;
674            }
675    
676            protected Request toRequest() {
677                if (pendingProperties.size() == 1) {
678                    Map.Entry<Name, Property> entry = pendingProperties.entrySet().iterator().next();
679                    Property property = entry.getValue();
680                    if (property == null) {
681                        return new RemovePropertyRequest(location, workspaceName, entry.getKey());
682                    }
683                    return new SetPropertyRequest(location, workspaceName, property);
684                }
685                return new UpdatePropertiesRequest(location, workspaceName, pendingProperties);
686            }
687        }
688    
689    }