View Javadoc

1   /*
2    * Copyright 2004-2010 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  
17  package org.seasar.cubby.filter;
18  
19  import static org.seasar.cubby.CubbyConstants.ATTR_FILTER_CHAIN;
20  import static org.seasar.cubby.CubbyConstants.ATTR_PARAMS;
21  import static org.seasar.cubby.CubbyConstants.ATTR_ROUTING;
22  import static org.seasar.cubby.internal.util.LogMessages.format;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.StringTokenizer;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import javax.servlet.Filter;
35  import javax.servlet.FilterChain;
36  import javax.servlet.FilterConfig;
37  import javax.servlet.ServletException;
38  import javax.servlet.ServletRequest;
39  import javax.servlet.ServletResponse;
40  import javax.servlet.http.HttpServletRequest;
41  import javax.servlet.http.HttpServletResponse;
42  
43  import org.seasar.cubby.CubbyConstants;
44  import org.seasar.cubby.controller.MessagesBehaviour;
45  import org.seasar.cubby.internal.controller.ActionProcessor;
46  import org.seasar.cubby.internal.controller.ActionResultWrapper;
47  import org.seasar.cubby.internal.controller.ThreadContext;
48  import org.seasar.cubby.internal.controller.impl.ActionProcessorImpl;
49  import org.seasar.cubby.internal.util.RequestUtils;
50  import org.seasar.cubby.internal.util.StringUtils;
51  import org.seasar.cubby.plugin.Plugin;
52  import org.seasar.cubby.plugin.PluginRegistry;
53  import org.seasar.cubby.plugin.RequestProcessingInvocation;
54  import org.seasar.cubby.plugin.RoutingInvocation;
55  import org.seasar.cubby.routing.PathInfo;
56  import org.seasar.cubby.routing.PathResolver;
57  import org.seasar.cubby.routing.Routing;
58  import org.seasar.cubby.spi.ContainerProvider;
59  import org.seasar.cubby.spi.PathResolverProvider;
60  import org.seasar.cubby.spi.ProviderFactory;
61  import org.seasar.cubby.spi.RequestParserProvider;
62  import org.seasar.cubby.spi.container.Container;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  /**
67   * Cubby フィルター。
68   * <p>
69   * 要求を解析し、対応するアクションが登録されている場合はアクションを実行します。
70   * </p>
71   * 
72   * @author agata
73   * @author baba
74   */
75  public class CubbyFilter implements Filter {
76  
77  	/** ロガー */
78  	private static final Logger logger = LoggerFactory
79  			.getLogger(CubbyFilter.class);
80  
81  	/** ルーティングの対象外とするパスの初期パラメータ名。 */
82  	public static final String IGNORE_PATH_PATTERN = "ignorePathPattern";
83  
84  	/** ルーティングの対象外とするパスの正規表現パターンのリスト。 */
85  	private final List<Pattern> ignorePathPatterns = new ArrayList<Pattern>();
86  
87  	/**
88  	 * このフィルタを初期化します。
89  	 * <p>
90  	 * 使用可能な初期化パラメータ
91  	 * <table>
92  	 * <thead>
93  	 * <th>初期化パラメータ名</th>
94  	 * <th>初期化パラメータの値</th>
95  	 * <th>例</th>
96  	 * </thead> <tbody>
97  	 * <tr>
98  	 * <td>{@link #IGNORE_PATH_PATTERN}</td>
99  	 * <td>ルーティングの対象外とするパスの正規表現をカンマ区切りで指定します。 HotDeploy
100 	 * 時のパフォーマンスにも影響するので、画像やスクリプトを特定のディレクトリに
101 	 * 格納していてアクションを実行するパスと明確に区別できる場合はできる限り指定するようにしてください。</td>
102 	 * <td>
103 	 * 
104 	 * <pre>
105 	 * &lt;param-name&gt;ignorePathPattern&amp;lt/param-name&gt;
106 	 * &lt;param-value&gt;/img/.*,/js/.*&lt;param-name&gt;
107 	 * </pre>
108 	 * 
109 	 * この例では /img と /js 以下のパスをルーティングの対象外にします。</td>
110 	 * </tr>
111 	 * </tbody>
112 	 * </p>
113 	 * 
114 	 * @param config
115 	 *            フィルタ設定のためのオブジェクト
116 	 * @throws ServletException
117 	 *             初期化処理で例外が発生した場合
118 	 */
119 	public void init(final FilterConfig config) throws ServletException {
120 		final String ignorePathPatternString = config
121 				.getInitParameter(IGNORE_PATH_PATTERN);
122 		if (!StringUtils.isEmpty(ignorePathPatternString)) {
123 
124 			for (final StringTokenizer tokenizer = new StringTokenizer(
125 					ignorePathPatternString, ","); tokenizer.hasMoreTokens();) {
126 				final String token = tokenizer.nextToken();
127 				final Pattern pattern = Pattern.compile(token);
128 				ignorePathPatterns.add(pattern);
129 			}
130 		}
131 	}
132 
133 	/**
134 	 * {@inheritDoc}
135 	 */
136 	public void destroy() {
137 	}
138 
139 	/**
140 	 * フィルター処理を行います。
141 	 * <p>
142 	 * 要求された URI に対応する情報が
143 	 * {@link #routing(HttpServletRequest, HttpServletResponse, List)}
144 	 * から取得できた場合は、
145 	 * {@link #processRequest(HttpServletRequest, HttpServletResponse, PathInfo)}
146 	 * によって要求を処理します。URI に対応する情報が取得できなかった場合はフィルタチェインで次のフィルタに処理を委譲します。
147 	 * </p>
148 	 * 
149 	 * @param req
150 	 *            要求
151 	 * @param res
152 	 *            応答
153 	 * @param chain
154 	 *            フィルターチェーン
155 	 * @throws IOException
156 	 *             要求の転送や要求のチェーンがこの例外をスローする場合
157 	 * @throws ServletException
158 	 *             要求の転送や要求のチェーンがこの例外をスローする場合
159 	 */
160 	public void doFilter(final ServletRequest req, final ServletResponse res,
161 			final FilterChain chain) throws IOException, ServletException {
162 		final HttpServletRequest request = (HttpServletRequest) req;
163 		final HttpServletResponse response = (HttpServletResponse) res;
164 
165 		final String path = RequestUtils.getPath(request);
166 		if (logger.isDebugEnabled()) {
167 			logger.debug(format("DCUB0006", path));
168 		}
169 
170 		final PathInfo pathInfo = findPathInfo(request, response, path,
171 				ignorePathPatterns);
172 
173 		if (pathInfo != null) {
174 			request.setAttribute(ATTR_FILTER_CHAIN, chain);
175 			try {
176 				processRequest(request, response, pathInfo);
177 			} catch (final Exception e) {
178 				if (e instanceof IOException) {
179 					throw (IOException) e;
180 				} else if (e instanceof ServletException) {
181 					throw (ServletException) e;
182 				} else {
183 					throw new ServletException(e);
184 				}
185 			}
186 		} else {
187 			chain.doFilter(request, response);
188 		}
189 	}
190 
191 	/**
192 	 * {@link PathInfo} を検索します。
193 	 * <p>
194 	 * <ul>
195 	 * <li>リクエストの属性 {@link CubbyConstants#ATTR_ROUTING} に値({@link Routing}
196 	 * のインスタンス)が設定されている(= アクション間のフォワード)場合はその値をもとにした {@link PathInfo} を</li>
197 	 * <li>指定されたパスが処理対象外({@link #isIgnorePath(String, List)} == true)の場合は
198 	 * <code>null</code> を</li>
199 	 * <li>上記以外の場合は
200 	 * {@link #routing(HttpServletRequest, HttpServletResponse, String)} の戻り値を</li>
201 	 * </ul>
202 	 * それぞれ返します。
203 	 * </p>
204 	 * 
205 	 * @param request
206 	 *            要求
207 	 * @param response
208 	 *            応答
209 	 * @param path
210 	 *            パス
211 	 * @param ignorePathPatterns
212 	 *            除外するパスのパターンのリスト
213 	 * @return 検索した {@link PathInfo}
214 	 */
215 	protected PathInfo findPathInfo(final HttpServletRequest request,
216 			final HttpServletResponse response, final String path,
217 			List<Pattern> ignorePathPatterns) {
218 		final PathInfo pathInfo;
219 		final Routing routing = RequestUtils
220 				.getAttribute(request, ATTR_ROUTING);
221 		if (routing != null) {
222 			request.removeAttribute(ATTR_ROUTING);
223 			pathInfo = new ForwardFromActionPathInfo(routing);
224 		} else if (isIgnorePath(path, ignorePathPatterns)) {
225 			pathInfo = null;
226 		} else {
227 			final PathResolver pathResolver = createPathResolver();
228 			pathInfo = invokeRouting(request, response, path, pathResolver);
229 		}
230 		return pathInfo;
231 	}
232 
233 	/**
234 	 * 指定された path が ignorePathPatterns にマッチするかを示します。
235 	 * 
236 	 * @param path
237 	 *            パス
238 	 * @param ignorePathPatterns
239 	 *            対象外パターンのリスト
240 	 * @return path が ignorePathPatterns にマッチする場合は <code>true</code>、そうでない場合は
241 	 *         <code>false</code>
242 	 */
243 	private static boolean isIgnorePath(final String path,
244 			final List<Pattern> ignorePathPatterns) {
245 		for (final Pattern pattern : ignorePathPatterns) {
246 			final Matcher matcher = pattern.matcher(path);
247 			if (matcher.matches()) {
248 				return true;
249 			}
250 		}
251 		return false;
252 	}
253 
254 	/**
255 	 * 指定されたパスに対応するアクションを決定し、 {@link PathInfo} を返します。
256 	 * 
257 	 * @param request
258 	 *            要求
259 	 * @param response
260 	 *            応答
261 	 * @param path
262 	 *            パス
263 	 * @param pathResolver
264 	 *            パスのリゾルバ
265 	 * @return 要求に対応する内部フォワード情報、URI と要求メソッドに対応する内部フォワード情報がない場合や URI
266 	 *         が対象外とするパスのパターンにマッチする場合は <code>null</code>
267 	 */
268 	protected PathInfo invokeRouting(final HttpServletRequest request,
269 			final HttpServletResponse response, final String path,
270 			final PathResolver pathResolver) {
271 		final RoutingInvocation routingInvocation = new RoutingInvocationImpl(
272 				path, pathResolver, request, response);
273 		try {
274 			return routingInvocation.proceed();
275 		} catch (final Exception e) {
276 			logger.warn("routing failed.", e);
277 			return null;
278 		}
279 	}
280 
281 	/**
282 	 * 要求処理の実行情報の実装です。
283 	 * 
284 	 * @author baba
285 	 */
286 	static class RoutingInvocationImpl implements RoutingInvocation {
287 
288 		/** パス。 */
289 		private final String path;
290 
291 		/** パスのリゾルバ。 */
292 		private final PathResolver pathResolver;
293 
294 		/** 要求。 */
295 		private final HttpServletRequest request;
296 
297 		/** 応答。 */
298 		private final HttpServletResponse response;
299 
300 		/** プラグインのイテレータ。 */
301 		private final Iterator<Plugin> pluginsIterator;
302 
303 		/**
304 		 * インスタンス化します。
305 		 * 
306 		 * @param path
307 		 *            パス
308 		 * @param pathResolver
309 		 *            パスのリゾルバ
310 		 * @param request
311 		 *            要求
312 		 * @param response
313 		 *            応答
314 		 */
315 		public RoutingInvocationImpl(final String path,
316 				final PathResolver pathResolver,
317 				final HttpServletRequest request,
318 				final HttpServletResponse response) {
319 			this.path = path;
320 			this.pathResolver = pathResolver;
321 			this.request = request;
322 			this.response = response;
323 
324 			final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
325 			this.pluginsIterator = pluginRegistry.getPlugins().iterator();
326 		}
327 
328 		/**
329 		 * {@inheritDoc}
330 		 */
331 		public PathInfo proceed() throws Exception {
332 			if (pluginsIterator.hasNext()) {
333 				final Plugin plugin = pluginsIterator.next();
334 				return plugin.invokeRouting(this);
335 			} else {
336 				final PathInfo pathInfo = pathResolver.getPathInfo(path,
337 						request.getMethod(), request.getCharacterEncoding());
338 				return pathInfo;
339 			}
340 		}
341 
342 		/**
343 		 * {@inheritDoc}
344 		 */
345 		public String getPath() {
346 			return path;
347 		}
348 
349 		/**
350 		 * {@inheritDoc}
351 		 */
352 		public PathResolver getPathResolver() {
353 			return pathResolver;
354 		}
355 
356 		/**
357 		 * {@inheritDoc}
358 		 */
359 		public HttpServletRequest getRequest() {
360 			return request;
361 		}
362 
363 		/**
364 		 * {@inheritDoc}
365 		 */
366 		public HttpServletResponse getResponse() {
367 			return response;
368 		}
369 
370 	}
371 
372 	/**
373 	 * 指定された {@link PathInfo} に対する処理を行います。
374 	 * 
375 	 * @param request
376 	 *            要求
377 	 * @param response
378 	 *            応答
379 	 * @param pathInfo
380 	 *            パスの情報
381 	 * @throws Exception
382 	 *             要求の処理で例外が発生した場合
383 	 */
384 	protected void processRequest(final HttpServletRequest request,
385 			final HttpServletResponse response, final PathInfo pathInfo)
386 			throws Exception {
387 		final HttpServletRequest wrappedRequest = new CubbyHttpServletRequestWrapper(
388 				this, request, pathInfo.getURIParameters());
389 		final RequestProcessingInvocation invocation = new RequestProcessingInvocationImpl(
390 				this, wrappedRequest, response, pathInfo);
391 		invocation.proceed();
392 	}
393 
394 	/**
395 	 * 指定された要求のパラメータをパースして {@link Map} に変換します。
396 	 * 
397 	 * @param request
398 	 *            要求
399 	 * @return パース結果の {@link Map}
400 	 */
401 	protected Map<String, Object[]> parseRequest(
402 			final HttpServletRequest request) {
403 		final RequestParserProvider requestParserProvider = ProviderFactory
404 				.get(RequestParserProvider.class);
405 		final Map<String, Object[]> parameterMap = requestParserProvider
406 				.getParameterMap(request);
407 		return parameterMap;
408 	}
409 
410 	/**
411 	 * 要求処理の実行情報の実装です。
412 	 * 
413 	 * @author baba
414 	 */
415 	static class RequestProcessingInvocationImpl implements
416 			RequestProcessingInvocation {
417 
418 		/** CubbyFilter */
419 		private CubbyFilter cubbyFilter;
420 
421 		/** 要求。 */
422 		private final HttpServletRequest request;
423 
424 		/** 応答。 */
425 		private final HttpServletResponse response;
426 
427 		/** パスから取得した情報。 */
428 		private final PathInfo pathInfo;
429 
430 		/** プラグインのイテレータ。 */
431 		private final Iterator<Plugin> pluginsIterator;
432 
433 		/**
434 		 * インスタンス化します。
435 		 * 
436 		 * @param cubbyFilter
437 		 *            CubbyFilter
438 		 * @param request
439 		 *            要求
440 		 * @param response
441 		 *            応答
442 		 * @param pathInfo
443 		 *            パスから取得した情報
444 		 */
445 		public RequestProcessingInvocationImpl(final CubbyFilter cubbyFilter,
446 				final HttpServletRequest request,
447 				final HttpServletResponse response, final PathInfo pathInfo) {
448 			this.cubbyFilter = cubbyFilter;
449 			this.request = request;
450 			this.response = response;
451 			this.pathInfo = pathInfo;
452 
453 			final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
454 			this.pluginsIterator = pluginRegistry.getPlugins().iterator();
455 		}
456 
457 		/**
458 		 * {@inheritDoc}
459 		 */
460 		public Void proceed() throws Exception {
461 			if (pluginsIterator.hasNext()) {
462 				final Plugin plugin = pluginsIterator.next();
463 				plugin.invokeRequestProcessing(this);
464 			} else {
465 				final HttpServletRequest request = getRequest();
466 				final Map<String, Object[]> parameterMap = cubbyFilter
467 						.parseRequest(request);
468 				request.setAttribute(ATTR_PARAMS, parameterMap);
469 				final Routing routing = pathInfo.dispatch(parameterMap);
470 				ThreadContext.enter(request, response);
471 				try {
472 					final ActionProcessor actionProcessor = cubbyFilter
473 							.createActionProcessor();
474 					final ActionResultWrapper actionResultWrapper = actionProcessor
475 							.process(request, response, routing);
476 					actionResultWrapper.execute(request, response);
477 				} finally {
478 					ThreadContext.exit();
479 				}
480 			}
481 			return null;
482 		}
483 
484 		/**
485 		 * {@inheritDoc}
486 		 */
487 		public HttpServletRequest getRequest() {
488 			return request;
489 		}
490 
491 		/**
492 		 * {@inheritDoc}
493 		 */
494 		public HttpServletResponse getResponse() {
495 			return response;
496 		}
497 
498 		/**
499 		 * {@inheritDoc}
500 		 */
501 		public PathInfo getPathInfo() {
502 			return pathInfo;
503 		}
504 
505 	}
506 
507 	/**
508 	 * {@link PathResolver} を生成します。
509 	 * 
510 	 * @return {@link PathResolver}
511 	 */
512 	protected PathResolver createPathResolver() {
513 		final PathResolverProvider pathResolverProvider = ProviderFactory
514 				.get(PathResolverProvider.class);
515 		final PathResolver pathResolver = pathResolverProvider
516 				.getPathResolver();
517 		return pathResolver;
518 	}
519 
520 	/**
521 	 * {@link ActionProcessor} を生成します。
522 	 * 
523 	 * @return {@link ActionProcessor}
524 	 */
525 	protected ActionProcessor createActionProcessor() {
526 		final ActionProcessor actionProcessor = new ActionProcessorImpl();
527 		return actionProcessor;
528 	}
529 
530 	/**
531 	 * {@link MessagesBehaviour} を生成します。
532 	 * 
533 	 * @return {@link MessagesBehaviour}
534 	 */
535 	protected MessagesBehaviour createMessagesBehaviour() {
536 		final Container container = ProviderFactory
537 				.get(ContainerProvider.class).getContainer();
538 		final MessagesBehaviour messagesBehaviour = container
539 				.lookup(MessagesBehaviour.class);
540 		return messagesBehaviour;
541 	}
542 
543 	/**
544 	 * アクションからフォワードされてきたときに使用する {@link PathInfo} です。
545 	 * 
546 	 * @author baba
547 	 */
548 	static class ForwardFromActionPathInfo implements PathInfo {
549 
550 		private final Routing routing;
551 
552 		private final Map<String, String[]> uriParameters = Collections
553 				.emptyMap();
554 
555 		public ForwardFromActionPathInfo(final Routing routing) {
556 			this.routing = routing;
557 		}
558 
559 		/**
560 		 * {@inheritDoc}
561 		 */
562 		public Map<String, String[]> getURIParameters() {
563 			return uriParameters;
564 		}
565 
566 		/**
567 		 * {@inheritDoc}
568 		 */
569 		public Routing dispatch(final Map<String, Object[]> parameterMap) {
570 			return routing;
571 		}
572 
573 	}
574 
575 }