Coverage Report - org.seasar.cubby.filter.CubbyFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
CubbyFilter
86%
58/67
70%
14/20
1.8
CubbyFilter$ForwardFromActionPathInfo
83%
5/6
N/A
1.8
CubbyFilter$RequestProcessingInvocationImpl
92%
25/27
100%
2/2
1.8
CubbyFilter$RoutingInvocationImpl
77%
14/18
100%
2/2
1.8
 
 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  13
 public class CubbyFilter implements Filter {
 76  
 
 77  
         /** ロガー */
 78  1
         private static final Logger logger = LoggerFactory
 79  
                         .getLogger(CubbyFilter.class);
 80  
 
 81  
         /** ルーティングの対象外とするパスの初期パラメータ名。 */
 82  
         public static final String IGNORE_PATH_PATTERN = "ignorePathPattern";
 83  
 
 84  
         /** ルーティングの対象外とするパスの正規表現パターンのリスト。 */
 85  13
         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  2
                 final String ignorePathPatternString = config
 121  
                                 .getInitParameter(IGNORE_PATH_PATTERN);
 122  2
                 if (!StringUtils.isEmpty(ignorePathPatternString)) {
 123  
 
 124  2
                         for (final StringTokenizer tokenizer = new StringTokenizer(
 125  6
                                         ignorePathPatternString, ","); tokenizer.hasMoreTokens();) {
 126  4
                                 final String token = tokenizer.nextToken();
 127  4
                                 final Pattern pattern = Pattern.compile(token);
 128  4
                                 ignorePathPatterns.add(pattern);
 129  4
                         }
 130  
                 }
 131  2
         }
 132  
 
 133  
         /**
 134  
          * {@inheritDoc}
 135  
          */
 136  
         public void destroy() {
 137  2
         }
 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  2
                 final HttpServletRequest request = (HttpServletRequest) req;
 163  2
                 final HttpServletResponse response = (HttpServletResponse) res;
 164  
 
 165  2
                 final String path = RequestUtils.getPath(request);
 166  2
                 if (logger.isDebugEnabled()) {
 167  2
                         logger.debug(format("DCUB0006", path));
 168  
                 }
 169  
 
 170  2
                 final PathInfo pathInfo = findPathInfo(request, response, path,
 171  
                                 ignorePathPatterns);
 172  
 
 173  2
                 if (pathInfo != null) {
 174  1
                         request.setAttribute(ATTR_FILTER_CHAIN, chain);
 175  
                         try {
 176  1
                                 processRequest(request, response, pathInfo);
 177  0
                         } catch (final Exception e) {
 178  0
                                 if (e instanceof IOException) {
 179  0
                                         throw (IOException) e;
 180  0
                                 } else if (e instanceof ServletException) {
 181  0
                                         throw (ServletException) e;
 182  
                                 } else {
 183  0
                                         throw new ServletException(e);
 184  
                                 }
 185  1
                         }
 186  
                 } else {
 187  1
                         chain.doFilter(request, response);
 188  
                 }
 189  2
         }
 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  5
                 final Routing routing = RequestUtils
 220  
                                 .getAttribute(request, ATTR_ROUTING);
 221  5
                 if (routing != null) {
 222  1
                         request.removeAttribute(ATTR_ROUTING);
 223  1
                         pathInfo = new ForwardFromActionPathInfo(routing);
 224  4
                 } else if (isIgnorePath(path, ignorePathPatterns)) {
 225  1
                         pathInfo = null;
 226  
                 } else {
 227  3
                         final PathResolver pathResolver = createPathResolver();
 228  3
                         pathInfo = invokeRouting(request, response, path, pathResolver);
 229  
                 }
 230  5
                 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  4
                 for (final Pattern pattern : ignorePathPatterns) {
 246  5
                         final Matcher matcher = pattern.matcher(path);
 247  5
                         if (matcher.matches()) {
 248  1
                                 return true;
 249  
                         }
 250  4
                 }
 251  3
                 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  3
                 final RoutingInvocation routingInvocation = new RoutingInvocationImpl(
 272  
                                 path, pathResolver, request, response);
 273  
                 try {
 274  3
                         return routingInvocation.proceed();
 275  0
                 } catch (final Exception e) {
 276  0
                         logger.warn("routing failed.", e);
 277  0
                         return null;
 278  
                 }
 279  
         }
 280  
 
 281  
         /**
 282  
          * 要求処理の実行情報の実装です。
 283  
          * 
 284  
          * @author baba
 285  
          */
 286  4
         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  3
                                 final HttpServletResponse response) {
 319  3
                         this.path = path;
 320  3
                         this.pathResolver = pathResolver;
 321  3
                         this.request = request;
 322  3
                         this.response = response;
 323  
 
 324  3
                         final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
 325  3
                         this.pluginsIterator = pluginRegistry.getPlugins().iterator();
 326  3
                 }
 327  
 
 328  
                 /**
 329  
                  * {@inheritDoc}
 330  
                  */
 331  
                 public PathInfo proceed() throws Exception {
 332  4
                         if (pluginsIterator.hasNext()) {
 333  1
                                 final Plugin plugin = pluginsIterator.next();
 334  1
                                 return plugin.invokeRouting(this);
 335  
                         } else {
 336  3
                                 final PathInfo pathInfo = pathResolver.getPathInfo(path,
 337  
                                                 request.getMethod(), request.getCharacterEncoding());
 338  3
                                 return pathInfo;
 339  
                         }
 340  
                 }
 341  
 
 342  
                 /**
 343  
                  * {@inheritDoc}
 344  
                  */
 345  
                 public String getPath() {
 346  0
                         return path;
 347  
                 }
 348  
 
 349  
                 /**
 350  
                  * {@inheritDoc}
 351  
                  */
 352  
                 public PathResolver getPathResolver() {
 353  0
                         return pathResolver;
 354  
                 }
 355  
 
 356  
                 /**
 357  
                  * {@inheritDoc}
 358  
                  */
 359  
                 public HttpServletRequest getRequest() {
 360  0
                         return request;
 361  
                 }
 362  
 
 363  
                 /**
 364  
                  * {@inheritDoc}
 365  
                  */
 366  
                 public HttpServletResponse getResponse() {
 367  0
                         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  2
                 final HttpServletRequest wrappedRequest = new CubbyHttpServletRequestWrapper(
 388  
                                 this, request, pathInfo.getURIParameters());
 389  2
                 final RequestProcessingInvocation invocation = new RequestProcessingInvocationImpl(
 390  
                                 this, wrappedRequest, response, pathInfo);
 391  2
                 invocation.proceed();
 392  2
         }
 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  1
                 final RequestParserProvider requestParserProvider = ProviderFactory
 404  
                                 .get(RequestParserProvider.class);
 405  1
                 final Map<String, Object[]> parameterMap = requestParserProvider
 406  
                                 .getParameterMap(request);
 407  1
                 return parameterMap;
 408  
         }
 409  
 
 410  
         /**
 411  
          * 要求処理の実行情報の実装です。
 412  
          * 
 413  
          * @author baba
 414  
          */
 415  3
         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  2
                                 final HttpServletResponse response, final PathInfo pathInfo) {
 448  2
                         this.cubbyFilter = cubbyFilter;
 449  2
                         this.request = request;
 450  2
                         this.response = response;
 451  2
                         this.pathInfo = pathInfo;
 452  
 
 453  2
                         final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
 454  2
                         this.pluginsIterator = pluginRegistry.getPlugins().iterator();
 455  2
                 }
 456  
 
 457  
                 /**
 458  
                  * {@inheritDoc}
 459  
                  */
 460  
                 public Void proceed() throws Exception {
 461  3
                         if (pluginsIterator.hasNext()) {
 462  1
                                 final Plugin plugin = pluginsIterator.next();
 463  1
                                 plugin.invokeRequestProcessing(this);
 464  1
                         } else {
 465  2
                                 final HttpServletRequest request = getRequest();
 466  2
                                 final Map<String, Object[]> parameterMap = cubbyFilter
 467  
                                                 .parseRequest(request);
 468  2
                                 request.setAttribute(ATTR_PARAMS, parameterMap);
 469  2
                                 final Routing routing = pathInfo.dispatch(parameterMap);
 470  2
                                 ThreadContext.enter(request, response);
 471  
                                 try {
 472  2
                                         final ActionProcessor actionProcessor = cubbyFilter
 473  
                                                         .createActionProcessor();
 474  2
                                         final ActionResultWrapper actionResultWrapper = actionProcessor
 475  
                                                         .process(request, response, routing);
 476  2
                                         actionResultWrapper.execute(request, response);
 477  
                                 } finally {
 478  2
                                         ThreadContext.exit();
 479  2
                                 }
 480  
                         }
 481  3
                         return null;
 482  
                 }
 483  
 
 484  
                 /**
 485  
                  * {@inheritDoc}
 486  
                  */
 487  
                 public HttpServletRequest getRequest() {
 488  2
                         return request;
 489  
                 }
 490  
 
 491  
                 /**
 492  
                  * {@inheritDoc}
 493  
                  */
 494  
                 public HttpServletResponse getResponse() {
 495  0
                         return response;
 496  
                 }
 497  
 
 498  
                 /**
 499  
                  * {@inheritDoc}
 500  
                  */
 501  
                 public PathInfo getPathInfo() {
 502  0
                         return pathInfo;
 503  
                 }
 504  
 
 505  
         }
 506  
 
 507  
         /**
 508  
          * {@link PathResolver} を生成します。
 509  
          * 
 510  
          * @return {@link PathResolver}
 511  
          */
 512  
         protected PathResolver createPathResolver() {
 513  1
                 final PathResolverProvider pathResolverProvider = ProviderFactory
 514  
                                 .get(PathResolverProvider.class);
 515  1
                 final PathResolver pathResolver = pathResolverProvider
 516  
                                 .getPathResolver();
 517  1
                 return pathResolver;
 518  
         }
 519  
 
 520  
         /**
 521  
          * {@link ActionProcessor} を生成します。
 522  
          * 
 523  
          * @return {@link ActionProcessor}
 524  
          */
 525  
         protected ActionProcessor createActionProcessor() {
 526  1
                 final ActionProcessor actionProcessor = new ActionProcessorImpl();
 527  1
                 return actionProcessor;
 528  
         }
 529  
 
 530  
         /**
 531  
          * {@link MessagesBehaviour} を生成します。
 532  
          * 
 533  
          * @return {@link MessagesBehaviour}
 534  
          */
 535  
         protected MessagesBehaviour createMessagesBehaviour() {
 536  5
                 final Container container = ProviderFactory
 537  
                                 .get(ContainerProvider.class).getContainer();
 538  5
                 final MessagesBehaviour messagesBehaviour = container
 539  
                                 .lookup(MessagesBehaviour.class);
 540  5
                 return messagesBehaviour;
 541  
         }
 542  
 
 543  
         /**
 544  
          * アクションからフォワードされてきたときに使用する {@link PathInfo} です。
 545  
          * 
 546  
          * @author baba
 547  
          */
 548  13
         static class ForwardFromActionPathInfo implements PathInfo {
 549  
 
 550  
                 private final Routing routing;
 551  
 
 552  1
                 private final Map<String, String[]> uriParameters = Collections
 553  
                                 .emptyMap();
 554  
 
 555  1
                 public ForwardFromActionPathInfo(final Routing routing) {
 556  1
                         this.routing = routing;
 557  1
                 }
 558  
 
 559  
                 /**
 560  
                  * {@inheritDoc}
 561  
                  */
 562  
                 public Map<String, String[]> getURIParameters() {
 563  0
                         return uriParameters;
 564  
                 }
 565  
 
 566  
                 /**
 567  
                  * {@inheritDoc}
 568  
                  */
 569  
                 public Routing dispatch(final Map<String, Object[]> parameterMap) {
 570  1
                         return routing;
 571  
                 }
 572  
 
 573  
         }
 574  
 
 575  
 }