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   * </p>
88   * <p>
89   * フォワード前には {@link Action#invokePreRenderMethod(Method)} を実行します。 フォワード後には
90   * {@link Action#invokePostRenderMethod(Method)} を実行し、フラッシュメッセージをクリアします。
91   * </p>
92   * 
93   * @author baba
94   * @since 1.0.0
95   */
96  public class Forward implements ActionResult {
97  
98  	/** ロガー。 */
99  	private static final Logger logger = Logger.getLogger(Forward.class);
100 
101 	/** 空のパラメータ。 */
102 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
103 			.emptyMap();
104 
105 	/** フォワード先のパス。 */
106 	private String path;
107 
108 	/** ルーティング。 */
109 	private final Map<String, Routing> routings;
110 
111 	/** フォワード先のアクションクラス */
112 	private Class<? extends Action> actionClass;
113 
114 	/** フォワード先のアクションクラスのメソッド名 */
115 	private String methodName;
116 
117 	/** フォワード時のパラメータ */
118 	private Map<String, String[]> parameters;
119 
120 	/**
121 	 * インスタンスを生成します。
122 	 * 
123 	 * @param path
124 	 *            フォワード先のパス
125 	 */
126 	public Forward(final String path) {
127 		this.path = path;
128 		this.routings = null;
129 	}
130 
131 	/**
132 	 * インスタンスを生成します。
133 	 * 
134 	 * @param actionClass
135 	 *            アクションクラス
136 	 * @param methodName
137 	 *            アクションメソッド名
138 	 * @param parameters
139 	 *            パラメータ
140 	 * @since 1.1.0
141 	 */
142 	public Forward(final Class<? extends Action> actionClass,
143 			final String methodName, final Map<String, String[]> parameters) {
144 		this.actionClass = actionClass;
145 		this.methodName = methodName;
146 		this.parameters = parameters;
147 		final Method method = ClassUtil.getMethod(actionClass, methodName,
148 				new Class[0]);
149 		final Routing routing = new ForwardRouting(actionClass, method);
150 		this.routings = Collections.singletonMap(null, routing);
151 	}
152 
153 	/**
154 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
155 	 * 
156 	 * @param actionClass
157 	 *            アクションクラス
158 	 * @since 1.1.0
159 	 */
160 	public Forward(final Class<? extends Action> actionClass) {
161 		this(actionClass, "index");
162 	}
163 
164 	/**
165 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
166 	 * 
167 	 * @param actionClass
168 	 *            アクションクラス
169 	 * @param methodName
170 	 *            アクションメソッド名
171 	 * @since 1.1.0
172 	 */
173 	public Forward(final Class<? extends Action> actionClass,
174 			final String methodName) {
175 		this(actionClass, methodName, EMPTY_PARAMETERS);
176 	}
177 
178 	/**
179 	 * パスを取得します。
180 	 * 
181 	 * @return パス
182 	 */
183 	public String getPath() {
184 		if (isReverseLookupRedirect()) {
185 			final S2Container container = SingletonS2ContainerFactory
186 			.getContainer();
187 			final PathResolver pathResolver = (PathResolver) container
188 					.getComponent(PathResolver.class);
189 			this.path = pathResolver.buildInternalForwardPath(parameters);
190 		}
191 		return this.path;
192 	}
193 
194 	
195 	/**
196 	 * アクションクラスを指定したフォワードかどうかを判定します。
197 	 * @return アクションクラスを指定したフォワードならtrue
198 	 */
199 	private boolean isReverseLookupRedirect() {
200 		return this.actionClass != null && this.methodName != null && this.parameters != null;
201 	}
202 
203 	/**
204 	 * {@inheritDoc}
205 	 */
206 	public void execute(final Action action,
207 			final Class<? extends Action> actionClass, final Method method,
208 			final HttpServletRequest request, final HttpServletResponse response)
209 			throws ServletException, IOException {
210 		action.invokePreRenderMethod(method);
211 
212 		final String forwardPath = calculateForwardPath(getPath(), actionClass);
213 		if (this.routings != null) {
214 			request.setAttribute(ATTR_ROUTINGS, this.routings);
215 		}
216 		if (logger.isDebugEnabled()) {
217 			logger.log("DCUB0001", new Object[] { forwardPath, routings });
218 		}
219 		final RequestDispatcher dispatcher = request
220 				.getRequestDispatcher(forwardPath);
221 		dispatcher.forward(request, response);
222 		if (logger.isDebugEnabled()) {
223 			logger.log("DCUB0002", new Object[] { forwardPath });
224 		}
225 
226 		action.invokePostRenderMethod(method);
227 
228 		action.getFlash().clear();
229 	}
230 
231 	/**
232 	 * フォワードするパスを計算します。
233 	 * 
234 	 * @param actionClass
235 	 *            アクションクラス
236 	 * @return フォワードするパス
237 	 */
238 	protected String calculateForwardPath(final String path,
239 			final Class<? extends Action> actionClass) {
240 		final String absolutePath;
241 		if (getPath().startsWith("/")) {
242 			absolutePath = path;
243 		} else {
244 			final String actionDirectory = CubbyUtils
245 					.getActionDirectory(actionClass);
246 			if (StringUtil.isEmpty(actionDirectory)) {
247 				absolutePath = "/" + path;
248 			} else {
249 				final StringBuilder builder = new StringBuilder();
250 				if (!actionDirectory.startsWith("/")) {
251 					builder.append("/");
252 				}
253 				builder.append(actionDirectory);
254 				if (!actionDirectory.endsWith("/")) {
255 					builder.append("/");
256 				}
257 				builder.append(path);
258 				absolutePath = builder.toString();
259 			}
260 		}
261 		return absolutePath;
262 	}
263 
264 
265 	/**
266 	 * パラメータを追加します。
267 	 * @param paramName パラメータ名
268 	 * @param paramValue パラメータの値。{@code Object#toString()}の結果が値として使用されます。
269 	 * @return フォワードする URL
270 	 */
271 	public Forward param(String paramName, Object paramValue) {
272 		return param(paramName, new String[] { paramValue.toString() });
273 	}
274 	
275 	/**
276 	 * パラメータを追加します。
277 	 * @param paramName パラメータ名
278 	 * @param paramValues パラメータの値の配列。配列の要素の{@code Object#toString()}の結果がそれぞれの値として使用されます。
279 	 * @return フォワードする URL
280 	 */
281 	public Forward param(final String paramName, final Object[] paramValues) {
282 		return param(paramName, toStringArray(paramValues));
283 	}
284 
285 	/**
286 	 * パラメータを追加します。
287 	 * @param paramName パラメータ名
288 	 * @param paramValues パラメータの値
289 	 * @return フォワードする URL
290 	 */
291 	public Forward param(final String paramName, final String[] paramValues) {
292 		if (isReverseLookupRedirect()) {
293 			if (this.parameters == EMPTY_PARAMETERS) {
294 				this.parameters = new HashMap<String, String[]>();
295 			}
296 			this.parameters.put(paramName, paramValues);
297 		} else {
298 			QueryStringBuilder builder = new QueryStringBuilder(this.path);
299 			builder.addParam(paramName, paramValues);
300 			this.path = builder.toString();
301 		}
302 		return this;
303 	}
304 	
305 	/**
306 	 * {@code Object#toString()}型の配列を{@code Object#toString()}型の配列に変換します。
307 	 * <p>
308 	 * 配列のそれぞれの要素に対して{@code Object#toString()}を使用して変換します。
309 	 * </p>
310 	 * @param paramValues {@code Object#toString()}型の配列
311 	 * @return {@code Object#toString()}型の配列。
312 	 */
313 	private String[] toStringArray(final Object[] paramValues) {
314 		String[] values = new String[paramValues.length];
315 		for (int i = 0; i < paramValues.length; i++) {
316 			values[i] = paramValues[i].toString();
317 		}
318 		return values;
319 	}
320 
321 	/**
322 	 * アクションメソッドへフォワードするためのルーティング。
323 	 * 
324 	 * @author baba
325 	 * @since 1.1.0
326 	 */
327 	private static class ForwardRouting implements Routing {
328 
329 		/** アクションクラス。 */
330 		private Class<? extends Action> actionClass;
331 
332 		/** アクションメソッド。 */
333 		private Method method;
334 
335 		/**
336 		 * {@inheritDoc}
337 		 */
338 		private ForwardRouting(final Class<? extends Action> actionClass,
339 				final Method method) {
340 			this.actionClass = actionClass;
341 			this.method = method;
342 		}
343 
344 		/**
345 		 * {@inheritDoc}
346 		 */
347 		public Class<? extends Action> getActionClass() {
348 			return actionClass;
349 		}
350 
351 		/**
352 		 * {@inheritDoc}
353 		 */
354 		public Method getMethod() {
355 			return method;
356 		}
357 
358 		/**
359 		 * {@inheritDoc}
360 		 */
361 		public String getActionPath() {
362 			return null;
363 		}
364 
365 		/**
366 		 * {@inheritDoc}
367 		 */
368 		public List<String> getUriParameterNames() {
369 			return null;
370 		}
371 
372 		/**
373 		 * {@inheritDoc}
374 		 */
375 		public Pattern getPattern() {
376 			return null;
377 		}
378 
379 		/**
380 		 * {@inheritDoc}
381 		 */
382 		public RequestMethod getRequestMethod() {
383 			return null;
384 		}
385 
386 		/**
387 		 * {@inheritDoc}
388 		 */
389 		public String getOnSubmit() {
390 			return null;
391 		}
392 
393 		/**
394 		 * {@inheritDoc}
395 		 */
396 		public int getPriority() {
397 			return 0;
398 		}
399 
400 		/**
401 		 * {@inheritDoc}
402 		 */
403 		public boolean isAuto() {
404 			return false;
405 		}
406 
407 		/**
408 		 * {@inheritDoc}
409 		 */
410 		public boolean isAcceptable(final String requestMethod) {
411 			return true;
412 		}
413 
414 		/**
415 		 * {@inheritDoc}
416 		 */
417 		@Override
418 		public String toString() {
419 			return new StringBuilder().append("[").append(method).append("]")
420 					.toString();
421 		}
422 
423 	}
424 
425 }