View Javadoc

1   /*
2    * Copyright 2004-2009 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_ROUTING;
19  import static org.seasar.cubby.internal.util.LogMessages.format;
20  
21  import java.io.IOException;
22  import java.lang.reflect.Method;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.regex.Pattern;
28  
29  import javax.servlet.RequestDispatcher;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.seasar.cubby.internal.util.MetaUtils;
35  import org.seasar.cubby.internal.util.QueryStringBuilder;
36  import org.seasar.cubby.internal.util.StringUtils;
37  import org.seasar.cubby.routing.PathResolver;
38  import org.seasar.cubby.routing.Routing;
39  import org.seasar.cubby.spi.PathResolverProvider;
40  import org.seasar.cubby.spi.ProviderFactory;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * 指定されたパスにフォワードする {@link ActionResult} です。
46   * <p>
47   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにフォワードします。
48   * </p>
49   * <p>
50   * 使用例1 : フォワード先を相対パスで指定
51   * 
52   * <pre>
53   * return new Forward(&quot;list.jsp&quot;);
54   * </pre>
55   * 
56   * </p>
57   * <p>
58   * 使用例2 : フォワード先を絶対パスで指定
59   * 
60   * <pre>
61   * return new Forward(&quot;/todo/list.jsp&quot;);
62   * </pre>
63   * 
64   * </p>
65   * <p>
66   * 使用例2 : フォワード先をクラスとメソッド名で指定
67   * 
68   * <pre>
69   * return new Forward(TodoListAction.class, &quot;show&quot;);
70   * </pre>
71   * 
72   * <p>
73   * 使用例3 : フォワード先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
74   * 
75   * <pre>
76   * return new Forward(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
77   * </pre>
78   * 
79   * </p>
80   * <p>
81   * 使用例3 : フォワード先をクラスとメソッド名で指定(Mapによるパラメータつき)
82   * 
83   * <pre>
84   * Map&lt;String, String[]&gt; parameters = new HashMap();
85   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
86   * return new Forward(TodoListAction.class, &quot;show&quot;, parameters);
87   * </pre>
88   * 
89   * </p>
90   * <p>
91   * フォワード前には {@link Action#invokePreRenderMethod(Method)} を実行します。 フォワード後には
92   * {@link Action#invokePostRenderMethod(Method)} を実行し、フラッシュメッセージをクリアします。
93   * </p>
94   * 
95   * @author baba
96   * @since 1.0.0
97   */
98  public class Forward implements ActionResult {
99  
100 	/** ロガー。 */
101 	private static final Logger logger = LoggerFactory.getLogger(Forward.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 Routing routing;
112 
113 	/** フォワード先のアクションクラス */
114 	private Class<?> actionClass;
115 
116 	/** フォワード先のアクションクラスのメソッド名 */
117 	private String methodName;
118 
119 	/** フォワード時のパラメータ */
120 	private Map<String, String[]> parameters;
121 
122 	/**
123 	 * インスタンスを生成します。
124 	 * 
125 	 * @param path
126 	 *            フォワード先のパス
127 	 */
128 	public Forward(final String path) {
129 		this.path = path;
130 		this.routing = null;
131 	}
132 
133 	/**
134 	 * インスタンスを生成します。
135 	 * 
136 	 * @param actionClass
137 	 *            アクションクラス
138 	 * @param methodName
139 	 *            アクションメソッド名
140 	 * @param parameters
141 	 *            パラメータ
142 	 * @since 1.1.0
143 	 */
144 	public Forward(final Class<?> actionClass, final String methodName,
145 			final Map<String, String[]> parameters) {
146 		this.actionClass = actionClass;
147 		this.methodName = methodName;
148 		this.parameters = parameters;
149 		try {
150 			final Method method = actionClass.getMethod(methodName);
151 			this.routing = new ForwardRouting(actionClass, method);
152 		} catch (final NoSuchMethodException e) {
153 			throw new IllegalArgumentException(e);
154 		}
155 	}
156 
157 	/**
158 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
159 	 * 
160 	 * @param actionClass
161 	 *            アクションクラス
162 	 * @since 1.1.0
163 	 */
164 	public Forward(final Class<?> actionClass) {
165 		this(actionClass, "index");
166 	}
167 
168 	/**
169 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
170 	 * 
171 	 * @param actionClass
172 	 *            アクションクラス
173 	 * @param methodName
174 	 *            アクションメソッド名
175 	 * @since 1.1.0
176 	 */
177 	public Forward(final Class<?> actionClass, final String methodName) {
178 		this(actionClass, methodName, EMPTY_PARAMETERS);
179 	}
180 
181 	/**
182 	 * パスを取得します。
183 	 * 
184 	 * @param characterEncoding
185 	 *            URI のエンコーディング
186 	 * @return パス
187 	 */
188 	public String getPath(final String characterEncoding) {
189 		if (isReverseLookupRedirect()) {
190 			final PathResolverProvider pathResolverProvider = ProviderFactory
191 					.get(PathResolverProvider.class);
192 			final PathResolver pathResolver = pathResolverProvider
193 					.getPathResolver();
194 			final String forwardPath = pathResolver.reverseLookup(actionClass,
195 					methodName, parameters, characterEncoding);
196 			this.path = forwardPath;
197 		}
198 		return this.path;
199 	}
200 
201 	/**
202 	 * アクションクラスを指定したフォワードかどうかを判定します。
203 	 * 
204 	 * @return アクションクラスを指定したフォワードならtrue
205 	 */
206 	private boolean isReverseLookupRedirect() {
207 		return this.actionClass != null && this.methodName != null
208 				&& this.parameters != null;
209 	}
210 
211 	/**
212 	 * {@inheritDoc}
213 	 */
214 	public void execute(final ActionContext actionContext,
215 			final HttpServletRequest request, final HttpServletResponse response)
216 			throws ServletException, IOException {
217 		actionContext.invokePreRenderMethod();
218 
219 		final String forwardPath = calculateForwardPath(getPath(request
220 				.getCharacterEncoding()), actionContext.getActionClass(),
221 				request.getCharacterEncoding());
222 		if (this.routing != null) {
223 			request.setAttribute(ATTR_ROUTING, this.routing);
224 		}
225 		if (logger.isDebugEnabled()) {
226 			logger.debug(format("DCUB0001", forwardPath, routing));
227 		}
228 		final RequestDispatcher dispatcher = request
229 				.getRequestDispatcher(forwardPath);
230 		dispatcher.forward(request, response);
231 		if (logger.isDebugEnabled()) {
232 			logger.debug(format("DCUB0002", forwardPath));
233 		}
234 
235 		actionContext.invokePostRenderMethod();
236 		actionContext.clearFlash();
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<?> actionClass, final String characterEncoding) {
250 		final String absolutePath;
251 		if (getPath(characterEncoding).startsWith("/")) {
252 			absolutePath = path;
253 		} else {
254 			final String actionDirectory = MetaUtils
255 					.getActionDirectory(actionClass);
256 			if (StringUtils.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(final String paramName, final 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 			final 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 		final 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 	 * @author baba
345 	 * @since 1.1.0
346 	 */
347 	private static class ForwardRouting implements Routing {
348 
349 		/** アクションクラス。 */
350 		private final Class<?> actionClass;
351 
352 		/** アクションメソッド。 */
353 		private final Method actionMethod;
354 
355 		/**
356 		 * 指定されたアクションメソッドを実行する新規ルーティングを生成します。
357 		 */
358 		private ForwardRouting(final Class<?> actionClass,
359 				final Method actionMethod) {
360 			this.actionClass = actionClass;
361 			this.actionMethod = actionMethod;
362 		}
363 
364 		/**
365 		 * {@inheritDoc}
366 		 */
367 		public Class<?> getActionClass() {
368 			return actionClass;
369 		}
370 
371 		/**
372 		 * {@inheritDoc}
373 		 */
374 		public Method getActionMethod() {
375 			return actionMethod;
376 		}
377 
378 		/**
379 		 * {@inheritDoc}
380 		 */
381 		public String getActionPath() {
382 			return null;
383 		}
384 
385 		/**
386 		 * {@inheritDoc}
387 		 */
388 		public List<String> getUriParameterNames() {
389 			return null;
390 		}
391 
392 		/**
393 		 * {@inheritDoc}
394 		 */
395 		public Pattern getPattern() {
396 			return null;
397 		}
398 
399 		/**
400 		 * {@inheritDoc}
401 		 */
402 		public RequestMethod getRequestMethod() {
403 			return null;
404 		}
405 
406 		/**
407 		 * {@inheritDoc}
408 		 */
409 		public String getOnSubmit() {
410 			return null;
411 		}
412 
413 		/**
414 		 * {@inheritDoc}
415 		 */
416 		public int getPriority() {
417 			return 0;
418 		}
419 
420 		/**
421 		 * {@inheritDoc}
422 		 */
423 		public boolean isAcceptable(final String requestMethod) {
424 			return true;
425 		}
426 
427 		/**
428 		 * {@inheritDoc}
429 		 */
430 		@Override
431 		public String toString() {
432 			return new StringBuilder().append("[").append(actionMethod).append(
433 					"]").toString();
434 		}
435 
436 	}
437 
438 }