values;
279 |
280 | public Header() {
281 | }
282 |
283 | /**
284 | * First value
285 | *
286 | * @return The first value
287 | */
288 | public String value() {
289 | return values.get(0);
290 | }
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/src/main/java/org/resthub/web/springmvc/router/Router.java:
--------------------------------------------------------------------------------
1 | package org.resthub.web.springmvc.router;
2 |
3 | import java.io.IOException;
4 | import java.io.UnsupportedEncodingException;
5 | import java.net.URLEncoder;
6 | import java.util.*;
7 |
8 | import jregex.Matcher;
9 | import jregex.Pattern;
10 | import jregex.REFlags;
11 |
12 | import org.apache.commons.io.FileUtils;
13 | import org.apache.commons.io.IOUtils;
14 | import org.resthub.web.springmvc.router.exceptions.NoHandlerFoundException;
15 | import org.resthub.web.springmvc.router.exceptions.NoRouteFoundException;
16 | import org.resthub.web.springmvc.router.exceptions.RouteFileParsingException;
17 | import org.slf4j.Logger;
18 | import org.slf4j.LoggerFactory;
19 | import org.springframework.core.io.Resource;
20 |
21 | /**
22 | * The router matches HTTP requests to action invocations.
23 | *
Courtesy of Play! Framework Router
24 | *
25 | * @author Play! Framework developers
26 | * @author Brian Clozel
27 | * @see org.resthub.web.springmvc.router.RouterHandlerMapping
28 | */
29 | public class Router {
30 |
31 | static Pattern routePattern = new Pattern("^({method}GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|\\*)[(]?({headers}[^)]*)(\\))?\\s+({path}.*/[^\\s]*)\\s+({action}[^\\s(]+)({params}.+)?(\\s*)$");
32 | /**
33 | * Pattern used to locate a method override instruction
34 | */
35 | static Pattern methodOverride = new Pattern("^.*x-http-method-override=({method}GET|PUT|POST|DELETE|PATCH).*$");
36 |
37 | /**
38 | * Timestamp the routes file was last loaded at.
39 | */
40 | public static long lastLoading = -1;
41 | private static final Logger logger = LoggerFactory.getLogger(Router.class);
42 |
43 | public static void clear() {
44 | routes.clear();
45 | }
46 |
47 | /**
48 | * Parse the routes file. This is called at startup.
49 | *
50 | */
51 | public static void load(List fileResources) throws IOException {
52 | routes.clear();
53 | for (Resource res : fileResources) {
54 | parse(res);
55 | }
56 |
57 | lastLoading = System.currentTimeMillis();
58 | }
59 |
60 | /**
61 | * This one can be called to add new route. Last added is first in the route
62 | * list.
63 | */
64 | public static void prependRoute(String method, String path, String action, String headers) {
65 | prependRoute(method, path, action, null, headers);
66 | }
67 |
68 | /**
69 | * This one can be called to add new route. Last added is first in the route
70 | * list.
71 | */
72 | public static void prependRoute(String method, String path, String action) {
73 | prependRoute(method, path, action, null, null);
74 | }
75 |
76 | /**
77 | * Add a route at the given position
78 | */
79 | public static void addRoute(int position, String method, String path, String action, String params, String headers) {
80 | if (position > routes.size()) {
81 | position = routes.size();
82 | }
83 | routes.add(position, getRoute(method, path, action, params, headers));
84 | }
85 |
86 | /**
87 | * Add a route at the given position
88 | */
89 | public static void addRoute(int position, String method, String path, String headers) {
90 | addRoute(position, method, path, null, null, headers);
91 | }
92 |
93 | /**
94 | * Add a route at the given position
95 | */
96 | public static void addRoute(int position, String method, String path, String action, String headers) {
97 | addRoute(position, method, path, action, null, headers);
98 | }
99 |
100 | /**
101 | * Add a new route. Will be first in the route list
102 | */
103 | public static void addRoute(String method, String path, String action) {
104 | prependRoute(method, path, action);
105 | }
106 |
107 | /**
108 | * Add a route at the given position
109 | */
110 | public static void addRoute(String method, String path, String action, String headers) {
111 | addRoute(method, path, action, null, headers);
112 | }
113 |
114 | /**
115 | * Add a route
116 | */
117 | public static void addRoute(String method, String path, String action, String params, String headers) {
118 | appendRoute(method, path, action, params, headers, null, 0);
119 | }
120 |
121 | /**
122 | * This is used internally when reading the route file. The order the routes
123 | * are added matters and we want the method to append the routes to the
124 | * list.
125 | */
126 | public static void appendRoute(String method, String path, String action, String params, String headers, String sourceFile, int line) {
127 | routes.add(getRoute(method, path, action, params, headers, sourceFile, line));
128 | }
129 |
130 | public static Route getRoute(String method, String path, String action, String params, String headers) {
131 | return getRoute(method, path, action, params, headers, null, 0);
132 | }
133 |
134 | public static Route getRoute(String method, String path, String action, String params, String headers, String sourceFile, int line) {
135 | Route route = new Route();
136 | route.method = method;
137 | route.path = path.replace("//", "/");
138 | route.action = action;
139 | route.routesFile = sourceFile;
140 | route.routesFileLine = line;
141 | route.addFormat(headers);
142 | route.addParams(params);
143 | route.compute();
144 | if (logger.isDebugEnabled()) {
145 | logger.debug("Adding [" + route.toString() + "] with params [" + params + "] and headers [" + headers + "]");
146 | }
147 |
148 | return route;
149 | }
150 |
151 | /**
152 | * Add a new route at the beginning of the route list
153 | */
154 | public static void prependRoute(String method, String path, String action, String params, String headers) {
155 | routes.add(0, getRoute(method, path, action, params, headers));
156 | }
157 |
158 | /**
159 | * Parse a route file.
160 | *
161 | * @param fileResource
162 | * @throws IOException
163 | */
164 | static void parse(Resource fileResource) throws IOException {
165 |
166 | String fileAbsolutePath = fileResource.getFile().getAbsolutePath();
167 | String content = IOUtils.toString(fileResource.getInputStream());
168 |
169 | parse(content, fileAbsolutePath);
170 | }
171 |
172 | static void parse(String content, String fileAbsolutePath) throws IOException {
173 | int lineNumber = 0;
174 | for (String line : content.split("\n")) {
175 | lineNumber++;
176 | line = line.trim().replaceAll("\\s+", " ");
177 | if (line.length() == 0 || line.startsWith("#")) {
178 | continue;
179 | }
180 | Matcher matcher = routePattern.matcher(line);
181 | if (matcher.matches()) {
182 |
183 | String action = matcher.group("action");
184 | String method = matcher.group("method");
185 | String path = matcher.group("path");
186 | String params = matcher.group("params");
187 | String headers = matcher.group("headers");
188 | appendRoute(method, path, action, params, headers, fileAbsolutePath, lineNumber);
189 | } else {
190 | logger.error("Invalid route definition : " + line);
191 | }
192 | }
193 | }
194 |
195 | public static void detectChanges(List fileResources) throws IOException {
196 |
197 | boolean hasChanged = false;
198 |
199 | for (Resource res : fileResources) {
200 | if (FileUtils.isFileNewer(res.getFile(), lastLoading)) {
201 | hasChanged = true;
202 | break;
203 | }
204 | }
205 |
206 | if (hasChanged) {
207 | load(fileResources);
208 | }
209 | }
210 |
211 | public static List routes = new ArrayList(500);
212 |
213 | public static Route route(HTTPRequestAdapter request) {
214 | if (logger.isTraceEnabled()) {
215 | logger.trace("Route: " + request.path + " - " + request.querystring);
216 | }
217 | // request method may be overriden if a x-http-method-override parameter is given
218 | if (request.querystring != null && methodOverride.matches(request.querystring)) {
219 | Matcher matcher = methodOverride.matcher(request.querystring);
220 | if (matcher.matches()) {
221 | if (logger.isTraceEnabled()) {
222 | logger.trace("request method %s overriden to %s ", request.method, matcher.group("method"));
223 | }
224 | request.method = matcher.group("method");
225 | }
226 | }
227 |
228 | for (Route route : routes) {
229 | String format = request.format;
230 | String host = request.host;
231 | Map args = route.matches(request.method, request.path, format, host);
232 |
233 | if (args != null) {
234 | request.routeArgs = args;
235 | request.action = route.action;
236 | if (args.containsKey("format")) {
237 | request.setFormat(args.get("format"));
238 | }
239 | if (request.action.indexOf("{") > -1) { // more optimization ?
240 | for (String arg : request.routeArgs.keySet()) {
241 | request.action = request.action.replace("{" + arg + "}", request.routeArgs.get(arg));
242 | }
243 | }
244 | return route;
245 | }
246 | }
247 | // Not found - if the request was a HEAD, let's see if we can find a corresponding GET
248 | if (request.method.equalsIgnoreCase("head")) {
249 | request.method = "GET";
250 | Route route = route(request);
251 | request.method = "HEAD";
252 | if (route != null) {
253 | return route;
254 | }
255 | }
256 | throw new NoRouteFoundException(request.method, request.path);
257 | }
258 |
259 | public static Map route(String method, String path) {
260 | return route(method, path, null, null);
261 | }
262 |
263 | public static Map route(String method, String path, String headers) {
264 | return route(method, path, headers, null);
265 | }
266 |
267 | public static Map route(String method, String path, String headers, String host) {
268 | for (Route route : routes) {
269 | Map args = route.matches(method, path, headers, host);
270 | if (args != null) {
271 | args.put("action", route.action);
272 | return args;
273 | }
274 | }
275 | return new HashMap(16);
276 | }
277 |
278 | public static ActionDefinition reverse(String action) {
279 | // Note the map is not Collections.EMPTY_MAP
because it will be copied and changed.
280 | return reverse(action, new HashMap(16));
281 | }
282 |
283 | public static String getFullUrl(String action, Map args) {
284 | return HTTPRequestAdapter.getCurrent().getBase() + reverse(action, args);
285 | }
286 |
287 | public static String getFullUrl(String action) {
288 | // Note the map is not Collections.EMPTY_MAP
because it will be copied and changed.
289 | return getFullUrl(action, new HashMap(16));
290 | }
291 |
292 | public static Collection resolveActions(String action) {
293 |
294 | List candidateRoutes = new ArrayList(3);
295 |
296 | for (Route route : routes) {
297 | if (route.actionPattern != null) {
298 | Matcher matcher = route.actionPattern.matcher(action);
299 | if (matcher.matches()) {
300 | candidateRoutes.add(route);
301 | }
302 | }
303 | }
304 |
305 | return candidateRoutes;
306 | }
307 |
308 | public static ActionDefinition reverse(String action, Map args) {
309 |
310 | HTTPRequestAdapter currentRequest = HTTPRequestAdapter.getCurrent();
311 |
312 | Map argsbackup = new HashMap(args);
313 | for (Route route : routes) {
314 | if (route.actionPattern != null) {
315 | Matcher matcher = route.actionPattern.matcher(action);
316 | if (matcher.matches()) {
317 | for (String group : route.actionArgs) {
318 | String v = matcher.group(group);
319 | if (v == null) {
320 | continue;
321 | }
322 | args.put(group, v.toLowerCase());
323 | }
324 | List inPathArgs = new ArrayList(16);
325 | boolean allRequiredArgsAreHere = true;
326 | // les noms de parametres matchent ils ?
327 | for (Route.Arg arg : route.args) {
328 | inPathArgs.add(arg.name);
329 | Object value = args.get(arg.name);
330 | if (value == null) {
331 | // This is a hack for reverting on hostname that are a regex expression.
332 | // See [#344] for more into. This is not optimal and should retough. However,
333 | // it allows us to do things like {(.*)}.domain.com
334 | String host = route.host.replaceAll("\\{", "").replaceAll("\\}", "");
335 | if (host.equals(arg.name) || host.matches(arg.name)) {
336 | args.put(arg.name, "");
337 | value = "";
338 | } else {
339 | allRequiredArgsAreHere = false;
340 | break;
341 | }
342 | } else {
343 | if (value instanceof List>) {
344 | @SuppressWarnings("unchecked")
345 | List