org.spincast.plugins.routing.SpincastRouter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spincast-plugins-routing Show documentation
Show all versions of spincast-plugins-routing Show documentation
The default Spincast Routing plugin
The newest version!
package org.spincast.plugins.routing;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spincast.core.config.ISpincastConfig;
import org.spincast.core.config.ISpincastDictionary;
import org.spincast.core.exchange.IRequestContext;
import org.spincast.core.filters.ISpincastFilters;
import org.spincast.core.routing.HttpMethod;
import org.spincast.core.routing.IHandler;
import org.spincast.core.routing.IRoute;
import org.spincast.core.routing.IRouteBuilder;
import org.spincast.core.routing.IRouteBuilderFactory;
import org.spincast.core.routing.IRouteHandlerMatch;
import org.spincast.core.routing.IRouter;
import org.spincast.core.routing.IRoutingResult;
import org.spincast.core.routing.IStaticResource;
import org.spincast.core.routing.IStaticResourceBuilder;
import org.spincast.core.routing.IStaticResourceBuilderFactory;
import org.spincast.core.routing.RoutingType;
import org.spincast.core.server.IServer;
import org.spincast.core.utils.SpincastStatics;
import org.spincast.core.websocket.IWebsocketContext;
import org.spincast.core.websocket.IWebsocketRoute;
import org.spincast.core.websocket.IWebsocketRouteBuilder;
import org.spincast.core.websocket.IWebsocketRouteBuilderFactory;
import org.spincast.core.websocket.IWebsocketRouteHandlerFactory;
import org.spincast.shaded.org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.net.HttpHeaders;
import com.google.inject.Inject;
/**
* Spincast router
*/
public class SpincastRouter, W extends IWebsocketContext>> implements IRouter {
protected final Logger logger = LoggerFactory.getLogger(SpincastRouter.class);
private final IRouteHandlerMatchFactory routeHandlerMatchFactory;
private final IRouteBuilderFactory routeBuilderFactory;
private final IStaticResourceBuilderFactory staticResourceBuilderFactory;
private final IStaticResourceFactory staticResourceFactory;
private final ISpincastRouterConfig spincastRouterConfig;
private final IRouteFactory routeFactory;
private final ISpincastConfig spincastConfig;
private final ISpincastDictionary spincastDictionary;
private final ISpincastFilters spincastFilters;
private final IWebsocketRouteBuilderFactory websocketRouteBuilderFactory;
private final IWebsocketRouteHandlerFactory websocketRouteHandlerFactory;
private TreeMap>> globalBeforeFiltersPerPosition;
private TreeMap>> globalAfterFiltersPerPosition;
private List> globalBeforeFilters;
private List> globalAfterFilters;
private List> mainRoutes;
private final IServer server;
private final Map routeParamPatternAliases = new HashMap();
private final Map patternCache = new HashMap();
@Inject
public SpincastRouter(ISpincastRouterConfig spincastRouterConfig,
IRouteFactory routeFactory,
ISpincastConfig spincastConfig,
ISpincastDictionary spincastDictionary,
IServer server,
ISpincastFilters spincastFilters,
IRouteBuilderFactory routeBuilderFactory,
IStaticResourceBuilderFactory staticResourceBuilderFactory,
IRouteHandlerMatchFactory routeHandlerMatchFactory,
IStaticResourceFactory staticResourceFactory,
IWebsocketRouteBuilderFactory websocketRouteBuilderFactory,
IWebsocketRouteHandlerFactory websocketRouteHandlerFactory) {
this.spincastRouterConfig = spincastRouterConfig;
this.routeFactory = routeFactory;
this.spincastConfig = spincastConfig;
this.spincastDictionary = spincastDictionary;
this.server = server;
this.spincastFilters = spincastFilters;
this.routeBuilderFactory = routeBuilderFactory;
this.staticResourceBuilderFactory = staticResourceBuilderFactory;
this.routeHandlerMatchFactory = routeHandlerMatchFactory;
this.staticResourceFactory = staticResourceFactory;
this.websocketRouteBuilderFactory = websocketRouteBuilderFactory;
this.websocketRouteHandlerFactory = websocketRouteHandlerFactory;
}
@Inject
protected void init() {
validation();
}
protected void validation() {
int corsFilterPosition = getSpincastRouterConfig().getCorsFilterPosition();
if(corsFilterPosition >= 0) {
throw new RuntimeException("The position of the Cors filter must be less than 0. " +
"Currently : " + corsFilterPosition);
}
}
protected ISpincastRouterConfig getSpincastRouterConfig() {
return this.spincastRouterConfig;
}
protected IRouteFactory getRouteFactory() {
return this.routeFactory;
}
protected ISpincastConfig getSpincastConfig() {
return this.spincastConfig;
}
protected ISpincastDictionary getSpincastDictionary() {
return this.spincastDictionary;
}
protected IServer getServer() {
return this.server;
}
protected ISpincastFilters getSpincastFilters() {
return this.spincastFilters;
}
protected IRouteBuilderFactory getRouteBuilderFactory() {
return this.routeBuilderFactory;
}
protected IWebsocketRouteBuilderFactory getWebsocketRouteBuilderFactory() {
return this.websocketRouteBuilderFactory;
}
protected IWebsocketRouteHandlerFactory getWebsocketRouteHandlerFactory() {
return this.websocketRouteHandlerFactory;
}
protected IStaticResourceBuilderFactory getStaticResourceBuilderFactory() {
return this.staticResourceBuilderFactory;
}
protected IRouteHandlerMatchFactory getRouteHandlerMatchFactory() {
return this.routeHandlerMatchFactory;
}
protected IStaticResourceFactory getStaticResourceFactory() {
return this.staticResourceFactory;
}
protected Pattern getPattern(String patternStr) {
Pattern pattern = this.patternCache.get(patternStr);
if(pattern == null) {
pattern = Pattern.compile(patternStr);
this.patternCache.put(patternStr, pattern);
}
return pattern;
}
@Override
public Map getRouteParamPatternAliases() {
return this.routeParamPatternAliases;
}
@Override
public IRoute getRoute(String routeId) {
for(IRoute route : getGlobalBeforeFiltersRoutes()) {
if((routeId == null && route.getId() == null) || (routeId != null && routeId.equals(route.getId()))) {
return route;
}
}
for(IRoute route : getMainRoutes()) {
if((routeId == null && route.getId() == null) || (routeId != null && routeId.equals(route.getId()))) {
return route;
}
}
for(IRoute route : getGlobalAfterFiltersRoutes()) {
if((routeId == null && route.getId() == null) || (routeId != null && routeId.equals(route.getId()))) {
return route;
}
}
return null;
}
protected Map>> getGlobalBeforeFiltersPerPosition() {
if(this.globalBeforeFiltersPerPosition == null) {
this.globalBeforeFiltersPerPosition = new TreeMap>>();
}
return this.globalBeforeFiltersPerPosition;
}
@Override
public List> getGlobalBeforeFiltersRoutes() {
if(this.globalBeforeFilters == null) {
this.globalBeforeFilters = new ArrayList>();
Collection>> routesLists = getGlobalBeforeFiltersPerPosition().values();
if(routesLists != null) {
for(List> routeList : routesLists) {
this.globalBeforeFilters.addAll(routeList);
}
}
}
return this.globalBeforeFilters;
}
protected Map>> getGlobalAfterFiltersPerPosition() {
if(this.globalAfterFiltersPerPosition == null) {
this.globalAfterFiltersPerPosition = new TreeMap>>();
}
return this.globalAfterFiltersPerPosition;
}
@Override
public List> getGlobalAfterFiltersRoutes() {
if(this.globalAfterFilters == null) {
this.globalAfterFilters = new ArrayList>();
Collection>> routesLists = getGlobalAfterFiltersPerPosition().values();
if(routesLists != null) {
for(List> routeList : routesLists) {
this.globalAfterFilters.addAll(routeList);
}
}
}
return this.globalAfterFilters;
}
@Override
public List> getMainRoutes() {
if(this.mainRoutes == null) {
this.mainRoutes = new ArrayList<>();
}
return this.mainRoutes;
}
@Override
public void addRoute(IRoute route) {
if(route == null ||
route.getMainHandler() == null ||
route.getHttpMethods() == null) {
return;
}
validateId(route.getId());
validatePath(route.getPath());
List positions = route.getPositions();
for(int position : positions) {
if(position < 0) {
this.globalBeforeFilters = null; // reset cache
List> routes = getGlobalBeforeFiltersPerPosition().get(position);
if(routes == null) {
routes = new ArrayList>();
getGlobalBeforeFiltersPerPosition().put(position, routes);
}
routes.add(route);
} else if(position == 0) {
// Keep main routes in order they are added.
getMainRoutes().add(route);
} else {
this.globalAfterFilters = null; // reset cache
List> routes = getGlobalAfterFiltersPerPosition().get(position);
if(routes == null) {
routes = new ArrayList>();
getGlobalAfterFiltersPerPosition().put(position, routes);
}
routes.add(route);
}
}
}
protected void validateId(String id) {
if(id == null) {
return; //ok
}
IRoute sameIdRoute = null;
for(IRoute route : getGlobalBeforeFiltersRoutes()) {
if(id.equals(route.getId())) {
sameIdRoute = route;
break;
}
}
if(sameIdRoute == null) {
for(IRoute route : getGlobalAfterFiltersRoutes()) {
if(id.equals(route.getId())) {
sameIdRoute = route;
break;
}
}
}
if(sameIdRoute == null) {
for(IRoute route : getMainRoutes()) {
if(id.equals(route.getId())) {
sameIdRoute = route;
break;
}
}
}
if(sameIdRoute != null) {
throw new RuntimeException("A route already use the id '" + id + "' : " + sameIdRoute + ". Ids " +
"must be uniques!");
}
}
/**
* Validate the path of a route.
* Throws an exception if not valide.
*/
protected void validatePath(String path) {
if(path == null) {
return;
}
Set paramNames = new HashSet();
String[] pathTokens = path.split("/");
boolean splatFound = false;
for(String pathToken : pathTokens) {
if(StringUtils.isBlank(pathToken)) {
continue;
}
if(pathToken.startsWith("${") || pathToken.startsWith("*{")) {
if(!pathToken.endsWith("}")) {
throw new RuntimeException("A parameter in the path of a route must end with '}'. Incorrect parameter : " +
pathToken);
}
if(pathToken.startsWith("*{")) {
if(splatFound) {
throw new RuntimeException("The path of a route can only contain one " +
"splat parameter (the one starting with a '*{'). The path is : " +
path);
}
if(pathToken.contains(":")) {
throw new RuntimeException("A splat parameter can't contain a pattern (so no ':' allowed) : " +
pathToken);
}
splatFound = true;
} else {
String token = pathToken.substring(2, pathToken.length() - 1);
int posColon = token.indexOf(":");
if(posColon > -1) {
token = token.substring(posColon + 1);
//==========================================
// Pattern aliases
//==========================================
if(token.startsWith("<")) {
if(!token.endsWith(">")) {
throw new RuntimeException("A parameter with an pattern alias must have a closing '>' : " +
pathToken);
}
token = token.substring(1, token.length() - 1);
String pattern = getPatternFromAlias(token);
if(pattern == null) {
throw new RuntimeException("Pattern not found using alias : " + token);
}
}
}
}
String paramName = pathToken.substring(2, pathToken.length() - 1);
if(!StringUtils.isBlank(paramName) && paramNames.contains(paramName)) {
throw new RuntimeException("Two parameters with the same name, '" + paramName + "', in route with path : " +
path);
}
paramNames.add(paramName);
}
}
}
@Override
public void removeAllRoutes() {
getGlobalBeforeFiltersPerPosition().clear();
this.globalBeforeFilters = null; // reset cache
getMainRoutes().clear();
getGlobalAfterFiltersPerPosition().clear();
this.globalAfterFilters = null; // reset cache
}
@Override
public void removeRoute(String routeId) {
if(routeId == null) {
return;
}
Collection>> routeLists = getGlobalBeforeFiltersPerPosition().values();
for(List> routes : routeLists) {
for(int i = routes.size() - 1; i >= 0; i--) {
IRoute route = routes.get(i);
if(route != null && routeId.equals(route.getId())) {
routes.remove(i);
}
}
}
this.globalBeforeFilters = null; // reset cache
routeLists = getGlobalAfterFiltersPerPosition().values();
for(List> routes : routeLists) {
for(int i = routes.size() - 1; i >= 0; i--) {
IRoute route = routes.get(i);
if(route != null && routeId.equals(route.getId())) {
routes.remove(i);
}
}
}
this.globalAfterFilters = null; // reset cache
List> routes = getMainRoutes();
for(int i = routes.size() - 1; i >= 0; i--) {
IRoute route = routes.get(i);
if(route != null && routeId.equals(route.getId())) {
routes.remove(i);
}
}
}
@Override
public IRoutingResult route(R requestContext) {
return route(requestContext, requestContext.request().getFullUrl(), RoutingType.FOUND);
}
@Override
public IRoutingResult route(R requestContext,
RoutingType routingType) {
return route(requestContext, requestContext.request().getFullUrl(), routingType);
}
public IRoutingResult route(R requestContext,
String fullUrl,
RoutingType routingType) {
try {
URL url = new URL(fullUrl);
HttpMethod httpMethod = requestContext.request().getHttpMethod();
List acceptedContentTypes = requestContext.request().getHeader(HttpHeaders.ACCEPT);
if(acceptedContentTypes == null) {
acceptedContentTypes = new ArrayList();
}
List> routeHandlerMatches = new ArrayList>();
//==========================================
// First check if there is a main handler for this request.
// We only keep the first match here!
//==========================================
List> mainRouteHandlerMatches = null;
for(IRoute route : getMainRoutes()) {
List> routeHandlerMatch = createRegularHandlerMatches(routingType,
route,
httpMethod,
acceptedContentTypes,
url,
0);
if(routeHandlerMatch != null && routeHandlerMatch.size() > 0) {
mainRouteHandlerMatches = routeHandlerMatch;
break;
}
}
//==========================================
// No main matches? Then no "before" or "after"
// filters either!
//==========================================
if(mainRouteHandlerMatches != null) {
//==========================================
// First, the global "before" filters.
//==========================================
for(IRoute route : getGlobalBeforeFiltersRoutes()) {
if(!isRoutingTypeMatch(routingType, route)) {
continue;
}
List> beforeRouteHandlerMatches = createRegularHandlerMatches(routingType,
route,
httpMethod,
acceptedContentTypes,
url,
-1);
if(beforeRouteHandlerMatches != null) {
routeHandlerMatches.addAll(beforeRouteHandlerMatches);
}
}
//==========================================
// The main handler match.
//==========================================
routeHandlerMatches.addAll(mainRouteHandlerMatches);
//==========================================
// Finally, the global "after" filters.
//==========================================
for(IRoute route : getGlobalAfterFiltersRoutes()) {
if(!isRoutingTypeMatch(routingType, route)) {
continue;
}
List> afterRouteHandlerMatches =
createRegularHandlerMatches(routingType,
route,
httpMethod,
acceptedContentTypes,
url,
1);
if(afterRouteHandlerMatches != null) {
routeHandlerMatches.addAll(afterRouteHandlerMatches);
}
}
}
if(routeHandlerMatches.size() == 0) {
return null;
}
IRoutingResult routingResult = createRoutingResult(routeHandlerMatches);
return routingResult;
} catch(Exception ex) {
throw SpincastStatics.runtimize(ex);
}
}
protected boolean isRoutingTypeMatch(RoutingType routingType, IRoute route) {
Objects.requireNonNull(routingType, "routingType can't be NULL");
Objects.requireNonNull(route, "route can't be NULL");
return route.getRoutingTypes() != null && route.getRoutingTypes().contains(routingType);
}
protected IRoutingResult createRoutingResult(List> routeHandlerMatches) {
IRoutingResult routingResult = new RoutingResult(routeHandlerMatches);
return routingResult;
}
/**
* Get the matches (filters and main handle) if the route matches the URL and
* HTTP method, or returns NULL otherwise.
*/
protected List> createRegularHandlerMatches(RoutingType routingType,
IRoute route,
HttpMethod httpMethod,
List acceptedContentTypes,
URL url,
int position) {
if(!isRoutingTypeMatch(routingType, route)) {
return null;
}
//==========================================
// Validate the HTTP method.
//==========================================
if(!isRouteMatchHttpMethod(route, httpMethod)) {
return null;
}
//==========================================
// Validate the Accept content-types.
//==========================================
if(!isRouteMatchAcceptedContentType(route, acceptedContentTypes)) {
return null;
}
//==========================================
// Validate the route path.
//==========================================
String routePath = route.getPath();
Map matchingParams = validatePath(routePath, url);
if(matchingParams == null) {
return null;
}
//==========================================
// Match!
//==========================================
List> matches = new ArrayList>();
IRouteHandlerMatch routeHandlerMatch = getRouteHandlerMatchFactory().create(route,
route.getMainHandler(),
matchingParams,
position);
matches.add(routeHandlerMatch);
//==========================================
// If the main handler has inline "before" filters,
// we add them with the same configurations as it.
//==========================================
List> beforeFilters = route.getBeforeFilters();
if(beforeFilters != null) {
for(IHandler beforeFilter : beforeFilters) {
if(beforeFilter != null) {
IRouteHandlerMatch beforeMethodRouteHandlerMatch =
createHandlerMatchForBeforeOrAfterFilter(routeHandlerMatch,
beforeFilter,
-1);
matches.add(0, beforeMethodRouteHandlerMatch);
}
}
}
//==========================================
// If the main handler has inline "after" filters,
// we add them with the same configurations as it.
//==========================================
List> afterFilters = route.getAfterFilters();
if(afterFilters != null) {
for(IHandler afterFilter : afterFilters) {
if(afterFilter != null) {
IRouteHandlerMatch afterMethodRouteHandlerMatch =
createHandlerMatchForBeforeOrAfterFilter(routeHandlerMatch,
afterFilter,
1);
matches.add(afterMethodRouteHandlerMatch);
}
}
}
return matches;
}
protected boolean isRouteMatchAcceptedContentType(IRoute route,
List requestContentTypes) {
if(requestContentTypes == null || requestContentTypes.size() == 0) {
return true;
}
Set routeContentTypes = route.getAcceptedContentTypes();
if(routeContentTypes == null || routeContentTypes.size() == 0) {
return true;
}
for(String contentType : requestContentTypes) {
if(contentType != null && routeContentTypes.contains(contentType.toLowerCase())) {
return true;
}
}
return false;
}
/**
* Creates an handler match with no matching params.
*/
protected IRouteHandlerMatch createNoMatchingParamsHandlerMatch(IRoute route,
String id,
IHandler handler,
int position) {
return getRouteHandlerMatchFactory().create(route,
handler,
null,
position);
}
/**
* Creates a new match for a "before" or "after" handler specific
* to a route. THis measn keeping the same informations as the
* main handler.
*/
protected IRouteHandlerMatch createHandlerMatchForBeforeOrAfterFilter(IRouteHandlerMatch mainRouteHandlerMatch,
IHandler beforeOrAfterMethod,
int position) {
IRouteHandlerMatch routeHandlerMatch = getRouteHandlerMatchFactory().create(mainRouteHandlerMatch.getSourceRoute(),
beforeOrAfterMethod,
mainRouteHandlerMatch.getParameters(),
position);
return routeHandlerMatch;
}
/**
* Validate if a route matches the given HTTP method.
*/
protected boolean isRouteMatchHttpMethod(IRoute route, HttpMethod httpMethod) {
return route.getHttpMethods().contains(httpMethod);
}
/**
* Validate if url matches the path of the route and if so, returns the
* parsed parameters, if any.
*
* Returns NULL if there is no match.
*/
protected Map validatePath(String routePath, URL url) {
String urlPath = url.getPath();
String urlPathSlashesStriped = StringUtils.strip(urlPath, "/ ");
String routePathSlashesStriped = StringUtils.strip(routePath, "/ ");
boolean routesAreCaseSensitive = getSpincastConfig().isRoutesCaseSensitive();
//==========================================
// URL and route path are the same : match!
//==========================================
if(routesAreCaseSensitive) {
if(urlPathSlashesStriped.equals(routePathSlashesStriped)) {
return new HashMap();
}
} else {
if(urlPathSlashesStriped.equalsIgnoreCase(routePathSlashesStriped)) {
return new HashMap();
}
}
//==========================================
// If the route path doesn't contain any "${" or "*{" there no
// need to validate furthermore...
//==========================================
int pos = routePathSlashesStriped.indexOf("*{");
boolean hasSplat = true;
if(pos < 0) {
hasSplat = false;
pos = routePathSlashesStriped.indexOf("${");
if(pos < 0) {
return null;
}
}
String[] routePathTokens = routePathSlashesStriped.split("/");
if(routePathTokens.length == 1 && routePathTokens[0].equals("")) {
routePathTokens = new String[0];
}
String[] urlPathTokens = urlPathSlashesStriped.split("/");
if(urlPathTokens.length == 1 && urlPathTokens[0].equals("")) {
urlPathTokens = new String[0];
}
//==========================================
// The Url should have at least as many tokens
// than the route path but can also have more in case
// a splat "*{" token is used in the route path!
//==========================================
if(!hasSplat && urlPathTokens.length > routePathTokens.length) {
return null;
}
Map params = new HashMap();
int routePathTokenPos = 0;
int urlTokenPos = 0;
for(; routePathTokenPos < routePathTokens.length; routePathTokenPos++) {
String routePathToken = routePathTokens[routePathTokenPos];
String urlPathToken = "";
if(!(routePathToken.startsWith("*{") && urlTokenPos >= urlPathTokens.length)) {
if(urlTokenPos + 1 > urlPathTokens.length) {
return null;
}
urlPathToken = urlPathTokens[urlTokenPos];
}
//==========================================
// The position of the url's tokens may increase
// faster than the one of the route path because
// of splat tokens!
//==========================================
urlTokenPos++;
//==========================================
// For a token that doesn't start with "${" or "*{", the
// same value must be in url and in the route path.
//==========================================
if(!routePathToken.startsWith("${") && !routePathToken.startsWith("*{")) {
if(routesAreCaseSensitive) {
if(!urlPathToken.equals(routePathToken)) {
return null;
}
} else {
if(!urlPathToken.equalsIgnoreCase(routePathToken)) {
return null;
}
}
} else {
String paramName = routePathToken.substring(2, routePathToken.length() - 1);
String paramValue;
try {
paramValue = URLDecoder.decode(urlPathToken, "UTF-8");
} catch(Exception ex) {
throw SpincastStatics.runtimize(ex);
}
//==========================================
// If there a pattern?
//==========================================
String pattern = null;
if(routePathToken.startsWith("${")) {
int posComma = paramName.indexOf(":");
if(posComma > -1) {
pattern = paramName.substring(posComma + 1);
paramName = paramName.substring(0, posComma);
if(StringUtils.isBlank(pattern)) {
pattern = null;
} else {
//==========================================
// Is it a pattern alias?
//==========================================
if(pattern.startsWith("<") && pattern.endsWith(">")) {
pattern = getPatternFromAlias(pattern.substring(1, pattern.length() - 1));
}
}
}
if(pattern != null && !getPattern(pattern).matcher(urlPathToken).matches()) {
this.logger.debug("Url token '" + urlPathToken + "' doesn't match pattern '" + pattern + "'.");
return null;
}
}
//==========================================
// Splat param : the value can contain multiple
// tokens from the url.
//==========================================
else if(routePathToken.startsWith("*{")) {
int nbrTokensInSplat = urlPathTokens.length - routePathTokens.length + 1;
if(nbrTokensInSplat > 1) {
StringBuilder builder = new StringBuilder(paramValue);
for(int i = 1; i < nbrTokensInSplat; i++) {
builder.append("/").append(urlPathTokens[urlTokenPos]);
urlTokenPos++;
}
paramValue = builder.toString();
}
}
//==========================================
// We do not collect the parameters without names.
//==========================================
if(!StringUtils.isBlank(paramName)) {
params.put(paramName, paramValue);
}
}
}
return params;
}
/**
* Get a path pattern from its alias.
* @return the pattern or NULL if not found.
*/
protected String getPatternFromAlias(String alias) {
if(alias == null) {
return null;
}
for(Entry entry : getRouteParamPatternAliases().entrySet()) {
if(alias.equals(entry.getKey())) {
return entry.getValue();
}
}
return null;
}
@Override
public void addRouteParamPatternAlias(String alias, String pattern) {
if(StringUtils.isBlank(alias)) {
throw new RuntimeException("The alias can't be empty.");
}
if(StringUtils.isBlank(pattern)) {
throw new RuntimeException("The pattern can't be empty.");
}
getRouteParamPatternAliases().put(alias, pattern);
}
@Override
public IRouteBuilder GET(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.GET();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder POST(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.POST();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder PUT(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.PUT();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder DELETE(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.DELETE();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder OPTIONS(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.OPTIONS();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder TRACE(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.TRACE();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder HEAD(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.HEAD();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder PATCH(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.PATCH();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder ALL(String path) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.ALL();
builder = builder.path(path);
return builder;
}
@Override
public IRouteBuilder SOME(String path, HttpMethod... httpMethods) {
if(httpMethods.length == 0) {
throw new RuntimeException("Using SOME(...), you have to specify at least one HTTP method.");
}
return SOME(path, Sets.newHashSet(httpMethods));
}
@Override
public IRouteBuilder SOME(String path, Set httpMethods) {
if(httpMethods == null || httpMethods.size() == 0) {
throw new RuntimeException("Using SOME(...), you have to specify at least one HTTP method.");
}
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.SOME(httpMethods);
builder = builder.path(path);
return builder;
}
@Override
public void before(IHandler handler) {
before(DEFAULT_ROUTE_PATH, handler);
}
@Override
public void before(String path, IHandler handler) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.ALL();
builder = builder.pos(-1);
builder = builder.path(path);
builder = addFilterDefaultRoutingTypes(builder);
builder.save(handler);
}
@Override
public void after(IHandler handler) {
after(DEFAULT_ROUTE_PATH, handler);
}
@Override
public void after(String path, IHandler handler) {
IRouteBuilder builder = getRouteBuilderFactory().create(this);
builder = builder.ALL();
builder = builder.pos(1);
builder = builder.path(path);
builder = addFilterDefaultRoutingTypes(builder);
builder.save(handler);
}
@Override
public void beforeAndAfter(IHandler handler) {
beforeAndAfter(DEFAULT_ROUTE_PATH, handler);
}
@Override
public void beforeAndAfter(String path, IHandler handler) {
before(path, handler);
after(path, handler);
}
protected IRouteBuilder addFilterDefaultRoutingTypes(IRouteBuilder builder) {
Set defaultRoutingTypes = getSpincastRouterConfig().getFilterDefaultRoutingTypes();
for(RoutingType routingType : defaultRoutingTypes) {
if(routingType == RoutingType.FOUND) {
builder.found();
} else if(routingType == RoutingType.NOT_FOUND) {
builder.notFound();
} else if(routingType == RoutingType.EXCEPTION) {
builder.exception();
} else {
throw new RuntimeException("Not managed : " + routingType);
}
}
return builder;
}
@Override
public void exception(IHandler handler) {
exception(DEFAULT_ROUTE_PATH, handler);
}
@Override
public void exception(String path, IHandler handler) {
ALL(path).exception().save(handler);
}
@Override
public void notFound(IHandler handler) {
notFound(DEFAULT_ROUTE_PATH, handler);
}
@Override
public void notFound(String path, IHandler handler) {
ALL(path).notFound().save(handler);
}
@Override
public void cors() {
cors(DEFAULT_ROUTE_PATH);
}
@Override
public void cors(Set allowedOrigins) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins);
}
@Override
public void cors(Set allowedOrigins, Set extraHeadersAllowedToBeRead) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins,
extraHeadersAllowedToBeRead);
}
@Override
public void cors(Set allowedOrigins, Set extraHeadersAllowedToBeRead,
Set extraHeadersAllowedToBeSent) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent);
}
@Override
public void cors(Set allowedOrigins, Set extraHeadersAllowedToBeRead, Set extraHeadersAllowedToBeSent,
boolean allowCookies) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies);
}
@Override
public void cors(Set allowedOrigins, Set extraHeadersAllowedToBeRead, Set extraHeadersAllowedToBeSent,
boolean allowCookies, Set allowedMethods) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies,
allowedMethods);
}
@Override
public void cors(Set allowedOrigins, Set extraHeadersAllowedToBeRead, Set extraHeadersAllowedToBeSent,
boolean allowCookies, Set allowedMethods, int maxAgeInSeconds) {
cors(DEFAULT_ROUTE_PATH,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies,
allowedMethods,
maxAgeInSeconds);
}
@Override
public void cors(String path) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins,
final Set extraHeadersAllowedToBeRead) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins,
extraHeadersAllowedToBeRead);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins,
final Set extraHeadersAllowedToBeRead,
final Set extraHeadersAllowedToBeSent) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins,
final Set extraHeadersAllowedToBeRead,
final Set extraHeadersAllowedToBeSent,
final boolean allowCookies) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins,
final Set extraHeadersAllowedToBeRead,
final Set extraHeadersAllowedToBeSent,
final boolean allowCookies,
final Set allowedMethods) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies,
allowedMethods);
}
});
}
@Override
public void cors(String path,
final Set allowedOrigins,
final Set extraHeadersAllowedToBeRead,
final Set extraHeadersAllowedToBeSent,
final boolean allowCookies,
final Set allowedMethods,
final int maxAgeInSeconds) {
ALL(path).pos(getSpincastRouterConfig().getCorsFilterPosition())
.found().notFound().save(new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().cors(context,
allowedOrigins,
extraHeadersAllowedToBeRead,
extraHeadersAllowedToBeSent,
allowCookies,
allowedMethods,
maxAgeInSeconds);
}
});
}
@Override
public IStaticResourceBuilder file(String url) {
IStaticResourceBuilder builder = getStaticResourceBuilderFactory().create(this, false);
builder = builder.url(url);
return builder;
}
@Override
public IStaticResourceBuilder dir(String url) {
IStaticResourceBuilder builder = getStaticResourceBuilderFactory().create(this, true);
builder = builder.url(url);
return builder;
}
@Override
public void addStaticResource(final IStaticResource staticResource) {
if(staticResource.getUrlPath() == null) {
throw new RuntimeException("The URL to the resource must be specified!");
}
if(staticResource.getResourcePath() == null) {
throw new RuntimeException("A classpath or a file system path must be specified!");
}
if(staticResource.isClasspath() && staticResource.getGenerator() != null) {
throw new RuntimeException("A resource generator can only be specified when a file system " +
"path is used, not a classpath path.");
}
String urlPath = staticResource.getUrlPath();
//==========================================
// A file resource can't contains any dynamic parameters.
// The serving handler of the HTTP server may not understand them.
//
// For a dir resource, we still allow them in the url path, but those
// dynamic parameters must all
// be *after* the regular tokens. This way, we can remove
// the dynamic part and the result is the prefix path to be used
// by the server.
//==========================================
boolean splatParamFound = false;
StringBuilder urlPathForServerBuilder = new StringBuilder("");
String[] tokens = staticResource.getUrlPath().split("/");
for(String token : tokens) {
token = token.trim();
if(staticResource.isFileResource()) {
if(token.startsWith("${") || token.startsWith("*{")) {
throw new RuntimeException("A file resource path can't contain dynamic parameters. Use 'dir()' instead or " +
"a regular route which won't be considered as a static resource.");
}
} else {
if(StringUtils.isBlank(token)) {
continue;
}
if(token.startsWith("${")) {
throw new RuntimeException("A dir static resource path can't contains any standard dynamic parameter. It can only contain " +
"a splat parameter, at the very end of the path : " + token);
} else if(token.startsWith("*{")) {
splatParamFound = true;
} else {
if(splatParamFound) {
throw new RuntimeException("A dir resource path can contain a splat parameter, only at the very end of the path! " +
"For example, this is invalid as a path : '/one/*{param1}/two', but this is valid : '/one/two/*{param1}'.");
} else {
urlPathForServerBuilder.append("/").append(token);
}
}
}
}
//==========================================
// We remove the splat parameters from the
// url path for the server.
//==========================================
String urlPathPrefixWithoutSplatParameter = staticResource.getUrlPath();
if(splatParamFound) {
urlPathPrefixWithoutSplatParameter = urlPathForServerBuilder.toString();
if(staticResource.isDirResource()) {
urlPathPrefixWithoutSplatParameter = urlPathForServerBuilder.toString();
if(StringUtils.isBlank(urlPathPrefixWithoutSplatParameter)) {
urlPathPrefixWithoutSplatParameter = "/";
}
}
IStaticResource staticResourceNoDynParams =
getStaticResourceFactory().create(staticResource.getStaticResourceType(),
urlPathPrefixWithoutSplatParameter,
staticResource.getResourcePath(),
staticResource.getGenerator(),
staticResource.getCorsConfig());
getServer().addStaticResourceToServe(staticResourceNoDynParams);
} else {
getServer().addStaticResourceToServe(staticResource);
}
//==========================================
// If the resource is dynamic, add its generator
// as a route for the same path!
// We also add an "after" filter which will try to
// automatically save the generated resource. The headers
// shouln't have been sent for that to work!
//==========================================
IRoute route = null;
IHandler saveResourceFilter = null;
if(staticResource.getGenerator() != null) {
if(staticResource.isFileResource()) {
saveResourceFilter = new IHandler() {
@Override
public void handle(R context) {
getSpincastFilters().saveGeneratedResource(context, staticResource.getResourcePath());
}
};
} else {
//==========================================
// We make the route listen on anything under
// the specified path! The path may already contain
// a splat param though!
//==========================================
if(!splatParamFound) {
urlPath = StringUtils.stripEnd(urlPath, "/") + DEFAULT_ROUTE_PATH;
}
final String urlPathPrefixWithoutDynamicParametersFinal = urlPathPrefixWithoutSplatParameter;
saveResourceFilter = new IHandler() {
@Override
public void handle(R context) {
String urlPathPrefix = StringUtils.stripStart(urlPathPrefixWithoutDynamicParametersFinal, "/");
String requestPath = context.request().getRequestPath();
requestPath = StringUtils.stripStart(requestPath, "/");
if(!requestPath.startsWith(urlPathPrefix)) {
throw new RuntimeException("The requestPath '" + requestPath +
"' should starts with the urlPathPrefix '" + urlPathPrefix +
"' here!");
}
requestPath = requestPath.substring(urlPathPrefix.length());
// Make sure the path of the resource to generate is safe!
String resourceToGeneratePath;
try {
resourceToGeneratePath =
new File(staticResource.getResourcePath() + "/" + requestPath).getCanonicalFile()
.getAbsolutePath();
String resourcesRoot =
new File(staticResource.getResourcePath()).getCanonicalFile().getAbsolutePath();
if(!resourceToGeneratePath.startsWith(resourcesRoot)) {
throw new RuntimeException("The requestPath '" + resourceToGeneratePath +
"' should be inside the root resources folder : " + resourcesRoot);
}
} catch(Exception ex) {
throw SpincastStatics.runtimize(ex);
}
getSpincastFilters().saveGeneratedResource(context, resourceToGeneratePath);
}
};
}
route = getRouteFactory().createRoute(null,
Sets.newHashSet(HttpMethod.GET),
urlPath,
Sets.newHashSet(RoutingType.FOUND),
null,
staticResource.getGenerator(),
saveResourceFilter != null ? Arrays.asList(saveResourceFilter) : null,
Sets.newHashSet(0),
null);
addRoute(route);
}
}
@Override
public void httpAuth(String pathPrefix, String realmName) {
if(StringUtils.isBlank(realmName)) {
throw new RuntimeException("The realm name can't be empty");
}
if(StringUtils.isBlank(pathPrefix)) {
pathPrefix = "/";
} else if(!pathPrefix.startsWith("/")) {
pathPrefix = "/" + pathPrefix;
}
String[] tokens = pathPrefix.split("/");
for(String token : tokens) {
token = token.trim();
if(token.startsWith("${") || token.startsWith("*{")) {
throw new RuntimeException("The path prefix for an HTTP authenticated section can't contain " +
"any dynamic parameters: " + token);
}
}
getServer().createHttpAuthenticationRealm(pathPrefix, realmName);
}
@Override
public IWebsocketRouteBuilder websocket(String path) {
IWebsocketRouteBuilder builder = getWebsocketRouteBuilderFactory().create(this);
builder = builder.path(path);
return builder;
}
@Override
public void addWebsocketRoute(IWebsocketRoute websocketRoute) {
//==========================================
// We create an HTTP route from the Websocket route
// informations: this allows the inital request to
// be routed exactly as a standard route and to have "before"
// filters applied.
//==========================================
IRoute httpRoute = createHttpRouteFromWebsocketRoute(websocketRoute);
addRoute(httpRoute);
}
protected IRoute createHttpRouteFromWebsocketRoute(final IWebsocketRoute websocketRoute) {
//==========================================
// We create the "main" route handler for this
// route: its job will be to convert the HTTP request
// to a Websocket connection.
//==========================================
final IHandler routeHandler = getWebsocketRouteHandlerFactory().createWebsocketRouteHandler(websocketRoute);
IRoute httpRoute = new IRoute() {
@Override
public String getId() {
return websocketRoute.getId();
}
@Override
public String getPath() {
return websocketRoute.getPath();
}
@Override
public Set getHttpMethods() {
//==========================================
// Websocket connection request only valid using a
// GET method.
//==========================================
return Sets.newHashSet(HttpMethod.GET);
}
@Override
public Set getAcceptedContentTypes() {
//==========================================
// Not interesting for a Websocket connection.
//==========================================
return null;
}
@Override
public Set getRoutingTypes() {
return Sets.newHashSet(RoutingType.FOUND);
}
@Override
public IHandler getMainHandler() {
//==========================================
// The Websocket route hander we just created...
//==========================================
return routeHandler;
}
@Override
public List> getBeforeFilters() {
return websocketRoute.getBeforeFilters();
}
@Override
public List> getAfterFilters() {
//==========================================
// No "after" filter for a Websocket route:
// if the Websocket connection is established,
// the HTTP request is not anymore.
//==========================================
return null;
}
@Override
public List getPositions() {
//==========================================
// Websocket routes can't be used as filters.
//==========================================
return Lists.newArrayList(0);
}
};
return httpRoute;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy