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 }