View Javadoc

1   /*
2    * Copyright 2004-2008 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
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   * クラスパスから {@link Action} を検索し、そのメソッドに指定された {@link org.seasar.cubby.action.Path}
49   * の情報によって、リクエストされたパスをどのメソッドに振り分けるかを決定します。
50   * 
51   * @author baba
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 }