Package org.restlet.routing

Source Code of org.restlet.routing.Router

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 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 LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* 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://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/

package org.restlet.routing;

import java.util.logging.Level;

import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.Status;
import org.restlet.resource.Directory;
import org.restlet.resource.Finder;
import org.restlet.resource.ServerResource;
import org.restlet.util.RouteList;

/**
* Restlet routing calls to one of the attached routes. Each route can compute
* an affinity score for each call depending on various criteria. The attach()
* method allow the creation of routes based on URI patterns matching the
* beginning of a the resource reference's remaining part.<br>
* <br>
* In addition, several routing modes are supported, implementing various
* algorithms:
* <ul>
* <li>Best match</li>
* <li>First match (default)</li>
* <li>Last match</li>
* <li>Random match</li>
* <li>Round robin</li>
* <li>Custom</li>
* </ul>
* <br>
* Note that for routes using URI patterns will update the resource reference's
* base reference during the routing if they are selected. It is also important
* to know that the routing is very strict about path separators in your URI
* patterns. Finally, you can modify the list of routes while handling incoming
* calls as the delegation code is ensured to be thread-safe.<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 <a href="http://wiki.restlet.org/docs_2.1/376-restlet.html">User Guide -
*      Routers and hierarchical URIs</a>
* @author Jerome Louvel
*/
public class Router extends Restlet {

    /**
     * Each call will be routed to the route with the best score, if the
     * required score is reached. See
     * {@link RouteList#getBest(Request, Response, float)} method for
     * implementation details.
     */
    public static final int MODE_BEST_MATCH = 1;

    /**
     * Each call will be routed according to a custom mode. Override the
     * {@link #getCustom(Request, Response)} method to provide your own logic.
     */
    public static final int MODE_CUSTOM = 6;

    /**
     * Each call is routed to the first route if the required score is reached.
     * If the required score is not reached, then the route is skipped and the
     * next one is considered. See
     * {@link RouteList#getFirst(Request, Response, float)} method for
     * implementation details.
     */
    public static final int MODE_FIRST_MATCH = 2;

    /**
     * Each call will be routed to the last route if the required score is
     * reached. If the required score is not reached, then the route is skipped
     * and the previous one is considered. See
     * {@link RouteList#getLast(Request, Response, float)} method for
     * implementation details.
     */
    public static final int MODE_LAST_MATCH = 3;

    /**
     * Each call is be routed to the next route target if the required score is
     * reached. The next route is relative to the previous call routed (round
     * robin mode). If the required score is not reached, then the route is
     * skipped and the next one is considered. If the last route is reached, the
     * first route will be considered. See
     * {@link RouteList#getNext(Request, Response, float)} method for
     * implementation details.
     */
    public static final int MODE_NEXT_MATCH = 4;

    /**
     * Each call will be randomly routed to one of the routes that reached the
     * required score. If the random route selected is not a match then the
     * immediate next route is evaluated until one matching route is found. If
     * we get back to the initial random route selected with no match, then we
     * return null. Unless all the routes score above the required score, this
     * mode will result in non uniform distribution of calls. See
     * {@link RouteList#getRandom(Request, Response, float)} method for
     * implementation details.
     */
    public static final int MODE_RANDOM_MATCH = 5;

    /** The default matching mode to use when selecting routes based on URIs. */
    private volatile int defaultMatchingMode;

    /**
     * The default setting for whether the routing should be done on URIs with
     * or without taking into account query string.
     */
    private volatile boolean defaultMatchingQuery;

    /** The default route tested if no other one was available. */
    private volatile Route defaultRoute;

    /** Finder class to instantiate. */
    private volatile Class<? extends Finder> finderClass;

    /**
     * The maximum number of attempts if no attachment could be matched on the
     * first attempt.
     */
    private volatile int maxAttempts;

    /** The minimum score required to have a match. */
    private volatile float requiredScore;

    /** The delay (in milliseconds) before a new attempt. */
    private volatile long retryDelay;

    /** The modifiable list of routes. */
    private volatile RouteList routes;

    /** The routing mode. */
    private volatile int routingMode;

