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 java.io.IOException;
19 import java.lang.reflect.Method;
20 import java.net.URLDecoder;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Comparator;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.TreeMap;
28 import java.util.Map.Entry;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.seasar.cubby.action.Action;
33 import org.seasar.cubby.action.RequestMethod;
34 import org.seasar.cubby.exception.DuplicateRoutingRuntimeException;
35 import org.seasar.cubby.routing.InternalForwardInfo;
36 import org.seasar.cubby.routing.PathResolver;
37 import org.seasar.cubby.util.CubbyUtils;
38 import org.seasar.cubby.util.QueryStringBuilder;
39 import org.seasar.framework.convention.NamingConvention;
40 import org.seasar.framework.exception.IORuntimeException;
41 import org.seasar.framework.log.Logger;
42 import org.seasar.framework.util.ArrayUtil;
43 import org.seasar.framework.util.ClassUtil;
44 import org.seasar.framework.util.Disposable;
45 import org.seasar.framework.util.DisposableUtil;
46 import org.seasar.framework.util.StringUtil;
47
48
49
50
51
52
53
54
55
56 public class PathResolverImpl implements PathResolver, Disposable {
57
58
59 private static final Logger logger = Logger
60 .getLogger(PathResolverImpl.class);
61
62
63 private static final String DEFAULT_URI_ENCODING = "UTF-8";
64
65
66 private static Pattern URI_PARAMETER_MATCHING_PATTERN = Pattern
67 .compile("([{]([^}]+)[}])([^{]*)");
68
69
70 private static final String DEFAULT_URI_PARAMETER_REGEX = "[a-zA-Z0-9]+";
71
72
73 private boolean initialized;
74
75
76 private NamingConvention namingConvention;
77
78
79 private final Comparator<Routing> routingComparator = new RoutingComparator();
80
81
82 private final Map<Routing, Routing> routings = new TreeMap<Routing, Routing>(
83 routingComparator);
84
85
86 private String uriEncoding = DEFAULT_URI_ENCODING;
87
88
89
90
91 public PathResolverImpl() {
92 }
93
94
95
96
97
98
99
100 public void setUriEncoding(final String uriEncoding) {
101 this.uriEncoding = uriEncoding;
102 }
103
104
105
106
107 public void initialize() {
108 if (!initialized) {
109 final ClassCollector classCollector = new ActionClassCollector();
110 classCollector.collect();
111
112 DisposableUtil.add(this);
113 initialized = true;
114 }
115 }
116
117
118
119
120 public void dispose() {
121 final List<Routing> removes = new ArrayList<Routing>();
122 for (final Routing routing : routings.keySet()) {
123 if (routing.isAuto()) {
124 removes.add(routing);
125 }
126 }
127 for (final Routing routing : removes) {
128 routings.remove(routing);
129 }
130 initialized = false;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 public void add(final String actionPath,
149 final Class<? extends Action> actionClass, final String methodName,
150 final RequestMethod... requestMethods) {
151
152 final Method method = ClassUtil.getMethod(actionClass, methodName,
153 new Class<?>[0]);
154 this.add(actionPath, actionClass, method, requestMethods, false);
155 }
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171 private void add(final String actionPath,
172 final Class<? extends Action> actionClass, final Method method,
173 final RequestMethod[] requestMethods, final boolean auto) {
174
175 String uriRegex = actionPath;
176 final List<String> uriParameterNames = new ArrayList<String>();
177 final Matcher matcher = URI_PARAMETER_MATCHING_PATTERN
178 .matcher(uriRegex);
179 while (matcher.find()) {
180 final String holder = matcher.group(2);
181 final String[] tokens = CubbyUtils.split2(holder, ',');
182 uriParameterNames.add(tokens[0]);
183 final String uriParameterRegex;
184 if (tokens.length == 1) {
185 uriParameterRegex = DEFAULT_URI_PARAMETER_REGEX;
186 } else {
187 uriParameterRegex = tokens[1];
188 }
189 uriRegex = StringUtil.replace(uriRegex, matcher.group(1),
190 regexGroup(uriParameterRegex));
191 }
192 uriRegex = "^" + uriRegex + "$";
193 final Pattern pattern = Pattern.compile(uriRegex);
194
195 final Routing routing = new Routing(actionClass, method,
196 uriParameterNames, pattern, requestMethods, auto);
197
198 if (routings.containsKey(routing)) {
199 final Routing duplication = routings.get(routing);
200 if (!routing.getActionClass().equals(duplication.getActionClass())
201 || !routing.getMethod().equals(duplication.getMethod())) {
202 throw new DuplicateRoutingRuntimeException("ECUB0001",
203 new Object[] { routing, duplication });
204 }
205 } else {
206 routings.put(routing, routing);
207 if (logger.isDebugEnabled()) {
208 logger.log("DCUB0007", new Object[] { routing });
209 }
210 }
211 }
212
213
214
215
216 public InternalForwardInfo getInternalForwardInfo(final String path,
217 final String requestMethod) {
218 initialize();
219
220 final String decodedPath;
221 try {
222 decodedPath = URLDecoder.decode(path, uriEncoding);
223 } catch (final IOException e) {
224 throw new IORuntimeException(e);
225 }
226
227 final InternalForwardInfo internalForwardInfo = findInternalForwardInfo(
228 decodedPath, requestMethod);
229 return internalForwardInfo;
230 }
231
232
233
234
235
236
237
238
239
240
241 private InternalForwardInfo findInternalForwardInfo(final String path,
242 final String requestMethod) {
243 final Map<String, String> uriParameters = new HashMap<String, String>();
244 for (final Routing routing : routings.values()) {
245 final Matcher matcher = routing.getPattern().matcher(path);
246 if (matcher.find()) {
247 if (routing.isAcceptable(requestMethod)) {
248 for (int i = 0; i < matcher.groupCount(); i++) {
249 final String name = routing.getUriParameterNames().get(
250 i);
251 final String value = matcher.group(i + 1);
252 uriParameters.put(name, value);
253 }
254 final String inernalFowardPath = buildInternalForwardPathWithQueryString(
255 routing, uriParameters);
256 final InternalForwardInfoImpl internalForwardInfo = new InternalForwardInfoImpl(
257 inernalFowardPath, routing, uriParameters);
258 return internalForwardInfo;
259 }
260 }
261 }
262 return null;
263 }
264
265
266
267
268
269
270
271
272
273
274 private String buildInternalForwardPathWithQueryString(
275 final Routing routing, final Map<String, String> uriParameters) {
276 final StringBuilder builder = new StringBuilder(100);
277 builder.append(CubbyUtils.getInternalForwardPath(routing
278 .getActionClass(), routing.getMethod().getName()));
279 if (!uriParameters.isEmpty()) {
280 builder.append("?");
281 final QueryStringBuilder query = new QueryStringBuilder();
282 final String encoding = PathResolverImpl.this.uriEncoding;
283 if (!StringUtil.isEmpty(encoding)) {
284 query.setEncode(encoding);
285 }
286 for (final Entry<String, String> entry : uriParameters.entrySet()) {
287 query.addParam(entry.getKey(), entry.getValue());
288 }
289 builder.append(query.toString());
290 }
291 return builder.toString();
292 }
293
294
295
296
297
298
299
300 public void setNamingConvention(final NamingConvention namingConvention) {
301 this.namingConvention = namingConvention;
302 }
303
304
305
306
307
308
309
310
311 private static String regexGroup(final String regex) {
312 return "(" + regex + ")";
313 }
314
315
316
317
318
319
320 static class RoutingComparator implements Comparator<Routing> {
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341 public int compare(final Routing routing1, final Routing routing2) {
342 int compare = routing1.getUriParameterNames().size()
343 - routing2.getUriParameterNames().size();
344 if (compare != 0) {
345 return compare;
346 }
347 compare = routing1.getPattern().pattern().compareTo(
348 routing2.getPattern().pattern());
349 if (compare != 0) {
350 return compare;
351 }
352 final RequestMethod[] requestMethods1 = routing1
353 .getRequestMethods();
354 final RequestMethod[] requestMethods2 = routing2
355 .getRequestMethods();
356 for (final RequestMethod requestMethod : requestMethods1) {
357 if (ArrayUtil.contains(requestMethods2, requestMethod)) {
358 return 0;
359 }
360 }
361 return 1;
362 }
363 }
364
365
366
367
368
369
370
371 static class Routing {
372
373
374 private final Class<? extends Action> actionClass;
375
376
377 private final Method method;
378
379
380 private final List<String> uriParameterNames;
381
382
383 private final Pattern pattern;
384
385
386 private final RequestMethod[] requestMethods;
387
388
389 private final boolean auto;
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 public Routing(final Class<? extends Action> actionClass,
408 final Method method, final List<String> uriParameterNames,
409 final Pattern pattern, final RequestMethod[] requestMethods,
410 final boolean auto) {
411 this.actionClass = actionClass;
412 this.method = method;
413 this.uriParameterNames = uriParameterNames;
414 this.pattern = pattern;
415 this.requestMethods = requestMethods;
416 this.auto = auto;
417 }
418
419
420
421
422
423
424 public Class<? extends Action> getActionClass() {
425 return actionClass;
426 }
427
428
429
430
431
432
433 public Method getMethod() {
434 return method;
435 }
436
437
438
439
440
441
442 public List<String> getUriParameterNames() {
443 return uriParameterNames;
444 }
445
446
447
448
449
450
451 public Pattern getPattern() {
452 return pattern;
453 }
454
455
456
457
458
459
460 public RequestMethod[] getRequestMethods() {
461 return requestMethods;
462 }
463
464
465
466
467
468
469
470 public boolean isAuto() {
471 return auto;
472 }
473
474
475
476
477
478
479
480
481 public boolean isAcceptable(final String requestMethod) {
482 for (final RequestMethod acceptableRequestMethod : requestMethods) {
483 if (StringUtil.equalsIgnoreCase(acceptableRequestMethod.name(),
484 requestMethod)) {
485 return true;
486 }
487 }
488 return false;
489 }
490
491
492
493
494
495
496 @Override
497 public String toString() {
498 return new StringBuilder().append("[regex=").append(this.pattern)
499 .append(",method=").append(this.method).append(
500 ",uriParameterNames=").append(uriParameterNames)
501 .append(",requestMethods=").append(
502 Arrays.deepToString(requestMethods)).append("]")
503 .toString();
504 }
505 }
506
507
508
509
510
511
512 class ActionClassCollector extends ClassCollector {
513
514
515
516
517 public ActionClassCollector() {
518 super(namingConvention);
519 }
520
521
522
523
524
525
526
527
528
529 public void processClass(final String packageName,
530 final String shortClassName) {
531 if (shortClassName.indexOf('$') != -1) {
532 return;
533 }
534 final String className = ClassUtil.concatName(packageName,
535 shortClassName);
536 if (!namingConvention.isTargetClassName(className)) {
537 return;
538 }
539 if (!className.endsWith(namingConvention.getActionSuffix())) {
540 return;
541 }
542 final Class<? extends Action> clazz = classForName(className);
543 if (!CubbyUtils.isActionClass(clazz)) {
544 return;
545 }
546 if (namingConvention.isSkipClass(clazz)) {
547 return;
548 }
549
550 for (final Method method : clazz.getMethods()) {
551 if (CubbyUtils.isActionMethod(method)) {
552 final String actionPath = CubbyUtils.getActionPath(clazz,
553 method);
554 final RequestMethod[] acceptableRequestMethods = CubbyUtils
555 .getAcceptableRequestMethods(clazz, method);
556 add(actionPath, clazz, method, acceptableRequestMethods,
557 true);
558 }
559 }
560 }
561
562 }
563
564
565
566
567
568
569
570
571
572
573 @SuppressWarnings("unchecked")
574 private static <T> Class<T> classForName(final String className) {
575 return ClassUtil.forName(className);
576 }
577
578 }