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.util.ArrayList;
022: import java.util.List;
023: import java.util.logging.Level;
024: import java.util.regex.Pattern;
025:
026: import org.restlet.data.Cookie;
027: import org.restlet.data.Form;
028: import org.restlet.data.Reference;
029: import org.restlet.data.Request;
030: import org.restlet.data.Response;
031: import org.restlet.data.Status;
032: import org.restlet.util.Series;
033: import org.restlet.util.Template;
034: import org.restlet.util.Variable;
035:
036: /**
037: * Filter scoring the affinity of calls with the attached Restlet. The score is
038: * used by an associated Router in order to determine the most appropriate
039: * Restlet for a given call. The routing is based on a reference template. It
040: * also supports the extraction of some attributes from a call. Multiple
041: * extractions can be defined, based on the query string of the resource
042: * reference, on the request form (ex: posted from a browser) or on cookies.
043: *
044: * @see org.restlet.util.Template
045: * @author Jerome Louvel (contact@noelios.com)
046: */
047: public class Route extends Filter {
048: /** Internal class holding extraction information. */
049: private static final class ExtractInfo {
050: /** Target attribute name. */
051: protected String attribute;
052:
053: /** Name of the parameter to look for. */
054: protected String parameter;
055:
056: /** Indicates how to handle repeating values. */
057: protected boolean first;
058:
059: /**
060: * Constructor.
061: *
062: * @param attribute
063: * Target attribute name.
064: * @param parameter
065: * Name of the parameter to look for.
066: * @param first
067: * Indicates how to handle repeating values.
068: */
069: public ExtractInfo(String attribute, String parameter,
070: boolean first) {
071: this .attribute = attribute;
072: this .parameter = parameter;
073: this .first = first;
074: }
075: }
076:
077: /** Internal class holding validation information. */
078: private static final class ValidateInfo {
079: /** Name of the attribute to look for. */
080: protected String attribute;
081:
082: /** Indicates if the attribute presence is required. */
083: protected boolean required;
084:
085: /** Format of the attribute value, using Regex pattern syntax. */
086: protected String format;
087:
088: /**
089: * Constructor.
090: *
091: * @param attribute
092: * Name of the attribute to look for.
093: * @param required
094: * Indicates if the attribute presence is required.
095: * @param format
096: * Format of the attribute value, using Regex pattern syntax.
097: */
098: public ValidateInfo(String attribute, boolean required,
099: String format) {
100: this .attribute = attribute;
101: this .required = required;
102: this .format = format;
103: }
104: }
105:
106: /** The parent router. */
107: private Router router;
108:
109: /** The list of attribute validations. */
110: private List<ValidateInfo> validations;
111:
112: /** The list of cookies to extract. */
113: private List<ExtractInfo> cookieExtracts;
114:
115: /** The list of query parameters to extract. */
116: private List<ExtractInfo> queryExtracts;
117:
118: /** The list of request entity parameters to extract. */
119: private List<ExtractInfo> entityExtracts;
120:
121: /** The reference template to match. */
122: private Template template;
123:
124: /**
125: * Constructor behaving as a simple extractor filter.
126: *
127: * @param next
128: * The next Restlet.
129: */
130: public Route(Restlet next) {
131: this (null, (Template) null, next);
132: }
133:
134: /**
135: * Constructor.
136: *
137: * @param router
138: * The parent router.
139: * @param uriTemplate
140: * The URI template.
141: * @param next
142: * The next Restlet.
143: */
144: public Route(Router router, String uriTemplate, Restlet next) {
145: this (router, new Template(router.getLogger(), uriTemplate,
146: Template.MODE_STARTS_WITH, Variable.TYPE_URI_SEGMENT,
147: "", true, false), next);
148: }
149:
150: /**
151: * Constructor.
152: *
153: * @param router
154: * The parent router.
155: * @param template
156: * The URI template.
157: * @param next
158: * The next Restlet.
159: */
160: public Route(Router router, Template template, Restlet next) {
161: super (router == null ? null : router.getContext(), next);
162: this .router = router;
163: this .cookieExtracts = null;
164: this .queryExtracts = null;
165: this .entityExtracts = null;
166: this .template = template;
167: this .validations = null;
168: }
169:
170: /**
171: * Allows filtering before its handling by the target Restlet. Does nothing
172: * by default.
173: *
174: * @param request
175: * The request to filter.
176: * @param response
177: * The response to filter.
178: */
179: protected void beforeHandle(Request request, Response response) {
180: // 1 - Parse the template variables and adjust the base reference
181: if (getTemplate() != null) {
182: String remainingPart = request.getResourceRef()
183: .getRemainingPart();
184: int matchedLength = getTemplate().parse(remainingPart,
185: request);
186:
187: if (getLogger().isLoggable(Level.FINER)) {
188: getLogger().finer(
189: "Attempting to match this pattern: "
190: + getTemplate().getPattern() + " >> "
191: + matchedLength);
192: }
193:
194: if (matchedLength != -1) {
195: // Updates the context
196: String matchedPart = remainingPart.substring(0,
197: matchedLength);
198: Reference baseRef = request.getResourceRef()
199: .getBaseRef();
200:
201: if (baseRef == null) {
202: baseRef = new Reference(matchedPart);
203: } else {
204: baseRef = new Reference(baseRef.toString(false,
205: false)
206: + matchedPart);
207: }
208:
209: request.getResourceRef().setBaseRef(baseRef);
210:
211: if (getLogger().isLoggable(Level.FINE)) {
212: getLogger().fine(
213: "New base URI: "
214: + request.getResourceRef()
215: .getBaseRef());
216: getLogger().fine(
217: "New remaining part: "
218: + request.getResourceRef()
219: .getRemainingPart());
220: }
221:
222: if (getLogger().isLoggable(Level.FINE)) {
223: getLogger()
224: .fine(
225: "Delegating the call to the target Restlet");
226: }
227: } else {
228: response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
229: }
230: }
231:
232: // 2 - Extract the attributes from form parameters (query, cookies,
233: // entity).
234: extractAttributes(request, response);
235:
236: // 3 - Validate the attributes extracted (or others)
237: validateAttributes(request, response);
238: }
239:
240: /**
241: * Extracts the attributes value from the request.
242: *
243: * @param request
244: * The request to process.
245: * @param response
246: * The response to process.
247: */
248: private void extractAttributes(Request request, Response response) {
249: // Extract the query parameters
250: if (this .queryExtracts != null) {
251: Form form = request.getResourceRef().getQueryAsForm();
252:
253: if (form != null) {
254: for (ExtractInfo ei : getQueryExtracts()) {
255: if (ei.first) {
256: request.getAttributes().put(ei.attribute,
257: form.getFirstValue(ei.parameter));
258: } else {
259: request.getAttributes().put(ei.attribute,
260: form.subList(ei.parameter));
261: }
262: }
263: }
264: }
265:
266: // Extract the request entity parameters
267: if (this .entityExtracts != null) {
268: Form form = request.getEntityAsForm();
269:
270: if (form != null) {
271: for (ExtractInfo ei : getEntityExtracts()) {
272: if (ei.first) {
273: request.getAttributes().put(ei.attribute,
274: form.getFirstValue(ei.parameter));
275: } else {
276: request.getAttributes().put(ei.attribute,
277: form.subList(ei.parameter));
278: }
279: }
280: }
281: }
282:
283: // Extract the cookie parameters
284: if (this .cookieExtracts != null) {
285: Series<Cookie> cookies = request.getCookies();
286:
287: if (cookies != null) {
288: for (ExtractInfo ei : getCookieExtracts()) {
289: if (ei.first) {
290: request.getAttributes().put(ei.attribute,
291: cookies.getFirstValue(ei.parameter));
292: } else {
293: request.getAttributes().put(ei.attribute,
294: cookies.subList(ei.parameter));
295: }
296: }
297: }
298: }
299: }
300:
301: /**
302: * Extracts an attribute from the request cookies.
303: *
304: * @param attribute
305: * The name of the request attribute to set.
306: * @param cookieName
307: * The name of the cookies to extract.
308: * @param first
309: * Indicates if only the first cookie should be set. Otherwise as
310: * a List instance might be set in the attribute value.
311: * @return The current Filter.
312: */
313: public Route extractCookie(String attribute, String cookieName,
314: boolean first) {
315: getCookieExtracts().add(
316: new ExtractInfo(attribute, cookieName, first));
317: return this ;
318: }
319:
320: /**
321: * Extracts an attribute from the request entity form.
322: *
323: * @param attribute
324: * The name of the request attribute to set.
325: * @param parameter
326: * The name of the entity form parameter to extract.
327: * @param first
328: * Indicates if only the first cookie should be set. Otherwise as
329: * a List instance might be set in the attribute value.
330: * @return The current Filter.
331: */
332: public Route extractEntity(String attribute, String parameter,
333: boolean first) {
334: getEntityExtracts().add(
335: new ExtractInfo(attribute, parameter, first));
336: return this ;
337: }
338:
339: /**
340: * Extracts an attribute from the query string of the resource reference.
341: *
342: * @param attribute
343: * The name of the request attribute to set.
344: * @param parameter
345: * The name of the query string parameter to extract.
346: * @param first
347: * Indicates if only the first cookie should be set. Otherwise as
348: * a List instance might be set in the attribute value.
349: * @return The current Filter.
350: */
351: public Route extractQuery(String attribute, String parameter,
352: boolean first) {
353: getQueryExtracts().add(
354: new ExtractInfo(attribute, parameter, first));
355: return this ;
356: }
357:
358: /**
359: * Returns the list of query extracts.
360: *
361: * @return The list of query extracts.
362: */
363: private List<ExtractInfo> getCookieExtracts() {
364: if (this .cookieExtracts == null)
365: this .cookieExtracts = new ArrayList<ExtractInfo>();
366: return this .cookieExtracts;
367: }
368:
369: /**
370: * Returns the list of query extracts.
371: *
372: * @return The list of query extracts.
373: */
374: private List<ExtractInfo> getEntityExtracts() {
375: if (this .entityExtracts == null)
376: this .entityExtracts = new ArrayList<ExtractInfo>();
377: return this .entityExtracts;
378: }
379:
380: /**
381: * Returns the list of query extracts.
382: *
383: * @return The list of query extracts.
384: */
385: private List<ExtractInfo> getQueryExtracts() {
386: if (this .queryExtracts == null)
387: this .queryExtracts = new ArrayList<ExtractInfo>();
388: return this .queryExtracts;
389: }
390:
391: /**
392: * Returns the parent router.
393: *
394: * @return The parent router.
395: */
396: public Router getRouter() {
397: return this .router;
398: }
399:
400: /**
401: * Returns the reference template to match.
402: *
403: * @return The reference template to match.
404: */
405: public Template getTemplate() {
406: return this .template;
407: }
408:
409: /**
410: * Returns the list of attribute validations.
411: *
412: * @return The list of attribute validations.
413: */
414: private List<ValidateInfo> getValidations() {
415: if (this .validations == null)
416: this .validations = new ArrayList<ValidateInfo>();
417: return this .validations;
418: }
419:
420: /**
421: * Returns the score for a given call (between 0 and 1.0).
422: *
423: * @param request
424: * The request to score.
425: * @param response
426: * The response to score.
427: * @return The score for a given call (between 0 and 1.0).
428: */
429: public float score(Request request, Response response) {
430: float result = 0F;
431:
432: if ((getRouter() != null) && (request.getResourceRef() != null)
433: && (getTemplate() != null)) {
434: String remainingPart = request.getResourceRef()
435: .getRemainingPart();
436: if (remainingPart != null) {
437: int matchedLength = getTemplate().match(remainingPart);
438:
439: if (matchedLength != -1) {
440: float totalLength = remainingPart.length();
441:
442: if (totalLength > 0.0F) {
443: result = getRouter().getRequiredScore()
444: + (1.0F - getRouter()
445: .getRequiredScore())
446: * (((float) matchedLength) / totalLength);
447: } else {
448: result = 1.0F;
449: }
450: }
451: }
452:
453: if (getLogger().isLoggable(Level.FINER)) {
454: getLogger().finer(
455: "Call score for the \""
456: + getTemplate().getPattern()
457: + "\" URI pattern: " + result);
458: }
459: }
460:
461: return result;
462: }
463:
464: /**
465: * Sets the reference template to match.
466: *
467: * @param template
468: * The reference template to match.
469: */
470: public void setTemplate(Template template) {
471: this .template = template;
472: }
473:
474: /**
475: * Checks the request attributes for presence, format, etc. If the check
476: * fails, then a response status CLIENT_ERROR_BAD_REQUEST is returned with
477: * the proper status description.
478: *
479: * @param attribute
480: * Name of the attribute to look for.
481: * @param required
482: * Indicates if the attribute presence is required.
483: * @param format
484: * Format of the attribute value, using Regex pattern syntax.
485: */
486: public void validate(String attribute, boolean required,
487: String format) {
488: getValidations().add(
489: new ValidateInfo(attribute, required, format));
490: }
491:
492: /**
493: * Validates the attributes from the request.
494: *
495: * @param request
496: * The request to process.
497: * @param response
498: * The response to process.
499: */
500: private void validateAttributes(Request request, Response response) {
501: if (this .validations != null) {
502: for (ValidateInfo validate : getValidations()) {
503: if (validate.required
504: && !request.getAttributes().containsKey(
505: validate.attribute)) {
506: response
507: .setStatus(
508: Status.CLIENT_ERROR_BAD_REQUEST,
509: "Unable to find the \""
510: + validate.attribute
511: + "\" attribute in the request. Please check your request.");
512: } else if (validate.format != null) {
513: Object value = request.getAttributes().get(
514: validate.attribute);
515: if (value == null) {
516: response
517: .setStatus(
518: Status.CLIENT_ERROR_BAD_REQUEST,
519: "Unable to validate the \""
520: + validate.attribute
521: + "\" attribute with a null value. Please check your request.");
522: } else {
523: if (!Pattern.matches(validate.format, value
524: .toString())) {
525: response
526: .setStatus(
527: Status.CLIENT_ERROR_BAD_REQUEST,
528: "Unable to validate the value of the \""
529: + validate.attribute
530: + "\" attribute. The expected format is: "
531: + validate.format
532: + " (Java Regex). Please check your request.");
533: }
534: }
535: }
536: }
537: }
538: }
539: }
|