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