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   * @since 1.0.0
99   */
100 public class Redirect implements ActionResult {
101 
102 	/** ロガー。 */
103 	private static final Logger logger = LoggerFactory
104 			.getLogger(Redirect.class);
105 
106 	/** 空のパラメータ。 */
107 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
108 			.emptyMap();
109 
110 	/** リンクビルダ。 */
111 	private final LinkBuilder linkBuilder = new LinkBuilder();
112 
113 	/** リダイレクト先のパス。 */
114 	private String path;
115 
116 	/** リダイレクト先のアクションクラス。 */
117 	private Class<?> 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 = path;
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);
152 		linkBuilder.setProtocol(protocol);
153 	}
154 
155 	/**
156 	 * インスタンスを生成します。
157 	 * 
158 	 * @param path
159 	 *            リダイレクト先のパス
160 	 * @param protocol
161 	 *            リダイレクト先のプロトコル
162 	 * @param port
163 	 *            リダイレクト先のポート
164 	 * @since 1.1.0
165 	 */
166 	public Redirect(final String path, final String protocol, final int port) {
167 		this(path);
168 		linkBuilder.setProtocol(protocol);
169 		linkBuilder.setPort(port);
170 	}
171 
172 	/**
173 	 * 指定されたアクションクラスのindexメソッドへリダイレクトするインスタンスを生成します。
174 	 * 
175 	 * @param actionClass
176 	 *            アクションクラス
177 	 * @since 1.1.0
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 	 * @since 1.1.0
191 	 */
192 	public Redirect(final Class<?> actionClass, final String methodName) {
193 		this(actionClass, methodName, EMPTY_PARAMETERS);
194 	}
195 
196 	/**
197 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
198 	 * 
199 	 * @param actionClass
200 	 *            アクションクラス
201 	 * @param methodName
202 	 *            メソッド名
203 	 * @param parameters
204 	 *            パラメータ
205 	 * @since 1.1.0
206 	 */
207 	public Redirect(final Class<?> actionClass, final String methodName,
208 			final Map<String, String[]> parameters) {
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 	 * @since 1.1.0
226 	 */
227 	public Redirect(final Class<?> actionClass, final String methodName,
228 			final Map<String, String[]> parameters, final String protocol) {
229 		this(actionClass, methodName, parameters);
230 		linkBuilder.setProtocol(protocol);
231 	}
232 
233 	/**
234 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
235 	 * 
236 	 * @param actionClass
237 	 *            アクションクラス
238 	 * @param methodName
239 	 *            メソッド名
240 	 * @param parameters
241 	 *            パラメータ
242 	 * @param protocol
243 	 *            リダイレクト先のプロトコル
244 	 * @param port
245 	 *            リダイレクト先のポート
246 	 * @since 1.1.0
247 	 */
248 	public Redirect(final Class<?> actionClass, final String methodName,
249 			final Map<String, String[]> parameters, final String protocol,
250 			final int port) {
251 		this(actionClass, methodName, parameters);
252 		linkBuilder.setProtocol(protocol);
253 		linkBuilder.setPort(port);
254 	}
255 
256 	/**
257 	 * パスを取得します。
258 	 * 
259 	 * @param characterEncoding
260 	 *            URI のエンコーディング
261 	 * @return パス
262 	 */
263 	public String getPath(final String characterEncoding) {
264 		if (isReverseLookupRedirect()) {
265 			final PathResolverProvider pathResolverProvider = ProviderFactory
266 					.get(PathResolverProvider.class);
267 			final PathResolver pathResolver = pathResolverProvider
268 					.getPathResolver();
269 			final String redirectPath = pathResolver.reverseLookup(actionClass,
270 					methodName, parameters, characterEncoding);
271 			this.path = redirectPath;
272 		}
273 		return this.path;
274 	}
275 
276 	/**
277 	 * アクションクラスを指定したリダイレクトかどうかを判定します。
278 	 * 
279 	 * @return アクションクラスを指定したリダイレクトならtrue
280 	 */
281 	private boolean isReverseLookupRedirect() {
282 		return this.actionClass != null && this.methodName != null
283 				&& this.parameters != null;
284 	}
285 
286 	/**
287 	 * {@inheritDoc}
288 	 */
289 	public void execute(final ActionContext actionContext,
290 			final HttpServletRequest request, final HttpServletResponse response)
291 			throws Exception {
292 		final String characterEncoding;
293 		if (this.characterEncoding == null) {
294 			characterEncoding = request.getCharacterEncoding();
295 		} else {
296 			characterEncoding = this.characterEncoding;
297 		}
298 		final String redirectURL = calculateRedirectURL(
299 				getPath(characterEncoding), actionContext.getActionClass(),
300 				request);
301 		final String encodedRedirectURL = encodeURL(redirectURL, response);
302 		if (logger.isDebugEnabled()) {
303 			logger.debug(format("DCUB0003", encodedRedirectURL));
304 		}
305 		response.sendRedirect(encodedRedirectURL);
306 	}
307 
308 	/**
309 	 * リダイレクトする URL を計算します。
310 	 * 
311 	 * @param path
312 	 *            パス
313 	 * @param actionClass
314 	 *            アクションクラス
315 	 * @param request
316 	 *            リクエスト
317 	 * @return URL
318 	 */
319 	protected String calculateRedirectURL(final String path,
320 			final Class<?> actionClass, final HttpServletRequest request) {
321 		try {
322 			final String redirectURL = new URL(path).toExternalForm();
323 			return redirectURL;
324 		} catch (final MalformedURLException e) {
325 			final String redirectURL = calculateInternalRedirectURL(path,
326 					actionClass, request);
327 			return redirectURL;
328 		}
329 	}
330 
331 	/**
332 	 * リダイレクトする URL を計算します。
333 	 * 
334 	 * @param path
335 	 *            パス
336 	 * @param actionClass
337 	 *            アクションクラス
338 	 * @param request
339 	 *            リクエスト
340 	 * @return URL
341 	 */
342 	private String calculateInternalRedirectURL(final String path,
343 			final Class<?> actionClass, final HttpServletRequest request) {
344 		final String redirectPath;
345 		final String contextPath;
346 		if ("/".equals(request.getContextPath())) {
347 			contextPath = "";
348 		} else {
349 			contextPath = request.getContextPath();
350 		}
351 		if (path.startsWith("/")) {
352 			redirectPath = contextPath + path;
353 		} else {
354 			final String actionDirectory = MetaUtils
355 					.getActionDirectory(actionClass);
356 			if (StringUtils.isEmpty(actionDirectory)) {
357 				final StringBuilder builder = new StringBuilder();
358 				builder.append(contextPath);
359 				if (!contextPath.endsWith("/")) {
360 					builder.append("/");
361 				}
362 				builder.append(path);
363 				redirectPath = builder.toString();
364 			} else {
365 				final StringBuilder builder = new StringBuilder();
366 				builder.append(contextPath);
367 				if (!contextPath.endsWith("/")
368 						&& !actionDirectory.startsWith("/")) {
369 					builder.append("/");
370 				}
371 				builder.append(actionDirectory);
372 				if (!actionDirectory.endsWith("/")) {
373 					builder.append("/");
374 				}
375 				builder.append(path);
376 				redirectPath = builder.toString();
377 			}
378 		}
379 
380 		try {
381 			return linkBuilder.file(redirectPath).toLink(request);
382 		} catch (final MalformedURLException e) {
383 			throw new ActionException(e);
384 		}
385 	}
386 
387 	/**
388 	 * URL をエンコードします。
389 	 * 
390 	 * @param url
391 	 *            URL
392 	 * @param response
393 	 *            レスポンス
394 	 * @return エンコードされた URL
395 	 * @see HttpServletResponse#encodeRedirectURL(String)
396 	 */
397 	protected String encodeURL(final String url,
398 			final HttpServletResponse response) {
399 		if (encodeURL) {
400 			return response.encodeRedirectURL(url);
401 		} else {
402 			return url;
403 		}
404 	}
405 
406 	/**
407 	 * {@link HttpServletResponse#encodeRedirectURL(String)}
408 	 * によってエンコードせずにリダイレクトします。
409 	 * <p>
410 	 * URL 埋め込みのセッション ID を出力したくない場合に使用してください。
411 	 * </p>
412 	 * 
413 	 * @return このオブジェクト
414 	 * @since 1.1.0
415 	 */
416 	public Redirect noEncodeURL() {
417 		this.encodeURL = false;
418 		return this;
419 	}
420 
421 	/**
422 	 * パラメータを追加します。
423 	 * 
424 	 * @param paramName
425 	 *            パラメータ名
426 	 * @param paramValue
427 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
428 	 * @return このオブジェクト
429 	 * @since 1.1.0
430 	 */
431 	public Redirect param(final String paramName, final Object paramValue) {
432 		return param(paramName, new String[] { paramValue.toString() });
433 	}
434 
435 	/**
436 	 * パラメータを追加します。
437 	 * 
438 	 * @param paramName
439 	 *            パラメータ名
440 	 * @param paramValues
441 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
442 	 * @return このオブジェクト
443 	 * @since 1.1.0
444 	 */
445 	public Redirect param(final String paramName, final Object[] paramValues) {
446 		return param(paramName, toStringArray(paramValues));
447 	}
448 
449 	/**
450 	 * パラメータを追加します。
451 	 * 
452 	 * @param paramName
453 	 *            パラメータ名
454 	 * @param paramValues
455 	 *            パラメータの値
456 	 * @return このオブジェクト
457 	 * @since 1.1.0
458 	 */
459 	public Redirect param(final String paramName, final String[] paramValues) {
460 		if (isReverseLookupRedirect()) {
461 			if (this.parameters == EMPTY_PARAMETERS) {
462 				this.parameters = new HashMap<String, String[]>();
463 			}
464 			this.parameters.put(paramName, paramValues);
465 		} else {
466 			final QueryStringBuilder builder = new QueryStringBuilder(this.path);
467 			builder.addParam(paramName, paramValues);
468 			this.path = builder.toString();
469 		}
470 		return this;
471 	}
472 
473 	/**
474 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
475 	 * <p>
476 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
477 	 * </p>
478 	 * 
479 	 * @param paramValues
480 	 *            {@code Object#toString()}型の配列
481 	 * @return {@code Object#toString()}型の配列。
482 	 */
483 	private String[] toStringArray(final Object[] paramValues) {
484 		final String[] values = new String[paramValues.length];
485 		for (int i = 0; i < paramValues.length; i++) {
486 			values[i] = paramValues[i].toString();
487 		}
488 		return values;
489 	}
490 
491 	/**
492 	 * URI のエンコーディングを指定します。
493 	 * 
494 	 * @param characterEncoding
495 	 *            URI のエンコーディング
496 	 * @return このオブジェクト
497 	 * @since 1.1.1
498 	 */
499 	public Redirect characterEncoding(final String characterEncoding) {
500 		this.characterEncoding = characterEncoding;
501 		return this;
502 	}
503 
504 	/**
505 	 * パスを取得します。
506 	 * 
507 	 * @return パス
508 	 * @deprecated use {@link #getPath(String)}
509 	 */
510 	@Deprecated
511 	public String getPath() {
512 		return getPath("UTF-8");
513 	}
514 
515 }