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     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed 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.processor;
025    
026    import java.util.Collections;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Queue;
031    import net.jcip.annotations.Immutable;
032    import org.jboss.dna.common.util.CheckArg;
033    import org.jboss.dna.graph.ExecutionContext;
034    import org.jboss.dna.graph.GraphI18n;
035    import org.jboss.dna.graph.Location;
036    import org.jboss.dna.graph.cache.CachePolicy;
037    import org.jboss.dna.graph.connector.RepositorySourceException;
038    import org.jboss.dna.graph.property.DateTime;
039    import org.jboss.dna.graph.property.Name;
040    import org.jboss.dna.graph.property.Path;
041    import org.jboss.dna.graph.property.Property;
042    import org.jboss.dna.graph.property.ReferentialIntegrityException;
043    import org.jboss.dna.graph.request.CacheableRequest;
044    import org.jboss.dna.graph.request.CloneWorkspaceRequest;
045    import org.jboss.dna.graph.request.CompositeRequest;
046    import org.jboss.dna.graph.request.CopyBranchRequest;
047    import org.jboss.dna.graph.request.CreateNodeRequest;
048    import org.jboss.dna.graph.request.CreateWorkspaceRequest;
049    import org.jboss.dna.graph.request.DeleteBranchRequest;
050    import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
051    import org.jboss.dna.graph.request.GetWorkspacesRequest;
052    import org.jboss.dna.graph.request.InvalidRequestException;
053    import org.jboss.dna.graph.request.MoveBranchRequest;
054    import org.jboss.dna.graph.request.ReadAllChildrenRequest;
055    import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
056    import org.jboss.dna.graph.request.ReadBlockOfChildrenRequest;
057    import org.jboss.dna.graph.request.ReadBranchRequest;
058    import org.jboss.dna.graph.request.ReadNextBlockOfChildrenRequest;
059    import org.jboss.dna.graph.request.ReadNodeRequest;
060    import org.jboss.dna.graph.request.ReadPropertyRequest;
061    import org.jboss.dna.graph.request.RemovePropertyRequest;
062    import org.jboss.dna.graph.request.RenameNodeRequest;
063    import org.jboss.dna.graph.request.Request;
064    import org.jboss.dna.graph.request.SetPropertyRequest;
065    import org.jboss.dna.graph.request.UnsupportedRequestException;
066    import org.jboss.dna.graph.request.UpdatePropertiesRequest;
067    import org.jboss.dna.graph.request.VerifyNodeExistsRequest;
068    import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
069    
070    /**
071     * A component that is used to process and execute {@link Request}s. This class is intended to be subclassed and methods
072     * overwritten to define the behavior for executing the different kinds of requests. Abstract methods must be overridden, but
073     * non-abstract methods all have meaningful default implementations.
074     * 
075     * @author Randall Hauch
076     */
077    @Immutable
078    public abstract class RequestProcessor {
079    
080        private final ExecutionContext context;
081        private final String sourceName;
082        private final DateTime nowInUtc;
083        private final CachePolicy defaultCachePolicy;
084    
085        protected RequestProcessor( String sourceName,
086                                    ExecutionContext context ) {
087            this(sourceName, context, null, null);
088        }
089    
090        protected RequestProcessor( String sourceName,
091                                    ExecutionContext context,
092                                    DateTime now ) {
093            this(sourceName, context, now, null);
094        }
095    
096        protected RequestProcessor( String sourceName,
097                                    ExecutionContext context,
098                                    DateTime now,
099                                    CachePolicy defaultCachePolicy ) {
100            CheckArg.isNotEmpty(sourceName, "sourceName");
101            CheckArg.isNotNull(context, "context");
102            this.context = context;
103            this.sourceName = sourceName;
104            this.nowInUtc = now != null ? now : context.getValueFactories().getDateFactory().createUtc();
105            this.defaultCachePolicy = defaultCachePolicy;
106        }
107    
108        /**
109         * Get the name of the source against which this processor is executing.
110         * 
111         * @return the repository source name; never null or empty
112         */
113        public String getSourceName() {
114            return sourceName;
115        }
116    
117        /**
118         * The execution context that this process is operating within.
119         * 
120         * @return the execution context; never null
121         */
122        public ExecutionContext getExecutionContext() {
123            return this.context;
124        }
125    
126        /**
127         * Get the 'current time' for this processor, which is usually a constant during its lifetime.
128         * 
129         * @return the current time in UTC; never null
130         */
131        protected DateTime getNowInUtc() {
132            return this.nowInUtc;
133        }
134    
135        /**
136         * Set the supplied request to have the default cache policy and the {@link #getNowInUtc() current time in UTC}.
137         * 
138         * @param request the cacheable request
139         */
140        protected void setCacheableInfo( CacheableRequest request ) {
141            request.setCachePolicy(defaultCachePolicy);
142            request.setTimeLoaded(nowInUtc);
143        }
144    
145        /**
146         * Set the supplied request to have the supplied cache policy and the {@link #getNowInUtc() current time in UTC}.
147         * 
148         * @param request the cacheable request
149         * @param cachePolicy the cache policy for the request; may be null if there is to be no cache policy
150         */
151        protected void setCacheableInfo( CacheableRequest request,
152                                         CachePolicy cachePolicy ) {
153            request.setCachePolicy(cachePolicy);
154            request.setTimeLoaded(nowInUtc);
155        }
156    
157        /**
158         * Process a request by determining the type of request and delegating to the appropriate <code>process</code> method for that
159         * type.
160         * <p>
161         * This method does nothing if the request is null.
162         * </p>
163         * 
164         * @param request the general request
165         */
166        public void process( Request request ) {
167            if (request == null) return;
168            if (request.isCancelled()) return;
169            if (request instanceof CompositeRequest) {
170                process((CompositeRequest)request);
171            } else if (request instanceof CopyBranchRequest) {
172                process((CopyBranchRequest)request);
173            } else if (request instanceof CreateNodeRequest) {
174                process((CreateNodeRequest)request);
175            } else if (request instanceof DeleteBranchRequest) {
176                process((DeleteBranchRequest)request);
177            } else if (request instanceof MoveBranchRequest) {
178                process((MoveBranchRequest)request);
179            } else if (request instanceof ReadAllChildrenRequest) {
180                process((ReadAllChildrenRequest)request);
181            } else if (request instanceof ReadNextBlockOfChildrenRequest) {
182                process((ReadNextBlockOfChildrenRequest)request);
183            } else if (request instanceof ReadBlockOfChildrenRequest) {
184                process((ReadBlockOfChildrenRequest)request);
185            } else if (request instanceof ReadBranchRequest) {
186                process((ReadBranchRequest)request);
187            } else if (request instanceof ReadNodeRequest) {
188                process((ReadNodeRequest)request);
189            } else if (request instanceof ReadAllPropertiesRequest) {
190                process((ReadAllPropertiesRequest)request);
191            } else if (request instanceof ReadPropertyRequest) {
192                process((ReadPropertyRequest)request);
193            } else if (request instanceof RemovePropertyRequest) {
194                process((RemovePropertyRequest)request);
195            } else if (request instanceof SetPropertyRequest) {
196                process((SetPropertyRequest)request);
197            } else if (request instanceof RenameNodeRequest) {
198                process((RenameNodeRequest)request);
199            } else if (request instanceof UpdatePropertiesRequest) {
200                process((UpdatePropertiesRequest)request);
201            } else if (request instanceof VerifyNodeExistsRequest) {
202                process((VerifyNodeExistsRequest)request);
203            } else if (request instanceof VerifyWorkspaceRequest) {
204                process((VerifyWorkspaceRequest)request);
205            } else if (request instanceof GetWorkspacesRequest) {
206                process((GetWorkspacesRequest)request);
207            } else if (request instanceof CreateWorkspaceRequest) {
208                process((CreateWorkspaceRequest)request);
209            } else if (request instanceof CloneWorkspaceRequest) {
210                process((CloneWorkspaceRequest)request);
211            } else if (request instanceof DestroyWorkspaceRequest) {
212                process((DestroyWorkspaceRequest)request);
213            } else {
214                processUnknownRequest(request);
215            }
216        }
217    
218        /**
219         * Process a request that is composed of multiple other (non-composite) requests. If any of the embedded requests
220         * {@link Request#hasError() has an error} after it is processed, the submitted request will be marked with an error.
221         * <p>
222         * This method does nothing if the request is null.
223         * </p>
224         * 
225         * @param request the composite request
226         */
227        public void process( CompositeRequest request ) {
228            if (request == null) return;
229            int numberOfErrors = 0;
230            List<Throwable> errors = null;
231            for (Request embedded : request) {
232                assert embedded != null;
233                if (embedded.isCancelled()) return;
234                process(embedded);
235                if (embedded.hasError()) {
236                    if (numberOfErrors == 0) {
237                        errors = new LinkedList<Throwable>();
238                    }
239                    assert errors != null;
240                    errors.add(embedded.getError());
241                    ++numberOfErrors;
242                }
243            }
244            if (numberOfErrors == 0) return;
245            assert errors != null;
246            if (numberOfErrors == 1) {
247                request.setError(errors.get(0));
248            } else {
249                StringBuilder errorString = new StringBuilder();
250                for (Throwable error : errors) {
251                    errorString.append("\n");
252                    errorString.append("\t" + error.getMessage());
253                }
254                String msg = GraphI18n.multipleErrorsWhileExecutingRequests.text(numberOfErrors,
255                                                                                 request.size(),
256                                                                                 errorString.toString());
257                request.setError(new RepositorySourceException(getSourceName(), msg));
258            }
259        }
260    
261        /**
262         * Method that is called by {@link #process(Request)} when the request was found to be of a request type that is not known by
263         * this processor. By default this method sets an {@link UnsupportedRequestException unsupported request error} on the
264         * request.
265         * 
266         * @param request the unknown request
267         */
268        protected void processUnknownRequest( Request request ) {
269            request.setError(new InvalidRequestException(GraphI18n.unsupportedRequestType.text(request.getClass().getName(), request)));
270        }
271    
272        /**
273         * Process a request to verify a named workspace.
274         * <p>
275         * This method does nothing if the request is null.
276         * </p>
277         * 
278         * @param request the request
279         */
280        public abstract void process( VerifyWorkspaceRequest request );
281    
282        /**
283         * Process a request to get the information about the available workspaces.
284         * <p>
285         * This method does nothing if the request is null.
286         * </p>
287         * 
288         * @param request the request
289         */
290        public abstract void process( GetWorkspacesRequest request );
291    
292        /**
293         * Process a request to create a new workspace.
294         * <p>
295         * This method does nothing if the request is null.
296         * </p>
297         * 
298         * @param request the request
299         */
300        public abstract void process( CreateWorkspaceRequest request );
301    
302        /**
303         * Process a request to clone an existing workspace as a new workspace.
304         * <p>
305         * This method does nothing if the request is null.
306         * </p>
307         * 
308         * @param request the request
309         */
310        public abstract void process( CloneWorkspaceRequest request );
311    
312        /**
313         * Process a request to permanently destroy a workspace.
314         * <p>
315         * This method does nothing if the request is null.
316         * </p>
317         * 
318         * @param request the request
319         */
320        public abstract void process( DestroyWorkspaceRequest request );
321    
322        /**
323         * Process a request to copy a branch into another location.
324         * <p>
325         * This method does nothing if the request is null.
326         * </p>
327         * 
328         * @param request the copy request
329         */
330        public abstract void process( CopyBranchRequest request );
331    
332        /**
333         * Process a request to create a node at a specified location.
334         * <p>
335         * This method does nothing if the request is null.
336         * </p>
337         * 
338         * @param request the create request
339         */
340        public abstract void process( CreateNodeRequest request );
341    
342        /**
343         * Process a request to delete a branch at a specified location.
344         * <p>
345         * This method does nothing if the request is null.
346         * </p>
347         * 
348         * @param request the delete request
349         * @throws ReferentialIntegrityException if the delete could not be performed because some references to deleted nodes would
350         *         have remained after the delete operation completed
351         */
352        public abstract void process( DeleteBranchRequest request );
353    
354        /**
355         * Process a request to move a branch at a specified location into a different location.
356         * <p>
357         * This method does nothing if the request is null.
358         * </p>
359         * 
360         * @param request the move request
361         */
362        public abstract void process( MoveBranchRequest request );
363    
364        /**
365         * Process a request to read all of the children of a node.
366         * <p>
367         * This method does nothing if the request is null.
368         * </p>
369         * 
370         * @param request the read request
371         */
372        public abstract void process( ReadAllChildrenRequest request );
373    
374        /**
375         * Process a request to read a block of the children of a node. The block is defined by a
376         * {@link ReadBlockOfChildrenRequest#startingAtIndex() starting index} and a {@link ReadBlockOfChildrenRequest#count() maximum
377         * number of children to include in the block}.
378         * <p>
379         * This method does nothing if the request is null. The default implementation converts the command to a
380         * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
381         * implementation may not be efficient and may need to be overridden.
382         * </p>
383         * 
384         * @param request the read request
385         */
386        public void process( ReadBlockOfChildrenRequest request ) {
387            if (request == null) return;
388            // Convert the request to a ReadAllChildrenRequest and execute it ...
389            ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(request.of(), request.inWorkspace());
390            process(readAll);
391            if (readAll.hasError()) {
392                request.setError(readAll.getError());
393                return;
394            }
395            List<Location> allChildren = readAll.getChildren();
396    
397            // If there aren't enough children for the block's range ...
398            if (allChildren.size() < request.startingAtIndex()) return;
399    
400            // Now, find the children in the block ...
401            int endIndex = Math.min(request.endingBefore(), allChildren.size());
402            for (int i = request.startingAtIndex(); i != endIndex; ++i) {
403                request.addChild(allChildren.get(i));
404            }
405            // Set the actual location ...
406            request.setActualLocationOfNode(readAll.getActualLocationOfNode());
407            setCacheableInfo(request);
408        }
409    
410        /**
411         * Process a request to read the next block of the children of a node, starting after a previously-retrieved child.
412         * <p>
413         * This method does nothing if the request is null. The default implementation converts the command to a
414         * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
415         * implementation may not be efficient and may need to be overridden.
416         * </p>
417         * 
418         * @param request the read request
419         */
420        public void process( ReadNextBlockOfChildrenRequest request ) {
421            if (request == null) return;
422    
423            // Get the parent path ...
424            Location actualSiblingLocation = request.startingAfter();
425            Path path = actualSiblingLocation.getPath();
426            Path parentPath = null;
427            if (path != null) parentPath = path.getParent();
428            if (parentPath == null) {
429                // Need to find the parent path, so get the actual location of the sibling ...
430                VerifyNodeExistsRequest verifySibling = new VerifyNodeExistsRequest(request.startingAfter(), request.inWorkspace());
431                process(verifySibling);
432                actualSiblingLocation = verifySibling.getActualLocationOfNode();
433                parentPath = actualSiblingLocation.getPath().getParent();
434            }
435            assert parentPath != null;
436    
437            // Convert the request to a ReadAllChildrenRequest and execute it ...
438            ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(Location.create(parentPath), request.inWorkspace());
439            process(readAll);
440            if (readAll.hasError()) {
441                request.setError(readAll.getError());
442                return;
443            }
444            List<Location> allChildren = readAll.getChildren();
445    
446            // Iterate through the children, looking for the 'startingAfter' child ...
447            boolean found = false;
448            int count = 0;
449            for (Location child : allChildren) {
450                if (count > request.count()) break;
451                if (!found) {
452                    // Set to true if we find the child we're looking for ...
453                    found = child.equals(request.startingAfter());
454                } else {
455                    // Add the child to the block ...
456                    ++count;
457                    request.addChild(child);
458                }
459            }
460    
461            // Set the actual location ...
462            request.setActualLocationOfStartingAfterNode(actualSiblingLocation);
463            setCacheableInfo(request);
464        }
465    
466        /**
467         * Process a request to read a branch or subgraph that's below a node at a specified location.
468         * <p>
469         * This method does nothing if the request is null. The default implementation processes the branch by submitting the
470         * equivalent requests to {@link ReadNodeRequest read the nodes} and the {@link ReadAllChildrenRequest children}. It starts by
471         * doing this for the top-level node, then proceeds for each of the children of that node, and so forth.
472         * </p>
473         * 
474         * @param request the request to read the branch
475         */
476        public void process( ReadBranchRequest request ) {
477            if (request == null) return;
478            // Create a queue for locations that need to be read ...
479            Queue<LocationWithDepth> locationsToRead = new LinkedList<LocationWithDepth>();
480            locationsToRead.add(new LocationWithDepth(request.at(), 1));
481    
482            // Now read the locations ...
483            boolean first = true;
484            while (locationsToRead.peek() != null) {
485                if (request.isCancelled()) return;
486                LocationWithDepth read = locationsToRead.poll();
487    
488                // Check the depth ...
489                if (read.depth > request.maximumDepth()) break;
490    
491                // Read the properties ...
492                ReadNodeRequest readNode = new ReadNodeRequest(read.location, request.inWorkspace());
493                process(readNode);
494                if (readNode.hasError()) {
495                    request.setError(readNode.getError());
496                    return;
497                }
498                Location actualLocation = readNode.getActualLocationOfNode();
499                if (first) {
500                    // Set the actual location on the original request
501                    request.setActualLocationOfNode(actualLocation);
502                    first = false;
503                }
504    
505                // Record in the request the children and properties that were read on this node ...
506                request.setChildren(actualLocation, readNode.getChildren());
507                request.setProperties(actualLocation, readNode.getProperties());
508    
509                // Add each of the children to the list of locations that we need to read ...
510                for (Location child : readNode.getChildren()) {
511                    locationsToRead.add(new LocationWithDepth(child, read.depth + 1));
512                }
513            }
514            setCacheableInfo(request);
515        }
516    
517        /**
518         * Process a request to read the properties of a node at the supplied location.
519         * <p>
520         * This method does nothing if the request is null.
521         * </p>
522         * 
523         * @param request the read request
524         */
525        public abstract void process( ReadAllPropertiesRequest request );
526    
527        /**
528         * Process a request to read the properties and children of a node at the supplied location.
529         * <p>
530         * This method does nothing if the request is null. Unless overridden, this method converts the single request into a
531         * {@link ReadAllChildrenRequest} and a {@link ReadAllPropertiesRequest}.
532         * </p>
533         * 
534         * @param request the read request
535         */
536        public void process( ReadNodeRequest request ) {
537            if (request == null) return;
538            // Read the properties ...
539            ReadAllPropertiesRequest readProperties = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
540            process(readProperties);
541            if (readProperties.hasError()) {
542                request.setError(readProperties.getError());
543                return;
544            }
545            // Set the actual location ...
546            request.setActualLocationOfNode(readProperties.getActualLocationOfNode());
547    
548            // Read the children ...
549            ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at(), request.inWorkspace());
550            process(readChildren);
551            if (readChildren.hasError()) {
552                request.setError(readChildren.getError());
553                return;
554            }
555            if (request.isCancelled()) return;
556            // Now, copy all of the results into the submitted request ...
557            for (Property property : readProperties) {
558                request.addProperty(property);
559            }
560            for (Location child : readChildren) {
561                request.addChild(child);
562            }
563            setCacheableInfo(request);
564        }
565    
566        /**
567         * Process a request to read a single property of a node at the supplied location.
568         * <p>
569         * This method does nothing if the request is null. Unless overridden, this method converts the request that
570         * {@link ReadAllPropertiesRequest reads the node} and simply returns the one property.
571         * </p>
572         * 
573         * @param request the read request
574         */
575        public void process( ReadPropertyRequest request ) {
576            if (request == null) return;
577            ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.on(), request.inWorkspace());
578            process(readNode);
579            if (readNode.hasError()) {
580                request.setError(readNode.getError());
581                return;
582            }
583            Property property = readNode.getPropertiesByName().get(request.named());
584            request.setProperty(property);
585            // Set the actual location ...
586            request.setActualLocationOfNode(readNode.getActualLocationOfNode());
587            setCacheableInfo(request);
588        }
589    
590        /**
591         * Process a request to verify that a node exists at the supplied location.
592         * <p>
593         * This method does nothing if the request is null. Unless overridden, this method converts the request that
594         * {@link ReadAllPropertiesRequest reads the node} and uses the result to determine if the node exists.
595         * </p>
596         * 
597         * @param request the read request
598         */
599        public void process( VerifyNodeExistsRequest request ) {
600            if (request == null) return;
601            ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
602            process(readNode);
603            if (readNode.hasError()) {
604                request.setError(readNode.getError());
605                return;
606            }
607            // Set the actual location ...
608            request.setActualLocationOfNode(readNode.getActualLocationOfNode());
609            setCacheableInfo(request);
610        }
611    
612        /**
613         * Process a request to remove the specified property from a node.
614         * <p>
615         * This method does nothing if the request is null. Unless overridden, this method converts this request into a
616         * {@link UpdatePropertiesRequest}.
617         * </p>
618         * 
619         * @param request the request to remove the property
620         */
621        public void process( RemovePropertyRequest request ) {
622            if (request == null) return;
623            Map<Name, Property> properties = Collections.singletonMap(request.propertyName(), null);
624            UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.from(), request.inWorkspace(), properties);
625            process(update);
626            if (update.hasError()) {
627                request.setError(update.getError());
628            }
629            // Set the actual location ...
630            request.setActualLocationOfNode(update.getActualLocationOfNode());
631        }
632    
633        /**
634         * Process a request to set the specified property on a node.
635         * <p>
636         * This method does nothing if the request is null. Unless overridden, this method converts this request into a
637         * {@link UpdatePropertiesRequest}.
638         * </p>
639         * 
640         * @param request the request to set the property
641         */
642        public void process( SetPropertyRequest request ) {
643            if (request == null) return;
644            Property property = request.property();
645            Map<Name, Property> properties = Collections.singletonMap(property.getName(), property);
646            UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.on(), request.inWorkspace(), properties);
647            process(update);
648            if (update.hasError()) {
649                request.setError(update.getError());
650            } else {
651                // Set the actual location ...
652                request.setActualLocationOfNode(update.getActualLocationOfNode());
653            }
654        }
655    
656        /**
657         * Process a request to remove the specified properties from a node.
658         * <p>
659         * This method does nothing if the request is null.
660         * </p>
661         * 
662         * @param request the remove request
663         */
664        public abstract void process( UpdatePropertiesRequest request );
665    
666        /**
667         * Process a request to rename a node specified location into a different location.
668         * <p>
669         * This method does nothing if the request is null. Unless overridden, this method converts the rename into a
670         * {@link MoveBranchRequest move}. However, this only works if the <code>request</code> has a {@link Location#hasPath() path}
671         * for its {@link RenameNodeRequest#at() location}. (If not, this method throws an {@link UnsupportedOperationException} and
672         * must be overridden.)
673         * </p>
674         * 
675         * @param request the rename request
676         */
677        public void process( RenameNodeRequest request ) {
678            if (request == null) return;
679            Location from = request.at();
680            if (!from.hasPath()) {
681                throw new UnsupportedOperationException();
682            }
683            Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(from.getPath(), request.toName());
684            Location to = Location.create(newPath);
685            MoveBranchRequest move = new MoveBranchRequest(from, to, request.inWorkspace());
686            process(move);
687            // Set the actual locations ...
688            request.setActualLocations(move.getActualLocationBefore(), move.getActualLocationAfter());
689        }
690    
691        /**
692         * Close this processor, allowing it to clean up any open resources.
693         */
694        public void close() {
695            // do nothing
696        }
697    
698        /**
699         * A class that represents a location at a known depth
700         * 
701         * @author Randall Hauch
702         */
703        @Immutable
704        protected static class LocationWithDepth {
705            protected final Location location;
706            protected final int depth;
707    
708            protected LocationWithDepth( Location location,
709                                         int depth ) {
710                this.location = location;
711                this.depth = depth;
712            }
713    
714            @Override
715            public int hashCode() {
716                return location.hashCode();
717            }
718    
719            @Override
720            public String toString() {
721                return location.toString() + " at depth " + depth;
722            }
723        }
724    
725    }