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 }