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.internal.util.LogMessages.format;
    19
    20  import java.net.MalformedURLException;
    21  import java.net.URL;
    22  import java.util.Collections;
    23  import java.util.HashMap;
    24  import java.util.Map;
    25
    26  import javax.servlet.http.HttpServletRequest;
    27  import javax.servlet.http.HttpServletResponse;
    28
    29  import org.seasar.cubby.internal.util.MetaUtils;
    30  import org.seasar.cubby.internal.util.QueryStringBuilder;
    31  import org.seasar.cubby.internal.util.StringUtils;
    32  import org.seasar.cubby.routing.PathResolver;
    33  import org.seasar.cubby.spi.PathResolverProvider;
    34  import org.seasar.cubby.spi.ProviderFactory;
    35  import org.seasar.cubby.util.LinkBuilder;
    36  import org.slf4j.Logger;
    37  import org.slf4j.LoggerFactory;
    38
    39  /**
    40   * 指定されたパスにリダイレクトする {@link ActionResult} です。
    41   * <p>
    42   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにリダイレクトします。
    43   * </p>
    44   * <p>
    45   * 使用例1 : リダイレクト先を相対パスで指定
    46   *
    47   * <pre>
    48   * return new Redirect(&quot;list&quot;);
    49   * </pre>
    50   *
    51   * </p>
    52   * <p>
    53   * 使用例2 : リダイレクト先を絶対パスで指定
    54   *
    55   * <pre>
    56   * return new Redirect(&quot;/todo/list&quot;);
    57   * </pre>
    58   *
    59   * </p>
    60   * <p>
    61   * 使用例3 : リダイレクト先をクラスとメソッド名で指定
    62   *
    63   * <pre>
    64   * return new Redirect(TodoListAction.class, &quot;show&quot;);
    65   * </pre>
    66   *
    67   * </p>
    68   * <p>
    69   * 使用例4 : リダイレクト先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
    70   *
    71   * <pre>
    72   * return new Redirect(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
    73   * </pre>
    74   *
    75   * </p>
    76   * <p>
    77   * 使用例5 : リダイレクト先をクラスとメソッド名で指定(Mapによるパラメータつき)
    78   *
    79   * <pre>
    80   * Map&lt;String, String[]&gt; parameters = new HashMap();
    81   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
    82   * return new Redirect(TodoListAction.class, &quot;show&quot;, parameters);
    83   * </pre>
    84   *
    85   * </p>
    86   * <p>
    87   * 通常は {@link HttpServletResponse#encodeRedirectURL(String)} によってエンコードされた URL
    88   * にリダイレクトするため、URL にセッション ID が埋め込まれます。 URL にセッション ID を埋め込みたくない場合は、noEncodeURL()
    89   * を使用してください。
    90   *
    91   * <pre>
    92   * return new Redirect(&quot;/todo/list&quot;).noEnocdeURL();
    93   * </pre>
    94   *
    95   * </p>
    96   *
    97   * @author baba
    98   */
    99  public class Redirect implements ActionResult {
    100
    101 	/** ロガー。 */
    102 	private static final Logger logger = LoggerFactory
    103 			.getLogger(Redirect.class);
    104
    105 	/** 空のパラメータ。 */
    106 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
    107 			.emptyMap();
    108
    109 	/** {@link PathResolver} のプロバイダ。 */
    110 	private final PathResolverProvider pathResolverProvider;
    111
    112 	/** リンクビルダ。 */
    113 	private final LinkBuilder linkBuilder = new LinkBuilder();
    114
    115 	/** リダイレクト先のパス。 */
    116 	private String path;
    117
    118 	/** リダイレクト先のアクションクラス。 */
    119 	private Class<?> actionClass;
    120
    121 	/** リダイレクト先のアクションクラスのメソッド名。 */
    122 	private String methodName;
    123
    124 	/** リダイレクト時のパラメータ。 */
    125 	private Map<String, String[]> parameters;
    126
    127 	/** URI のエンコーディング。 */
    128 	private String characterEncoding;
    129
    130 	/** URL をエンコードするか。 */
    131 	private boolean encodeURL = true;
    132
    133 	/**
    134 	 * インスタンスを生成します。
    135 	 *
    136 	 * @param path
    137 	 *            リダイレクト先のパス
    138 	 */
    139 	public Redirect(final String path) {
    140 		this.pathResolverProvider = null;
    141 		this.path = path;
    142 	}
    143
    144 	/**
    145 	 * インスタンスを生成します。
    146 	 *
    147 	 * @param path
    148 	 *            リダイレクト先のパス
    149 	 * @param protocol
    150 	 *            リダイレクト先のプロトコル
    151 	 */
    152 	public Redirect(final String path, final String protocol) {
    153 		this(path);
    154 		linkBuilder.setProtocol(protocol);
    155 	}
    156
    157 	/**
    158 	 * インスタンスを生成します。
    159 	 *
    160 	 * @param path
    161 	 *            リダイレクト先のパス
    162 	 * @param protocol
    163 	 *            リダイレクト先のプロトコル
    164 	 * @param port
    165 	 *            リダイレクト先のポート
    166 	 */
    167 	public Redirect(final String path, final String protocol, final int port) {
    168 		this(path);
    169 		linkBuilder.setProtocol(protocol);
    170 		linkBuilder.setPort(port);
    171 	}
    172
    173 	/**
    174 	 * 指定されたアクションクラスのindexメソッドへリダイレクトするインスタンスを生成します。
    175 	 *
    176 	 * @param actionClass
    177 	 *            アクションクラス
    178 	 */
    179 	public Redirect(final Class<?> actionClass) {
    180 		this(actionClass, "index");
    181 	}
    182
    183 	/**
    184 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
    185 	 *
    186 	 * @param actionClass
    187 	 *            アクションクラス
    188 	 * @param methodName
    189 	 *            メソッド名
    190 	 */
    191 	public Redirect(final Class<?> actionClass, final String methodName) {
    192 		this(actionClass, methodName, EMPTY_PARAMETERS);
    193 	}
    194
    195 	/**
    196 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
    197 	 *
    198 	 * @param actionClass
    199 	 *            アクションクラス
    200 	 * @param methodName
    201 	 *            メソッド名
    202 	 * @param parameters
    203 	 *            パラメータ
    204 	 */
    205 	public Redirect(final Class<?> actionClass, final String methodName,
    206 			final Map<String, String[]> parameters) {
    207 		this.pathResolverProvider = ProviderFactory
    208 				.get(PathResolverProvider.class);
    209 		this.actionClass = actionClass;
    210 		this.methodName = methodName;
    211 		this.parameters = parameters;
    212 	}
    213
    214 	/**
    215 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
    216 	 *
    217 	 * @param actionClass
    218 	 *            アクションクラス
    219 	 * @param methodName
    220 	 *            メソッド名
    221 	 * @param parameters
    222 	 *            パラメータ
    223 	 * @param protocol
    224 	 *            リダイレクト先のプロトコル
    225 	 */
    226 	public Redirect(final Class<?> actionClass, final String methodName,
    227 			final Map<String, String[]> parameters, final String protocol) {
    228 		this(actionClass, methodName, parameters);
    229 		linkBuilder.setProtocol(protocol);
    230 	}
    231
    232 	/**
    233 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
    234 	 *
    235 	 * @param actionClass
    236 	 *            アクションクラス
    237 	 * @param methodName
    238 	 *            メソッド名
    239 	 * @param parameters
    240 	 *            パラメータ
    241 	 * @param protocol
    242 	 *            リダイレクト先のプロトコル
    243 	 * @param port
    244 	 *            リダイレクト先のポート
    245 	 */
    246 	public Redirect(final Class<?> actionClass, final String methodName,
    247 			final Map<String, String[]> parameters, final String protocol,
    248 			final int port) {
    249 		this(actionClass, methodName, parameters);
    250 		linkBuilder.setProtocol(protocol);
    251 		linkBuilder.setPort(port);
    252 	}
    253
    254 	/**
    255 	 * パスを取得します。
    256 	 *
    257 	 * @param characterEncoding
    258 	 *            URI のエンコーディング
    259 	 * @return パス
    260 	 */
    261 	public String getPath(final String characterEncoding) {
    262 		if (isReverseLookupRedirect()) {
    263 			final PathResolver pathResolver = this.pathResolverProvider
    264 					.getPathResolver();
    265 			final String redirectPath = pathResolver.reverseLookup(actionClass,
    266 					methodName, parameters, characterEncoding);
    267 			this.path = redirectPath;
    268 		}
    269 		return this.path;
    270 	}
    271
    272 	/**
    273 	 * アクションクラスを指定したリダイレクトかどうかを判定します。
    274 	 *
    275 	 * @return アクションクラスを指定したリダイレクトならtrue
    276 	 */
    277 	private boolean isReverseLookupRedirect() {
    278 		return this.actionClass != null && this.methodName != null
    279 				&& this.parameters != null;
    280 	}
    281
    282 	/**
    283 	 * {@inheritDoc}
    284 	 */
    285 	public void execute(final ActionContext actionContext,
    286 			final HttpServletRequest request, final HttpServletResponse response)
    287 			throws Exception {
    288 		final String characterEncoding;
    289 		if (this.characterEncoding == null) {
    290 			characterEncoding = request.getCharacterEncoding();
    291 		} else {
    292 			characterEncoding = this.characterEncoding;
    293 		}
    294 		final String redirectURL = calculateRedirectURL(
    295 				getPath(characterEncoding), actionContext.getActionClass(),
    296 				request);
    297 		final String encodedRedirectURL = encodeURL(redirectURL, response);
    298 		if (logger.isDebugEnabled()) {
    299 			logger.debug(format("DCUB0003", encodedRedirectURL));
    300 		}
    301 		response.sendRedirect(encodedRedirectURL);
    302 	}
    303
    304 	/**
    305 	 * リダイレクトする URL を計算します。
    306 	 *
    307 	 * @param path
    308 	 *            パス
    309 	 * @param actionClass
    310 	 *            アクションクラス
    311 	 * @param request
    312 	 *            要求
    313 	 * @return URL
    314 	 */
    315 	protected String calculateRedirectURL(final String path,
    316 			final Class<?> actionClass, final HttpServletRequest request) {
    317 		try {
    318 			final String redirectURL = new URL(path).toExternalForm();
    319 			return redirectURL;
    320 		} catch (final MalformedURLException e) {
    321 			final String redirectURL = calculateInternalRedirectURL(path,
    322 					actionClass, request);
    323 			return redirectURL;
    324 		}
    325 	}
    326
    327 	/**
    328 	 * リダイレクトする URL を計算します。
    329 	 *
    330 	 * @param path
    331 	 *            パス
    332 	 * @param actionClass
    333 	 *            アクションクラス
    334 	 * @param request
    335 	 *            要求
    336 	 * @return URL
    337 	 */
    338 	private String calculateInternalRedirectURL(final String path,
    339 			final Class<?> actionClass, final HttpServletRequest request) {
    340 		final String redirectPath;
    341 		final String contextPath;
    342 		if ("/".equals(request.getContextPath())) {
    343 			contextPath = "";
    344 		} else {
    345 			contextPath = request.getContextPath();
    346 		}
    347 		if (path.startsWith("/")) {
    348 			redirectPath = contextPath + path;
    349 		} else {
    350 			final String actionDirectory = MetaUtils
    351 					.getActionDirectory(actionClass);
    352 			if (StringUtils.isEmpty(actionDirectory)) {
    353 				final StringBuilder builder = new StringBuilder();
    354 				builder.append(contextPath);
    355 				if (!contextPath.endsWith("/")) {
    356 					builder.append("/");
    357 				}
    358 				builder.append(path);
    359 				redirectPath = builder.toString();
    360 			} else {
    361 				final StringBuilder builder = new StringBuilder();
    362 				builder.append(contextPath);
    363 				if (!contextPath.endsWith("/")
    364 						&& !actionDirectory.startsWith("/")) {
    365 					builder.append("/");
    366 				}
    367 				builder.append(actionDirectory);
    368 				if (!actionDirectory.endsWith("/")) {
    369 					builder.append("/");
    370 				}
    371 				builder.append(path);
    372 				redirectPath = builder.toString();
    373 			}
    374 		}
    375
    376 		try {
    377 			return linkBuilder.file(redirectPath).toLink(request);
    378 		} catch (final MalformedURLException e) {
    379 			throw new ActionException(e);
    380 		}
    381 	}
    382
    383 	/**
    384 	 * URL をエンコードします。
    385 	 *
    386 	 * @param url
    387 	 *            URL
    388 	 * @param response
    389 	 *            応答
    390 	 * @return エンコードされた URL
    391 	 * @see HttpServletResponse#encodeRedirectURL(String)
    392 	 */
    393 	protected String encodeURL(final String url,
    394 			final HttpServletResponse response) {
    395 		if (encodeURL) {
    396 			return response.encodeRedirectURL(url);
    397 		} else {
    398 			return url;
    399 		}
    400 	}
    401
    402 	/**
    403 	 * {@link HttpServletResponse#encodeRedirectURL(String)}
    404 	 * によってエンコードせずにリダイレクトします。
    405 	 * <p>
    406 	 * URL 埋め込みのセッション ID を出力したくない場合に使用してください。
    407 	 * </p>
    408 	 *
    409 	 * @return このオブジェクト
    410 	 */
    411 	public Redirect noEncodeURL() {
    412 		this.encodeURL = false;
    413 		return this;
    414 	}
    415
    416 	/**
    417 	 * パラメータを追加します。
    418 	 *
    419 	 * @param paramName
    420 	 *            パラメータ名
    421 	 * @param paramValue
    422 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
    423 	 * @return このオブジェクト
    424 	 */
    425 	public Redirect param(final String paramName, final Object paramValue) {
    426 		return param(paramName, new String[] { paramValue.toString() });
    427 	}
    428
    429 	/**
    430 	 * パラメータを追加します。
    431 	 *
    432 	 * @param paramName
    433 	 *            パラメータ名
    434 	 * @param paramValues
    435 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
    436 	 * @return このオブジェクト
    437 	 */
    438 	public Redirect param(final String paramName, final Object[] paramValues) {
    439 		return param(paramName, toStringArray(paramValues));
    440 	}
    441
    442 	/**
    443 	 * パラメータを追加します。
    444 	 *
    445 	 * @param paramName
    446 	 *            パラメータ名
    447 	 * @param paramValues
    448 	 *            パラメータの値
    449 	 * @return このオブジェクト
    450 	 */
    451 	public Redirect param(final String paramName, final String[] paramValues) {
    452 		if (isReverseLookupRedirect()) {
    453 			if (this.parameters == EMPTY_PARAMETERS) {
    454 				this.parameters = new HashMap<String, String[]>();
    455 			}
    456 			this.parameters.put(paramName, paramValues);
    457 		} else {
    458 			final QueryStringBuilder builder = new QueryStringBuilder(this.path);
    459 			builder.addParam(paramName, paramValues);
    460 			this.path = builder.toString();
    461 		}
    462 		return this;
    463 	}
    464
    465 	/**
    466 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
    467 	 * <p>
    468 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
    469 	 * </p>
    470 	 *
    471 	 * @param paramValues
    472 	 *            {@code Object#toString()}型の配列
    473 	 * @return {@code Object#toString()}型の配列。
    474 	 */
    475 	private String[] toStringArray(final Object[] paramValues) {
    476 		final String[] values = new String[paramValues.length];
    477 		for (int i = 0; i < paramValues.length; i++) {
    478 			values[i] = paramValues[i].toString();
    479 		}
    480 		return values;
    481 	}
    482
    483 	/**
    484 	 * URI のエンコーディングを指定します。
    485 	 *
    486 	 * @param characterEncoding
    487 	 *            URI のエンコーディング
    488 	 * @return このオブジェクト
    489 	 */
    490 	public Redirect characterEncoding(final String characterEncoding) {
    491 		this.characterEncoding = characterEncoding;
    492 		return this;
    493 	}
    494
    495 	/**
    496 	 * パスを取得します。
    497 	 *
    498 	 * @return パス
    499 	 * @deprecated use {@link #getPath(String)}
    500 	 */
    501 	@Deprecated
    502 	public String getPath() {
    503 		return getPath("UTF-8");
    504 	}
    505
    506 }