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 		if (action.getFlash() != null) {
235 			action.getFlash().clear();
236 		}
237 	}
238 
239 	/**
240 	 * フォワードするパスを計算します。
241 	 * 
242 	 * @param actionClass
243 	 *            アクションクラス
244 	 * @param characterEncoding
245 	 *            URI のエンコーディング
246 	 * @return フォワードするパス
247 	 */
248 	protected String calculateForwardPath(final String path,
249 			final Class<? extends Action> actionClass,
250 			final String characterEncoding) {
251 		final String absolutePath;
252 		if (getPath(characterEncoding).startsWith("/")) {
253 			absolutePath = path;
254 		} else {
255 			final String actionDirectory = CubbyUtils
256 					.getActionDirectory(actionClass);
257 			if (StringUtil.isEmpty(actionDirectory)) {
258 				absolutePath = "/" + path;
259 			} else {
260 				final StringBuilder builder = new StringBuilder();
261 				if (!actionDirectory.startsWith("/")) {
262 					builder.append("/");
263 				}
264 				builder.append(actionDirectory);
265 				if (!actionDirectory.endsWith("/")) {
266 					builder.append("/");
267 				}
268 				builder.append(path);
269 				absolutePath = builder.toString();
270 			}
271 		}
272 		return absolutePath;
273 	}
274 
275 	/**
276 	 * パラメータを追加します。
277 	 * 
278 	 * @param paramName
279 	 *            パラメータ名
280 	 * @param paramValue
281 	 *            パラメータの値。{@code Object#toString()}の結果が値として使用されます。
282 	 * @return フォワードする URL
283 	 */
284 	public Forward param(String paramName, Object paramValue) {
285 		return param(paramName, new String[] { paramValue.toString() });
286 	}
287 
288 	/**
289 	 * パラメータを追加します。
290 	 * 
291 	 * @param paramName
292 	 *            パラメータ名
293 	 * @param paramValues
294 	 *            パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
295 	 * @return フォワードする URL
296 	 */
297 	public Forward param(final String paramName, final Object[] paramValues) {
298 		return param(paramName, toStringArray(paramValues));
299 	}
300 
301 	/**
302 	 * パラメータを追加します。
303 	 * 
304 	 * @param paramName
305 	 *            パラメータ名
306 	 * @param paramValues
307 	 *            パラメータの値
308 	 * @return フォワードする URL
309 	 */
310 	public Forward param(final String paramName, final String[] paramValues) {
311 		if (isReverseLookupRedirect()) {
312 			if (this.parameters == EMPTY_PARAMETERS) {
313 				this.parameters = new HashMap<String, String[]>();
314 			}
315 			this.parameters.put(paramName, paramValues);
316 		} else {
317 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
318 			builder.addParam(paramName, paramValues);
319 			this.path = builder.toString();
320 		}
321 		return this;
322 	}
323 
324 	/**
325 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
326 	 * <p>
327 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
328 	 * </p>
329 	 * 
330 	 * @param paramValues
331 	 *            {@code Object#toString()}型の配列
332 	 * @return {@code Object#toString()}型の配列。
333 	 */
334 	private String[] toStringArray(final Object[] paramValues) {
335 		String[] values = new String[paramValues.length];
336 		for (int i = 0; i < paramValues.length; i++) {
337 			values[i] = paramValues[i].toString();
338 		}
339 		return values;
340 	}
341 
342 	/**
343 	 * パスを取得します。
344 	 * 
345 	 * @return パス
346 	 * @deprecated use {@link #getPath(String)}
347 	 */
348 	@Deprecated
349 	public String getPath() {
350 		return getPath("UTF-8");
351 	}
352 
353 	/**
354 	 * アクションメソッドへフォワードするためのルーティング。
355 	 * 
356 	 * @author baba
357 	 * @since 1.1.0
358 	 */
359 	private static class ForwardRouting implements Routing {
360 
361 		/** アクションクラス。 */
362 		private Class<? extends Action> actionClass;
363 
364 		/** アクションメソッド。 */
365 		private Method method;
366 
367 		/**
368 		 * {@inheritDoc}
369 		 */
370 		private ForwardRouting(final Class<? extends Action> actionClass,
371 				final Method method) {
372 			this.actionClass = actionClass;
373 			this.method = method;
374 		}
375 
376 		/**
377 		 * {@inheritDoc}
378 		 */
379 		public Class<? extends Action> getActionClass() {
380 			return actionClass;
381 		}
382 
383 		/**
384 		 * {@inheritDoc}
385 		 */
386 		public Method getMethod() {
387 			return method;
388 		}
389 
390 		/**
391 		 * {@inheritDoc}
392 		 */
393 		public String getActionPath() {
394 			return null;
395 		}
396 
397 		/**
398 		 * {@inheritDoc}
399 		 */
400 		public List<String> getUriParameterNames() {
401 			return null;
402 		}
403 
404 		/**
405 		 * {@inheritDoc}
406 		 */
407 		public Pattern getPattern() {
408 			return null;
409 		}
410 
411 		/**
412 		 * {@inheritDoc}
413 		 */
414 		public RequestMethod getRequestMethod() {
415 			return null;
416 		}
417 
418 		/**
419 		 * {@inheritDoc}
420 		 */
421 		public String getOnSubmit() {
422 			return null;
423 		}
424 
425 		/**
426 		 * {@inheritDoc}
427 		 */
428 		public int getPriority() {
429 			return 0;
430 		}
431 
432 		/**
433 		 * {@inheritDoc}
434 		 */
435 		public boolean isAuto() {
436 			return false;
437 		}
438 
439 		/**
440 		 * {@inheritDoc}
441 		 */
442 		public boolean isAcceptable(final String requestMethod) {
443 			return true;
444 		}
445 
446 		/**
447 		 * {@inheritDoc}
448 		 */
449 		@Override
450 		public String toString() {
451 			return new StringBuilder().append("[").append(method).append("]")
452 					.toString();
453 		}
454 
455 	}
456 
457 }