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