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