/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.routing; import java.util.logging.Level; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Reference; import org.restlet.data.Status; /** * Filter scoring the affinity of calls with the attached Restlet. The score is * used by an associated Router in order to determine the most appropriate * Restlet for a given call. The routing is based on a reference template.<br> * <br> * Concurrency note: instances of this class or its subclasses can be invoked by * several threads at the same time and therefore must be thread-safe. You * should be especially careful when storing state in member variables. * * @see org.restlet.routing.Template * @author Jerome Louvel */ public class TemplateRoute extends Route { /** * Indicates whether the query part should be taken into account when * matching a reference with the template. */ private volatile boolean matchingQuery; /** The reference template to match. */ private volatile Template template; /** * Constructor behaving as a simple extractor filter. * * @param next * The next Restlet. */ public TemplateRoute(Restlet next) { this(null, (Template) null, next); } /** * Constructor. The URIs will be matched agains the template using the * {@link Template#MODE_STARTS_WITH} matching mode. This can be changed by * getting the template and calling {@link Template#setMatchingMode(int)} * with {@link Template#MODE_EQUALS} for exact matching. * * @param router * The parent router. * @param uriTemplate * The URI template. * @param next * The next Restlet. */ public TemplateRoute(Router router, String uriTemplate, Restlet next) { this(router, new Template(uriTemplate, Template.MODE_STARTS_WITH, Variable.TYPE_URI_SEGMENT, "", true, false), next); } /** * Constructor. * * @param router * The parent router. * @param template * The URI template. * @param next * The next Restlet. */ public TemplateRoute(Router router, Template template, Restlet next) { super(router, next); this.matchingQuery = (router == null) ? true : router .getDefaultMatchingQuery(); this.template = template; } /** * Allows filtering before its handling by the target Restlet. By default it * parses the template variable, adjust the base reference of the target * resource's reference. * * @param request * The request to filter. * @param response * The response to filter. * @return The continuation status. */ @Override protected int beforeHandle(Request request, Response response) { // 1 - Parse the template variables and adjust the base reference if (getTemplate() != null) { String remainingPart = request.getResourceRef().getRemainingPart( false, isMatchingQuery()); int matchedLength = getTemplate().parse(remainingPart, request); if (matchedLength == 0) { if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { getLogger().finer("No characters were matched"); } } else if (matchedLength > 0) { if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { getLogger().finer( "" + matchedLength + " characters were matched"); } // Updates the context String matchedPart = remainingPart.substring(0, matchedLength); Reference baseRef = request.getResourceRef().getBaseRef(); if (baseRef == null) { baseRef = new Reference(matchedPart); } else { baseRef = new Reference(baseRef.toString(false, false) + matchedPart); } request.getResourceRef().setBaseRef(baseRef); if (request.isLoggable()) { if (getLogger().isLoggable(Level.FINE)) { remainingPart = request.getResourceRef() .getRemainingPart(false, isMatchingQuery()); if ((remainingPart != null) && (!"".equals(remainingPart))) { getLogger().fine( "New base URI: \"" + request.getResourceRef() .getBaseRef() + "\". New remaining part: \"" + remainingPart + "\""); } else { getLogger().fine( "New base URI: \"" + request.getResourceRef() .getBaseRef() + "\". No remaining part to match"); } } if (getLogger().isLoggable(Level.FINER)) { getLogger().finer( "Delegating the call to the target Restlet"); } } } else { if (request.isLoggable() && getLogger().isLoggable(Level.FINE)) { getLogger().fine( "Unable to match this pattern: " + getTemplate().getPattern()); } response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); } } return CONTINUE; } /** * Returns the matching mode to use on the template when parsing a formatted * reference. * * @return The matching mode to use. */ public int getMatchingMode() { return getTemplate().getMatchingMode(); } /** * Returns the reference template to match. * * @return The reference template to match. */ public Template getTemplate() { return this.template; } /** * Indicates whether the query part should be taken into account when * matching a reference with the template. * * @return True if the query part of the reference should be taken into * account, false otherwise. */ public boolean isMatchingQuery() { return this.matchingQuery; } /** * Returns the score for a given call (between 0 and 1.0). * * @param request * The request to score. * @param response * The response to score. * @return The score for a given call (between 0 and 1.0). */ public float score(Request request, Response response) { float result = 0F; if ((getRouter() != null) && (request.getResourceRef() != null) && (getTemplate() != null)) { final String remainingPart = request.getResourceRef() .getRemainingPart(false, isMatchingQuery()); if (remainingPart != null) { final int matchedLength = getTemplate().match(remainingPart); if (matchedLength != -1) { final float totalLength = remainingPart.length(); if (totalLength > 0.0F) { result = getRouter().getRequiredScore() + (1.0F - getRouter().getRequiredScore()) * (matchedLength / totalLength); } else { result = 1.0F; } } } if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { getLogger().finer( "Call score for the \"" + getTemplate().getPattern() + "\" URI pattern: " + result); } } return result; } /** * Sets the matching mode to use on the template when parsing a formatted * reference. * * @param matchingMode * The matching mode to use. */ public void setMatchingMode(int matchingMode) { getTemplate().setMatchingMode(matchingMode); } /** * Sets whether the matching should be done on the URI with or without query * string. * * @param matchingQuery * True if the matching should be done with the query string, * false otherwise. */ public void setMatchingQuery(boolean matchingQuery) { this.matchingQuery = matchingQuery; } /** * Sets the reference template to match. * * @param template * The reference template to match. */ public void setTemplate(Template template) { this.template = template; } @Override public String toString() { return "\"" + ((getTemplate() == null) ? super.toString() : getTemplate() .getPattern()) + "\" -> " + ((getNext() == null) ? "null" : getNext().toString()); } }