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