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.*;
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 			setupWrapeeRequest(request);
175 			request.setAttribute(ATTR_FILTER_CHAIN, chain);
176 			try {
177 				processRequest(request, response, pathInfo);
178 			} catch (final Exception e) {
179 				if (e instanceof IOException) {
180 					throw (IOException) e;
181 				} else if (e instanceof ServletException) {
182 					throw (ServletException) e;
183 				} else {
184 					throw new ServletException(e);
185 				}
186 			}
187 		} else {
188 			chain.doFilter(request, response);
189 		}
190 	}
191 
192 	private void setupWrapeeRequest(final HttpServletRequest request) {
193 		final HttpServletRequest wrapeeRequest = (HttpServletRequest) request
194 				.getAttribute(ATTR_WRAPEE_REQUEST);
195 		if (wrapeeRequest == null) {
196 			request.setAttribute(ATTR_WRAPEE_REQUEST, request);
197 		}
198 	}
199 
200 	/**
201 	 * {@link PathInfo} を検索します。
202 	 * <p>
203 	 * <ul>
204 	 * <li>リクエストの属性 {@link CubbyConstants#ATTR_ROUTING} に値({@link Routing}
205 	 * のインスタンス)が設定されている(= アクション間のフォワード)場合はその値をもとにした {@link PathInfo} を</li>
206 	 * <li>指定されたパスが処理対象外({@link #isIgnorePath(String, List)} == true)の場合は
207 	 * <code>null</code> を</li>
208 	 * <li>上記以外の場合は
209 	 * {@link #routing(HttpServletRequest, HttpServletResponse, String)} の戻り値を</li>
210 	 * </ul>
211 	 * それぞれ返します。
212 	 * </p>
213 	 * 
214 	 * @param request
215 	 *            要求
216 	 * @param response
217 	 *            応答
218 	 * @param path
219 	 *            パス
220 	 * @param ignorePathPatterns
221 	 *            除外するパスのパターンのリスト
222 	 * @return 検索した {@link PathInfo}
223 	 */
224 	protected PathInfo findPathInfo(final HttpServletRequest request,
225 			final HttpServletResponse response, final String path,
226 			List<Pattern> ignorePathPatterns) {
227 		final PathInfo pathInfo;
228 		final Routing routing = RequestUtils
229 				.getAttribute(request, ATTR_ROUTING);
230 		if (routing != null) {
231 			request.removeAttribute(ATTR_ROUTING);
232 			pathInfo = new ForwardFromActionPathInfo(routing);
233 		} else if (isIgnorePath(path, ignorePathPatterns)) {
234 			pathInfo = null;
235 		} else {
236 			final PathResolver pathResolver = createPathResolver();
237 			pathInfo = invokeRouting(request, response, path, pathResolver);
238 		}
239 		return pathInfo;
240 	}
241 
242 	/**
243 	 * 指定された path が ignorePathPatterns にマッチするかを示します。
244 	 * 
245 	 * @param path
246 	 *            パス
247 	 * @param ignorePathPatterns
248 	 *            対象外パターンのリスト
249 	 * @return path が ignorePathPatterns にマッチする場合は <code>true</code>、そうでない場合は
250 	 *         <code>false</code>
251 	 */
252 	private static boolean isIgnorePath(final String path,
253 			final List<Pattern> ignorePathPatterns) {
254 		for (final Pattern pattern : ignorePathPatterns) {
255 			final Matcher matcher = pattern.matcher(path);
256 			if (matcher.matches()) {
257 				return true;
258 			}
259 		}
260 		return false;
261 	}
262 
263 	/**
264 	 * 指定されたパスに対応するアクションを決定し、 {@link PathInfo} を返します。
265 	 * 
266 	 * @param request
267 	 *            要求
268 	 * @param response
269 	 *            応答
270 	 * @param path
271 	 *            パス
272 	 * @param pathResolver
273 	 *            パスのリゾルバ
274 	 * @return 要求に対応する内部フォワード情報、URI と要求メソッドに対応する内部フォワード情報がない場合や URI
275 	 *         が対象外とするパスのパターンにマッチする場合は <code>null</code>
276 	 */
277 	protected PathInfo invokeRouting(final HttpServletRequest request,
278 			final HttpServletResponse response, final String path,
279 			final PathResolver pathResolver) {
280 		final RoutingInvocation routingInvocation = new RoutingInvocationImpl(
281 				path, pathResolver, request, response);
282 		try {
283 			return routingInvocation.proceed();
284 		} catch (final Exception e) {
285 			logger.warn("routing failed.", e);
286 			return null;
287 		}
288 	}
289 
290 	/**
291 	 * 要求処理の実行情報の実装です。
292 	 * 
293 	 * @author baba
294 	 */
295 	static class RoutingInvocationImpl implements RoutingInvocation {
296 
297 		/** パス。 */
298 		private final String path;
299 
300 		/** パスのリゾルバ。 */
301 		private final PathResolver pathResolver;
302 
303 		/** 要求。 */
304 		private final HttpServletRequest request;
305 
306 		/** 応答。 */
307 		private final HttpServletResponse response;
308 
309 		/** プラグインのイテレータ。 */
310 		private final Iterator<Plugin> pluginsIterator;
311 
312 		/**
313 		 * インスタンス化します。
314 		 * 
315 		 * @param path
316 		 *            パス
317 		 * @param pathResolver
318 		 *            パスのリゾルバ
319 		 * @param request
320 		 *            要求
321 		 * @param response
322 		 *            応答
323 		 */
324 		public RoutingInvocationImpl(final String path,
325 				final PathResolver pathResolver,
326 				final HttpServletRequest request,
327 				final HttpServletResponse response) {
328 			this.path = path;
329 			this.pathResolver = pathResolver;
330 			this.request = request;
331 			this.response = response;
332 
333 			final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
334 			this.pluginsIterator = pluginRegistry.getPlugins().iterator();
335 		}
336 
337 		/**
338 		 * {@inheritDoc}
339 		 */
340 		public PathInfo proceed() throws Exception {
341 			if (pluginsIterator.hasNext()) {
342 				final Plugin plugin = pluginsIterator.next();
343 				return plugin.invokeRouting(this);
344 			} else {
345 				final PathInfo pathInfo = pathResolver.getPathInfo(path,
346 						request.getMethod(), request.getCharacterEncoding());
347 				return pathInfo;
348 			}
349 		}
350 
351 		/**
352 		 * {@inheritDoc}
353 		 */
354 		public String getPath() {
355 			return path;
356 		}
357 
358 		/**
359 		 * {@inheritDoc}
360 		 */
361 		public PathResolver getPathResolver() {
362 			return pathResolver;
363 		}
364 
365 		/**
366 		 * {@inheritDoc}
367 		 */
368 		public HttpServletRequest getRequest() {
369 			return request;
370 		}
371 
372 		/**
373 		 * {@inheritDoc}
374 		 */
375 		public HttpServletResponse getResponse() {
376 			return response;
377 		}
378 
379 	}
380 
381 	/**
382 	 * 指定された {@link PathInfo} に対する処理を行います。
383 	 * 
384 	 * @param request
385 	 *            要求
386 	 * @param response
387 	 *            応答
388 	 * @param pathInfo
389 	 *            パスの情報
390 	 * @throws Exception
391 	 *             要求の処理で例外が発生した場合
392 	 */
393 	protected void processRequest(final HttpServletRequest request,
394 			final HttpServletResponse response, final PathInfo pathInfo)
395 			throws Exception {
396 		final HttpServletRequest wrappedRequest = new CubbyHttpServletRequestWrapper(
397 				this, request, pathInfo.getURIParameters());
398 		final RequestProcessingInvocation invocation = new RequestProcessingInvocationImpl(
399 				this, wrappedRequest, response, pathInfo);
400 		invocation.proceed();
401 	}
402 
403 	/**
404 	 * 指定された要求のパラメータをパースして {@link Map} に変換します。
405 	 * 
406 	 * @param request
407 	 *            要求
408 	 * @return パース結果の {@link Map}
409 	 */
410 	protected Map<String, Object[]> parseRequest(
411 			final HttpServletRequest request) {
412 		final RequestParserProvider requestParserProvider = ProviderFactory
413 				.get(RequestParserProvider.class);
414 		final Map<String, Object[]> parameterMap = requestParserProvider
415 				.getParameterMap(request);
416 		return parameterMap;
417 	}
418 
419 	/**
420 	 * 要求処理の実行情報の実装です。
421 	 * 
422 	 * @author baba
423 	 */
424 	static class RequestProcessingInvocationImpl implements
425 			RequestProcessingInvocation {
426 
427 		/** CubbyFilter */
428 		private CubbyFilter cubbyFilter;
429 
430 		/** 要求。 */
431 		private final HttpServletRequest request;
432 
433 		/** 応答。 */
434 		private final HttpServletResponse response;
435 
436 		/** パスから取得した情報。 */
437 		private final PathInfo pathInfo;
438 
439 		/** プラグインのイテレータ。 */
440 		private final Iterator<Plugin> pluginsIterator;
441 
442 		/**
443 		 * インスタンス化します。
444 		 * 
445 		 * @param cubbyFilter
446 		 *            CubbyFilter
447 		 * @param request
448 		 *            要求
449 		 * @param response
450 		 *            応答
451 		 * @param pathInfo
452 		 *            パスから取得した情報
453 		 */
454 		public RequestProcessingInvocationImpl(final CubbyFilter cubbyFilter,
455 				final HttpServletRequest request,
456 				final HttpServletResponse response, final PathInfo pathInfo) {
457 			this.cubbyFilter = cubbyFilter;
458 			this.request = request;
459 			this.response = response;
460 			this.pathInfo = pathInfo;
461 
462 			final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
463 			this.pluginsIterator = pluginRegistry.getPlugins().iterator();
464 		}
465 
466 		/**
467 		 * {@inheritDoc}
468 		 */
469 		public Void proceed() throws Exception {
470 			if (pluginsIterator.hasNext()) {
471 				final Plugin plugin = pluginsIterator.next();
472 				plugin.invokeRequestProcessing(this);
473 			} else {
474 				final HttpServletRequest request = getRequest();
475 				final Map<String, Object[]> parameterMap = cubbyFilter
476 						.parseRequest(request);
477 				request.setAttribute(ATTR_PARAMS, parameterMap);
478 				final Routing routing = pathInfo.dispatch(parameterMap);
479 				ThreadContext.enter(request, response);
480 				try {
481 					final ActionProcessor actionProcessor = cubbyFilter
482 							.createActionProcessor();
483 					final ActionResultWrapper actionResultWrapper = actionProcessor
484 							.process(request, response, routing);
485 					actionResultWrapper.execute(request, response);
486 				} finally {
487 					ThreadContext.exit();
488 				}
489 			}
490 			return null;
491 		}
492 
493 		/**
494 		 * {@inheritDoc}
495 		 */
496 		public HttpServletRequest getRequest() {
497 			return request;
498 		}
499 
500 		/**
501 		 * {@inheritDoc}
502 		 */
503 		public HttpServletResponse getResponse() {
504 			return response;
505 		}
506 
507 		/**
508 		 * {@inheritDoc}
509 		 */
510 		public PathInfo getPathInfo() {
511 			return pathInfo;
512 		}
513 
514 	}
515 
516 	/**
517 	 * {@link PathResolver} を生成します。
518 	 * 
519 	 * @return {@link PathResolver}
520 	 */
521 	protected PathResolver createPathResolver() {
522 		final PathResolverProvider pathResolverProvider = ProviderFactory
523 				.get(PathResolverProvider.class);
524 		final PathResolver pathResolver = pathResolverProvider
525 				.getPathResolver();
526 		return pathResolver;
527 	}
528 
529 	/**
530 	 * {@link ActionProcessor} を生成します。
531 	 * 
532 	 * @return {@link ActionProcessor}
533 	 */
534 	protected ActionProcessor createActionProcessor() {
535 		final ActionProcessor actionProcessor = new ActionProcessorImpl();
536 		return actionProcessor;
537 	}
538 
539 	/**
540 	 * {@link MessagesBehaviour} を生成します。
541 	 * 
542 	 * @return {@link MessagesBehaviour}
543 	 */
544 	protected MessagesBehaviour createMessagesBehaviour() {
545 		final Container container = ProviderFactory
546 				.get(ContainerProvider.class).getContainer();
547 		final MessagesBehaviour messagesBehaviour = container
548 				.lookup(MessagesBehaviour.class);
549 		return messagesBehaviour;
550 	}
551 
552 	/**
553 	 * アクションからフォワードされてきたときに使用する {@link PathInfo} です。
554 	 * 
555 	 * @author baba
556 	 */
557 	static class ForwardFromActionPathInfo implements PathInfo {
558 
559 		private final Routing routing;
560 
561 		private final Map<String, String[]> uriParameters = Collections
562 				.emptyMap();
563 
564 		public ForwardFromActionPathInfo(final Routing routing) {
565 			this.routing = routing;
566 		}
567 
568 		/**
569 		 * {@inheritDoc}
570 		 */
571 		public Map<String, String[]> getURIParameters() {
572 			return uriParameters;
573 		}
574 
575 		/**
576 		 * {@inheritDoc}
577 		 */
578 		public Routing dispatch(final Map<String, Object[]> parameterMap) {
579 			return routing;
580 		}
581 
582 	}
583 
584 }