View Javadoc

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