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.cubby.util.LinkBuilder;
32  import org.seasar.framework.container.S2Container;
33  import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
34  import org.seasar.framework.exception.IORuntimeException;
35  import org.seasar.framework.log.Logger;
36  import org.seasar.framework.util.StringUtil;
37  
38  /**
39   * 指定されたパスにリダイレクトする {@link ActionResult} です。
40   * <p>
41   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにリダイレクトします。
42   * </p>
43   * <p>
44   * 使用例1 : リダイレクト先を相対パスで指定
45   * 
46   * <pre>
47   * return new Redirect(&quot;list&quot;);
48   * </pre>
49   * 
50   * </p>
51   * <p>
52   * 使用例2 : リダイレクト先を絶対パスで指定
53   * 
54   * <pre>
55   * return new Redirect(&quot;/todo/list&quot;);
56   * </pre>
57   * 
58   * </p>
59   * <p>
60   * 使用例3 : リダイレクト先をクラスとメソッド名で指定
61   * 
62   * <pre>
63   * return new Redirect(TodoListAction.class, &quot;show&quot;);
64   * </pre>
65   * 
66   * </p>
67   * <p>
68   * 使用例4 : リダイレクト先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
69   * 
70   * <pre>
71   * return new Redirect(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
72   * </pre>
73   * 
74   * </p>
75   * <p>
76   * 使用例5 : リダイレクト先をクラスとメソッド名で指定(Mapによるパラメータつき)
77   * 
78   * <pre>
79   * Map&lt;String, String[]&gt; parameters = new HashMap();
80   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
81   * return new Redirect(TodoListAction.class, &quot;show&quot;, parameters);
82   * </pre>
83   * 
84   * </p>
85   * <p>
86   * 通常は {@link HttpServletResponse#encodeRedirectURL(String)} によってエンコードされた URL
87   * にリダイレクトするため、URL にセッション ID が埋め込まれます。 URL にセッション ID を埋め込みたくない場合は、noEncodeURL()
88   * を使用してください。
89   * 
90   * <pre>
91   * return new Redirect(&quot;/todo/list&quot;).noEnocdeURL();
92   * </pre>
93   * 
94   * </p>
95   * 
96   * @author baba
97   * @since 1.0.0
98   */
99  public class Redirect implements ActionResult {
100 
101 	/** ロガー。 */
102 	private static final Logger logger = Logger.getLogger(Redirect.class);
103 
104 	/** 空のパラメータ。 */
105 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
106 			.emptyMap();
107 
108 	/** リンクビルダ。 */
109 	private final LinkBuilder linkBuilder = new LinkBuilder();
110 
111 	/** リダイレクト先のパス。 */
112 	private String path;
113 
114 	/** リダイレクト先のアクションクラス。 */
115 	private Class<? extends Action> actionClass;
116 
117 	/** リダイレクト先のアクションクラスのメソッド名。 */
118 	private String methodName;
119 
120 	/** リダイレクト時のパラメータ。 */
121 	private Map<String, String[]> parameters;
122 
123 	/** URI のエンコーディング。 */
124 	private String characterEncoding;
125 
126 	/** URL をエンコードするか。 */
127 	private boolean encodeURL = true;
128 
129 	/**
130 	 * インスタンスを生成します。
131 	 * 
132 	 * @param path
133 	 *            リダイレクト先のパス
134 	 */
135 	public Redirect(final String path) {
136 		this.path = path;
137 	}
138 
139 	/**
140 	 * インスタンスを生成します。
141 	 * 
142 	 * @param path
143 	 *            リダイレクト先のパス
144 	 * @param protocol
145 	 *            リダイレクト先のプロトコル
146 	 * @since 1.1.0
147 	 */
148 	public Redirect(final String path, final String protocol) {
149 		this(path);
150 		linkBuilder.setProtocol(protocol);
151 	}
152 
153 	/**
154 	 * インスタンスを生成します。
155 	 * 
156 	 * @param path
157 	 *            リダイレクト先のパス
158 	 * @param protocol
159 	 *            リダイレクト先のプロトコル
160 	 * @param port
161 	 *            リダイレクト先のポート
162 	 * @since 1.1.0
163 	 */
164 	public Redirect(final String path, final String protocol, final int port) {
165 		this(path);
166 		linkBuilder.setProtocol(protocol);
167 		linkBuilder.setPort(port);
168 	}
169 
170 	/**
171 	 * 指定されたアクションクラスのindexメソッドへリダイレクトするインスタンスを生成します。
172 	 * 
173 	 * @param actionClass
174 	 *            アクションクラス
175 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
176 	 *             リダイレクト先パスの構築に失敗した場合
177 	 * @since 1.1.0
178 	 */
179 	public Redirect(final Class<? extends Action> actionClass) {
180 		this(actionClass, "index");
181 	}
182 
183 	/**
184 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
185 	 * 
186 	 * @param actionClass
187 	 *            アクションクラス
188 	 * @param methodName
189 	 *            メソッド名
190 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
191 	 *             リダイレクト先パスの構築に失敗した場合
192 	 * @since 1.1.0
193 	 */
194 	public Redirect(final Class<? extends Action> actionClass,
195 			final String methodName) {
196 		this(actionClass, methodName, EMPTY_PARAMETERS);
197 	}
198 
199 	/**
200 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
201 	 * 
202 	 * @param actionClass
203 	 *            アクションクラス
204 	 * @param methodName
205 	 *            メソッド名
206 	 * @param parameters
207 	 *            パラメータ
208 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
209 	 *             リダイレクト先パスの構築に失敗した場合
210 	 * @since 1.1.0
211 	 */
212 	public Redirect(final Class<? extends Action> actionClass,
213 			final String methodName, final Map<String, String[]> parameters) {
214 		this.actionClass = actionClass;
215 		this.methodName = methodName;
216 		this.parameters = parameters;
217 	}
218 
219 	/**
220 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
221 	 * 
222 	 * @param actionClass
223 	 *            アクションクラス
224 	 * @param methodName
225 	 *            メソッド名
226 	 * @param parameters
227 	 *            パラメータ
228 	 * @param protocol
229 	 *            リダイレクト先のプロトコル
230 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
231 	 *             リダイレクト先パスの構築に失敗した場合
232 	 * @since 1.1.0
233 	 */
234 	public Redirect(final Class<? extends Action> actionClass,
235 			final String methodName, final Map<String, String[]> parameters,
236 			final String protocol) {
237 		this(actionClass, methodName, parameters);
238 		linkBuilder.setProtocol(protocol);
239 	}
240 
241 	/**
242 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
243 	 * 
244 	 * @param actionClass
245 	 *            アクションクラス
246 	 * @param methodName
247 	 *            メソッド名
248 	 * @param parameters
249 	 *            パラメータ
250 	 * @param protocol
251 	 *            リダイレクト先のプロトコル
252 	 * @param port
253 	 *            リダイレクト先のポート
254 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
255 	 *             リダイレクト先パスの構築に失敗した場合
256 	 * @since 1.1.0
257 	 */
258 	public Redirect(final Class<? extends Action> actionClass,
259 			final String methodName, final Map<String, String[]> parameters,
260 			final String protocol, final int port) {
261 		this(actionClass, methodName, parameters);
262 		linkBuilder.setProtocol(protocol);
263 		linkBuilder.setPort(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 		try {
393 			return linkBuilder.file(redirectPath).toLink(request);
394 		} catch (final MalformedURLException e) {
395 			throw new IORuntimeException(e);
396 		}
397 	}
398 
399 	/**
400 	 * URL をエンコードします。
401 	 * 
402 	 * @param url
403 	 *            URL
404 	 * @param response
405 	 *            レスポンス
406 	 * @return エンコードされた URL
407 	 * @see HttpServletResponse#encodeRedirectURL(String)
408 	 */
409 	protected String encodeURL(final String url,
410 			final HttpServletResponse response) {
411 		if (encodeURL) {
412 			return response.encodeRedirectURL(url);
413 		} else {
414 			return url;
415 		}
416 	}
417 
418 	/**
419 	 * {@link HttpServletResponse#encodeRedirectURL(String)}
420 	 * によってエンコードせずにリダイレクトします。
421 	 * <p>
422 	 * URL 埋め込みのセッション ID を出力したくない場合に使用してください。
423 	 * </p>
424 	 * 
425 	 * @return このオブジェクト
426 	 * @since 1.1.0
427 	 */
428 	public Redirect noEncodeURL() {
429 		this.encodeURL = false;
430 		return this;
431 	}
432 
433 	/**
434 	 * パラメータを追加します。
435 	 * 
436 	 * @param paramName
437 	 *            パラメータ名
438 	 * @param paramValue
439 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
440 	 * @return このオブジェクト
441 	 * @since 1.1.0
442 	 */
443 	public Redirect param(String paramName, Object paramValue) {
444 		return param(paramName, new String[] { paramValue.toString() });
445 	}
446 
447 	/**
448 	 * パラメータを追加します。
449 	 * 
450 	 * @param paramName
451 	 *            パラメータ名
452 	 * @param paramValues
453 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
454 	 * @return このオブジェクト
455 	 * @since 1.1.0
456 	 */
457 	public Redirect param(final String paramName, final Object[] paramValues) {
458 		return param(paramName, toStringArray(paramValues));
459 	}
460 
461 	/**
462 	 * パラメータを追加します。
463 	 * 
464 	 * @param paramName
465 	 *            パラメータ名
466 	 * @param paramValues
467 	 *            パラメータの値
468 	 * @return このオブジェクト
469 	 * @since 1.1.0
470 	 */
471 	public Redirect param(final String paramName, final String[] paramValues) {
472 		if (isReverseLookupRedirect()) {
473 			if (this.parameters == EMPTY_PARAMETERS) {
474 				this.parameters = new HashMap<String, String[]>();
475 			}
476 			this.parameters.put(paramName, paramValues);
477 		} else {
478 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
479 			builder.addParam(paramName, paramValues);
480 			this.path = builder.toString();
481 		}
482 		return this;
483 	}
484 
485 	/**
486 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
487 	 * <p>
488 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
489 	 * </p>
490 	 * 
491 	 * @param paramValues
492 	 *            {@code Object#toString()}型の配列
493 	 * @return {@code Object#toString()}型の配列。
494 	 */
495 	private String[] toStringArray(final Object[] paramValues) {
496 		String[] values = new String[paramValues.length];
497 		for (int i = 0; i < paramValues.length; i++) {
498 			values[i] = paramValues[i].toString();
499 		}
500 		return values;
501 	}
502 
503 	/**
504 	 * URI のエンコーディングを指定します。
505 	 * 
506 	 * @param characterEncoding
507 	 *            URI のエンコーディング
508 	 * @return このオブジェクト
509 	 * @since 1.1.1
510 	 */
511 	public Redirect characterEncoding(final String characterEncoding) {
512 		this.characterEncoding = characterEncoding;
513 		return this;
514 	}
515 
516 	/**
517 	 * パスを取得します。
518 	 * 
519 	 * @return パス
520 	 * @deprecated use {@link #getPath(String)}
521 	 */
522 	@Deprecated
523 	public String getPath() {
524 		return getPath("UTF-8");
525 	}
526 
527 }