1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.seasar.cubby.routing.impl;
17
18 import static org.seasar.cubby.internal.util.LogMessages.format;
19
20 import java.io.UnsupportedEncodingException;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.TreeMap;
29 import java.util.Map.Entry;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 import org.seasar.cubby.action.Path;
34 import org.seasar.cubby.action.RequestMethod;
35 import org.seasar.cubby.internal.util.MetaUtils;
36 import org.seasar.cubby.internal.util.QueryStringBuilder;
37 import org.seasar.cubby.internal.util.URLBodyEncoder;
38 import org.seasar.cubby.routing.PathInfo;
39 import org.seasar.cubby.routing.PathResolver;
40 import org.seasar.cubby.routing.PathTemplateParser;
41 import org.seasar.cubby.routing.Routing;
42 import org.seasar.cubby.routing.RoutingException;
43 import org.seasar.cubby.util.ActionUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47
48
49
50
51
52
53 public class PathResolverImpl implements PathResolver {
54
55
56 private static final Logger logger = LoggerFactory
57 .getLogger(PathResolverImpl.class);
58
59
60 private final Map<RoutingKey, Routing> routings = new TreeMap<RoutingKey, Routing>();
61
62
63 private PathTemplateParser pathTemplateParser;
64
65
66
67
68
69
70
71 public PathResolverImpl(final PathTemplateParser pathTemplateParser) {
72 this.pathTemplateParser = pathTemplateParser;
73 }
74
75
76
77
78
79
80 public Collection<Routing> getRoutings() {
81 return routings.values();
82 }
83
84
85
86
87 public void add(final Class<?> actionClass) {
88 for (final Method method : actionClass.getMethods()) {
89 if (ActionUtils.isActionMethod(method)) {
90 final String actionPath = MetaUtils.getActionPath(actionClass,
91 method);
92 final RequestMethod[] acceptableRequestMethods = MetaUtils
93 .getAcceptableRequestMethods(actionClass, method);
94 for (final RequestMethod requestMethod : acceptableRequestMethods) {
95 final String onSubmit = MetaUtils.getOnSubmit(method);
96 final int priority = MetaUtils.getPriority(method);
97 this.add(actionPath, actionClass, method, requestMethod,
98 onSubmit, priority);
99 }
100 }
101 }
102 }
103
104
105
106
107 public void addAll(final Collection<Class<?>> actionClasses) {
108 for (final Class<?> actionClass : actionClasses) {
109 add(actionClass);
110 }
111 }
112
113
114
115
116 public void clear() {
117 routings.clear();
118 }
119
120
121
122
123 public void add(final String actionPath, final Class<?> actionClass,
124 final String methodName, final RequestMethod requestMethod,
125 final String onSubmit, final int priority) {
126 try {
127 final Method method = actionClass.getMethod(methodName);
128 this.add(actionPath, actionClass, method, requestMethod, onSubmit,
129 priority);
130 } catch (final NoSuchMethodException e) {
131 throw new RoutingException(e);
132 }
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 private void add(final String actionPath, final Class<?> actionClass,
152 final Method method, final RequestMethod requestMethod,
153 final String onSubmit, final int priority) {
154 if (!ActionUtils.isActionMethod(method)) {
155 throw new RoutingException(format("ECUB0003", method));
156 }
157
158 final List<String> uriParameterNames = new ArrayList<String>();
159 final String uriRegex = pathTemplateParser.parse(actionPath,
160 new PathTemplateParser.Handler() {
161
162 public String handle(final String name, final String regex) {
163 uriParameterNames.add(name);
164 return regexGroup(regex);
165 }
166
167 });
168 final Pattern pattern = Pattern.compile("^" + uriRegex + "$");
169
170 final Routing routing = new RoutingImpl(actionClass, method,
171 actionPath, uriParameterNames, pattern, requestMethod,
172 onSubmit, priority);
173 final RoutingKey key = new RoutingKey(routing);
174
175 if (logger.isDebugEnabled()) {
176 logger.debug(format("DCUB0007", routing));
177 }
178 if (routings.containsKey(key)) {
179 final Routing duplication = routings.get(key);
180 throw new RoutingException(format("ECUB0001", routing, duplication));
181 }
182 routings.put(key, routing);
183 }
184
185
186
187
188 public PathInfo getPathInfo(final String path, final String requestMethod,
189 final String characterEncoding) {
190 final Iterator<Routing> iterator = getRoutings().iterator();
191 while (iterator.hasNext()) {
192 final Routing routing = iterator.next();
193 final Matcher matcher = routing.getPattern().matcher(path);
194 if (matcher.find()) {
195 if (routing.isAcceptable(requestMethod)) {
196 final Map<String, Routing> onSubmitRoutings = new HashMap<String, Routing>();
197 onSubmitRoutings.put(routing.getOnSubmit(), routing);
198 while (iterator.hasNext()) {
199 final Routing anotherRouting = iterator.next();
200 if (routing.getPattern().pattern().equals(
201 anotherRouting.getPattern().pattern())
202 && routing.getRequestMethod().equals(
203 anotherRouting.getRequestMethod())) {
204 onSubmitRoutings.put(anotherRouting.getOnSubmit(),
205 anotherRouting);
206 }
207 }
208
209 final Map<String, String[]> uriParameters = new HashMap<String, String[]>();
210 for (int i = 0; i < matcher.groupCount(); i++) {
211 final String name = routing.getUriParameterNames().get(
212 i);
213 final String value = matcher.group(i + 1);
214 uriParameters.put(name, new String[] { value });
215 }
216
217 final PathInfo pathInfo = new ResolvedPathInfo(
218 uriParameters, onSubmitRoutings);
219
220 return pathInfo;
221 }
222 }
223 }
224
225 return null;
226 }
227
228
229
230
231
232
233
234
235 private static String regexGroup(final String regex) {
236 return "(" + regex + ")";
237 }
238
239
240
241
242 public String reverseLookup(final Class<?> actionClass,
243 final String methodName, final Map<String, String[]> parameters,
244 final String characterEncoding) {
245 final Collection<Routing> routings = getRoutings();
246 final Routing routing = findRouting(routings, actionClass, methodName);
247 final String actionPath = routing.getActionPath();
248 final Map<String, String[]> copyOfParameters = new HashMap<String, String[]>(
249 parameters);
250 final StringBuilder path = new StringBuilder(100);
251 path.append(pathTemplateParser.parse(actionPath,
252 new PathTemplateParser.Handler() {
253
254 public String handle(final String name, final String regex) {
255 if (!copyOfParameters.containsKey(name)) {
256 throw new RoutingException(format("ECUB0104",
257 actionPath, name));
258 }
259 final String value = copyOfParameters.remove(name)[0];
260 if (!value.matches(regex)) {
261 throw new RoutingException(format("ECUB0105",
262 actionPath, name, value, regex));
263 }
264 return encode(value, characterEncoding);
265 }
266
267 }));
268
269 if (!copyOfParameters.isEmpty()) {
270 final QueryStringBuilder builder = new QueryStringBuilder();
271 if (characterEncoding != null) {
272 builder.setEncode(characterEncoding);
273 }
274 for (final Entry<String, String[]> entry : copyOfParameters
275 .entrySet()) {
276 for (final String value : entry.getValue()) {
277 builder.addParam(entry.getKey(), value);
278 }
279 }
280 path.append('?');
281 path.append(builder.toString());
282 }
283
284 return path.toString();
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300 private static Routing findRouting(final Collection<Routing> routings,
301 final Class<?> actionClass, final String methodName) {
302 for (final Routing routing : routings) {
303 if (actionClass.getCanonicalName().equals(
304 routing.getActionClass().getCanonicalName())) {
305 if (methodName.equals(routing.getActionMethod().getName())) {
306 return routing;
307 }
308 }
309 }
310 throw new RoutingException(format("ECUB0103", actionClass, methodName));
311 }
312
313
314
315
316
317
318
319
320
321
322 private static String encode(final String str,
323 final String characterEncoding) {
324 if (characterEncoding == null) {
325 return str;
326 }
327 try {
328 return URLBodyEncoder.encode(str, characterEncoding);
329 } catch (final UnsupportedEncodingException e) {
330 throw new RoutingException(e);
331 }
332 }
333
334
335
336
337
338
339
340 static class RoutingKey implements Comparable<RoutingKey> {
341
342 private final int priority;
343
344 private final List<String> uriParameterNames;
345
346 private final Pattern pattern;
347
348 private final RequestMethod requestMethod;
349
350 private final String onSubmit;
351
352 public RoutingKey(final Routing routing) {
353 this.priority = routing.getPriority();
354 this.uriParameterNames = routing.getUriParameterNames();
355 this.pattern = routing.getPattern();
356 this.requestMethod = routing.getRequestMethod();
357 this.onSubmit = routing.getOnSubmit();
358 }
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378 public int compareTo(final RoutingKey another) {
379 int compare = this.priority - another.priority;
380 if (compare != 0) {
381 return compare;
382 }
383 compare = this.uriParameterNames.size()
384 - another.uriParameterNames.size();
385 if (compare != 0) {
386 return compare;
387 }
388 compare = this.pattern.pattern().compareTo(
389 another.pattern.pattern());
390 if (compare != 0) {
391 return compare;
392 }
393 compare = this.requestMethod.compareTo(another.requestMethod);
394 if (compare != 0) {
395 return compare;
396 }
397 if (this.onSubmit == another.onSubmit) {
398 compare = 0;
399 } else if (this.onSubmit == null) {
400 compare = -1;
401 } else if (another.onSubmit == null) {
402 compare = 1;
403 } else {
404 compare = this.onSubmit.compareTo(another.onSubmit);
405 }
406 return compare;
407 }
408
409
410
411
412 @Override
413 public int hashCode() {
414 final int prime = 31;
415 int result = 1;
416 result = prime * result
417 + ((onSubmit == null) ? 0 : onSubmit.hashCode());
418 result = prime
419 * result
420 + ((pattern.pattern() == null) ? 0 : pattern.pattern()
421 .hashCode());
422 result = prime * result + priority;
423 result = prime * result
424 + ((requestMethod == null) ? 0 : requestMethod.hashCode());
425 result = prime
426 * result
427 + ((uriParameterNames == null) ? 0 : uriParameterNames
428 .hashCode());
429 return result;
430 }
431
432
433
434
435 @Override
436 public boolean equals(final Object obj) {
437 if (this == obj) {
438 return true;
439 }
440 if (obj == null) {
441 return false;
442 }
443 if (getClass() != obj.getClass()) {
444 return false;
445 }
446 final RoutingKey other = (RoutingKey) obj;
447 if (onSubmit == null) {
448 if (other.onSubmit != null) {
449 return false;
450 }
451 } else if (!onSubmit.equals(other.onSubmit)) {
452 return false;
453 }
454 if (pattern == null) {
455 if (other.pattern != null) {
456 return false;
457 }
458 } else if (!pattern.pattern().equals(other.pattern.pattern())) {
459 return false;
460 }
461 if (priority != other.priority) {
462 return false;
463 }
464 if (requestMethod == null) {
465 if (other.requestMethod != null) {
466 return false;
467 }
468 } else if (!requestMethod.equals(other.requestMethod)) {
469 return false;
470 }
471 if (uriParameterNames == null) {
472 if (other.uriParameterNames != null) {
473 return false;
474 }
475 } else if (!uriParameterNames.equals(other.uriParameterNames)) {
476 return false;
477 }
478 return true;
479 }
480
481 }
482
483
484
485
486
487
488
489 static class ResolvedPathInfo implements PathInfo {
490
491
492 private final Map<String, String[]> uriParameters;
493
494
495 private final Map<String, Routing> routings;
496
497
498
499
500
501
502
503
504
505 public ResolvedPathInfo(final Map<String, String[]> uriParameters,
506 final Map<String, Routing> routings) {
507 this.uriParameters = uriParameters;
508 this.routings = routings;
509 }
510
511
512
513
514 public Map<String, String[]> getURIParameters() {
515 return uriParameters;
516 }
517
518
519
520
521 public Routing dispatch(final Map<String, Object[]> parameterMap) {
522 for (final Entry<String, Routing> entry : routings.entrySet()) {
523 if (parameterMap.containsKey(entry.getKey())) {
524 return entry.getValue();
525 }
526 }
527 return routings.get(null);
528 }
529
530 }
531
532 }