001 /* 002 * JBoss, Home of Professional Open Source. 003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors 004 * as indicated by the @author tags. See the copyright.txt file in the 005 * distribution for a full listing of individual contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 package org.jboss.dna.graph.requests.processor; 023 024 import java.util.ArrayList; 025 import java.util.Collection; 026 import java.util.LinkedList; 027 import java.util.List; 028 import java.util.Queue; 029 import net.jcip.annotations.Immutable; 030 import org.jboss.dna.common.util.CheckArg; 031 import org.jboss.dna.graph.ExecutionContext; 032 import org.jboss.dna.graph.GraphI18n; 033 import org.jboss.dna.graph.Location; 034 import org.jboss.dna.graph.connectors.RepositorySourceException; 035 import org.jboss.dna.graph.properties.DateTime; 036 import org.jboss.dna.graph.properties.Name; 037 import org.jboss.dna.graph.properties.Path; 038 import org.jboss.dna.graph.properties.Property; 039 import org.jboss.dna.graph.properties.basic.BasicEmptyProperty; 040 import org.jboss.dna.graph.requests.CompositeRequest; 041 import org.jboss.dna.graph.requests.CopyBranchRequest; 042 import org.jboss.dna.graph.requests.CreateNodeRequest; 043 import org.jboss.dna.graph.requests.DeleteBranchRequest; 044 import org.jboss.dna.graph.requests.MoveBranchRequest; 045 import org.jboss.dna.graph.requests.ReadAllChildrenRequest; 046 import org.jboss.dna.graph.requests.ReadAllPropertiesRequest; 047 import org.jboss.dna.graph.requests.ReadBlockOfChildrenRequest; 048 import org.jboss.dna.graph.requests.ReadBranchRequest; 049 import org.jboss.dna.graph.requests.ReadNextBlockOfChildrenRequest; 050 import org.jboss.dna.graph.requests.ReadNodeRequest; 051 import org.jboss.dna.graph.requests.ReadPropertyRequest; 052 import org.jboss.dna.graph.requests.RemovePropertiesRequest; 053 import org.jboss.dna.graph.requests.RenameNodeRequest; 054 import org.jboss.dna.graph.requests.Request; 055 import org.jboss.dna.graph.requests.UpdatePropertiesRequest; 056 057 /** 058 * A component that is used to process and execute {@link Request}s. This class is intended to be subclassed and methods 059 * overwritten to define the behavior for executing the different kinds of requests. Abstract methods must be overridden, but 060 * non-abstract methods all have meaningful default implementations. 061 * 062 * @author Randall Hauch 063 */ 064 @Immutable 065 public abstract class RequestProcessor { 066 067 private final ExecutionContext context; 068 private final String sourceName; 069 private final DateTime nowInUtc; 070 071 protected RequestProcessor( String sourceName, 072 ExecutionContext context ) { 073 this(sourceName, context, null); 074 } 075 076 protected RequestProcessor( String sourceName, 077 ExecutionContext context, 078 DateTime now ) { 079 CheckArg.isNotEmpty(sourceName, "sourceName"); 080 CheckArg.isNotNull(context, "context"); 081 this.context = context; 082 this.sourceName = sourceName; 083 this.nowInUtc = now != null ? now : context.getValueFactories().getDateFactory().createUtc(); 084 } 085 086 /** 087 * Get the name of the source against which this processor is executing. 088 * 089 * @return the repository source name; never null or empty 090 */ 091 public String getSourceName() { 092 return sourceName; 093 } 094 095 /** 096 * The execution context that this process is operating within. 097 * 098 * @return the execution context; never null 099 */ 100 public ExecutionContext getExecutionContext() { 101 return this.context; 102 } 103 104 /** 105 * Get the 'current time' for this processor, which is usually a constant during its lifetime. 106 * 107 * @return the current time in UTC; never null 108 */ 109 protected DateTime getNowInUtc() { 110 return this.nowInUtc; 111 } 112 113 /** 114 * Process a request by determining the type of request and delegating to the appropriate <code>process</code> method for that 115 * type. 116 * <p> 117 * This method does nothing if the request is null. 118 * </p> 119 * 120 * @param request the general request 121 */ 122 public void process( Request request ) { 123 if (request == null) return; 124 if (request.isCancelled()) return; 125 if (request instanceof CompositeRequest) { 126 process((CompositeRequest)request); 127 } else if (request instanceof CopyBranchRequest) { 128 process((CopyBranchRequest)request); 129 } else if (request instanceof CreateNodeRequest) { 130 process((CreateNodeRequest)request); 131 } else if (request instanceof DeleteBranchRequest) { 132 process((DeleteBranchRequest)request); 133 } else if (request instanceof MoveBranchRequest) { 134 process((MoveBranchRequest)request); 135 } else if (request instanceof ReadAllChildrenRequest) { 136 process((ReadAllChildrenRequest)request); 137 } else if (request instanceof ReadNextBlockOfChildrenRequest) { 138 process((ReadNextBlockOfChildrenRequest)request); 139 } else if (request instanceof ReadBlockOfChildrenRequest) { 140 process((ReadBlockOfChildrenRequest)request); 141 } else if (request instanceof ReadBranchRequest) { 142 process((ReadBranchRequest)request); 143 } else if (request instanceof ReadNodeRequest) { 144 process((ReadNodeRequest)request); 145 } else if (request instanceof ReadAllPropertiesRequest) { 146 process((ReadAllPropertiesRequest)request); 147 } else if (request instanceof ReadPropertyRequest) { 148 process((ReadPropertyRequest)request); 149 } else if (request instanceof RemovePropertiesRequest) { 150 process((RemovePropertiesRequest)request); 151 } else if (request instanceof RenameNodeRequest) { 152 process((RenameNodeRequest)request); 153 } else if (request instanceof UpdatePropertiesRequest) { 154 process((UpdatePropertiesRequest)request); 155 } 156 } 157 158 /** 159 * Process a request that is composed of multiple other (non-composite) requests. If any of the embedded requests 160 * {@link Request#hasError() has an error} after it is processed, the submitted request will be marked with an error. 161 * <p> 162 * This method does nothing if the request is null. 163 * </p> 164 * 165 * @param request the composite request 166 */ 167 public void process( CompositeRequest request ) { 168 if (request == null) return; 169 int numberOfErrors = 0; 170 Throwable firstError = null; 171 for (Request embedded : request) { 172 assert embedded != null; 173 if (embedded.isCancelled()) return; 174 process(embedded); 175 if (embedded.hasError()) { 176 if (numberOfErrors == 0) firstError = embedded.getError(); 177 ++numberOfErrors; 178 } 179 } 180 if (firstError == null) return; 181 if (numberOfErrors == 1) { 182 request.setError(firstError); 183 } else { 184 String msg = GraphI18n.multipleErrorsWhileExecutingRequests.text(numberOfErrors, request.size()); 185 request.setError(new RepositorySourceException(getSourceName(), msg)); 186 } 187 } 188 189 /** 190 * Process a request to copy a branch into another location. 191 * <p> 192 * This method does nothing if the request is null. 193 * </p> 194 * 195 * @param request the copy request 196 */ 197 public abstract void process( CopyBranchRequest request ); 198 199 /** 200 * Process a request to create a node at a specified location. 201 * <p> 202 * This method does nothing if the request is null. 203 * </p> 204 * 205 * @param request the create request 206 */ 207 public abstract void process( CreateNodeRequest request ); 208 209 /** 210 * Process a request to delete a branch at a specified location. 211 * <p> 212 * This method does nothing if the request is null. 213 * </p> 214 * 215 * @param request the delete request 216 */ 217 public abstract void process( DeleteBranchRequest request ); 218 219 /** 220 * Process a request to move a branch at a specified location into a different location. 221 * <p> 222 * This method does nothing if the request is null. 223 * </p> 224 * 225 * @param request the move request 226 */ 227 public abstract void process( MoveBranchRequest request ); 228 229 /** 230 * Process a request to read all of the children of a node. 231 * <p> 232 * This method does nothing if the request is null. 233 * </p> 234 * 235 * @param request the read request 236 */ 237 public abstract void process( ReadAllChildrenRequest request ); 238 239 /** 240 * Process a request to read a block of the children of a node. The block is defined by a 241 * {@link ReadBlockOfChildrenRequest#startingAtIndex() starting index} and a {@link ReadBlockOfChildrenRequest#count() maximum 242 * number of children to include in the block}. 243 * <p> 244 * This method does nothing if the request is null. The default implementation converts the command to a 245 * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this 246 * implementation may not be efficient and may need to be overridden. 247 * </p> 248 * 249 * @param request the read request 250 */ 251 public void process( ReadBlockOfChildrenRequest request ) { 252 if (request == null) return; 253 // Convert the request to a ReadAllChildrenRequest and execute it ... 254 ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(request.of()); 255 process(readAll); 256 if (readAll.hasError()) { 257 request.setError(readAll.getError()); 258 return; 259 } 260 List<Location> allChildren = readAll.getChildren(); 261 262 // If there aren't enough children for the block's range ... 263 if (allChildren.size() < request.startingAtIndex()) return; 264 265 // Now, find the children in the block ... 266 int endIndex = Math.min(request.endingBefore(), allChildren.size()); 267 for (int i = request.startingAtIndex(); i != endIndex; ++i) { 268 request.addChild(allChildren.get(i)); 269 } 270 // Set the actual location ... 271 request.setActualLocationOfNode(readAll.getActualLocationOfNode()); 272 } 273 274 /** 275 * Process a request to read the next block of the children of a node, starting after a previously-retrieved child. 276 * <p> 277 * This method does nothing if the request is null. The default implementation converts the command to a 278 * {@link ReadAllChildrenRequest}, and then finds the children within the block. Obviously for large numbers of children, this 279 * implementation may not be efficient and may need to be overridden. 280 * </p> 281 * 282 * @param request the read request 283 */ 284 public void process( ReadNextBlockOfChildrenRequest request ) { 285 if (request == null) return; 286 // Convert the request to a ReadAllChildrenRequest and execute it ... 287 ReadAllChildrenRequest readAll = new ReadAllChildrenRequest(request.of()); 288 process(readAll); 289 if (readAll.hasError()) { 290 request.setError(readAll.getError()); 291 return; 292 } 293 List<Location> allChildren = readAll.getChildren(); 294 295 // Iterate through the children, looking for the 'startingAfter' child ... 296 boolean found = false; 297 int count = 0; 298 for (Location child : allChildren) { 299 if (count > request.count()) break; 300 if (!found) { 301 // Set to true if we find the child we're looking for ... 302 found = child.equals(request.startingAfter()); 303 } else { 304 // Add the child to the block ... 305 ++count; 306 request.addChild(child); 307 } 308 } 309 310 // Set the actual location ... 311 request.setActualLocationOfNode(readAll.getActualLocationOfNode()); 312 } 313 314 /** 315 * Process a request to read a branch or subgraph that's below a node at a specified location. 316 * <p> 317 * This method does nothing if the request is null. The default implementation processes the branch by submitting the 318 * equivalent requests to {@link ReadNodeRequest read the nodes} and the {@link ReadAllChildrenRequest children}. It starts by 319 * doing this for the top-level node, then proceeds for each of the children of that node, and so forth. 320 * </p> 321 * 322 * @param request the request to read the branch 323 */ 324 public void process( ReadBranchRequest request ) { 325 if (request == null) return; 326 // Create a queue for locations that need to be read ... 327 Queue<LocationWithDepth> locationsToRead = new LinkedList<LocationWithDepth>(); 328 locationsToRead.add(new LocationWithDepth(request.at(), 1)); 329 330 // Now read the locations ... 331 boolean first = true; 332 while (locationsToRead.peek() != null) { 333 if (request.isCancelled()) return; 334 LocationWithDepth read = locationsToRead.poll(); 335 336 // Check the depth ... 337 if (read.depth > request.maximumDepth()) break; 338 339 // Read the properties ... 340 ReadNodeRequest readNode = new ReadNodeRequest(read.location); 341 process(readNode); 342 if (readNode.hasError()) { 343 request.setError(readNode.getError()); 344 return; 345 } 346 Location actualLocation = readNode.getActualLocationOfNode(); 347 if (first) { 348 // Set the actual location on the original request 349 request.setActualLocationOfNode(actualLocation); 350 first = false; 351 } 352 353 // Record in the request the children and properties that were read on this node ... 354 request.setChildren(actualLocation, readNode.getChildren()); 355 request.setProperties(actualLocation, readNode.getProperties()); 356 357 // Add each of the children to the list of locations that we need to read ... 358 for (Location child : readNode.getChildren()) { 359 locationsToRead.add(new LocationWithDepth(child, read.depth + 1)); 360 } 361 } 362 } 363 364 /** 365 * Process a request to read the properties of a node at the supplied location. 366 * <p> 367 * This method does nothing if the request is null. 368 * </p> 369 * 370 * @param request the read request 371 */ 372 public abstract void process( ReadAllPropertiesRequest request ); 373 374 /** 375 * Process a request to read the properties and children of a node at the supplied location. 376 * <p> 377 * This method does nothing if the request is null. Unless overridden, this method converts the single request into a 378 * {@link ReadAllChildrenRequest} and a {@link ReadAllPropertiesRequest}. 379 * </p> 380 * 381 * @param request the read request 382 */ 383 public void process( ReadNodeRequest request ) { 384 if (request == null) return; 385 // Read the properties ... 386 ReadAllPropertiesRequest readProperties = new ReadAllPropertiesRequest(request.at()); 387 process(readProperties); 388 if (readProperties.hasError()) { 389 request.setError(readProperties.getError()); 390 return; 391 } 392 // Set the actual location ... 393 request.setActualLocationOfNode(readProperties.getActualLocationOfNode()); 394 395 // Read the children ... 396 ReadAllChildrenRequest readChildren = new ReadAllChildrenRequest(request.at()); 397 process(readChildren); 398 if (readChildren.hasError()) { 399 request.setError(readChildren.getError()); 400 return; 401 } 402 if (request.isCancelled()) return; 403 // Now, copy all of the results into the submitted request ... 404 for (Property property : readProperties) { 405 request.addProperty(property); 406 } 407 for (Location child : readChildren) { 408 request.addChild(child); 409 } 410 } 411 412 /** 413 * Process a request to read a single property of a node at the supplied location. 414 * <p> 415 * This method does nothing if the request is null. Unless overridden, this method converts the request that 416 * {@link ReadNodeRequest reads the node} and simply returns the one property. 417 * </p> 418 * 419 * @param request the read request 420 */ 421 public void process( ReadPropertyRequest request ) { 422 if (request == null) return; 423 ReadAllPropertiesRequest readNode = new ReadAllPropertiesRequest(request.on()); 424 process(readNode); 425 if (readNode.hasError()) { 426 request.setError(readNode.getError()); 427 return; 428 } 429 Property property = readNode.getPropertiesByName().get(request.named()); 430 request.setProperty(property); 431 // Set the actual location ... 432 request.setActualLocationOfNode(readNode.getActualLocationOfNode()); 433 } 434 435 /** 436 * Process a request to remove the specified properties from a node. 437 * <p> 438 * This method does nothing if the request is null. Unless overridden, this method converts this request into a 439 * {@link UpdatePropertiesRequest}. 440 * </p> 441 * 442 * @param request the request to remove the properties with certain names 443 */ 444 public void process( RemovePropertiesRequest request ) { 445 if (request == null) return; 446 Collection<Name> names = request.propertyNames(); 447 if (names.isEmpty()) return; 448 List<Property> emptyProperties = new ArrayList<Property>(names.size()); 449 for (Name propertyName : names) { 450 emptyProperties.add(new BasicEmptyProperty(propertyName)); 451 } 452 UpdatePropertiesRequest update = new UpdatePropertiesRequest(request.from(), emptyProperties); 453 process(update); 454 if (update.hasError()) { 455 request.setError(update.getError()); 456 } 457 // Set the actual location ... 458 request.setActualLocationOfNode(update.getActualLocationOfNode()); 459 } 460 461 /** 462 * Process a request to remove the specified properties from a node. 463 * <p> 464 * This method does nothing if the request is null. 465 * </p> 466 * 467 * @param request the remove request 468 */ 469 public abstract void process( UpdatePropertiesRequest request ); 470 471 /** 472 * Process a request to rename a node specified location into a different location. 473 * <p> 474 * This method does nothing if the request is null. Unless overridden, this method converts the rename into a 475 * {@link MoveBranchRequest move}. However, this only works if the <code>request</code> has a {@link Location#hasPath() path} 476 * for its {@link RenameNodeRequest#at() location}. (If not, this method throws an {@link UnsupportedOperationException} and 477 * must be overriddent.) 478 * </p> 479 * 480 * @param request the rename request 481 */ 482 public void process( RenameNodeRequest request ) { 483 if (request == null) return; 484 Location from = request.at(); 485 if (!from.hasPath()) { 486 throw new UnsupportedOperationException(); 487 } 488 Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(from.getPath(), request.toName()); 489 Location to = new Location(newPath); 490 MoveBranchRequest move = new MoveBranchRequest(from, to); 491 process(move); 492 // Set the actual locations ... 493 request.setActualLocations(move.getActualLocationBefore(), move.getActualLocationAfter()); 494 } 495 496 /** 497 * Close this processor, allowing it to clean up any open resources. 498 */ 499 public void close() { 500 // do nothing 501 } 502 503 /** 504 * A class that represents a location at a known depth 505 * 506 * @author Randall Hauch 507 */ 508 @Immutable 509 protected static class LocationWithDepth { 510 protected final Location location; 511 protected final int depth; 512 513 protected LocationWithDepth( Location location, 514 int depth ) { 515 this.location = location; 516 this.depth = depth; 517 } 518 519 @Override 520 public int hashCode() { 521 return location.hashCode(); 522 } 523 524 @Override 525 public String toString() { 526 return location.toString() + " at depth " + depth; 527 } 528 } 529 530 }