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 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
167 	 * 
168 	 * @param actionClass
169 	 *            アクションクラス
170 	 * @param methodName
171 	 *            メソッド名
172 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
173 	 *             リダイレクト先パスの構築に失敗した場合
174 	 * @since 1.1.0
175 	 */
176 	public Redirect(final Class<? extends Action> actionClass,
177 			final String methodName) {
178 		this(actionClass, methodName, EMPTY_PARAMETERS);
179 	}
180 
181 	/**
182 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
183 	 * 
184 	 * @param actionClass
185 	 *            アクションクラス
186 	 * @param methodName
187 	 *            メソッド名
188 	 * @param parameters
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, final Map<String, String[]> parameters) {
196 		this(actionClass, methodName, parameters, null);
197 	}
198 
199 	/**
200 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
201 	 * 
202 	 * @param actionClass
203 	 *            アクションクラス
204 	 * @param methodName
205 	 *            メソッド名
206 	 * @param parameters
207 	 *            パラメータ
208 	 * @param protocol
209 	 *            リダイレクト先のプロトコル
210 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
211 	 *             リダイレクト先パスの構築に失敗した場合
212 	 * @since 1.1.0
213 	 */
214 	public Redirect(final Class<? extends Action> actionClass,
215 			final String methodName, final Map<String, String[]> parameters,
216 			final String protocol) {
217 		this(actionClass, methodName, parameters, protocol, -1);
218 	}
219 
220 	/**
221 	 * 指定されたアクションメソッドへリダイレクトするインスタンスを生成します。
222 	 * 
223 	 * @param actionClass
224 	 *            アクションクラス
225 	 * @param methodName
226 	 *            メソッド名
227 	 * @param parameters
228 	 *            パラメータ
229 	 * @param protocol
230 	 *            リダイレクト先のプロトコル
231 	 * @param port
232 	 *            リダイレクト先のポート
233 	 * @throws org.seasar.cubby.exception.ActionRuntimeException
234 	 *             リダイレクト先パスの構築に失敗した場合
235 	 * @since 1.1.0
236 	 */
237 	public Redirect(final Class<? extends Action> actionClass,
238 			final String methodName, final Map<String, String[]> parameters,
239 			final String protocol, final int port) {
240 		this.actionClass = actionClass;
241 		this.methodName = methodName;
242 		this.parameters = parameters;
243 		this.protocol = protocol;
244 		this.port = port;
245 	}
246 
247 	/**
248 	 * パスを取得します。
249 	 * 
250 	 * @return パス
251 	 */
252 	public String getPath() {
253 		if (isReverseLookupRedirect()) {
254 			final S2Container container = SingletonS2ContainerFactory
255 			.getContainer();
256 			final PathResolver pathResolver = (PathResolver) container
257 			.getComponent(PathResolver.class);
258 			final String redirectPath = pathResolver.reverseLookup(actionClass,
259 			methodName, parameters);
260 			this.path = redirectPath;
261 		}
262 		return this.path;
263 	}
264 	
265 	/**
266 	 * アクションクラスを指定したリダイレクトかどうかを判定します。
267 	 * @return アクションクラスを指定したリダイレクトならtrue
268 	 */
269 	private boolean isReverseLookupRedirect() {
270 		return this.actionClass != null && this.methodName != null && this.parameters != null;
271 	}
272 
273 	/**
274 	 * {@inheritDoc}
275 	 */
276 	public void execute(final Action action,
277 			final Class<? extends Action> actionClass, final Method method,
278 			final HttpServletRequest request, final HttpServletResponse response)
279 			throws Exception {
280 		final String redirectURL = calculateRedirectURL(getPath(), actionClass,
281 				request);
282 		final String encodedRedirectURL = encodeURL(redirectURL, response);
283 		if (logger.isDebugEnabled()) {
284 			logger.log("DCUB0003", new String[] { encodedRedirectURL });
285 		}
286 		response.sendRedirect(encodedRedirectURL);
287 	}
288 
289 	/**
290 	 * リダイレクトする URL を計算します。
291 	 * 
292 	 * @param path
293 	 *            パス
294 	 * @param actionClass
295 	 *            アクションクラス
296 	 * @param request
297 	 *            リクエスト
298 	 * @return URL
299 	 */
300 	protected String calculateRedirectURL(final String path,
301 			final Class<? extends Action> actionClass,
302 			final HttpServletRequest request) {
303 		try {
304 			final String redirectURL = new URL(path).toExternalForm();
305 			return redirectURL;
306 		} catch (MalformedURLException e) {
307 			final String redirectURL = calculateInternalRedirectURL(path,
308 					actionClass, request);
309 			return redirectURL;
310 		}
311 	}
312 
313 	/**
314 	 * リダイレクトする URL を計算します。
315 	 * 
316 	 * @param path
317 	 *            パス
318 	 * @param actionClass
319 	 *            アクションクラス
320 	 * @param request
321 	 *            リクエスト
322 	 * @return URL
323 	 */
324 	private String calculateInternalRedirectURL(final String path,
325 			final Class<? extends Action> actionClass,
326 			final HttpServletRequest request) {
327 		final String redirectPath;
328 		final String contextPath;
329 		if ("/".equals(request.getContextPath())) {
330 			contextPath = "";
331 		} else {
332 			contextPath = request.getContextPath();
333 		}
334 		if (path.startsWith("/")) {
335 			redirectPath = contextPath + path;
336 		} else {
337 			final String actionDirectory = CubbyUtils
338 					.getActionDirectory(actionClass);
339 			if (StringUtil.isEmpty(actionDirectory)) {
340 				final StringBuilder builder = new StringBuilder();
341 				builder.append(contextPath);
342 				if (!contextPath.endsWith("/")) {
343 					builder.append("/");
344 				}
345 				builder.append(path);
346 				redirectPath = builder.toString();
347 			} else {
348 				final StringBuilder builder = new StringBuilder();
349 				builder.append(contextPath);
350 				if (!contextPath.endsWith("/")
351 						&& !actionDirectory.startsWith("/")) {
352 					builder.append("/");
353 				}
354 				builder.append(actionDirectory);
355 				if (!actionDirectory.endsWith("/")) {
356 					builder.append("/");
357 				}
358 				builder.append(path);
359 				redirectPath = builder.toString();
360 			}
361 		}
362 
363 		if (protocol == null) {
364 			return redirectPath;
365 		} else {
366 			try {
367 				final URL currentURL = new URL(request.getRequestURL()
368 						.toString());
369 				final String redirectProtocol = this.protocol == null ? currentURL
370 						.getProtocol()
371 						: this.protocol;
372 				final int redirectPort = this.port < 0 ? currentURL.getPort()
373 						: this.port;
374 				final URL redirectURL = new URL(redirectProtocol, currentURL
375 						.getHost(), redirectPort, redirectPath);
376 				return redirectURL.toExternalForm();
377 			} catch (MalformedURLException e) {
378 				throw new IORuntimeException(e);
379 			}
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 		return response.encodeRedirectURL(url);
396 	}
397 
398 	/**
399 	 * {@link HttpServletResponse#encodeRedirectURL(String)}
400 	 * によってエンコードせずにリダイレクトします。
401 	 * <p>
402 	 * URL 埋め込みのセッション ID を出力したくない場合に使用してください。
403 	 * </p>
404 	 * 
405 	 * @return リダイレクトする URL
406 	 * @since 1.1.0
407 	 */
408 	public ActionResult noEncodeURL() {
409 		return new Redirect(path, protocol, port) {
410 			@Override
411 			protected String encodeURL(final String url,
412 					final HttpServletResponse response) {
413 				return url;
414 			}
415 		};
416 	}
417 	
418 	/**
419 	 * パラメータを追加します。
420 	 * @param paramName パラメータ名
421 	 * @param paramValue パラメータの値。{@code Object#toString()}の結果が値として使用されます。
422 	 * @return リダイレクトする URL
423 	 */
424 	public Redirect param(String paramName, Object paramValue) {
425 		return param(paramName, new String[] { paramValue.toString() });
426 	}
427 	
428 	/**
429 	 * パラメータを追加します。
430 	 * @param paramName パラメータ名
431 	 * @param paramValues パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
432 	 * @return リダイレクトする URL
433 	 */
434 	public Redirect param(final String paramName, final Object[] paramValues) {
435 		return param(paramName, toStringArray(paramValues));
436 	}
437 
438 	/**
439 	 * パラメータを追加します。
440 	 * @param paramName パラメータ名
441 	 * @param paramValues パラメータの値
442 	 * @return リダイレクトする URL
443 	 */
444 	public Redirect param(final String paramName, final String[] paramValues) {
445 		if (isReverseLookupRedirect()) {
446 			if (this.parameters == EMPTY_PARAMETERS) {
447 				this.parameters = new HashMap<String, String[]>();
448 			}
449 			this.parameters.put(paramName, paramValues);
450 		} else {
451 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
452 			builder.addParam(paramName, paramValues);
453 			this.path = builder.toString();
454 		}
455 		return this;
456 	}
457 	
458 	/**
459 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
460 	 * <p>
461 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
462 	 * </p>
463 	 * @param paramValues {@code Object#toString()}型の配列
464 	 * @return {@code Object#toString()}型の配列。
465 	 */
466 	private String[] toStringArray(final Object[] paramValues) {
467 		String[] values = new String[paramValues.length];
468 		for (int i = 0; i < paramValues.length; i++) {
469 			values[i] = paramValues[i].toString();
470 		}
471 		return values;
472 	}
473 }