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                if (property == null) continue;
471                pendingRequest.pendingProperties.put(property.getName(), property);
472            }
473            return this;
474        }
475    
476        /**
477         * Add a request to remove the property with the supplied name from the given node. Supplying a name for a property that does
478         * not exist will not cause an error.
479         * 
480         * @param on the location of the node to be read
481         * @param workspaceName the name of the workspace containing the node
482         * @param propertyName the name of the property that is to be removed
483         * @return this builder for method chaining; never null
484         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
485         */
486        public BatchRequestBuilder removeProperty( Location on,
487                                                   String workspaceName,
488                                                   Name propertyName ) {
489            // If there's a pending request ...
490            if (pendingRequest != null) {
491                // Compare the supplied location with that of the pending request
492                if (pendingRequest.location.equals(on)) {
493                    // They are the same location, so we can add the properties to the pending request ...
494                    pendingRequest.pendingProperties.put(propertyName, null);
495                    return this;
496                }
497                // Not the exact same location, so push the existing pending request ...
498                addPending();
499            }
500    
501            // Record this operation as a pending change ...
502            pendingRequest = new NodeChange(on, workspaceName);
503            pendingRequest.pendingProperties.put(propertyName, null);
504            return this;
505        }
506    
507        /**
508         * Add a request to remove from the node the properties with the supplied names. Supplying a name for a property that does not
509         * exist will not cause an error.
510         * 
511         * @param on the location of the node to be read
512         * @param workspaceName the name of the workspace containing the node
513         * @param propertyNames the names of the properties that are to be removed
514         * @return this builder for method chaining; never null
515         * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to remove
516         */
517        public BatchRequestBuilder removeProperties( Location on,
518                                                     String workspaceName,
519                                                     Name... propertyNames ) {
520            // If there's a pending request ...
521            if (pendingRequest != null) {
522                // Compare the supplied location with that of the pending request
523                if (pendingRequest.location.equals(on)) {
524                    // They are the same location, so we can add the properties to the pending request ...
525                    for (Name propertyName : propertyNames) {
526                        pendingRequest.pendingProperties.put(propertyName, null);
527                    }
528                    return this;
529                }
530                // Not the exact same location, so push the existing pending request ...
531                addPending();
532            }
533    
534            // Record this operation as a pending change ...
535            pendingRequest = new NodeChange(on, workspaceName);
536            for (Name propertyName : propertyNames) {
537                pendingRequest.pendingProperties.put(propertyName, null);
538            }
539            return this;
540        }
541    
542        /**
543         * Add a request to rename the node at the supplied location.
544         * 
545         * @param at the location of the node to be read
546         * @param workspaceName the name of the workspace containing the node
547         * @param newName the new name for the node
548         * @return this builder for method chaining; never null
549         * @throws IllegalArgumentException if the location or workspace name is null
550         */
551        public BatchRequestBuilder renameNode( Location at,
552                                               String workspaceName,
553                                               Name newName ) {
554            return add(new RenameNodeRequest(at, workspaceName, newName));
555        }
556    
557        /**
558         * Add a request to copy a branch to another.
559         * 
560         * @param from the location of the top node in the existing branch that is to be copied
561         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
562         * @param into the location of the existing node into which the copy should be placed
563         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
564         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
565         *        used
566         * @return this builder for method chaining; never null
567         * @throws IllegalArgumentException if either of the locations or workspace names are null
568         */
569        public BatchRequestBuilder copyBranch( Location from,
570                                               String fromWorkspace,
571                                               Location into,
572                                               String intoWorkspace,
573                                               Name nameForCopy ) {
574            return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy,
575                                             CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
576        }
577    
578        /**
579         * Add a request to copy a branch to another.
580         * 
581         * @param from the location of the top node in the existing branch that is to be copied
582         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
583         * @param into the location of the existing node into which the copy should be placed
584         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
585         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
586         *        used
587         * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
588         *        location, or null if the default conflict behavior should be used
589         * @return this builder for method chaining; never null
590         * @throws IllegalArgumentException if either of the locations or workspace names are null
591         */
592        public BatchRequestBuilder copyBranch( Location from,
593                                               String fromWorkspace,
594                                               Location into,
595                                               String intoWorkspace,
596                                               Name nameForCopy,
597                                               NodeConflictBehavior conflictBehavior ) {
598            if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR;
599            return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior));
600        }
601    
602        /**
603         * Create a request to move a branch from one location into another.
604         * 
605         * @param from the location of the top node in the existing branch that is to be moved
606         * @param into the location of the existing node into which the branch should be moved
607         * @param workspaceName the name of the workspace
608         * @return this builder for method chaining; never null
609         * @throws IllegalArgumentException if any of the parameters are null
610         */
611        public BatchRequestBuilder moveBranch( Location from,
612                                               Location into,
613                                               String workspaceName ) {
614            return add(new MoveBranchRequest(from, into, workspaceName, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
615        }
616    
617        /**
618         * Create a request to move a branch from one location into another.
619         * 
620         * @param from the location of the top node in the existing branch that is to be moved
621         * @param into the location of the existing node into which the branch should be moved
622         * @param workspaceName the name of the workspace
623         * @param newNameForNode the new name for the node being moved, or null if the name of the original should be used
624         * @return this builder for method chaining; never null
625         * @throws IllegalArgumentException if any of the parameters are null
626         */
627        public BatchRequestBuilder moveBranch( Location from,
628                                               Location into,
629                                               String workspaceName,
630                                               Name newNameForNode ) {
631            return add(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
632        }
633    
634        /**
635         * Create a request to move a branch from one location into another.
636         * 
637         * @param from the location of the top node in the existing branch that is to be moved
638         * @param into the location of the existing node into which the branch should be moved
639         * @param before the location of the node before which the branch should be moved; may be null
640         * @param workspaceName the name of the workspace
641         * @param newNameForNode the new name for the node being moved, or null if the name of the original should be used
642         * @return this builder for method chaining; never null
643         * @throws IllegalArgumentException if any of the parameters are null
644         */
645        public BatchRequestBuilder moveBranch( Location from,
646                                               Location into,
647                                               Location before,
648                                               String workspaceName,
649                                               Name newNameForNode ) {
650            return add(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR));
651        }
652    
653        /**
654         * Create a request to move a branch from one location into another.
655         * 
656         * @param from the location of the top node in the existing branch that is to be moved
657         * @param into the location of the existing node into which the branch should be moved
658         * @param workspaceName the name of the workspace
659         * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
660         *        location
661         * @return this builder for method chaining; never null
662         * @throws IllegalArgumentException if any of the parameters are null
663         */
664        public BatchRequestBuilder moveBranch( Location from,
665                                               Location into,
666                                               String workspaceName,
667                                               NodeConflictBehavior conflictBehavior ) {
668            if (conflictBehavior == null) conflictBehavior = MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR;
669            return add(new MoveBranchRequest(from, into, workspaceName, conflictBehavior));
670        }
671    
672        /**
673         * Add a request to delete a branch.
674         * 
675         * @param at the location of the top node in the existing branch that is to be deleted
676         * @param workspaceName the name of the workspace containing the parent
677         * @return this builder for method chaining; never null
678         * @throws IllegalArgumentException if the location or workspace name is null
679         */
680        public BatchRequestBuilder deleteBranch( Location at,
681                                                 String workspaceName ) {
682            return add(new DeleteBranchRequest(at, workspaceName));
683        }
684    
685        /**
686         * {@inheritDoc}
687         * 
688         * @see java.lang.Object#toString()
689         */
690        @Override
691        public String toString() {
692            StringBuilder sb = new StringBuilder();
693            for (Request request : requests) {
694                sb.append(request.toString());
695                sb.append("\n");
696            }
697            return sb.toString();
698        }
699    
700        protected class NodeChange {
701            protected final Location location;
702            protected final String workspaceName;
703            protected final Map<Name, Property> pendingProperties = new HashMap<Name, Property>();
704    
705            protected NodeChange( Location location,
706                                  String workspaceName ) {
707                this.location = location;
708                this.workspaceName = workspaceName;
709            }
710    
711            protected Request toRequest() {
712                if (pendingProperties.size() == 1) {
713                    Map.Entry<Name, Property> entry = pendingProperties.entrySet().iterator().next();
714                    Property property = entry.getValue();
715                    if (property == null) {
716                        return new RemovePropertyRequest(location, workspaceName, entry.getKey());
717                    }
718                    return new SetPropertyRequest(location, workspaceName, property);
719                }
720                return new UpdatePropertiesRequest(location, workspaceName, pendingProperties);
721            }
722        }
723    
724    }