1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.seasar.cubby.routing.impl;
18
19 import static org.seasar.cubby.internal.util.LogMessages.format;
20
21 import java.io.UnsupportedEncodingException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.TreeMap;
30 import java.util.Map.Entry;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.seasar.cubby.action.Path;
35 import org.seasar.cubby.action.RequestMethod;
36 import org.seasar.cubby.internal.util.MetaUtils;
37 import org.seasar.cubby.internal.util.QueryStringBuilder;
38 import org.seasar.cubby.internal.util.URLBodyEncoder;
39 import org.seasar.cubby.routing.PathInfo;
40 import org.seasar.cubby.routing.PathResolver;
41 import org.seasar.cubby.routing.PathTemplateParser;
42 import org.seasar.cubby.routing.Routing;
43 import org.seasar.cubby.routing.RoutingException;
44 import org.seasar.cubby.util.ActionUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
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 final 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 static class RoutingKey implements Comparable<RoutingKey> {
340
341 private final int priority;
342
343 private final List<String> uriParameterNames;
344
345 private final Pattern pattern;
346
347 private final RequestMethod requestMethod;
348
349 private final String onSubmit;
350
351 public RoutingKey(final Routing routing) {
352 this.priority = routing.getPriority();
353 this.uriParameterNames = routing.getUriParameterNames();
354 this.pattern = routing.getPattern();
355 this.requestMethod = routing.getRequestMethod();
356 this.onSubmit = routing.getOnSubmit();
357 }
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public int compareTo(final RoutingKey another) {
378 int compare = this.priority - another.priority;
379 if (compare != 0) {
380 return compare;
381 }
382 compare = this.uriParameterNames.size()
383 - another.uriParameterNames.size();
384 if (compare != 0) {
385 return compare;
386 }
387 compare = this.pattern.pattern().compareTo(
388 another.pattern.pattern());
389 if (compare != 0) {
390 return compare;
391 }
392 compare = this.requestMethod.compareTo(another.requestMethod);
393 if (compare != 0) {
394 return compare;
395 }
396 if (this.onSubmit == another.onSubmit) {
397 compare = 0;
398 } else if (this.onSubmit == null) {
399 compare = -1;
400 } else if (another.onSubmit == null) {
401 compare = 1;
402 } else {
403 compare = this.onSubmit.compareTo(another.onSubmit);
404 }
405 return compare;
406 }
407
408
409
410
411 @Override
412 public int hashCode() {
413 final int prime = 31;
414 int result = 1;
415 result = prime * result
416 + ((onSubmit == null) ? 0 : onSubmit.hashCode());
417 result = prime
418 * result
419 + ((pattern.pattern() == null) ? 0 : pattern.pattern()
420 .hashCode());
421 result = prime * result + priority;
422 result = prime * result
423 + ((requestMethod == null) ? 0 : requestMethod.hashCode());
424 result = prime
425 * result
426 + ((uriParameterNames == null) ? 0 : uriParameterNames
427 .hashCode());
428 return result;
429 }
430
431
432
433
434 @Override
435 public boolean equals(final Object obj) {
436 if (this == obj) {
437 return true;
438 }
439 if (obj == null) {
440 return false;
441 }
442 if (getClass() != obj.getClass()) {
443 return false;
444 }
445 final RoutingKey other = (RoutingKey) obj;
446 if (onSubmit == null) {
447 if (other.onSubmit != null) {
448 return false;
449 }
450 } else if (!onSubmit.equals(other.onSubmit)) {
451 return false;
452 }
453 if (pattern == null) {
454 if (other.pattern != null) {
455 return false;
456 }
457 } else if (!pattern.pattern().equals(other.pattern.pattern())) {
458 return false;
459 }
460 if (priority != other.priority) {
461 return false;
462 }
463 if (requestMethod == null) {
464 if (other.requestMethod != null) {
465 return false;
466 }
467 } else if (!requestMethod.equals(other.requestMethod)) {
468 return false;
469 }
470 if (uriParameterNames == null) {
471 if (other.uriParameterNames != null) {
472 return false;
473 }
474 } else if (!uriParameterNames.equals(other.uriParameterNames)) {
475 return false;
476 }
477 return true;
478 }
479
480 }
481
482
483
484
485
486
487 static class ResolvedPathInfo implements PathInfo {
488
489
490 private final Map<String, String[]> uriParameters;
491
492
493 private final Map<String, Routing> routings;
494
495
496
497
498
499
500
501
502
503 public ResolvedPathInfo(final Map<String, String[]> uriParameters,
504 final Map<String, Routing> routings) {
505 this.uriParameters = uriParameters;
506 this.routings = routings;
507 }
508
509
510
511
512 public Map<String, String[]> getURIParameters() {
513 return uriParameters;
514 }
515
516
517
518
519 public Routing dispatch(final Map<String, Object[]> parameterMap) {
520 for (final Entry<String, Routing> entry : routings.entrySet()) {
521 if (parameterMap.containsKey(entry.getKey())) {
522 return entry.getValue();
523 }
524 }
525 return routings.get(null);
526 }
527
528 }
529
530 }