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   */
97  public class Forward implements ActionResult {
98  
99  	/** ロガー。 */
100 	private static final Logger logger = LoggerFactory.getLogger(Forward.class);
101 
102 	/** 空のパラメータ。 */
103 	private static final Map<String, String[]> EMPTY_PARAMETERS = Collections
104 			.emptyMap();
105 
106 	/** {@link PathResolver} のプロバイダ。 */
107 	private final PathResolverProvider pathResolverProvider;
108 
109 	/** フォワード先のパス。 */
110 	private String path;
111 
112 	/** ルーティング。 */
113 	private final Routing routing;
114 
115 	/** フォワード先のアクションクラス */
116 	private Class<?> actionClass;
117 
118 	/** フォワード先のアクションクラスのメソッド名 */
119 	private String methodName;
120 
121 	/** フォワード時のパラメータ */
122 	private Map<String, String[]> parameters;
123 
124 	/**
125 	 * インスタンスを生成します。
126 	 * 
127 	 * @param path
128 	 *            フォワード先のパス
129 	 */
130 	public Forward(final String path) {
131 		this.pathResolverProvider = null;
132 		this.path = path;
133 		this.routing = null;
134 	}
135 
136 	/**
137 	 * インスタンスを生成します。
138 	 * 
139 	 * @param actionClass
140 	 *            アクションクラス
141 	 * @param methodName
142 	 *            アクションメソッド名
143 	 * @param parameters
144 	 *            パラメータ
145 	 */
146 	public Forward(final Class<?> actionClass, final String methodName,
147 			final Map<String, String[]> parameters) {
148 		this.pathResolverProvider = ProviderFactory
149 				.get(PathResolverProvider.class);
150 		this.actionClass = actionClass;
151 		this.methodName = methodName;
152 		this.parameters = parameters;
153 		try {
154 			final Method method = actionClass.getMethod(methodName);
155 			this.routing = new ForwardRouting(actionClass, method);
156 		} catch (final NoSuchMethodException e) {
157 			throw new IllegalArgumentException(e);
158 		}
159 	}
160 
161 	/**
162 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
163 	 * 
164 	 * @param actionClass
165 	 *            アクションクラス
166 	 */
167 	public Forward(final Class<?> actionClass) {
168 		this(actionClass, "index");
169 	}
170 
171 	/**
172 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
173 	 * 
174 	 * @param actionClass
175 	 *            アクションクラス
176 	 * @param methodName
177 	 *            アクションメソッド名
178 	 */
179 	public Forward(final Class<?> actionClass, final String methodName) {
180 		this(actionClass, methodName, EMPTY_PARAMETERS);
181 	}
182 
183 	/**
184 	 * パスを取得します。
185 	 * 
186 	 * @param characterEncoding
187 	 *            URI のエンコーディング
188 	 * @return パス
189 	 */
190 	public String getPath(final String characterEncoding) {
191 		if (isReverseLookupRedirect()) {
192 			final PathResolver pathResolver = this.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 	 */
346 	private static class ForwardRouting implements Routing {
347 
348 		/** アクションクラス。 */
349 		private final Class<?> actionClass;
350 
351 		/** アクションメソッド。 */
352 		private final Method actionMethod;
353 
354 		/**
355 		 * 指定されたアクションメソッドを実行する新規ルーティングを生成します。
356 		 */
357 		private ForwardRouting(final Class<?> actionClass,
358 				final Method actionMethod) {
359 			this.actionClass = actionClass;
360 			this.actionMethod = actionMethod;
361 		}
362 
363 		/**
364 		 * {@inheritDoc}
365 		 */
366 		public Class<?> getActionClass() {
367 			return actionClass;
368 		}
369 
370 		/**
371 		 * {@inheritDoc}
372 		 */
373 		public Method getActionMethod() {
374 			return actionMethod;
375 		}
376 
377 		/**
378 		 * {@inheritDoc}
379 		 */
380 		public String getActionPath() {
381 			return null;
382 		}
383 
384 		/**
385 		 * {@inheritDoc}
386 		 */
387 		public List<String> getUriParameterNames() {
388 			return null;
389 		}
390 
391 		/**
392 		 * {@inheritDoc}
393 		 */
394 		public Pattern getPattern() {
395 			return null;
396 		}
397 
398 		/**
399 		 * {@inheritDoc}
400 		 */
401 		public RequestMethod getRequestMethod() {
402 			return null;
403 		}
404 
405 		/**
406 		 * {@inheritDoc}
407 		 */
408 		public String getOnSubmit() {
409 			return null;
410 		}
411 
412 		/**
413 		 * {@inheritDoc}
414 		 */
415 		public int getPriority() {
416 			return 0;
417 		}
418 
419 		/**
420 		 * {@inheritDoc}
421 		 */
422 		public boolean isAcceptable(final String requestMethod) {
423 			return true;
424 		}
425 
426 		/**
427 		 * {@inheritDoc}
428 		 */
429 		@Override
430 		public String toString() {
431 			return new StringBuilder().append("[").append(actionMethod).append(
432 					"]").toString();
433 		}
434 
435 	}
436 
437 }