    /**
     * Constructor. Note that usage of this constructor is not recommended as
     * the Router won't have a proper context set. In general you will prefer to
     * use the other constructor and pass it the parent application's context or
     * eventually the parent component's context if you don't use applications.
     */
    public Router() {
        this(null);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     */
    public Router(Context context) {
        super(context);
        this.routes = new RouteList();
        this.defaultMatchingMode = Template.MODE_EQUALS;
        this.defaultMatchingQuery = false;
        this.defaultRoute = null;
        this.finderClass = Finder.class;
        this.routingMode = MODE_FIRST_MATCH;
        this.requiredScore = 0.5F;
        this.maxAttempts = 1;
        this.retryDelay = 500L;
    }

    /**
     * Attaches a target Restlet to this router with an empty URI pattern. A new
     * route using the matching mode returned by
     * {@link #getMatchingMode(Restlet)} will be added routing to the target
     * when any call is received.
     *
     * @param target
     *            The target Restlet to attach.
     * @return The created route.
     */
    public TemplateRoute attach(Restlet target) {
        return attach(target, getMatchingMode(target));
    }

    /**
     * Attaches a target Restlet to this router with an empty URI pattern. A new
     * route will be added routing to the target when any call is received.
     *
     * @param target
     *            The target Restlet to attach.
     * @param matchingMode
     *            The matching mode.
     * @return The created route.
     */
    public TemplateRoute attach(Restlet target, int matchingMode) {
        return attach("", target, matchingMode);
    }

    /**
     * Attaches a target Resource class to this router based on a given URI
     * pattern. A new route using the matching mode returned by
     * {@link #getMatchingMode(Restlet)} will be added routing to the target
     * when calls with a URI matching the pattern will be received.
     *
     * @param pathTemplate
     *            The URI path template that must match the relative part of the
     *            resource URI.
     * @param targetClass
     *            The target Resource class to attach.
     * @return The created route.
     */
    public TemplateRoute attach(String pathTemplate,
            Class<? extends ServerResource> targetClass) {
        return attach(pathTemplate, createFinder(targetClass));
    }

    /**
     * Attaches a target Resource class to this router based on a given URI
     * pattern. A new route will be added routing to the target when calls with
     * a URI matching the pattern will be received.
     *
     * @param pathTemplate
     *            The URI path template that must match the relative part of the
     *            resource URI.
     * @param targetClass
     *            The target Resource class to attach.
     * @param matchingMode
     *            The matching mode.
     * @return The created route.
     */
    public TemplateRoute attach(String pathTemplate,
            Class<? extends ServerResource> targetClass, int matchingMode) {
        return attach(pathTemplate, createFinder(targetClass), matchingMode);
    }

    /**
     * Attaches a target Restlet to this router based on a given URI pattern. A
     * new route using the matching mode returned by
     * {@link #getMatchingMode(Restlet)} will be added routing to the target
     * when calls with a URI matching the pattern will be received.
     *
     * @param pathTemplate
     *            The URI path template that must match the relative part of the
     *            resource URI.
     * @param target
     *            The target Restlet to attach.
     * @return The created route.
     */
    public TemplateRoute attach(String pathTemplate, Restlet target) {
        return attach(pathTemplate, target, getMatchingMode(target));
    }

    /**
     * Attaches a target Restlet to this router based on a given URI pattern. A
     * new route will be added routing to the target when calls with a URI
     * matching the pattern will be received.
     *
     * @param pathTemplate
     *            The URI path template that must match the relative part of the
     *            resource URI.
     * @param target
     *            The target Restlet to attach.
     * @param matchingMode
     *            The matching mode.
     * @return The created route.
     */
    public TemplateRoute attach(String pathTemplate, Restlet target,
            int matchingMode) {
        TemplateRoute result = createRoute(pathTemplate, target, matchingMode);
        getRoutes().add(result);
        return result;
    }

    /**
     * Attaches a Resource class to this router as the default target to invoke
     * when no route matches. It actually sets a default route that scores all
     * calls to 1.0.
     *
     * @param defaultTargetClass
     *            The target Resource class to attach.
     * @return The created route.
     */
    public TemplateRoute attachDefault(
            Class<? extends ServerResource> defaultTargetClass) {
        return attachDefault(createFinder(defaultTargetClass));
    }

    /**
     * Attaches a Restlet to this router as the default target to invoke when no
     * route matches. It actually sets a default route that scores all calls to
     * 1.0.
     *
     * @param defaultTarget
     *            The Restlet to use as the default target.
     * @return The created route.
     */
    public TemplateRoute attachDefault(Restlet defaultTarget) {
        TemplateRoute result = createRoute("", defaultTarget);
        result.setMatchingMode(Template.MODE_STARTS_WITH);
        setDefaultRoute(result);
        return result;
    }

    /**
     * Creates a new finder instance based on the "targetClass" property.
     *
     * @param targetClass
     *            The target Resource class to attach.
     * @return The new finder instance.
     */
    public Finder createFinder(Class<? extends ServerResource> targetClass) {
        return Finder.createFinder(targetClass, getFinderClass(), getContext(),
                getLogger());
    }

    /**
     * Creates a new route for the given URI pattern and target. The route will
     * match the URI query string depending on the result of
     * {@link #getDefaultMatchingQuery()} and the matching mode will be given by
     * {@link #getMatchingMode(Restlet)}.
     *
     * @param uriPattern
     *            The URI pattern that must match the relative part of the
     *            resource URI.
     * @param target
     *            The target Restlet to attach.
     * @return The created route.
     */
    protected TemplateRoute createRoute(String uriPattern, Restlet target) {
        return createRoute(uriPattern, target, getMatchingMode(target));
    }

    /**
     * Creates a new route for the given URI pattern, target and matching mode.
     * The route will match the URI query string depending on the result of
     * {@link #getDefaultMatchingQuery()}.
     *
     * @param uriPattern
     *            The URI pattern that must match the relative part of the
     *            resource URI.
     * @param target
     *            The target Restlet to attach.
     * @param matchingMode
     *            The matching mode.
     * @return The created route.
     */
    protected TemplateRoute createRoute(String uriPattern, Restlet target,
            int matchingMode) {
        TemplateRoute result = new TemplateRoute(this, uriPattern, target);
        result.getTemplate().setMatchingMode(matchingMode);
        result.setMatchingQuery(getDefaultMatchingQuery());
        return result;
    }

    /**
     * Detaches the target from this router. All routes routing to this target
     * Restlet are removed from the list of routes and the default route is set
     * to null.
     *
     * @param targetClass
     *            The target class to detach.
     */
    public void detach(Class<?> targetClass) {
        for (int i = getRoutes().size() - 1; i >= 0; i--) {
            Restlet target = getRoutes().get(i).getNext();
            if (target != null
                    && Finder.class.isAssignableFrom(target.getClass())) {
                Finder finder = (Finder) target;
                if (finder.getTargetClass().equals(targetClass)) {
                    getRoutes().remove(i);
                }
            }
        }

        if (getDefaultRoute() != null) {
            Restlet target = getDefaultRoute().getNext();
            if (target != null
                    && Finder.class.isAssignableFrom(target.getClass())) {
                Finder finder = (Finder) target;
                if (finder.getTargetClass().equals(targetClass)) {
                    setDefaultRoute(null);
                }
            }
        }
    }

    /**
     * Detaches the target from this router. All routes routing to this target
     * Restlet are removed from the list of routes and the default route is set
     * to null.
     *
     * @param target
     *            The target Restlet to detach.
     */
    public void detach(Restlet target) {
        getRoutes().removeAll(target);
        if ((getDefaultRoute() != null)
                && (getDefaultRoute().getNext() == target)) {
            setDefaultRoute(null);
        }
    }

    /**
     * Effectively handles the call using the selected next {@link Restlet},
     * typically the selected {@link Route}. By default, it just invokes the
     * next Restlet.
     *
     * @param next
     *            The next Restlet to invoke.
     * @param request
     *            The request.
     * @param response
     *            The response.
     */
    protected void doHandle(Restlet next, Request request, Response response) {
        next.handle(request, response);
    }

    /**
     * Returns the matched route according to a custom algorithm. To use in
     * combination of the {@link #MODE_CUSTOM} option. The default
     * implementation (to be overridden), returns null.
     *
     * @param request
     *            The request to handle.
     * @param response
     *            The response to update.
     * @return The matched route if available or null.
     */
    protected Route getCustom(Request request, Response response) {
        return null;
    }

    /**
     * Returns the default matching mode to use when selecting routes based on
     * URIs. By default it returns {@link Template#MODE_EQUALS}.
     *
     * @return The default matching mode.
     */
    public int getDefaultMatchingMode() {
        return this.defaultMatchingMode;
    }

    /**
     * Returns the default setting for whether the routing should be done on
     * URIs with or without taking into account query string. By default, it
     * returns false.
     *
     * @return the default setting for whether the routing should be done on
     *         URIs with or without taking into account query string.
     */
    public boolean getDefaultMatchingQuery() {
        return this.defaultMatchingQuery;
    }

    /**
     * Returns the default route to test if no other one was available after
     * retrying the maximum number of attempts.
     *
     * @return The default route tested if no other one was available.
     */
    public Route getDefaultRoute() {
        return this.defaultRoute;
    }

    /**
     * Returns the finder class to instantiate.
     *
     * @return the finder class to instantiate.
     */
    public Class<? extends Finder> getFinderClass() {
        return this.finderClass;
    }

    /**
     * Returns the matching mode for the target Restlet. By default it returns
     * {@link #getDefaultMatchingMode()}. If the target is an instance of
     * {@link Directory} or {@link Router} then the mode returned is
     * {@link Template#MODE_STARTS_WITH} to allow further routing by those
     * objects. If the target is an instance of {@link Filter}, then it returns
     * the matching mode for the {@link Filter#getNext()} Restlet recursively.
     *
     * @param target
     *            The target Restlet.
     * @return The preferred matching mode.
     */
    protected int getMatchingMode(Restlet target) {
        int result = getDefaultMatchingMode();

        if ((target instanceof Directory) || (target instanceof Router)) {
            result = Template.MODE_STARTS_WITH;
        } else if (target instanceof Filter) {
            result = getMatchingMode(((Filter) target).getNext());
        }

        return result;
    }

    /**
     * Returns the maximum number of attempts if no attachment could be matched
     * on the first attempt. This is useful when the attachment scoring is
     * dynamic and therefore could change on a retry. The default value is set
     * to 1.
     *
     * @return The maximum number of attempts if no attachment could be matched
     *         on the first attempt.
     */
    public int getMaxAttempts() {
        return this.maxAttempts;
    }

    /**
     * Returns the next Restlet if available.
     *
     * @param request
     *            The request to handle.
     * @param response
     *            The response to update.
     * @return The next Restlet if available or null.
     */
    public Restlet getNext(Request request, Response response) {
        Route result = null;

        for (int i = 0; (result == null) && (i < getMaxAttempts()); i++) {
            if (i > 0) {
                // Before attempting another time, let's
                // sleep during the "retryDelay" set.
                try {
                    Thread.sleep(getRetryDelay());
                } catch (InterruptedException e) {
                }
            }

            if (this.routes != null) {
                // Select the routing mode
                switch (getRoutingMode()) {
                case MODE_BEST_MATCH:
                    result = getRoutes().getBest(request, response,
                            getRequiredScore());
                    break;

                case MODE_FIRST_MATCH:
                    result = getRoutes().getFirst(request, response,
                            getRequiredScore());
                    break;

                case MODE_LAST_MATCH:
                    result = getRoutes().getLast(request, response,
                            getRequiredScore());
                    break;

                case MODE_NEXT_MATCH:
                    result = getRoutes().getNext(request, response,
                            getRequiredScore());
                    break;

                case MODE_RANDOM_MATCH:
                    result = getRoutes().getRandom(request, response,
                            getRequiredScore());
                    break;

                case MODE_CUSTOM:
                    result = getCustom(request, response);
                    break;
                }
            }
        }

        if (result == null) {
            // If nothing matched in the routes list,
            // check the default route
            if ((getDefaultRoute() != null)
                    && (getDefaultRoute().score(request, response) >= getRequiredScore())) {
                result = getDefaultRoute();
            } else {
                // No route could be found
                response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
            }
        }

        if (request.isLoggable()) {
            logRoute(result);
        }

        return result;
    }

    /**
     * Returns the minimum score required to have a match. By default, it
     * returns {@code 0.5}.
     *
     * @return The minimum score required to have a match.
     */
    public float getRequiredScore() {
        return this.requiredScore;
    }

    /**
     * Returns the delay in milliseconds before a new attempt is made. The
     * default value is {@code 500}.
     *
     * @return The delay in milliseconds before a new attempt is made.
     */
    public long getRetryDelay() {
        return this.retryDelay;
    }

    /**
     * Returns the modifiable list of routes. Creates a new instance if no one
     * has been set.
     *
     * @return The modifiable list of routes.
     */
    public RouteList getRoutes() {
        return this.routes;
    }

    /**
     * Returns the routing mode. By default, it returns the
     * {@link #MODE_FIRST_MATCH} mode.
     *
     * @return The routing mode.
     */
    public int getRoutingMode() {
        return this.routingMode;
    }

    /**
     * Handles a call by invoking the next Restlet if it is available.
     *
     * @param request
     *            The request to handle.
     * @param response
     *            The response to update.
     */
    @Override
    public void handle(Request request, Response response) {
        super.handle(request, response);
        Restlet next = getNext(request, response);

        if (next != null) {
            doHandle(next, request, response);
        } else {
            response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
        }
    }

    /**
     * Logs the route selected.
     *
     * @param route
     *            The route selected.
     */
    protected void logRoute(Route route) {
        if (getLogger().isLoggable(Level.FINE)) {
            if (getDefaultRoute() == route) {
                getLogger().fine("The default route was selected");
            } else {
                getLogger().fine("Selected route: " + route);
            }
        }
    }

    /**
     * Sets the default matching mode to use when selecting routes based on
     * URIs. By default it is set to {@link Template#MODE_EQUALS}.
     *
     * @param defaultMatchingMode
     *            The default matching mode.
     */
    public void setDefaultMatchingMode(int defaultMatchingMode) {
        this.defaultMatchingMode = defaultMatchingMode;
    }

    /**
     * Sets the default setting for whether the routing should be done on URIs
     * with or without taking into account query string. By default, it is set
     * to false.
     *
     * @param defaultMatchingQuery
     *            The default setting for whether the routing should be done on
     *            URIs with or without taking into account query string.
     *
     */
    public void setDefaultMatchingQuery(boolean defaultMatchingQuery) {
        this.defaultMatchingQuery = defaultMatchingQuery;
    }

    /**
     * Sets the default route tested if no other one was available.
     *
     * @param defaultRoute
     *            The default route tested if no other one was available.
     */
    public void setDefaultRoute(Route defaultRoute) {
        this.defaultRoute = defaultRoute;
    }

    /**
     * Sets the finder class to instantiate.
     *
     * @param finderClass
     *            The finder class to instantiate.
     */
    public void setFinderClass(Class<? extends Finder> finderClass) {
        this.finderClass = finderClass;
    }

    /**
     * Sets the maximum number of attempts if no attachment could be matched on
     * the first attempt. This is useful when the attachment scoring is dynamic
     * and therefore could change on a retry.
     *
     * @param maxAttempts
     *            The maximum number of attempts.
     */
    public void setMaxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    /**
     * Sets the score required to have a match. By default, it is set to
     * {@code 0.5}.
     *
     * @param score
     *            The score required to have a match.
     */
    public void setRequiredScore(float score) {
        this.requiredScore = score;
    }

    /**
     * Sets the delay in milliseconds before a new attempt is made. By default,
     * it is set to {@code 500}.
     *
     * @param retryDelay
     *            The delay in milliseconds before a new attempt is made.
     */
    public void setRetryDelay(long retryDelay) {
        this.retryDelay = retryDelay;
    }

    /**
     * Sets the modifiable list of routes.
     *
     * @param routes
     *            The modifiable list of routes.
     */
    public void setRoutes(RouteList routes) {
        this.routes = routes;
    }

    /**
     * Sets the routing mode. By default, it is set to the
     * {@link #MODE_FIRST_MATCH} mode.
     *
     * @param routingMode
     *            The routing mode.
     */
    public void setRoutingMode(int routingMode) {
        this.routingMode = routingMode;
    }

    /**
     * Starts the filter and the attached routes.
     */
    @Override
    public synchronized void start() throws Exception {
        if (isStopped()) {
            super.start();

            for (Route route : getRoutes()) {
                route.start();
            }

            if (getDefaultRoute() != null) {
                getDefaultRoute().start();
            }
        }
    }

    /**
     * Stops the filter and the attached routes.
     */
    @Override
    public synchronized void stop() throws Exception {
        if (isStarted()) {
            if (getDefaultRoute() != null) {
                getDefaultRoute().stop();
            }

            for (Route route : getRoutes()) {
                route.stop();
            }

            super.stop();
        }
    }

}
TOP

Related Classes of org.restlet.routing.Router

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.