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 }