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