001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2015 ForgeRock AS. 015 */ 016 017package org.forgerock.http.routing; 018 019import static org.forgerock.http.HttpApplication.LOGGER; 020import static org.forgerock.http.routing.RouteMatchers.getRemainingRequestUri; 021import static org.forgerock.util.promise.Promises.newResultPromise; 022 023import org.forgerock.services.context.Context; 024import org.forgerock.http.Handler; 025import org.forgerock.http.protocol.Request; 026import org.forgerock.http.protocol.Response; 027import org.forgerock.http.protocol.ResponseException; 028import org.forgerock.http.protocol.Status; 029import org.forgerock.services.routing.AbstractRouter; 030import org.forgerock.services.routing.IncomparableRouteMatchException; 031import org.forgerock.services.routing.RouteMatch; 032import org.forgerock.services.routing.RouteMatcher; 033import org.forgerock.util.Pair; 034import org.forgerock.util.promise.NeverThrowsException; 035import org.forgerock.util.promise.Promise; 036 037/** 038 * A router which routes requests based on route matchers. Each route is 039 * comprised of a {@link RouteMatcher route matcher} and a corresponding 040 * handler, when routing a request the router will call 041 * {@link RouteMatcher#evaluate(Context, Object)} for each 042 * registered route and use the returned {@link RouteMatch} to determine 043 * which route best matches the request. 044 * 045 * <p>Routes may be added and removed from a router as follows:</p> 046 * 047 * <pre> 048 * Handler users = ...; 049 * Router router = new Router(); 050 * RouteMatcher routeOne = RouteMatchers.requestUriMatcher(EQUALS, "users"); 051 * RouteMatcher routeTwo = RouteMatcher.requestUriMatcher(EQUALS, "users/{userId}"); 052 * router.addRoute(routeOne, users); 053 * router.addRoute(routeTwo, users); 054 * 055 * // Deregister a route. 056 * router.removeRoute(routeOne, routeTwo); 057 * </pre> 058 * 059 * @see AbstractRouter 060 * @see UriRouteMatcher 061 * @see RouteMatchers 062 */ 063public final class Router extends AbstractRouter<Router, Request, Handler> implements Handler { 064 065 /** 066 * Creates a new router with no routes defined. 067 */ 068 public Router() { 069 } 070 071 /** 072 * Creates a new router containing the same routes and default route as the 073 * provided router. Changes to the returned router's routing table will not 074 * impact the provided router. 075 * 076 * @param router The router to be copied. 077 */ 078 public Router(Router router) { 079 super(router); 080 } 081 082 @Override 083 protected Router getThis() { 084 return this; 085 } 086 087 @Override 088 public Promise<Response, NeverThrowsException> handle(Context context, Request request) { 089 try { 090 Pair<Context, Handler> bestMatch = getBestRoute(context, request); 091 if (bestMatch != null) { 092 return bestMatch.getSecond().handle(bestMatch.getFirst(), request); 093 } else { 094 return newResultPromise(new Response(Status.NOT_FOUND)); 095 } 096 } catch (IncomparableRouteMatchException e) { 097 LOGGER.trace(String.format("Route for '%s' not found", 098 getRemainingRequestUri(context, request).toString())); 099 return newResultPromise(new ResponseException(e.getMessage()).getResponse()); 100 } 101 } 102}