001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.requests; 019 020import io.stallion.Context; 021import io.stallion.exceptions.UsageException; 022import io.stallion.restfulEndpoints.RestEndpointBase; 023import io.stallion.services.Log; 024import org.apache.commons.lang3.ArrayUtils; 025 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030 031/** 032 * A registry of all routes defined in the stallion.toml settings file. 033 * 034 */ 035public class RoutesRegistry { 036 private List<RouteDefinition> routes = new ArrayList<>(); 037 038 private static RoutesRegistry _instance; 039 040 public static RoutesRegistry instance() { 041 if (_instance == null) { 042 throw new UsageException("You must call RoutesRegistry.load() before accessing the instance()"); 043 } 044 return _instance; 045 } 046 047 public static RoutesRegistry load() { 048 _instance = new RoutesRegistry(); 049 for(RouteDefinition route: Context.settings().getRoutes()) { 050 _instance.getRoutes().add(route); 051 } 052 return _instance; 053 } 054 055 056 public RouteResult routeForEndpoints(StRequest request, List<RestEndpointBase> endpoints) { 057 058 for (RestEndpointBase endpoint: endpoints) { 059 Log.finest("EndpointMethod: {0} RequestMethod: {1}", endpoint.getMethod(), request.getMethod()); 060 Log.finest("EndpointRoute: {0} RequestPath: {1}", endpoint.getRoute(), request.getPath()); 061 062 if (!endpoint.getMethod().equals(request.getMethod())) { 063 Log.finest("method does not match"); 064 continue; 065 } 066 067 068 // Catch-all 069 if (endpoint.getRoute().equals("*")) { 070 RouteResult result = new RouteResult() 071 .setEndpoint(endpoint); 072 return result; 073 } 074 075 076 Log.finest("Mapping pathParams"); 077 String requestPath = request.getPath(); 078 String[] requestParts = request.getPath().split("/"); 079 String endpointPath = endpoint.getRoute(); 080 String[] endpointParts = endpointPath.split("/"); 081 082 // If the last part endpoint is a wildcard/match-all, then we truncate the request parts to 083 // all the path sections before the wildcard, and ignore all parts after the wildcard. 084 if (endpointParts.length > 0) { 085 if (endpointParts[endpointParts.length - 1].equals("*") && requestParts.length >= endpointParts.length) { 086 requestParts = ArrayUtils.subarray(requestParts, 0, endpointParts.length - 1); 087 } 088 } 089 090 091 HashMap<String, String> pathParams = urlToParamsMap(requestParts, endpointParts); 092 if (pathParams != null) { 093 RouteResult result = new RouteResult() 094 .setParams(pathParams) 095 .setEndpoint(endpoint); 096 return result; 097 } else { 098 Log.finest("pathParams do not match"); 099 } 100 } 101 return null; 102 } 103 104 public RouteResult route(String path) { 105 if ("".equals(path)) { 106 path = "/"; 107 } 108 for(RouteDefinition definition: routes) { 109 HashMap<String, String> pathParams = urlToParamsMap(path.split("/"), definition.getRoute().split("/")); 110 if (pathParams != null) { 111 //val pathParamsNotNull:Map<String, String> = pathParams; 112 113 for(Map.Entry<String, String> entry: definition.getParams().entrySet()) { 114 pathParams.put(entry.getKey(), entry.getValue()); 115 } 116 RouteResult result = new RouteResult() 117 .setTemplate(definition.getTemplate()) 118 .setGroup(definition.getGroup()) 119 .setParams(pathParams) 120 .setPageTitle(definition.getPageTitle()) 121 .setMetaDescription(definition.getMetaDescription()) 122 .setRedirectUrl(definition.getDestination()) 123 .setPreempt(definition.getPreempt()) 124 .setName(definition.getName()) 125 ; 126 return result; 127 } 128 } 129 return null; 130 } 131 132 /** 133 * 134 * @param givenUrlSegments An array representing the URL path attempting to be opened (i.e. ["users", "42"]) 135 * @param routerUrlSegments An array representing a possible URL match for the router (i.e. ["users", ":id"]) 136 * @return A map of URL parameters if it's a match (i.e. {"id" => "42"}) or null if there is no match 137 */ 138 public HashMap<String, String> urlToParamsMap(String[] givenUrlSegments,String[] routerUrlSegments) { 139 HashMap<String, String> formatParams = new HashMap<String, String>(); 140 if (routerUrlSegments.length != givenUrlSegments.length) { 141 return null; 142 } 143 for (int index=0; index<routerUrlSegments.length; index++) { 144 String routerPart = routerUrlSegments[index]; 145 if (index >= givenUrlSegments.length) { 146 return null; 147 } 148 String givenPart = givenUrlSegments[index]; 149 if (routerPart.length() == 0) { 150 continue; 151 } 152 if (routerPart.charAt(0) == ':') { 153 String key = routerPart.substring(1, routerPart.length()); 154 formatParams.put(key, givenPart); 155 continue; 156 } 157 158 if (!routerPart.equals(givenPart)) { 159 return null; 160 } 161 } 162 return formatParams; 163 } 164 165 166 public static void shutdown() { 167 _instance = null; 168 } 169 170 171 public List<RouteDefinition> getRoutes() { 172 return routes; 173 } 174}