1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.seasar.cubby.convention.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.HashMap;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import javax.servlet.http.HttpServletRequest;
31
32 import org.seasar.cubby.action.Action;
33 import org.seasar.cubby.action.RequestMethod;
34 import org.seasar.cubby.convention.ForwardInfo;
35 import org.seasar.cubby.convention.PathResolver;
36 import org.seasar.cubby.util.CubbyUtils;
37 import org.seasar.cubby.util.QueryStringBuilder;
38 import org.seasar.framework.container.SingletonS2Container;
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.ClassUtil;
43 import org.seasar.framework.util.Disposable;
44 import org.seasar.framework.util.DisposableUtil;
45 import org.seasar.framework.util.StringUtil;
46
47
48
49
50
51
52
53 public class PathResolverImpl implements PathResolver, Disposable {
54
55 private static Pattern routingPattern = Pattern
56 .compile("([{]([^}]+)[}])([^{]*)");
57
58 private static final Logger logger = Logger
59 .getLogger(PathResolverImpl.class);
60
61 private boolean initialized;
62
63 private NamingConvention namingConvention;
64
65 private final Map<Pattern, RoutingInfo> routingPatterns = new LinkedHashMap<Pattern, RoutingInfo>();
66
67 private final Map<Pattern, RoutingInfo> customRoutingPatterns = new LinkedHashMap<Pattern, RoutingInfo>();
68
69 private String uriEncoding;
70
71 public PathResolverImpl() {
72 }
73
74 public void setUriEncoding(final String uriEncoding) {
75 this.uriEncoding = uriEncoding;
76 }
77
78 public void initialize() {
79 if (!initialized) {
80 routingPatterns.clear();
81 final ClassCollector classCollector = new ActionClassCollector();
82 classCollector.collect();
83
84 DisposableUtil.add(this);
85 initialized = true;
86 }
87 }
88
89 public void dispose() {
90 routingPatterns.clear();
91 initialized = false;
92 }
93
94 public void add(final String regexp,
95 final Class<? extends Action> actionClass, final String methodName,
96 final RequestMethod... requestMethods) {
97
98 final Method method = ClassUtil.getMethod(actionClass, methodName,
99 new Class<?>[0]);
100 this.add(regexp, actionClass, method, customRoutingPatterns,
101 requestMethods);
102 }
103
104 private void add(final String regexp,
105 final Class<? extends Action> actionClass, final Method method,
106 final Map<Pattern, RoutingInfo> patternToRoutingInfoMap,
107 final RequestMethod... requestMethods) {
108
109 String actionFullName = regexp;
110 final List<String> uriParameterNames = new ArrayList<String>();
111 final Matcher matcher = routingPattern.matcher(actionFullName);
112 while (matcher.find()) {
113 final String name = matcher.group(2);
114 final String[] names = name.split(",", 2);
115 if (names.length == 1) {
116 actionFullName = StringUtil.replace(actionFullName, matcher
117 .group(1), "([a-zA-Z0-9]+)");
118 uriParameterNames.add(matcher.group(2));
119 } else {
120 actionFullName = StringUtil.replace(actionFullName, matcher
121 .group(1), "(" + names[1] + ")");
122 uriParameterNames.add(names[0]);
123 }
124 }
125
126 final String forwardPath = this.fromActionClassToPath(actionClass,
127 method);
128
129 final RoutingInfo routingInfo = new RoutingInfo(actionClass, method,
130 uriParameterNames, forwardPath, requestMethods);
131 final Pattern pattern = Pattern.compile("^" + actionFullName + "$");
132
133 patternToRoutingInfoMap.put(pattern, routingInfo);
134 if (logger.isDebugEnabled()) {
135 logger.log("DCUB0007", new Object[] { actionFullName, method,
136 uriParameterNames });
137 }
138 }
139
140 private String fromActionClassToPath(
141 final Class<? extends Action> actionClass, final Method method) {
142 final String componentName = namingConvention
143 .fromClassNameToComponentName(actionClass.getCanonicalName());
144 final StringBuilder builder = new StringBuilder(100);
145 builder.append('/');
146 builder.append(componentName.substring(
147 0,
148 componentName.length()
149 - namingConvention.getActionSuffix().length())
150 .replaceAll("_", "/"));
151 builder.append('/');
152 builder.append(method.getName());
153 return builder.toString();
154 }
155
156 public ForwardInfo getForwardInfo(final String path) {
157 if (logger.isDebugEnabled()) {
158 logger.log("DCUB0006", new Object[] { path });
159 }
160
161 initialize();
162
163 final String decodedPath;
164 try {
165 decodedPath = URLDecoder.decode(path, uriEncoding);
166 } catch (final IOException e) {
167 throw new IORuntimeException(e);
168 }
169
170 ForwardInfo forwardInfo = findForwardInfo(decodedPath,
171 customRoutingPatterns);
172 if (forwardInfo == null) {
173 forwardInfo = findForwardInfo(decodedPath, routingPatterns);
174 }
175 return forwardInfo;
176 }
177
178 private ForwardInfo findForwardInfo(final String path,
179 final Map<Pattern, RoutingInfo> routingPatterns) {
180 final Map<String, String> uriParams = new HashMap<String, String>();
181 for (final Entry<Pattern, RoutingInfo> entry : routingPatterns
182 .entrySet()) {
183 final Matcher matcher = entry.getKey().matcher(path);
184 if (matcher.find()) {
185 final RoutingInfo routingInfo = entry.getValue();
186 final HttpServletRequest request = SingletonS2Container
187 .getComponent(HttpServletRequest.class);
188 if (routingInfo.isAcceptable(request)) {
189 for (int i = 1; i < matcher.groupCount() + 1; i++) {
190 final String name = routingInfo.getUriParameterNames()
191 .get(i - 1);
192 final String value = matcher.group(i);
193 uriParams.put(name, value);
194 }
195 final ForwardInfoImpl forwardInfo = new ForwardInfoImpl(
196 routingInfo, uriParams);
197 return forwardInfo;
198 }
199 }
200 }
201 return null;
202 }
203
204 public void setNamingConvention(final NamingConvention namingConvention) {
205 this.namingConvention = namingConvention;
206 }
207
208 class RoutingInfo {
209
210 private final Class<? extends Action> actionClass;
211
212 private final Method method;
213
214 private final List<String> uriParameterNames;
215
216 private final String forwardPath;
217
218 private final RequestMethod[] requestMethods;
219
220 public RoutingInfo(final Class<? extends Action> actionClass,
221 final Method method, final List<String> uriParameterNames,
222 final String forwardPath,
223 final RequestMethod[] requestMethods) {
224 this.actionClass = actionClass;
225 this.method = method;
226 this.uriParameterNames = uriParameterNames;
227 this.forwardPath = forwardPath;
228 this.requestMethods = requestMethods;
229 }
230
231 public String buildForwardPath(final Map<String, String> uriParams) {
232 final StringBuilder builder = new StringBuilder(100);
233 builder.append(forwardPath);
234 if (!uriParams.isEmpty()) {
235 builder.append("?");
236 final QueryStringBuilder query = new QueryStringBuilder();
237 final String encoding = PathResolverImpl.this.uriEncoding;
238 if (!StringUtil.isEmpty(encoding)) {
239 query.setEncode(encoding);
240 }
241 for (final Entry<String, String> entry : uriParams.entrySet()) {
242 query.addParam(entry.getKey(), entry.getValue());
243 }
244 builder.append(query.toString());
245 }
246 return builder.toString();
247 }
248
249 public Class<? extends Action> getActionClass() {
250 return actionClass;
251 }
252
253 public Method getMethod() {
254 return method;
255 }
256
257 public List<String> getUriParameterNames() {
258 return uriParameterNames;
259 }
260
261 public String getForwardPath() {
262 return forwardPath;
263 }
264
265 public RequestMethod[] getRequestMethods() {
266 return requestMethods;
267 }
268
269 public boolean isAcceptable(final HttpServletRequest request) {
270 final String requestMethod = request.getMethod();
271 for (final RequestMethod acceptableRequestMethod : requestMethods) {
272 if (StringUtil.equalsIgnoreCase(acceptableRequestMethod.name(),
273 requestMethod)) {
274 return true;
275 }
276 }
277 return false;
278 }
279
280 }
281
282 class ActionClassCollector extends ClassCollector {
283
284 public ActionClassCollector() {
285 super(namingConvention);
286 }
287
288 public void processClass(final String packageName,
289 final String shortClassName) {
290 if (shortClassName.indexOf('$') != -1) {
291 return;
292 }
293 final String className = ClassUtil.concatName(packageName,
294 shortClassName);
295 if (!namingConvention.isTargetClassName(className)) {
296 return;
297 }
298 if (!className.endsWith(namingConvention.getActionSuffix())) {
299 return;
300 }
301 final Class<? extends Action> clazz = classForName(className);
302 if (namingConvention.isSkipClass(clazz)) {
303 return;
304 }
305
306 for (final Method method : clazz.getMethods()) {
307 if (CubbyUtils.isActionMethod(method)) {
308 final String actionFullName = CubbyUtils.getActionUrl(
309 clazz, method);
310 final RequestMethod[] acceptableRequestMethods = CubbyUtils
311 .getAcceptableRequestMethods(clazz, method);
312 add(actionFullName, clazz, method, routingPatterns,
313 acceptableRequestMethods);
314 }
315 }
316 }
317
318 }
319
320 @SuppressWarnings("unchecked")
321 private static <T> Class<T> classForName(final String className) {
322 return ClassUtil.forName(className);
323 }
324
325 }