View Javadoc

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   * クラスパスから {@link Action} を検索し、そのメソッドに指定された {@link org.seasae.cubby.action.Url}
30   * の情報によって、リクエストされたURLをどのメソッドに振り分けるかを決定します。
31   * 
32   * @author baba
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 }