1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.graph.request.processor;
25
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Iterator;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Queue;
34 import net.jcip.annotations.Immutable;
35 import net.jcip.annotations.NotThreadSafe;
36 import org.modeshape.common.util.CheckArg;
37 import org.modeshape.graph.ExecutionContext;
38 import org.modeshape.graph.GraphI18n;
39 import org.modeshape.graph.Location;
40 import org.modeshape.graph.cache.CachePolicy;
41 import org.modeshape.graph.connector.LockFailedException;
42 import org.modeshape.graph.observe.Changes;
43 import org.modeshape.graph.observe.Observer;
44 import org.modeshape.graph.property.DateTime;
45 import org.modeshape.graph.property.Name;
46 import org.modeshape.graph.property.Path;
47 import org.modeshape.graph.property.Property;
48 import org.modeshape.graph.property.ReferentialIntegrityException;
49 import org.modeshape.graph.request.AccessQueryRequest;
50 import org.modeshape.graph.request.CacheableRequest;
51 import org.modeshape.graph.request.ChangeRequest;
52 import org.modeshape.graph.request.CloneBranchRequest;
53 import org.modeshape.graph.request.CloneWorkspaceRequest;
54 import org.modeshape.graph.request.CompositeRequest;
55 import org.modeshape.graph.request.CopyBranchRequest;
56 import org.modeshape.graph.request.CreateNodeRequest;
57 import org.modeshape.graph.request.CreateWorkspaceRequest;
58 import org.modeshape.graph.request.DeleteBranchRequest;
59 import org.modeshape.graph.request.DeleteChildrenRequest;
60 import org.modeshape.graph.request.DestroyWorkspaceRequest;
61 import org.modeshape.graph.request.FullTextSearchRequest;
62 import org.modeshape.graph.request.GetWorkspacesRequest;
63 import org.modeshape.graph.request.InvalidRequestException;
64 import org.modeshape.graph.request.LockBranchRequest;
65 import org.modeshape.graph.request.MoveBranchRequest;
66 import org.modeshape.graph.request.ReadAllChildrenRequest;
67 import org.modeshape.graph.request.ReadAllPropertiesRequest;
68 import org.modeshape.graph.request.ReadBlockOfChildrenRequest;
69 import org.modeshape.graph.request.ReadBranchRequest;
70 import org.modeshape.graph.request.ReadNextBlockOfChildrenRequest;
71 import org.modeshape.graph.request.ReadNodeRequest;
72 import org.modeshape.graph.request.ReadPropertyRequest;
73 import org.modeshape.graph.request.RemovePropertyRequest;
74 import org.modeshape.graph.request.RenameNodeRequest;
75 import org.modeshape.graph.request.Request;
76 import org.modeshape.graph.request.SetPropertyRequest;
77 import org.modeshape.graph.request.UnlockBranchRequest;
78 import org.modeshape.graph.request.UnsupportedRequestException;
79 import org.modeshape.graph.request.UpdatePropertiesRequest;
80 import org.modeshape.graph.request.UpdateValuesRequest;
81 import org.modeshape.graph.request.VerifyNodeExistsRequest;
82 import org.modeshape.graph.request.VerifyWorkspaceRequest;
83
84 /**
85 * A component that is used to process and execute {@link Request}s. This class is intended to be subclassed and methods
86 * overwritten to define the behavior for executing the different kinds of requests. Abstract methods must be overridden, but
87 * non-abstract methods all have meaningful default implementations.
88 */
89 @NotThreadSafe
90 public abstract class RequestProcessor {
91
92 private final ExecutionContext context;
93 private final String sourceName;
94 private final DateTime nowInUtc;
95 private final CachePolicy defaultCachePolicy;
96 private List<ChangeRequest> changes;
97 private final Observer observer;
98
99 protected RequestProcessor( String sourceName,
100 ExecutionContext context,
101 Observer observer ) {
102 this(sourceName, context, observer, null, null);
103 }
104
105 protected RequestProcessor( String sourceName,
106 ExecutionContext context,
107 Observer observer,
108 DateTime now ) {
109 this(sourceName, context, observer, now, null);
110 }
111
112 protected RequestProcessor( String sourceName,
113 ExecutionContext context,
114 Observer observer,
115 DateTime now,
116 CachePolicy defaultCachePolicy ) {
117 CheckArg.isNotEmpty(sourceName, "sourceName");
118 CheckArg.isNotNull(context, "context");
119 this.context = context;
120 this.sourceName = sourceName;
121 this.nowInUtc = now != null ? now : context.getValueFactories().getDateFactory().createUtc();
122 this.defaultCachePolicy = defaultCachePolicy;
123 this.changes = observer != null ? new LinkedList<ChangeRequest>() : null;
124 this.observer = observer;
125 }
126
127 /**
128 * Record the supplied change request for publishing through the event mechanism.
129 *
130 * @param request the completed change request; may not be null, and may not be cancelled or have an error
131 */
132 protected void recordChange( ChangeRequest request ) {
133 assert request != null;
134 assert !request.isCancelled();
135 assert !request.hasError();
136 if (changes != null) changes.add(request);
137 }
138
139 /**
140 * Get the name of the source against which this processor is executing.
141 *
142 * @return the repository source name; never null or empty
143 */
144 public final String getSourceName() {
145 return sourceName;
146 }
147
148 /**
149 * The execution context that this process is operating within.
150 *
151 * @return the execution context; never null
152 */
153 public final ExecutionContext getExecutionContext() {
154 return this.context;
155 }
156
157 /**
158 * Get the 'current time' for this processor, which is usually a constant during its lifetime.
159 *
160 * @return the current time in UTC; never null
161 */
162 public final DateTime getNowInUtc() {
163 return this.nowInUtc;
164 }
165
166 /**
167 * @return defaultCachePolicy
168 */
169 protected final CachePolicy getDefaultCachePolicy() {
170 return defaultCachePolicy;
171 }
172
173 /**
174 * Set the supplied request to have the default cache policy and the {@link #getNowInUtc() current time in UTC}.
175 *
176 * @param request the cacheable request
177 */
178 protected void setCacheableInfo( CacheableRequest request ) {
179 // Set it only if the request has no cache policy already ...
180 if (request.getCachePolicy() == null && defaultCachePolicy != null) {
181 request.setCachePolicy(defaultCachePolicy);
182 }
183 request.setTimeLoaded(nowInUtc);
184 }
185
186 /**
187 * Set the supplied request to have the supplied cache policy and the {@link #getNowInUtc() current time in UTC}.
188 *
189 * @param request the cacheable request
190 * @param cachePolicy the cache policy for the request; may be null if there is to be no cache policy
191 */
192 protected void setCacheableInfo( CacheableRequest request,
193 CachePolicy cachePolicy ) {
194 if (cachePolicy == null) cachePolicy = defaultCachePolicy;
195 if (cachePolicy != null) {
196 if (request.getCachePolicy() != null) {
197 // Set the supplied only if less than the current ...
198 if (request.getCachePolicy().getTimeToLive() > cachePolicy.getTimeToLive()) {
199 request.setCachePolicy(cachePolicy);
200 }
201 } else {
202 // There is no current policy, so set the supplied policy ...
203 request.setCachePolicy(cachePolicy);
204 }
205 }
206 request.setTimeLoaded(nowInUtc);
207 }
208
209 /**
210 * Process a request by determining the type of request and delegating to the appropriate <code>process</code> method for that
211 * type.
212 * <p>
213 * This method does nothing if the request is null.
214 * </p>
215 *
216 * @param request the general request
217 */
218 public void process( Request request ) {
219 if (request == null) return;
220 try {
221 if (request.isCancelled()) return;
222
223 switch (request.getType()) {
224 case ACCESS_QUERY:
225 process((AccessQueryRequest)request);
226 break;
227 case COMPOSITE:
228 process((CompositeRequest)request);
229 break;
230 case CLONE_BRANCH:
231 process((CloneBranchRequest)request);
232 break;
233 case CLONE_WORKSPACE:
234 process((CloneWorkspaceRequest)request);
235 break;
236 case COPY_BRANCH:
237 process((CopyBranchRequest)request);
238 break;
239 case CREATE_NODE:
240 process((CreateNodeRequest)request);
241 break;
242 case CREATE_WORKSPACE:
243 process((CreateWorkspaceRequest)request);
244 break;
245 case DELETE_BRANCH:
246 process((DeleteBranchRequest)request);
247 break;
248 case DELETE_CHILDREN:
249 process((DeleteChildrenRequest)request);
250 break;
251 case DESTROY_WORKSPACE:
252 process((DestroyWorkspaceRequest)request);
253 break;
254 case FULL_TEXT_SEARCH:
255 process((FullTextSearchRequest)request);
256 break;
257 case GET_WORKSPACES:
258 process((GetWorkspacesRequest)request);
259 break;
260 case LAST:
261 break;
262 case LOCK_BRANCH:
263 process((LockBranchRequest)request);
264 break;
265 case MOVE_BRANCH:
266 process((MoveBranchRequest)request);
267 break;
268 case READ_ALL_CHILDREN:
269 process((ReadAllChildrenRequest)request);
270 break;
271 case READ_ALL_PROPERTIES:
272 process((ReadAllPropertiesRequest)request);
273 break;
274 case READ_BLOCK_OF_CHILDREN:
275 process((ReadBlockOfChildrenRequest)request);
276 break;
277 case READ_BRANCH:
278 process((ReadBranchRequest)request);
279 break;
280 case READ_NEXT_BLOCK_OF_CHILDREN:
281 process((ReadNextBlockOfChildrenRequest)request);
282 break;
283 case READ_NODE:
284 process((ReadNodeRequest)request);
285 break;
286 case READ_PROPERTY:
287 process((ReadPropertyRequest)request);
288 break;
289 case REMOVE_PROPERTY:
290 process((RemovePropertyRequest)request);
291 break;
292 case RENAME_NODE:
293 process((RenameNodeRequest)request);
294 break;
295 case SET_PROPERTY:
296 process((SetPropertyRequest)request);
297 break;
298 case UNLOCK_BRANCH:
299 process((UnlockBranchRequest)request);
300 break;
301 case UPDATE_PROPERTIES:
302 process((UpdatePropertiesRequest)request);
303 break;
304 case UPDATE_VALUES:
305 process((UpdateValuesRequest)request);
306 break;
307 case VERIFY_NODE_EXISTS:
308 process((VerifyNodeExistsRequest)request);
309 break;
310 case VERIFY_WORKSPACE:
311 process((VerifyWorkspaceRequest)request);
312 break;
313 default:
314 processUnknownRequest(request);
315 }
316 } catch (RuntimeException e) {
317 request.setError(e);
318 } finally {
319 completeRequest(request);
320 }
321 }
322
323 protected void completeRequest( Request request ) {
324 request.freeze();
325 }
326
327 /**
328 * Process a request that is composed of multiple other (non-composite) requests. If any of the embedded requests
329 * {@link Request#hasError() has an error} after it is processed, each of the embedded requests will be marked with the error
330 * <i>and</i> the submitted composite request will be marked with an error. If one of the embedded requests attempts to
331 * {@link Request#isReadOnly() make a change} and results in an error, then the processing of all remaining embedded requests
332 * is aborted and the composite request will be marked with only this error from the change request.
333 * <p>
334 * This method does nothing if the request is null.
335 * </p>
336 *
337 * @param request the composite request
338 */
339 public void process( CompositeRequest request ) {
340 if (request == null) return;
341 boolean hasErrors = false;
342 boolean readonly = request.isReadOnly();
343 // Iterate over the requests in this composite, but only iterate once so that
344 Iterator<Request> iter = request.iterator();
345 while (iter.hasNext()) {
346 Request embedded = iter.next();
347 assert embedded != null;
348 if (embedded.isCancelled()) return;
349 try {
350 process(embedded);
351 } catch (RuntimeException e) {
352 embedded.setError(e);
353 }
354 if (!hasErrors && embedded.hasError()) {
355 hasErrors = true;
356 if (!readonly && !embedded.isReadOnly()) {
357 // The request is trying to make changes, and this embedded was a change that resulted in an error.
358 // Therefore, the whole execution needs to stop and we should set this one error message
359 // on the composite request ...
360 assert embedded.getError() != null;
361 request.setError(embedded.getError());
362 // We need to freeze all the remaining (unprocessed) requests before returning ...
363 while (iter.hasNext()) {
364 embedded = iter.next();
365 // Cancel this request and then freeze ...
366 embedded.cancel();
367 embedded.freeze();
368 }
369 return;
370 }
371 }
372 }
373 if (hasErrors) {
374 request.checkForErrors();
375 }
376 }
377
378 /**
379 * Method that is called by {@link #process(Request)} when the request was found to be of a request type that is not known by
380 * this processor. By default this method sets an {@link UnsupportedRequestException unsupported request error} on the
381 * request.
382 *
383 * @param request the unknown request
384 */
385 protected void processUnknownRequest( Request request ) {
386 request.setError(new InvalidRequestException(GraphI18n.unsupportedRequestType.text(request.getClass().getName(), request)));
387 }
388
389 /**
390 * Process a request to verify a named workspace.
391 * <p>
392 * This method does nothing if the request is null.
393 * </p>
394 *
395 * @param request the request
396 */
397 public abstract void process( VerifyWorkspaceRequest request );
398
399 /**
400 * Process a request to get the information about the available workspaces.
401 * <p>
402 * This method does nothing if the request is null.
403 * </p>
404 *
405 * @param request the request
406 */
407 public abstract void process( GetWorkspacesRequest request );
408
409 /**
410 * Process a request to create a new workspace.
411 * <p>
412 * This method does nothing if the request is null.
413 * </p>
414 *
415 * @param request the request
416 */
417 public abstract void process( CreateWorkspaceRequest request );
418
419 /**
420 * Process a request to clone a branch into a new workspace.
421 * <p>
422 * This method does nothing if the request is null.
423 * </p>
424 *
425 * @param request the request
426 */
427 public abstract void process( CloneBranchRequest request );
428
429 /**
430 * Process a request to clone an existing workspace as a new workspace.
431 * <p>
432 * This method does nothing if the request is null.
433 * </p>
434 *
435 * @param request the request
436 */
437 public abstract void process( CloneWorkspaceRequest request );
438
439 /**
440 * Process a request to permanently destroy a workspace.
441 * <p>
442 * This method does nothing if the request is null.
443 * </p>
444 *
445 * @param request the request
446 */
447 public abstract void process( DestroyWorkspaceRequest request );
448
449 /**
450 * Process a request to copy a branch into another location.
451 * <p>
452 * This method does nothing if the request is null.
453 * </p>
454 *
455 * @param request the copy request
456 */
457 public abstract void process( CopyBranchRequest request );
458
459 /**
460 * Process a request to create a node at a specified location.
461 * <p>
462 * This method does nothing if the request is null.
463 * </p>
464 *
465 * @param request the create request
466 */
467 public abstract void process( CreateNodeRequest request );
468
469 /**
470 * Process a request to delete a branch at a specified location.
471 * <p>
472 * This method does nothing if the request is null.
473 * </p>
474 *
475 * @param request the delete request
476 * @throws ReferentialIntegrityException if the delete could not be performed because some references to deleted nodes would
477 * have remained after the delete operation completed
478 */
479 public abstract void process( DeleteBranchRequest request );
480
481 /**
482 * Process a request to delete all of the child nodes under the supplied existing node.
483 * <p>
484 * This method does nothing if the request is null.
485 * </p>
486 *
487 * @param request the delete request
488 * @throws ReferentialIntegrityException if the delete could not be performed because some references to deleted nodes would
489 * have remained after the delete operation completed
490 */
491 public void process( DeleteChildrenRequest request ) {
492 if (request == null) return;
493 if (request.isCancelled()) return;
494 // First get all of the children under the node ...
495 ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at(), request.inWorkspace());
496 process(readChildren);
497 if (readChildren.hasError()) {
498 request.setError(readChildren.getError());
499 return;
500 }
501 if (readChildren.isCancelled()) return;
502
503 // Issue a DeleteBranchRequest for each child ...
504 for (Location child : readChildren) {
505 if (request.isCancelled()) return;
506 DeleteBranchRequest deleteChild = new DeleteBranchRequest(child, request.inWorkspace());
507 process(deleteChild);
508 request.addDeletedChild(child);
509 }
510
511 // Set the actual location of the parent node ...
512 request.setActualLocationOfNode(readChildren.getActualLocationOfNode());
513 }
514
515 /**
516 * Process a request to move a branch at a specified location into a different location.
517 * <p>
518 * This method does nothing if the request is null.
519 * </p>
520 *
521 * @param request the move request
522 */
523 public abstract void process( MoveBranchRequest request );
524
525 /**
526 * Process a request to read all of the children of a node.
527 * <p>
528 * This method does nothing if the request is null.
529 * </p>
530 *
531 * @param request the read request
532 */
533 public abstract void process( ReadAllChildrenRequest request );
534
535 /**
536 * Process a request to read a block of the children of a node. The block is defined by a
537 * {@link ReadBlockOfChildrenRequest#startingAtIndex() starting index} and a {@link ReadBlockOfChildrenRequest#count() maximum
538 * number of children to include in the block}.
539 * <p>
540 * This method does nothing if the request is null. The default implementation converts the command to a
541 * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
542 * implementation may not be efficient and may need to be overridden.
543 * </p>
544 *
545 * @param request the read request
546 */
547 public void process( ReadBlockOfChildrenRequest request ) {
548 if (request == null) return;
549 // Convert the request to a ReadAllChildrenRequest and execute it ...
550 ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(request.of(), request.inWorkspace());
551 process(readAll);
552 if (readAll.hasError()) {
553 request.setError(readAll.getError());
554 return;
555 }
556 List<Location> allChildren = readAll.getChildren();
557
558 // If there aren't enough children for the block's range ...
559 if (allChildren.size() < request.startingAtIndex()) return;
560
561 // Now, find the children in the block ...
562 int endIndex = Math.min(request.endingBefore(), allChildren.size());
563 for (int i = request.startingAtIndex(); i != endIndex; ++i) {
564 request.addChild(allChildren.get(i));
565 }
566 // Set the actual location ...
567 request.setActualLocationOfNode(readAll.getActualLocationOfNode());
568 setCacheableInfo(request);
569 }
570
571 /**
572 * Process a request to read the next block of the children of a node, starting after a previously-retrieved child.
573 * <p>
574 * This method does nothing if the request is null. The default implementation converts the command to a
575 * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this
576 * implementation may not be efficient and may need to be overridden.
577 * </p>
578 *
579 * @param request the read request
580 */
581 public void process( ReadNextBlockOfChildrenRequest request ) {
582 if (request == null) return;
583
584 // Get the parent path ...
585 Location actualSiblingLocation = request.startingAfter();
586 Path path = actualSiblingLocation.getPath();
587 Path parentPath = null;
588 if (path != null) parentPath = path.getParent();
589 if (parentPath == null) {
590 // Need to find the parent path, so get the actual location of the sibling ...
591 VerifyNodeExistsRequest verifySibling = new VerifyNodeExistsRequest(request.startingAfter(), request.inWorkspace());
592 process(verifySibling);
593 actualSiblingLocation = verifySibling.getActualLocationOfNode();
594 parentPath = actualSiblingLocation.getPath().getParent();
595 }
596 assert parentPath != null;
597
598 // Convert the request to a ReadAllChildrenRequest and execute it ...
599 ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(Location.create(parentPath), request.inWorkspace());
600 process(readAll);
601 if (readAll.hasError()) {
602 request.setError(readAll.getError());
603 return;
604 }
605 List<Location> allChildren = readAll.getChildren();
606
607 // Iterate through the children, looking for the 'startingAfter' child ...
608 boolean found = false;
609 int count = 0;
610 for (Location child : allChildren) {
611 if (count > request.count()) break;
612 if (!found) {
613 // Set to true if we find the child we're looking for ...
614 found = child.isSame(request.startingAfter());
615 } else {
616 // Add the child to the block ...
617 ++count;
618 request.addChild(child);
619 }
620 }
621
622 // Set the actual location ...
623 request.setActualLocationOfStartingAfterNode(actualSiblingLocation);
624 setCacheableInfo(request);
625 }
626
627 /**
628 * Process a request to read a branch or subgraph that's below a node at a specified location.
629 * <p>
630 * This method does nothing if the request is null. The default implementation processes the branch by submitting the
631 * equivalent requests to {@link ReadNodeRequest read the nodes} and the {@link ReadAllChildrenRequest children}. It starts by
632 * doing this for the top-level node, then proceeds for each of the children of that node, and so forth.
633 * </p>
634 *
635 * @param request the request to read the branch
636 */
637 public void process( ReadBranchRequest request ) {
638 if (request == null) return;
639 // Create a queue for locations that need to be read ...
640 Queue<LocationWithDepth> locationsToRead = new LinkedList<LocationWithDepth>();
641 locationsToRead.add(new LocationWithDepth(request.at(), 1));
642 int maxDepthPerRead = Math.min(request.maximumDepth(), absoluteMaximumDepthForBranchReads());
643
644 // Now read the locations ...
645 boolean first = true;
646 while (locationsToRead.peek() != null) {
647 if (request.isCancelled()) return;
648 LocationWithDepth read = locationsToRead.poll();
649
650 // Check the depth ...
651 if (read.depth > maxDepthPerRead) break;
652
653 // Read the properties ...
654 ReadNodeRequest readNode = new ReadNodeRequest(read.location, request.inWorkspace());
655 process(readNode);
656 if (readNode.hasError()) {
657 request.setError(readNode.getError());
658 return;
659 }
660
661 Location actualLocation = readNode.getActualLocationOfNode();
662 if (first) {
663 // Set the actual location on the original request
664 request.setActualLocationOfNode(actualLocation);
665 }
666
667 // Record in the request the children and properties that were read on this node ...
668 request.setChildren(actualLocation, readNode.getChildren());
669 request.setProperties(actualLocation, readNode.getProperties());
670
671 if (includeChildrenInSubgraph(actualLocation, readNode.getPropertiesByName(), first)) {
672
673 // Add each of the children to the list of locations that we need to read ...
674 for (Location child : readNode.getChildren()) {
675 locationsToRead.add(new LocationWithDepth(child, read.depth + 1));
676 }
677 }
678
679 if (first) first = false;
680 }
681 setCacheableInfo(request);
682 }
683
684 /**
685 * This method is called from {@link #process(ReadBranchRequest)} when determining the maximum depth for the subgraph. By
686 * default, this method returns {@link Integer#MAX_VALUE}, signaling that the ReadBranchRequest's
687 * {@link ReadBranchRequest#maximumDepth() maximum depth} should be honored. However, subclasses can override this method to
688 * return a constant value that will be used if less than the ReadBranchRequest's maximum depth.
689 *
690 * @return the maximum read depth allowed by the processor; must be positive
691 */
692 protected int absoluteMaximumDepthForBranchReads() {
693 return Integer.MAX_VALUE;
694 }
695
696 /**
697 * This method is called from {@link #process(ReadBranchRequest)} when determining whether particular nodes should be included
698 * in subgraph reads. For example, some processor implementations might want to always exclude nodes with certain names from
699 * all subgraph reads. If this is the case, subclasses should override this method (which always returns true), and determine
700 * whether the node at the supplied location should be included in the subgraph.
701 *
702 * @param location the location of the parent node; never null
703 * @param properties the properties of the parent node; never null
704 * @param topOfSubgraph true if the parent node (identified by the location) is the root of the subgraph
705 * @return true if the child nodes should be read and included in the subgraph, or false otherwise
706 */
707 protected boolean includeChildrenInSubgraph( Location location,
708 Map<Name, Property> properties,
709 boolean topOfSubgraph ) {
710 return true;
711 }
712
713 /**
714 * Process a request to read the properties of a node at the supplied location.
715 * <p>
716 * This method does nothing if the request is null.
717 * </p>
718 *
719 * @param request the read request
720 */
721 public abstract void process( ReadAllPropertiesRequest request );
722
723 /**
724 * Process a request to read the properties and children of a node at the supplied location.
725 * <p>
726 * This method does nothing if the request is null. Unless overridden, this method converts the single request into a
727 * {@link ReadAllChildrenRequest} and a {@link ReadAllPropertiesRequest}.
728 * </p>
729 *
730 * @param request the read request
731 */
732 public void process( ReadNodeRequest request ) {
733 if (request == null) return;
734 // Read the properties ...
735 ReadAllPropertiesRequest readProperties = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
736 process(readProperties);
737 if (readProperties.hasError()) {
738 request.setError(readProperties.getError());
739 return;
740 }
741 // Set the actual location ...
742 request.setActualLocationOfNode(readProperties.getActualLocationOfNode());
743
744 // Read the children ...
745 ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at(), request.inWorkspace());
746 process(readChildren);
747 if (readChildren.hasError()) {
748 request.setError(readChildren.getError());
749 return;
750 }
751 if (request.isCancelled()) return;
752 // Now, copy all of the results into the submitted request ...
753 for (Property property : readProperties) {
754 request.addProperty(property);
755 }
756 for (Location child : readChildren) {
757 request.addChild(child);
758 }
759 setCacheableInfo(request);
760 }
761
762 /**
763 * Process a request to read a single property of a node at the supplied location.
764 * <p>
765 * This method does nothing if the request is null. Unless overridden, this method converts the request that
766 * {@link ReadAllPropertiesRequest reads the node} and simply returns the one property.
767 * </p>
768 *
769 * @param request the read request
770 */
771 public void process( ReadPropertyRequest request ) {
772 if (request == null) return;
773 ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.on(), request.inWorkspace());
774 process(readNode);
775 if (readNode.hasError()) {
776 request.setError(readNode.getError());
777 return;
778 }
779 Property property = readNode.getPropertiesByName().get(request.named());
780 request.setProperty(property);
781 // Set the actual location ...
782 request.setActualLocationOfNode(readNode.getActualLocationOfNode());
783 setCacheableInfo(request);
784 }
785
786 /**
787 * Process a request to verify that a node exists at the supplied location.
788 * <p>
789 * This method does nothing if the request is null. Unless overridden, this method converts the request that
790 * {@link ReadAllPropertiesRequest reads the node} and uses the result to determine if the node exists.
791 * </p>
792 *
793 * @param request the read request
794 */
795 public void process( VerifyNodeExistsRequest request ) {
796 if (request == null) return;
797 ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.at(), request.inWorkspace());
798 process(readNode);
799 if (readNode.hasError()) {
800 request.setError(readNode.getError());
801 return;
802 }
803 // Set the actual location ...
804 request.setActualLocationOfNode(readNode.getActualLocationOfNode());
805 setCacheableInfo(request);
806 }
807
808 /**
809 * Process a request to remove the specified property from a node.
810 * <p>
811 * This method does nothing if the request is null. Unless overridden, this method converts this request into a
812 * {@link UpdatePropertiesRequest}.
813 * </p>
814 *
815 * @param request the request to remove the property
816 */
817 public void process( RemovePropertyRequest request ) {
818 if (request == null) return;
819 Map<Name, Property> properties = Collections.singletonMap(request.propertyName(), null);
820 UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.from(), request.inWorkspace(), properties);
821 process(update);
822 if (update.hasError()) {
823 request.setError(update.getError());
824 }
825 // Set the actual location ...
826 request.setActualLocationOfNode(update.getActualLocationOfNode());
827 }
828
829 /**
830 * Process a request to set the specified property on a node.
831 * <p>
832 * This method does nothing if the request is null. Unless overridden, this method converts this request into a
833 * {@link UpdatePropertiesRequest}.
834 * </p>
835 *
836 * @param request the request to set the property
837 */
838 public void process( SetPropertyRequest request ) {
839 if (request == null) return;
840 Property property = request.property();
841 Map<Name, Property> properties = Collections.singletonMap(property.getName(), property);
842 UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.on(), request.inWorkspace(), properties);
843 process(update);
844 if (update.hasError()) {
845 request.setError(update.getError());
846 } else {
847 // Set the actual location and created flags ...
848 request.setActualLocationOfNode(update.getActualLocationOfNode());
849 request.setNewProperty(update.isNewProperty(property.getName()));
850 }
851 }
852
853 /**
854 * Process a request to remove the specified properties from a node.
855 * <p>
856 * This method does nothing if the request is null.
857 * </p>
858 *
859 * @param request the remove request
860 */
861 public abstract void process( UpdatePropertiesRequest request );
862
863 /**
864 * Process a request to add and/or remove the specified values from a property on the given node.
865 * <p>
866 * This method does nothing if the request is null.
867 * </p>
868 *
869 * @param request the remove request
870 */
871 public void process( UpdateValuesRequest request ) {
872 String workspaceName = request.inWorkspace();
873 Location on = request.on();
874 Name propertyName = request.property();
875
876 // Read in the current values
877 ReadPropertyRequest readProperty = new ReadPropertyRequest(on, workspaceName, propertyName);
878 process(readProperty);
879
880 if (readProperty.hasError()) {
881 request.setError(readProperty.getError());
882 return;
883 }
884
885 Property property = readProperty.getProperty();
886 List<Object> actualRemovedValues = new ArrayList<Object>(request.removedValues().size());
887 List<Object> newValues = property == null ? new LinkedList<Object>() : new LinkedList<Object>(
888 Arrays.asList(property.getValuesAsArray()));
889 // Calculate what the new values should be
890 for (Object removedValue : request.removedValues()) {
891 for (Iterator<Object> iter = newValues.iterator(); iter.hasNext();) {
892 if (iter.next().equals(removedValue)) {
893 iter.remove();
894 actualRemovedValues.add(removedValue);
895 break;
896 }
897 }
898 }
899
900 newValues.addAll(request.addedValues());
901 Property newProperty = getExecutionContext().getPropertyFactory().create(propertyName, newValues);
902
903 // Update the current values
904 SetPropertyRequest setProperty = new SetPropertyRequest(on, workspaceName, newProperty);
905 process(setProperty);
906
907 if (setProperty.hasError()) {
908 request.setError(setProperty.getError());
909 } else {
910 // Set the actual location and property
911 request.setActualLocation(setProperty.getActualLocationOfNode(), request.addedValues(), actualRemovedValues);
912 request.setActualProperty(newProperty, setProperty.isNewProperty());
913 }
914
915 }
916
917 /**
918 * Process a request to rename a node specified location into a different location.
919 * <p>
920 * This method does nothing if the request is null. Unless overridden, this method converts the rename into a
921 * {@link MoveBranchRequest move}. However, this only works if the <code>request</code> has a {@link Location#hasPath() path}
922 * for its {@link RenameNodeRequest#at() location}. (If not, this method throws an {@link UnsupportedOperationException} and
923 * must be overridden.)
924 * </p>
925 *
926 * @param request the rename request
927 */
928 public void process( RenameNodeRequest request ) {
929 if (request == null) return;
930 Location from = request.at();
931 if (!from.hasPath()) {
932 throw new UnsupportedOperationException();
933 }
934 Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(from.getPath(), request.toName());
935 Location to = Location.create(newPath);
936 MoveBranchRequest move = new MoveBranchRequest(from, to, request.inWorkspace());
937 process(move);
938 // Set the actual locations ...
939 request.setActualLocations(move.getActualLocationBefore(), move.getActualLocationAfter());
940 }
941
942 /**
943 * Process a request to lock a node or branch within a workspace
944 * <p>
945 * The default implementation of this method does nothing, as most connectors will not support locking. Any implementation of
946 * this method should do nothing if the request is null.
947 * </p>
948 * <p>
949 * Implementations that do support locking should throw a {@link LockFailedException} if the request could not be fulfilled.
950 * </p>
951 *
952 * @param request the request
953 */
954 public void process( LockBranchRequest request ) {
955 Location actualLocation = request.at();
956 if (!actualLocation.hasPath()) {
957 VerifyNodeExistsRequest nodeExists = new VerifyNodeExistsRequest(request.at(), request.inWorkspace());
958
959 process(nodeExists);
960
961 if (nodeExists.hasError()) {
962 request.setError(nodeExists.getError());
963 return;
964 }
965
966 actualLocation = nodeExists.getActualLocationOfNode();
967 }
968
969 request.setActualLocation(actualLocation);
970 }
971
972 /**
973 * Process a request to unlock a node or branch within a workspace
974 * <p>
975 * The default implementation of this method does nothing, as most connectors will not support locking. Any implementation of
976 * this method should do nothing if the request is null.
977 * </p>
978 *
979 * @param request the request
980 */
981 public void process( UnlockBranchRequest request ) {
982 Location actualLocation = request.at();
983 if (!actualLocation.hasPath()) {
984 VerifyNodeExistsRequest nodeExists = new VerifyNodeExistsRequest(request.at(), request.inWorkspace());
985
986 process(nodeExists);
987
988 if (nodeExists.hasError()) {
989 request.setError(nodeExists.getError());
990 return;
991 }
992
993 actualLocation = nodeExists.getActualLocationOfNode();
994 }
995
996 request.setActualLocation(actualLocation);
997 }
998
999 /**
1000 * Process a request to query a workspace with an access query, which is is a low-level atomic query that is part of a larger,
1001 * planned query.
1002 * <p>
1003 * The default implementation of this method behaves as though the implementation does not support queries by setting an error
1004 * on the request
1005 * </p>
1006 *
1007 * @param request the request
1008 */
1009 public void process( AccessQueryRequest request ) {
1010 processUnknownRequest(request);
1011 }
1012
1013 /**
1014 * Process a request to search a workspace.
1015 * <p>
1016 * The default implementation of this method behaves as though the implementation does not support full-text searches by
1017 * setting an error on the request
1018 * </p>
1019 *
1020 * @param request the request
1021 */
1022 public void process( FullTextSearchRequest request ) {
1023 processUnknownRequest(request);
1024 }
1025
1026 /**
1027 * Close this processor, allowing it to clean up any open resources.
1028 */
1029 public void close() {
1030 // do nothing by default
1031 }
1032
1033 /**
1034 * Obtain the list of {@link ChangeRequest}s that were successfully processed by this processor.
1035 * <p>
1036 * Note that this list is modified during processing and thus should only be accessed by the caller when this processor has
1037 * been {@link #close() closed}.
1038 * </p>
1039 * <p>
1040 * Also, if this processor encounters errors while processing {@link Request#isReadOnly() change requests}, the processor does
1041 * not throw out any of the changes. Thus it is up to the caller to decide whether any of the changes are to be kept.
1042 * </p>
1043 *
1044 * @return the list of successful changes; never null but possibly empty.
1045 */
1046 public List<ChangeRequest> getChanges() {
1047 return changes;
1048 }
1049
1050 /**
1051 * Take any of the changes that have been accumulated by this processor and notify the observer. This should only be called
1052 * after {@link #close()} has been called.
1053 */
1054 public void notifyObserverOfChanges() {
1055 if (observer == null) {
1056 if (changes != null) changes.clear();
1057 return;
1058 }
1059 if (this.changes.isEmpty()) return;
1060 // then publish any changes ...
1061 String userName = context.getSecurityContext() != null ? context.getSecurityContext().getUserName() : null;
1062 if (userName == null) userName = "";
1063 String contextId = context.getId();
1064 String processId = context.getProcessId();
1065 Map<String, String> userData = context.getData();
1066 Changes changes = new Changes(processId, contextId, userName, getSourceName(), getNowInUtc(), this.changes, userData);
1067 observer.notify(changes);
1068 // Null the list, since this should have been closed
1069 this.changes = null;
1070 }
1071
1072 /**
1073 * A class that represents a location at a known depth
1074 *
1075 * @author Randall Hauch
1076 */
1077 @Immutable
1078 protected class LocationWithDepth {
1079 protected final Location location;
1080 protected final int depth;
1081
1082 public LocationWithDepth( Location location,
1083 int depth ) {
1084 this.location = location;
1085 this.depth = depth;
1086 }
1087
1088 public Location getLocation() {
1089 return location;
1090 }
1091
1092 public int getDepth() {
1093 return depth;
1094 }
1095
1096 @Override
1097 public int hashCode() {
1098 return location.hashCode();
1099 }
1100
1101 @Override
1102 public String toString() {
1103 return location.toString() + " at depth " + depth;
1104 }
1105 }
1106
1107 }