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 static org.seasar.cubby.CubbyConstants.ATTR_ROUTINGS;
19  
20  import java.io.IOException;
21  import java.lang.reflect.Method;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.regex.Pattern;
27  
28  import javax.servlet.RequestDispatcher;
29  import javax.servlet.ServletException;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.seasar.cubby.routing.PathResolver;
34  import org.seasar.cubby.routing.Routing;
35  import org.seasar.cubby.util.CubbyUtils;
36  import org.seasar.cubby.util.QueryStringBuilder;
37  import org.seasar.framework.container.S2Container;
38  import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
39  import org.seasar.framework.log.Logger;
40  import org.seasar.framework.util.ClassUtil;
41  import org.seasar.framework.util.StringUtil;
42  
43  /**
44   * 指定されたパスにフォワードする {@link ActionResult} です。
45   * <p>
46   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにフォワードします。
47   * </p>
48   * <p>
49   * 使用例1 : フォワード先を相対パスで指定
50   * 
51   * <pre>
52   * return new Forward(&quot;list.jsp&quot;);
53   * </pre>
54   * 
55   * </p>
56   * <p>
57   * 使用例2 : フォワード先を絶対パスで指定
58   * 
59   * <pre>
60   * return new Forward(&quot;/todo/list.jsp&quot;);
61   * </pre>
62   * 
63   * </p>
64   * <p>
65   * 使用例2 : フォワード先をクラスとメソッド名で指定
66   * 
67   * <pre>
68   * return new Forward(TodoListAction.class, &quot;show&quot;);
69   * </pre>
70   * 
71   * <p>
72   * 使用例3 : フォワード先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
73   * 
74   * <pre>
75   * return new Forward(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
76   * </pre>
77   * 
78   * </p>
79   * <p>
80   * 使用例3 : フォワード先をクラスとメソッド名で指定(Mapによるパラメータつき)
81   * 
82   * <pre>
83   * Map&lt;String, String[]&gt; parameters = new HashMap();
84   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
85   * return new Forward(TodoListAction.class, &quot;show&quot;, parameters);
86   * </pre>
87   * 
88   * </p>
89   * <p>
90   * フォワード前には {@link Action#invokePreRenderMethod(Method)} を実行します。 フォワード後には
91   * {@link Action#invokePostRenderMethod(Method)} を実行し、フラッシュメッセージをクリアします。
92   * </p>
93   * 
94   * @author baba
95   * @since 1.0.0
96   */
97  public class Forward implements ActionResult {
98  
99  	/** ロガー。 */
100 	private static final Logger logger = Logger.getLogger(Forward.class);
101 
102 	/** 空のパラメータ。 */
103 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
104 			.emptyMap();
105 
106 	/** フォワード先のパス。 */
107 	private String path;
108 
109 	/** ルーティング。 */
110 	private final Map<String, Routing> routings;
111 
112 	/** フォワード先のアクションクラス */
113 	private Class<? extends Action> actionClass;
114 
115 	/** フォワード先のアクションクラスのメソッド名 */
116 	private String methodName;
117 
118 	/** フォワード時のパラメータ */
119 	private Map<String, String[]> parameters;
120 
121 	/**
122 	 * インスタンスを生成します。
123 	 * 
124 	 * @param path
125 	 *            フォワード先のパス
126 	 */
127 	public Forward(final String path) {
128 		this.path = path;
129 		this.routings = null;
130 	}
131 
132 	/**
133 	 * インスタンスを生成します。
134 	 * 
135 	 * @param actionClass
136 	 *            アクションクラス
137 	 * @param methodName
138 	 *            アクションメソッド名
139 	 * @param parameters
140 	 *            パラメータ
141 	 * @since 1.1.0
142 	 */
143 	public Forward(final Class<? extends Action> actionClass,
144 			final String methodName, final Map<String, String[]> parameters) {
145 		this.actionClass = actionClass;
146 		this.methodName = methodName;
147 		this.parameters = parameters;
148 		final Method method = ClassUtil.getMethod(actionClass, methodName,
149 				new Class[0]);
150 		final Routing routing = new ForwardRouting(actionClass, method);
151 		this.routings = Collections.singletonMap(null, routing);
152 	}
153 
154 	/**
155 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
156 	 * 
157 	 * @param actionClass
158 	 *            アクションクラス
159 	 * @since 1.1.0
160 	 */
161 	public Forward(final Class<? extends Action> actionClass) {
162 		this(actionClass, "index");
163 	}
164 
165 	/**
166 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
167 	 * 
168 	 * @param actionClass
169 	 *            アクションクラス
170 	 * @param methodName
171 	 *            アクションメソッド名
172 	 * @since 1.1.0
173 	 */
174 	public Forward(final Class<? extends Action> actionClass,
175 			final String methodName) {
176 		this(actionClass, methodName, EMPTY_PARAMETERS);
177 	}
178 
179 	/**
180 	 * パスを取得します。
181 	 * 
182 	 * @param characterEncoding
183 	 *            URI のエンコーディング
184 	 * @return パス
185 	 */
186 	public String getPath(final String characterEncoding) {
187 		if (isReverseLookupRedirect()) {
188 			final S2Container container = SingletonS2ContainerFactory
189 					.getContainer();
190 			final PathResolver pathResolver = PathResolver.class.cast(container
191 					.getComponent(PathResolver.class));
192 			this.path = pathResolver.buildInternalForwardPath(parameters,
193 					characterEncoding);
194 		}
195 		return this.path;
196 	}
197 
198 	/**
199 	 * アクションクラスを指定したフォワードかどうかを判定します。
200 	 * 
201 	 * @return アクションクラスを指定したフォワードならtrue
202 	 */
203 	private boolean isReverseLookupRedirect() {
204 		return this.actionClass != null && this.methodName != null
205 				&& this.parameters != null;
206 	}
207 
208 	/**
209 	 * {@inheritDoc}
210 	 */
211 	public void execute(final Action action,
212 			final Class<? extends Action> actionClass, final Method method,
213 			final HttpServletRequest request, final HttpServletResponse response)
214 			throws ServletException, IOException {
215 		action.invokePreRenderMethod(method);
216 
217 		final String forwardPath = calculateForwardPath(getPath(request
218 				.getCharacterEncoding()), actionClass, request
219 				.getCharacterEncoding());
220 		if (this.routings != null) {
221 			request.setAttribute(ATTR_ROUTINGS, this.routings);
222 		}
223 		if (logger.isDebugEnabled()) {
224 			logger.log("DCUB0001", new Object[] { forwardPath, routings });
225 		}
226 		final RequestDispatcher dispatcher = request
227 				.getRequestDispatcher(forwardPath);
228 		dispatcher.forward(request, response);
229 		if (logger.isDebugEnabled()) {
230 			logger.log("DCUB0002", new Object[] { forwardPath });
231 		}
232 
233 		action.invokePostRenderMethod(method);
234 
235 		action.getFlash().clear();
236 	}
237 
238 	/**
239 	 * フォワードするパスを計算します。
240 	 * 
241 	 * @param actionClass
242 	 *            アクションクラス
243 	 * @param characterEncoding
244 	 *            URI のエンコーディング
245 	 * @return フォワードするパス
246 	 */
247 	protected String calculateForwardPath(final String path,
248 			final Class<? extends Action> actionClass,
249 			final String characterEncoding) {
250 		final String absolutePath;
251 		if (getPath(characterEncoding).startsWith("/")) {
252 			absolutePath = path;
253 		} else {
254 			final String actionDirectory = CubbyUtils
255 					.getActionDirectory(actionClass);
256 			if (StringUtil.isEmpty(actionDirectory)) {
257 				absolutePath = "/" + path;
258 			} else {
259 				final StringBuilder builder = new StringBuilder();
260 				if (!actionDirectory.startsWith("/")) {
261 					builder.append("/");
262 				}
263 				builder.append(actionDirectory);
264 				if (!actionDirectory.endsWith("/")) {
265 					builder.append("/");
266 				}
267 				builder.append(path);
268 				absolutePath = builder.toString();
269 			}
270 		}
271 		return absolutePath;
272 	}
273 
274 	/**
275 	 * パラメータを追加します。
276 	 * 
277 	 * @param paramName
278 	 *            パラメータ名
279 	 * @param paramValue
280 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
281 	 * @return フォワードする URL
282 	 */
283 	public Forward param(String paramName, Object paramValue) {
284 		return param(paramName, new String[] { paramValue.toString() });
285 	}
286 
287 	/**
288 	 * パラメータを追加します。
289 	 * 
290 	 * @param paramName
291 	 *            パラメータ名
292 	 * @param paramValues
293 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
294 	 * @return フォワードする URL
295 	 */
296 	public Forward param(final String paramName, final Object[] paramValues) {
297 		return param(paramName, toStringArray(paramValues));
298 	}
299 
300 	/**
301 	 * パラメータを追加します。
302 	 * 
303 	 * @param paramName
304 	 *            パラメータ名
305 	 * @param paramValues
306 	 *            パラメータの値
307 	 * @return フォワードする URL
308 	 */
309 	public Forward param(final String paramName, final String[] paramValues) {
310 		if (isReverseLookupRedirect()) {
311 			if (this.parameters == EMPTY_PARAMETERS) {
312 				this.parameters = new HashMap<String, String[]>();
313 			}
314 			this.parameters.put(paramName, paramValues);
315 		} else {
316 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
317 			builder.addParam(paramName, paramValues);
318 			this.path = builder.toString();
319 		}
320 		return this;
321 	}
322 
323 	/**
324 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
325 	 * <p>
326 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
327 	 * </p>
328 	 * 
329 	 * @param paramValues
330 	 *            {@code Object#toString()}型の配列
331 	 * @return {@code Object#toString()}型の配列。
332 	 */
333 	private String[] toStringArray(final Object[] paramValues) {
334 		String[] values = new String[paramValues.length];
335 		for (int i = 0; i < paramValues.length; i++) {
336 			values[i] = paramValues[i].toString();
337 		}
338 		return values;
339 	}
340 
341 	/**
342 	 * パスを取得します。
343 	 * 
344 	 * @return パス
345 	 * @deprecated use {@link #getPath(String)}
346 	 */
347 	@Deprecated
348 	public String getPath() {
349 		return getPath("UTF-8");
350 	}
351 
352 	/**
353 	 * アクションメソッドへフォワードするためのルーティング。
354 	 * 
355 	 * @author baba
356 	 * @since 1.1.0
357 	 */
358 	private static class ForwardRouting implements Routing {
359 
360 		/** アクションクラス。 */
361 		private Class<? extends Action> actionClass;
362 
363 		/** アクションメソッド。 */
364 		private Method method;
365 
366 		/**
367 		 * {@inheritDoc}
368 		 */
369 		private ForwardRouting(final Class<? extends Action> actionClass,
370 				final Method method) {
371 			this.actionClass = actionClass;
372 			this.method = method;
373 		}
374 
375 		/**
376 		 * {@inheritDoc}
377 		 */
378 		public Class<? extends Action> getActionClass() {
379 			return actionClass;
380 		}
381 
382 		/**
383 		 * {@inheritDoc}
384 		 */
385 		public Method getMethod() {
386 			return method;
387 		}
388 
389 		/**
390 		 * {@inheritDoc}
391 		 */
392 		public String getActionPath() {
393 			return null;
394 		}
395 
396 		/**
397 		 * {@inheritDoc}
398 		 */
399 		public List<String> getUriParameterNames() {
400 			return null;
401 		}
402 
403 		/**
404 		 * {@inheritDoc}
405 		 */
406 		public Pattern getPattern() {
407 			return null;
408 		}
409 
410 		/**
411 		 * {@inheritDoc}
412 		 */
413 		public RequestMethod getRequestMethod() {
414 			return null;
415 		}
416 
417 		/**
418 		 * {@inheritDoc}
419 		 */
420 		public String getOnSubmit() {
421 			return null;
422 		}
423 
424 		/**
425 		 * {@inheritDoc}
426 		 */
427 		public int getPriority() {
428 			return 0;
429 		}
430 
431 		/**
432 		 * {@inheritDoc}
433 		 */
434 		public boolean isAuto() {
435 			return false;
436 		}
437 
438 		/**
439 		 * {@inheritDoc}
440 		 */
441 		public boolean isAcceptable(final String requestMethod) {
442 			return true;
443 		}
444 
445 		/**
446 		 * {@inheritDoc}
447 		 */
448 		@Override
449 		public String toString() {
450 			return new StringBuilder().append("[").append(method).append("]")
451 					.toString();
452 		}
453 
454 	}
455 
456 }