001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package org.restlet;
020:
021: import java.lang.reflect.Constructor;
022: import java.util.logging.Level;
023:
024: import org.restlet.data.Request;
025: import org.restlet.data.Response;
026: import org.restlet.data.Status;
027: import org.restlet.resource.Resource;
028: import org.restlet.util.RouteList;
029:
030: /**
031: * Restlet routing calls to one of the attached routes. Each route can compute
032: * an affinity score for each call depending on various criteria. The attach()
033: * method allow the creation of routes based on URI patterns matching the
034: * beginning of a the resource reference's remaining part.<br>
035: * <br>
036: * In addition, several routing modes are supported, implementing various
037: * algorithms:
038: * <ul>
039: * <li>Best match (default)</li>
040: * <li>First match</li>
041: * <li>Last match</li>
042: * <li>Random match</li>
043: * <li>Round robin</li>
044: * <li>Custom</li>
045: * </ul>
046: * <br>
047: * Note that for routes using URI patterns will update the resource reference's
048: * base reference during the routing if they are selected. It is also important
049: * to know that the routing is very strict about path separators in your URI
050: * patterns. Finally, you can modify the list of routes while handling incoming
051: * calls as the delegation code is ensured to be thread-safe.
052: *
053: * @see <a href="http://www.restlet.org/documentation/1.0/tutorial#part11">Tutorial: Routers and
054: * hierarchical URIs</a>
055: * @author Jerome Louvel (contact@noelios.com)
056: */
057: public class Router extends Restlet {
058: /**
059: * Each call will be routed to the route with the best score, if the
060: * required score is reached.
061: */
062: public static final int BEST = 1;
063:
064: /**
065: * Each call is routed to the first route if the required score is reached.
066: * If the required score is not reached, then the route is skipped and the
067: * next one is considered.
068: */
069: public static final int FIRST = 2;
070:
071: /**
072: * Each call will be routed to the last route if the required score is
073: * reached. If the required score is not reached, then the route is skipped
074: * and the previous one is considered.
075: */
076: public static final int LAST = 3;
077:
078: /**
079: * Each call is be routed to the next route target if the required score is
080: * reached. The next route is relative to the previous call routed (round
081: * robin mode). If the required score is not reached, then the route is
082: * skipped and the next one is considered. If the last route is reached, the
083: * first route will be considered.
084: */
085: public static final int NEXT = 4;
086:
087: /**
088: * Each call will be randomly routed to one of the routes that reached the
089: * required score. If the random route selected is not a match then the
090: * immediate next route is evaluated until one matching route is found. If
091: * we get back to the inital random route selected with no match, then we
092: * return null.
093: */
094: public static final int RANDOM = 5;
095:
096: /**
097: * Each call will be routed according to a custom mode.
098: */
099: public static final int CUSTOM = 6;
100:
101: /** Finder class to instantiate. */
102: private Class<? extends Finder> finderClass;
103:
104: /** The modifiable list of routes. */
105: private RouteList routes;
106:
107: /** The default route tested if no other one was available. */
108: private Route defaultRoute;
109:
110: /** The routing mode. */
111: private int routingMode;
112:
113: /** The minimum score required to have a match. */
114: private float requiredScore;
115:
116: /**
117: * The maximum number of attempts if no attachment could be matched on the
118: * first attempt.
119: */
120: private int maxAttempts;
121:
122: /** The delay (in milliseconds) before a new attempt. */
123: private long retryDelay;
124:
125: /**
126: * Constructor. Note that usage of this constructor is not recommended as
127: * the Router won't have a proper context set. In general you will prefer to
128: * use the other constructor and pass it the parent application's context or
129: * eventually the parent component's context if you don't use applications.
130: */
131: public Router() {
132: this (null);
133: }
134:
135: /**
136: * Constructor.
137: *
138: * @param context
139: * The context.
140: */
141: public Router(Context context) {
142: super (context);
143: this .routes = null;
144: this .defaultRoute = null;
145: this .finderClass = Finder.class;
146: this .routingMode = BEST;
147: this .requiredScore = 0.5F;
148: this .maxAttempts = 1;
149: this .retryDelay = 500L;
150: }
151:
152: /**
153: * Attaches a target Restlet to this router with an empty URI pattern. A new
154: * route will be added routing to the target when any call is received.
155: *
156: * @param target
157: * The target Restlet to attach.
158: * @return The created route.
159: */
160: public Route attach(Restlet target) {
161: return attach("", target);
162: }
163:
164: /**
165: * Attaches a target Resource class to this router based on a given URI
166: * pattern. A new route will be added routing to the target when calls with
167: * a URI matching the pattern will be received.
168: *
169: * @param uriPattern
170: * The URI pattern that must match the relative part of the
171: * resource URI.
172: * @param targetClass
173: * The target Resource class to attach.
174: * @return The created route.
175: */
176: public Route attach(String uriPattern,
177: Class<? extends Resource> targetClass) {
178: return attach(uriPattern, createFinder(targetClass));
179: }
180:
181: /**
182: * Creates a new finder instance based on the "targetClass" property.
183: *
184: * @param targetClass
185: * The target Resource class to attach.
186: * @return The new finder instance.
187: */
188: private Finder createFinder(Class<? extends Resource> targetClass) {
189: Finder result = null;
190:
191: if (getFinderClass() != null) {
192: try {
193: Constructor<? extends Finder> constructor = getFinderClass()
194: .getConstructor(Context.class, Class.class);
195:
196: if (constructor != null) {
197: result = constructor.newInstance(getContext(),
198: targetClass);
199: }
200: } catch (Exception e) {
201: getLogger().log(Level.WARNING,
202: "Exception while instantiating the finder.", e);
203: }
204: }
205:
206: return result;
207: }
208:
209: /**
210: * Attaches a target Restlet to this router based on a given URI pattern. A
211: * new route will be added routing to the target when calls with a URI
212: * matching the pattern will be received.
213: *
214: * @param uriPattern
215: * The URI pattern that must match the relative part of the
216: * resource URI.
217: * @param target
218: * The target Restlet to attach.
219: * @return The created route.
220: */
221: public Route attach(String uriPattern, Restlet target) {
222: Route result = createRoute(uriPattern, target);
223: getRoutes().add(result);
224: return result;
225: }
226:
227: /**
228: * Attaches a Resource class to this router as the default target to invoke
229: * when no route matches. It actually sets a default route that scores all
230: * calls to 1.0.
231: *
232: * @param defaultTargetClass
233: * The target Resource class to attach.
234: * @return The created route.
235: */
236: public Route attachDefault(
237: Class<? extends Resource> defaultTargetClass) {
238: return attachDefault(createFinder(defaultTargetClass));
239: }
240:
241: /**
242: * Attaches a Restlet to this router as the default target to invoke when no
243: * route matches. It actually sets a default route that scores all calls to
244: * 1.0.
245: *
246: * @param defaultTarget
247: * The Restlet to use as the default target.
248: * @return The created route.
249: */
250: public Route attachDefault(Restlet defaultTarget) {
251: Route result = createRoute("", defaultTarget);
252: setDefaultRoute(result);
253: return result;
254: }
255:
256: /**
257: * Creates a new route for the given URI pattern and target.
258: *
259: * @param uriPattern
260: * The URI pattern that must match the relative part of the
261: * resource URI.
262: * @param target
263: * The target Restlet to attach.
264: * @return The created route.
265: */
266: protected Route createRoute(String uriPattern, Restlet target) {
267: return new Route(this , uriPattern, target);
268: }
269:
270: /**
271: * Detaches the target from this router. All routes routing to this target
272: * Restlet are removed from the list of routes and the default route is set
273: * to null.
274: *
275: * @param target
276: * The target Restlet to detach.
277: */
278: public void detach(Restlet target) {
279: getRoutes().removeAll(target);
280: if ((getDefaultRoute() != null)
281: && (getDefaultRoute().getNext() == target))
282: setDefaultRoute(null);
283: }
284:
285: /**
286: * Returns the matched route according to a custom algorithm. To use in
287: * combination of the RouterMode.CUSTOM enumeration. The default
288: * implementation (to be overriden), returns null.
289: *
290: * @param request
291: * The request to handle.
292: * @param response
293: * The response to update.
294: * @return The matched route if available or null.
295: */
296: protected Route getCustom(Request request, Response response) {
297: return null;
298: }
299:
300: /**
301: * Returns the default route to test if no other one was available after
302: * retrying the maximum number of attemps.
303: *
304: * @return The default route tested if no other one was available.
305: */
306: public Route getDefaultRoute() {
307: return this .defaultRoute;
308: }
309:
310: /**
311: * Returns the maximum number of attempts if no attachment could be matched
312: * on the first attempt. This is useful when the attachment scoring is
313: * dynamic and therefore could change on a retry. The default value is set
314: * to 1.
315: *
316: * @return The maximum number of attempts if no attachment could be matched
317: * on the first attempt.
318: */
319: public int getMaxAttempts() {
320: return this .maxAttempts;
321: }
322:
323: /**
324: * Returns the next Restlet if available.
325: *
326: * @param request
327: * The request to handle.
328: * @param response
329: * The response to update.
330: * @return The next Restlet if available or null.
331: */
332: public Restlet getNext(Request request, Response response) {
333: Route result = null;
334:
335: for (int i = 0; (result == null) && (i < getMaxAttempts()); i++) {
336: if (i > 0) {
337: // Before attempting another time, let's
338: // sleep during the "retryDelay" set.
339: try {
340: Thread.sleep(getRetryDelay());
341: } catch (InterruptedException e) {
342: }
343: }
344:
345: if (this .routes != null) {
346: // Select the routing mode
347: switch (getRoutingMode()) {
348: case BEST:
349: result = getRoutes().getBest(request, response,
350: getRequiredScore());
351: break;
352:
353: case FIRST:
354: result = getRoutes().getFirst(request, response,
355: getRequiredScore());
356: break;
357:
358: case LAST:
359: result = getRoutes().getLast(request, response,
360: getRequiredScore());
361: break;
362:
363: case NEXT:
364: result = getRoutes().getNext(request, response,
365: getRequiredScore());
366: break;
367:
368: case RANDOM:
369: result = getRoutes().getRandom(request, response,
370: getRequiredScore());
371: break;
372:
373: case CUSTOM:
374: result = getCustom(request, response);
375: break;
376: }
377: }
378: }
379:
380: if (result == null) {
381: // If nothing matched in the routes list, check the default
382: // route
383: if ((getDefaultRoute() != null)
384: && (getDefaultRoute().score(request, response) >= getRequiredScore())) {
385: result = getDefaultRoute();
386: } else {
387: // No route could be found
388: response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
389: }
390: }
391:
392: return result;
393: }
394:
395: /**
396: * Returns the minimum score required to have a match.
397: *
398: * @return The minimum score required to have a match.
399: */
400: public float getRequiredScore() {
401: return this .requiredScore;
402: }
403:
404: /**
405: * Returns the delay (in seconds) before a new attempt. The default value is
406: * 500 ms.
407: *
408: * @return The delay (in seconds) before a new attempt.
409: */
410: public long getRetryDelay() {
411: return this .retryDelay;
412: }
413:
414: /**
415: * Returns the modifiable list of routes.
416: *
417: * @return The modifiable list of routes.
418: */
419: public RouteList getRoutes() {
420: if (this .routes == null)
421: this .routes = new RouteList();
422: return this .routes;
423: }
424:
425: /**
426: * Returns the routing mode.
427: *
428: * @return The routing mode.
429: */
430: public int getRoutingMode() {
431: return this .routingMode;
432: }
433:
434: /**
435: * Handles a call by invoking the next Restlet if it is available.
436: *
437: * @param request
438: * The request to handle.
439: * @param response
440: * The response to update.
441: */
442: public void handle(Request request, Response response) {
443: init(request, response);
444:
445: Restlet next = getNext(request, response);
446: if (next != null) {
447: next.handle(request, response);
448: } else {
449: response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
450: }
451: }
452:
453: /**
454: * Sets the default route tested if no other one was available.
455: *
456: * @param defaultRoute
457: * The default route tested if no other one was available.
458: */
459: public void setDefaultRoute(Route defaultRoute) {
460: this .defaultRoute = defaultRoute;
461: }
462:
463: /**
464: * Sets the maximum number of attempts if no attachment could be matched on
465: * the first attempt. This is useful when the attachment scoring is dynamic
466: * and therefore could change on a retry.
467: *
468: * @param maxAttempts
469: * The maximum number of attempts.
470: */
471: public void setMaxAttempts(int maxAttempts) {
472: this .maxAttempts = maxAttempts;
473: }
474:
475: /**
476: * Sets the score required to have a match.
477: *
478: * @param score
479: * The score required to have a match.
480: */
481: public void setRequiredScore(float score) {
482: this .requiredScore = score;
483: }
484:
485: /**
486: * Sets the delay (in seconds) before a new attempt.
487: *
488: * @param retryDelay
489: * The delay (in seconds) before a new attempt.
490: */
491: public void setRetryDelay(long retryDelay) {
492: this .retryDelay = retryDelay;
493: }
494:
495: /**
496: * Sets the routing mode.
497: *
498: * @param routingMode
499: * The routing mode.
500: */
501: public void setRoutingMode(int routingMode) {
502: this .routingMode = routingMode;
503: }
504:
505: /**
506: * Returns the finder class to instantiate.
507: *
508: * @return the finder class to instantiate.
509: */
510: public Class<? extends Finder> getFinderClass() {
511: return this .finderClass;
512: }
513:
514: /**
515: * Sets the finder class to instantiate.
516: *
517: * @param finderClass
518: * The finder class to instantiate.
519: */
520: public void setFinderClass(Class<? extends Finder> finderClass) {
521: this.finderClass = finderClass;
522: }
523:
524: }
|