View Javadoc

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