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