Java tutorial
/* * This file is a component of thundr, a software library from 3wks. * Read more: http://3wks.github.io/thundr/ * Copyright (C) 2014 3wks, <thundr@3wks.com.au> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.threewks.thundr.route; import static com.atomicleopard.expressive.Expressive.list; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import com.threewks.thundr.logger.Logger; import com.threewks.thundr.route.controller.Controller; public class Router { private Map<Route, RouteResult> actionsForRoutes = new HashMap<Route, RouteResult>(); private Map<HttpMethod, Map<String, Route>> routes = createRoutesMap(); private Map<String, Route> namedRoutes = new HashMap<String, Route>(); private Map<Class<? extends RouteResult>, RouteResolver<?>> actionResolvers = new LinkedHashMap<Class<? extends RouteResult>, RouteResolver<?>>(); private boolean debug = true; public Router get(String route, Class<?> controller, String controllerMethod) { return this.get(route, controller, controllerMethod, null); } public Router get(String route, Class<?> controller, String controllerMethod, String name) { return this.add(HttpMethod.GET, route, controller, controllerMethod, name); } public <T extends RouteResult> Router get(String route, T action, String name) { return this.add(HttpMethod.GET, route, action, name); } public <T extends RouteResult> Router get(String route, T action) { return this.get(route, action, null); } public Router put(String route, Class<?> controller, String controllerMethod) { return this.put(route, controller, controllerMethod, null); } public Router put(String route, Class<?> controller, String controllerMethod, String name) { return this.add(HttpMethod.PUT, route, controller, controllerMethod, name); } public <T extends RouteResult> Router put(String route, T action) { return this.put(route, action, null); } public <T extends RouteResult> Router put(String route, T action, String name) { return this.add(HttpMethod.PUT, route, action, name); } public Router post(String route, Class<?> controller, String controllerMethod) { return this.post(route, controller, controllerMethod, null); } public Router post(String route, Class<?> controller, String controllerMethod, String name) { return this.add(HttpMethod.POST, route, controller, controllerMethod, name); } public <T extends RouteResult> Router post(String route, T action) { return this.post(route, action, null); } public <T extends RouteResult> Router post(String route, T action, String name) { return this.add(HttpMethod.POST, route, action, name); } public Router delete(String route, Class<?> controller, String method) { return this.delete(route, controller, method, null); } public Router delete(String route, Class<?> controller, String controllerMethod, String name) { return this.add(HttpMethod.DELETE, route, controller, controllerMethod, name); } public <T extends RouteResult> Router delete(String route, T action) { return this.delete(route, action, null); } public <T extends RouteResult> Router delete(String route, T action, String name) { return this.add(HttpMethod.DELETE, route, action, name); } public Router add(HttpMethod method, String route, Class<?> controller, String controllerMethod, String name) { return this.add(method, route, new Controller(controller, controllerMethod), name); } /** * Removes any previously registered route of the given method which matches the given route exactly. * The route is matched as a string, so any path variables must be exact. * * @param method * @param route * @return */ public Router remove(HttpMethod method, String route) { remove(getRoute(method, route)); return this; } /** * Removes any previously registered route with the given name * * @param name * @return */ public Router remove(String name) { remove(namedRoutes.get(name)); return this; } /** * Returns true is a route has been registered for the given method and the given route exactly. * The route is matched as a string, so any path variables must be exact. * * @param method * @param route * @return */ public boolean has(HttpMethod method, String route) { return getRoute(method, route) != null; } public boolean has(String name) { return getNamedRoute(name) != null; } public <T extends RouteResult> Router add(HttpMethod httpMethod, String routePath, T action, String name) { Route route = new Route(httpMethod, routePath, name); String path = route.getRouteMatchRegex(); Map<String, Route> routesForMethod = this.routes.get(httpMethod); if (routesForMethod.containsKey(path)) { Route existingRoute = routesForMethod.get(path); throw new RouteException( "Unable to add the route '%s %s' - the route '%s %s' already exists which matches the same pattern. To override, you can remove the existing route first.", route.getMethod(), route.getRoute(), existingRoute.getMethod(), existingRoute.getRoute()); } if (StringUtils.isNotBlank(name)) { if (namedRoutes.containsKey(name)) { Route existingRoute = namedRoutes.get(name); throw new RouteException( "Unable to add the route '%s %s' with the name '%s' - the route '%s %s' has already been registered with this name. To override, you can remove the named route first.", route.getMethod(), route.getRoute(), name, existingRoute.getMethod(), existingRoute.getRoute()); } this.namedRoutes.put(name, route); } routesForMethod.put(path, route); this.actionsForRoutes.put(route, action); return this; } public Route getNamedRoute(String name) { return namedRoutes.get(name); } @SuppressWarnings("unchecked") public <T extends RouteResult> Object invoke(String routePath, HttpMethod httpMethod, HttpServletRequest req, HttpServletResponse resp) { Logger.debug("Requesting '%s'", routePath); Route route = findMatchingRoute(routePath, httpMethod); if (route != null) { T action = (T) actionsForRoutes.get(route); return resolveAction(routePath, httpMethod, req, resp, route, action); } String debugString = debug ? listRoutes() : ""; throw new RouteNotFoundException("No route matching the request %s %s\n%s", httpMethod, routePath, debugString); } public Route findMatchingRoute(String routePath, HttpMethod method) { Map<String, Route> routesForMethod = routes.get(method); for (Route route : routesForMethod.values()) { if (route.matches(routePath)) { return route; } } return null; } @SuppressWarnings("unchecked") private <T extends RouteResult> Object resolveAction(final String routePath, final HttpMethod method, final HttpServletRequest req, final HttpServletResponse resp, final Route route, final T action) { Map<String, String> pathVars = route.getPathVars(routePath); RouteResolver<T> actionResolver = (RouteResolver<T>) actionResolvers.get(action.getClass()); Object resolve = actionResolver.resolve(action, method, req, resp, pathVars); return resolve; } public boolean isEmpty() { return actionsForRoutes.isEmpty(); } private static final String routeDisplayFormat = "%s: %s\n"; public String listRoutes() { Set<String> allRoutes = new HashSet<String>(); for (Map<String, Route> routeEntries : routes.values()) { allRoutes.addAll(routeEntries.keySet()); } List<String> allRouteNames = list(allRoutes); Collections.sort(allRouteNames); StringBuilder sb = new StringBuilder(); for (String route : allRouteNames) { for (HttpMethod method : HttpMethod.all()) { Map<String, Route> routesForType = routes.get(method); if (routesForType.containsKey(route)) { Route actualRoute = routesForType.get(route); RouteResult action = this.actionsForRoutes.get(actualRoute); sb.append(String.format(routeDisplayFormat, actualRoute, action)); } } } return sb.toString(); } public <A extends RouteResult> void addResolver(Class<A> actionType, RouteResolver<A> actionResolver) { actionResolvers.put(actionType, actionResolver); Logger.debug("Added action resolver %s for actions of type %s", actionResolver.getClass().getSimpleName(), actionType); } @SuppressWarnings("unchecked") public <A extends RouteResult> RouteResolver<A> getResolver(Class<A> actionType) { return (RouteResolver<A>) actionResolvers.get(actionType); } private Map<HttpMethod, Map<String, Route>> createRoutesMap() { Map<HttpMethod, Map<String, Route>> routesMap = new HashMap<HttpMethod, Map<String, Route>>(); for (HttpMethod type : HttpMethod.all()) { routesMap.put(type, new LinkedHashMap<String, Route>()); } return routesMap; } private void remove(Route route) { if (route != null) { Map<String, Route> routesForMethod = this.routes.get(route.getMethod()); routesForMethod.remove(route.getRouteMatchRegex()); actionsForRoutes.remove(route); if (route.getName() != null) { namedRoutes.remove(route.getName()); } } } private Route getRoute(HttpMethod method, String route) { Map<String, Route> routesForMethod = this.routes.get(method); if (routesForMethod != null) { for (Route potentialRoute : routesForMethod.values()) { if (potentialRoute.getRoute().equals(route)) { return potentialRoute; } } } return null; } }