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 	/**
126 	 * インスタンスを生成します。
127 	 * 
128 	 * @param path
129 	 *            リダイレクト先のパス
130 	 */
131 	public Redirect(final String path) {
132 		this(path, null);
133 	}
134 
135 	/**
136 	 * インスタンスを生成します。
137 	 * 
138 	 * @param path
139 	 *            リダイレクト先のパス
140 	 * @param protocol
141 	 *            リダイレクト先のプロトコル
142 	 * @since 1.1.0
143 	 */
144 	public Redirect(final String path, final String protocol) {
145 		this(path, protocol, -1);
146 	}
147 
148 	/**
149 	 * インスタンスを生成します。
150 	 * 
151 	 * @param path
152 	 *            リダイレクト先のパス
153 	 * @param protocol
154 	 *            リダイレクト先のプロトコル
155 	 * @param port
156 	 *            リダイレクト先のポート
157 	 * @since 1.1.0
158 	 */
159 	public Redirect(final String path, final String protocol, final int port) {
160 		this.path = path;
161 		this.protocol = protocol;
162 		this.port = port;
163 	}
164 
165 	/**
166 	 * 指定されたアクションクラスのindexメソッドへリダイレクトするインスタンスを生成します。
167 	 * 
168 	 * @param actionClass
169 	 *            アクションクラス
170 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
171 	 *             リダイレクト先パスの構築に失敗した場合
172 	 * @since 1.1.0
173 	 */
174 	public Redirect(final Class<? extends Action> actionClass) {
175 		this(actionClass, "index");
176 	}
177 
178 	/**
179 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
180 	 * 
181 	 * @param actionClass
182 	 *            アクションクラス
183 	 * @param methodName
184 	 *            メソッド名
185 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
186 	 *             リダイレクト先パスの構築に失敗した場合
187 	 * @since 1.1.0
188 	 */
189 	public Redirect(final Class<? extends Action> actionClass,
190 			final String methodName) {
191 		this(actionClass, methodName, EMPTY_PARAMETERS);
192 	}
193 
194 	/**
195 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
196 	 * 
197 	 * @param actionClass
198 	 *            アクションクラス
199 	 * @param methodName
200 	 *            メソッド名
201 	 * @param parameters
202 	 *            パラメータ
203 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
204 	 *             リダイレクト先パスの構築に失敗した場合
205 	 * @since 1.1.0
206 	 */
207 	public Redirect(final Class<? extends Action> actionClass,
208 			final String methodName, final Map<String, String[]> parameters) {
209 		this(actionClass, methodName, parameters, null);
210 	}
211 
212 	/**
213 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
214 	 * 
215 	 * @param actionClass
216 	 *            アクションクラス
217 	 * @param methodName
218 	 *            メソッド名
219 	 * @param parameters
220 	 *            パラメータ
221 	 * @param protocol
222 	 *            リダイレクト先のプロトコル
223 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
224 	 *             リダイレクト先パスの構築に失敗した場合
225 	 * @since 1.1.0
226 	 */
227 	public Redirect(final Class<? extends Action> actionClass,
228 			final String methodName, final Map<String, String[]> parameters,
229 			final String protocol) {
230 		this(actionClass, methodName, parameters, protocol, -1);
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 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
247 	 *             リダイレクト先パスの構築に失敗した場合
248 	 * @since 1.1.0
249 	 */
250 	public Redirect(final Class<? extends Action> actionClass,
251 			final String methodName, final Map<String, String[]> parameters,
252 			final String protocol, final int port) {
253 		this.actionClass = actionClass;
254 		this.methodName = methodName;
255 		this.parameters = parameters;
256 		this.protocol = protocol;
257 		this.port = port;
258 	}
259 
260 	/**
261 	 * パスを取得します。
262 	 * 
263 	 * @return パス
264 	 */
265 	public String getPath() {
266 		if (isReverseLookupRedirect()) {
267 			final S2Container container = SingletonS2ContainerFactory
268 			.getContainer();
269 			final PathResolver pathResolver = (PathResolver) container
270 			.getComponent(PathResolver.class);
271 			final String redirectPath = pathResolver.reverseLookup(actionClass,
272 			methodName, parameters);
273 			this.path = redirectPath;
274 		}
275 		return this.path;
276 	}
277 	
278 	/**
279 	 * アクションクラスを指定したリダイレクトかどうかを判定します。
280 	 * @return アクションクラスを指定したリダイレクトならtrue
281 	 */
282 	private boolean isReverseLookupRedirect() {
283 		return this.actionClass != null && this.methodName != null && this.parameters != null;
284 	}
285 
286 	/**
287 	 * {@inheritDoc}
288 	 */
289 	public void execute(final Action action,
290 			final Class<? extends Action> actionClass, final Method method,
291 			final HttpServletRequest request, final HttpServletResponse response)
292 			throws Exception {
293 		final String redirectURL = calculateRedirectURL(getPath(), actionClass,
294 				request);
295 		final String encodedRedirectURL = encodeURL(redirectURL, response);
296 		if (logger.isDebugEnabled()) {
297 			logger.log("DCUB0003", new String[] { encodedRedirectURL });
298 		}
299 		response.sendRedirect(encodedRedirectURL);
300 	}
301 
302 	/**
303 	 * リダイレクトする URL を計算します。
304 	 * 
305 	 * @param path
306 	 *            パス
307 	 * @param actionClass
308 	 *            アクションクラス
309 	 * @param request
310 	 *            リクエスト
311 	 * @return URL
312 	 */
313 	protected String calculateRedirectURL(final String path,
314 			final Class<? extends Action> actionClass,
315 			final HttpServletRequest request) {
316 		try {
317 			final String redirectURL = new URL(path).toExternalForm();
318 			return redirectURL;
319 		} catch (MalformedURLException e) {
320 			final String redirectURL = calculateInternalRedirectURL(path,
321 					actionClass, request);
322 			return redirectURL;
323 		}
324 	}
325 
326 	/**
327 	 * リダイレクトする URL を計算します。
328 	 * 
329 	 * @param path
330 	 *            パス
331 	 * @param actionClass
332 	 *            アクションクラス
333 	 * @param request
334 	 *            リクエスト
335 	 * @return URL
336 	 */
337 	private String calculateInternalRedirectURL(final String path,
338 			final Class<? extends Action> actionClass,
339 			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 = CubbyUtils
351 					.getActionDirectory(actionClass);
352 			if (StringUtil.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 		if (protocol == null) {
377 			return redirectPath;
378 		} else {
379 			try {
380 				final URL currentURL = new URL(request.getRequestURL()
381 						.toString());
382 				final String redirectProtocol = this.protocol == null ? currentURL
383 						.getProtocol()
384 						: this.protocol;
385 				final int redirectPort = this.port < 0 ? currentURL.getPort()
386 						: this.port;
387 				final URL redirectURL = new URL(redirectProtocol, currentURL
388 						.getHost(), redirectPort, redirectPath);
389 				return redirectURL.toExternalForm();
390 			} catch (MalformedURLException e) {
391 				throw new IORuntimeException(e);
392 			}
393 		}
394 	}
395 
396 	/**
397 	 * URL をエンコードします。
398 	 * 
399 	 * @param url
400 	 *            URL
401 	 * @param response
402 	 *            レスポンス
403 	 * @return エンコードされた URL
404 	 * @see HttpServletResponse#encodeRedirectURL(String)
405 	 */
406 	protected String encodeURL(final String url,
407 			final HttpServletResponse response) {
408 		return response.encodeRedirectURL(url);
409 	}
410 
411 	/**
412 	 * {@link HttpServletResponse#encodeRedirectURL(String)}
413 	 * によってエンコードせずにリダイレクトします。
414 	 * <p>
415 	 * URL 埋め込みのセッション ID を出力したくない場合に使用してください。
416 	 * </p>
417 	 * 
418 	 * @return リダイレクトする URL
419 	 * @since 1.1.0
420 	 */
421 	public ActionResult noEncodeURL() {
422 		return new Redirect(path, protocol, port) {
423 			@Override
424 			protected String encodeURL(final String url,
425 					final HttpServletResponse response) {
426 				return url;
427 			}
428 		};
429 	}
430 	
431 	/**
432 	 * パラメータを追加します。
433 	 * @param paramName パラメータ名
434 	 * @param paramValue パラメータの値。{@code Object#toString()}の結果が値として使用されます。
435 	 * @return リダイレクトする URL
436 	 */
437 	public Redirect param(String paramName, Object paramValue) {
438 		return param(paramName, new String[] { paramValue.toString() });
439 	}
440 	
441 	/**
442 	 * パラメータを追加します。
443 	 * @param paramName パラメータ名
444 	 * @param paramValues パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
445 	 * @return リダイレクトする URL
446 	 */
447 	public Redirect param(final String paramName, final Object[] paramValues) {
448 		return param(paramName, toStringArray(paramValues));
449 	}
450 
451 	/**
452 	 * パラメータを追加します。
453 	 * @param paramName パラメータ名
454 	 * @param paramValues パラメータの値
455 	 * @return リダイレクトする URL
456 	 */
457 	public Redirect param(final String paramName, final String[] paramValues) {
458 		if (isReverseLookupRedirect()) {
459 			if (this.parameters == EMPTY_PARAMETERS) {
460 				this.parameters = new HashMap<String, String[]>();
461 			}
462 			this.parameters.put(paramName, paramValues);
463 		} else {
464 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
465 			builder.addParam(paramName, paramValues);
466 			this.path = builder.toString();
467 		}
468 		return this;
469 	}
470 	
471 	/**
472 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
473 	 * <p>
474 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
475 	 * </p>
476 	 * @param paramValues {@code Object#toString()}型の配列
477 	 * @return {@code Object#toString()}型の配列。
478 	 */
479 	private String[] toStringArray(final Object[] paramValues) {
480 		String[] values = new String[paramValues.length];
481 		for (int i = 0; i < paramValues.length; i++) {
482 			values[i] = paramValues[i].toString();
483 		}
484 		return values;
485 	}
486 }