View Javadoc

    1   /*
    2    * Copyright 2004-2009 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.action;
    17
    18  import static org.seasar.cubby.CubbyConstants.ATTR_ROUTING;
    19  import static org.seasar.cubby.internal.util.LogMessages.format;
    20
    21  import java.io.IOException;
    22  import java.lang.reflect.Method;
    23  import java.util.Collections;
    24  import java.util.HashMap;
    25  import java.util.List;
    26  import java.util.Map;
    27  import java.util.regex.Pattern;
    28
    29  import javax.servlet.RequestDispatcher;
    30  import javax.servlet.ServletException;
    31  import javax.servlet.http.HttpServletRequest;
    32  import javax.servlet.http.HttpServletResponse;
    33
    34  import org.seasar.cubby.internal.util.MetaUtils;
    35  import org.seasar.cubby.internal.util.QueryStringBuilder;
    36  import org.seasar.cubby.internal.util.StringUtils;
    37  import org.seasar.cubby.routing.PathResolver;
    38  import org.seasar.cubby.routing.Routing;
    39  import org.seasar.cubby.spi.PathResolverProvider;
    40  import org.seasar.cubby.spi.ProviderFactory;
    41  import org.slf4j.Logger;
    42  import org.slf4j.LoggerFactory;
    43
    44  /**
    45   * 指定されたパスにフォワードする {@link ActionResult} です。
    46   * <p>
    47   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにフォワードします。
    48   * </p>
    49   * <p>
    50   * 使用例1 : フォワード先を相対パスで指定
    51   *
    52   * <pre>
    53   * return new Forward(&quot;list.jsp&quot;);
    54   * </pre>
    55   *
    56   * </p>
    57   * <p>
    58   * 使用例2 : フォワード先を絶対パスで指定
    59   *
    60   * <pre>
    61   * return new Forward(&quot;/todo/list.jsp&quot;);
    62   * </pre>
    63   *
    64   * </p>
    65   * <p>
    66   * 使用例2 : フォワード先をクラスとメソッド名で指定
    67   *
    68   * <pre>
    69   * return new Forward(TodoListAction.class, &quot;show&quot;);
    70   * </pre>
    71   *
    72   * <p>
    73   * 使用例3 : フォワード先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
    74   *
    75   * <pre>
    76   * return new Forward(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
    77   * </pre>
    78   *
    79   * </p>
    80   * <p>
    81   * 使用例3 : フォワード先をクラスとメソッド名で指定(Mapによるパラメータつき)
    82   *
    83   * <pre>
    84   * Map&lt;String, String[]&gt; parameters = new HashMap();
    85   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
    86   * return new Forward(TodoListAction.class, &quot;show&quot;, parameters);
    87   * </pre>
    88   *
    89   * </p>
    90   * <p>
    91   * フォワード前には {@link Action#invokePreRenderMethod(Method)} を実行します。 フォワード後には
    92   * {@link Action#invokePostRenderMethod(Method)} を実行し、フラッシュメッセージをクリアします。
    93   * </p>
    94   *
    95   * @author baba
    96   */
    97  public class Forward implements ActionResult {
    98
    99  	/** ロガー。 */
    100 	private static final Logger logger = LoggerFactory.getLogger(Forward.class);
    101
    102 	/** 空のパラメータ。 */
    103 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
    104 			.emptyMap();
    105
    106 	/** {@link PathResolver} のプロバイダ。 */
    107 	private final PathResolverProvider pathResolverProvider;
    108
    109 	/** フォワード先のパス。 */
    110 	private String path;
    111
    112 	/** ルーティング。 */
    113 	private final Routing routing;
    114
    115 	/** フォワード先のアクションクラス */
    116 	private Class<?> actionClass;
    117
    118 	/** フォワード先のアクションクラスのメソッド名 */
    119 	private String methodName;
    120
    121 	/** フォワード時のパラメータ */
    122 	private Map<String, String[]> parameters;
    123
    124 	/**
    125 	 * インスタンスを生成します。
    126 	 *
    127 	 * @param path
    128 	 *            フォワード先のパス
    129 	 */
    130 	public Forward(final String path) {
    131 		this.pathResolverProvider = null;
    132 		this.path = path;
    133 		this.routing = null;
    134 	}
    135
    136 	/**
    137 	 * インスタンスを生成します。
    138 	 *
    139 	 * @param actionClass
    140 	 *            アクションクラス
    141 	 * @param methodName
    142 	 *            アクションメソッド名
    143 	 * @param parameters
    144 	 *            パラメータ
    145 	 */
    146 	public Forward(final Class<?> actionClass, final String methodName,
    147 			final Map<String, String[]> parameters) {
    148 		this.pathResolverProvider = ProviderFactory
    149 				.get(PathResolverProvider.class);
    150 		this.actionClass = actionClass;
    151 		this.methodName = methodName;
    152 		this.parameters = parameters;
    153 		try {
    154 			final Method method = actionClass.getMethod(methodName);
    155 			this.routing = new ForwardRouting(actionClass, method);
    156 		} catch (final NoSuchMethodException e) {
    157 			throw new IllegalArgumentException(e);
    158 		}
    159 	}
    160
    161 	/**
    162 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
    163 	 *
    164 	 * @param actionClass
    165 	 *            アクションクラス
    166 	 */
    167 	public Forward(final Class<?> actionClass) {
    168 		this(actionClass, "index");
    169 	}
    170
    171 	/**
    172 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
    173 	 *
    174 	 * @param actionClass
    175 	 *            アクションクラス
    176 	 * @param methodName
    177 	 *            アクションメソッド名
    178 	 */
    179 	public Forward(final Class<?> actionClass, final String methodName) {
    180 		this(actionClass, methodName, EMPTY_PARAMETERS);
    181 	}
    182
    183 	/**
    184 	 * パスを取得します。
    185 	 *
    186 	 * @param characterEncoding
    187 	 *            URI のエンコーディング
    188 	 * @return パス
    189 	 */
    190 	public String getPath(final String characterEncoding) {
    191 		if (isReverseLookupRedirect()) {
    192 			final PathResolver pathResolver = this.pathResolverProvider
    193 					.getPathResolver();
    194 			final String forwardPath = pathResolver.reverseLookup(actionClass,
    195 					methodName, parameters, characterEncoding);
    196 			this.path = forwardPath;
    197 		}
    198 		return this.path;
    199 	}
    200
    201 	/**
    202 	 * アクションクラスを指定したフォワードかどうかを判定します。
    203 	 *
    204 	 * @return アクションクラスを指定したフォワードならtrue
    205 	 */
    206 	private boolean isReverseLookupRedirect() {
    207 		return this.actionClass != null && this.methodName != null
    208 				&& this.parameters != null;
    209 	}
    210
    211 	/**
    212 	 * {@inheritDoc}
    213 	 */
    214 	public void execute(final ActionContext actionContext,
    215 			final HttpServletRequest request, final HttpServletResponse response)
    216 			throws ServletException, IOException {
    217 		actionContext.invokePreRenderMethod();
    218
    219 		final String forwardPath = calculateForwardPath(getPath(request
    220 				.getCharacterEncoding()), actionContext.getActionClass(),
    221 				request.getCharacterEncoding());
    222 		if (this.routing != null) {
    223 			request.setAttribute(ATTR_ROUTING, this.routing);
    224 		}
    225 		if (logger.isDebugEnabled()) {
    226 			logger.debug(format("DCUB0001", forwardPath, routing));
    227 		}
    228 		final RequestDispatcher dispatcher = request
    229 				.getRequestDispatcher(forwardPath);
    230 		dispatcher.forward(request, response);
    231 		if (logger.isDebugEnabled()) {
    232 			logger.debug(format("DCUB0002", forwardPath));
    233 		}
    234
    235 		actionContext.invokePostRenderMethod();
    236 		actionContext.clearFlash();
    237 	}
    238
    239 	/**
    240 	 * フォワードするパスを計算します。
    241 	 *
    242 	 * @param actionClass
    243 	 *            アクションクラス
    244 	 * @param characterEncoding
    245 	 *            URI のエンコーディング
    246 	 * @return フォワードするパス
    247 	 */
    248 	protected String calculateForwardPath(final String path,
    249 			final Class<?> actionClass, final String characterEncoding) {
    250 		final String absolutePath;
    251 		if (getPath(characterEncoding).startsWith("/")) {
    252 			absolutePath = path;
    253 		} else {
    254 			final String actionDirectory = MetaUtils
    255 					.getActionDirectory(actionClass);
    256 			if (StringUtils.isEmpty(actionDirectory)) {
    257 				absolutePath = "/" + path;
    258 			} else {
    259 				final StringBuilder builder = new StringBuilder();
    260 				if (!actionDirectory.startsWith("/")) {
    261 					builder.append("/");
    262 				}
    263 				builder.append(actionDirectory);
    264 				if (!actionDirectory.endsWith("/")) {
    265 					builder.append("/");
    266 				}
    267 				builder.append(path);
    268 				absolutePath = builder.toString();
    269 			}
    270 		}
    271 		return absolutePath;
    272 	}
    273
    274 	/**
    275 	 * パラメータを追加します。
    276 	 *
    277 	 * @param paramName
    278 	 *            パラメータ名
    279 	 * @param paramValue
    280 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
    281 	 * @return フォワードする URL
    282 	 */
    283 	public Forward param(final String paramName, final Object paramValue) {
    284 		return param(paramName, new String[] { paramValue.toString() });
    285 	}
    286
    287 	/**
    288 	 * パラメータを追加します。
    289 	 *
    290 	 * @param paramName
    291 	 *            パラメータ名
    292 	 * @param paramValues
    293 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
    294 	 * @return フォワードする URL
    295 	 */
    296 	public Forward param(final String paramName, final Object[] paramValues) {
    297 		return param(paramName, toStringArray(paramValues));
    298 	}
    299
    300 	/**
    301 	 * パラメータを追加します。
    302 	 *
    303 	 * @param paramName
    304 	 *            パラメータ名
    305 	 * @param paramValues
    306 	 *            パラメータの値
    307 	 * @return フォワードする URL
    308 	 */
    309 	public Forward param(final String paramName, final String[] paramValues) {
    310 		if (isReverseLookupRedirect()) {
    311 			if (this.parameters == EMPTY_PARAMETERS) {
    312 				this.parameters = new HashMap<String, String[]>();
    313 			}
    314 			this.parameters.put(paramName, paramValues);
    315 		} else {
    316 			final QueryStringBuilder builder = new QueryStringBuilder(this.path);
    317 			builder.addParam(paramName, paramValues);
    318 			this.path = builder.toString();
    319 		}
    320 		return this;
    321 	}
    322
    323 	/**
    324 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
    325 	 * <p>
    326 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
    327 	 * </p>
    328 	 *
    329 	 * @param paramValues
    330 	 *            {@code Object#toString()}型の配列
    331 	 * @return {@code Object#toString()}型の配列。
    332 	 */
    333 	private String[] toStringArray(final Object[] paramValues) {
    334 		final String[] values = new String[paramValues.length];
    335 		for (int i = 0; i < paramValues.length; i++) {
    336 			values[i] = paramValues[i].toString();
    337 		}
    338 		return values;
    339 	}
    340
    341 	/**
    342 	 * アクションメソッドへフォワードするためのルーティングです。
    343 	 *
    344 	 * @author baba
    345 	 */
    346 	private static class ForwardRouting implements Routing {
    347
    348 		/** アクションクラス。 */
    349 		private final Class<?> actionClass;
    350
    351 		/** アクションメソッド。 */
    352 		private final Method actionMethod;
    353
    354 		/**
    355 		 * 指定されたアクションメソッドを実行する新規ルーティングを生成します。
    356 		 */
    357 		private ForwardRouting(final Class<?> actionClass,
    358 				final Method actionMethod) {
    359 			this.actionClass = actionClass;
    360 			this.actionMethod = actionMethod;
    361 		}
    362
    363 		/**
    364 		 * {@inheritDoc}
    365 		 */
    366 		public Class<?> getActionClass() {
    367 			return actionClass;
    368 		}
    369
    370 		/**
    371 		 * {@inheritDoc}
    372 		 */
    373 		public Method getActionMethod() {
    374 			return actionMethod;
    375 		}
    376
    377 		/**
    378 		 * {@inheritDoc}
    379 		 */
    380 		public String getActionPath() {
    381 			return null;
    382 		}
    383
    384 		/**
    385 		 * {@inheritDoc}
    386 		 */
    387 		public List<String> getUriParameterNames() {
    388 			return null;
    389 		}
    390
    391 		/**
    392 		 * {@inheritDoc}
    393 		 */
    394 		public Pattern getPattern() {
    395 			return null;
    396 		}
    397
    398 		/**
    399 		 * {@inheritDoc}
    400 		 */
    401 		public RequestMethod getRequestMethod() {
    402 			return null;
    403 		}
    404
    405 		/**
    406 		 * {@inheritDoc}
    407 		 */
    408 		public String getOnSubmit() {
    409 			return null;
    410 		}
    411
    412 		/**
    413 		 * {@inheritDoc}
    414 		 */
    415 		public int getPriority() {
    416 			return 0;
    417 		}
    418
    419 		/**
    420 		 * {@inheritDoc}
    421 		 */
    422 		public boolean isAcceptable(final String requestMethod) {
    423 			return true;
    424 		}
    425
    426 		/**
    427 		 * {@inheritDoc}
    428 		 */
    429 		@Override
    430 		public String toString() {
    431 			return new StringBuilder().append("[").append(actionMethod).append(
    432 					"]").toString();
    433 		}
    434
    435 	}
    436
    437 }