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