View Javadoc

1   /*
2    * Copyright 2004-2010 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  
17  package org.seasar.cubby.action;
18  
19  import static org.seasar.cubby.CubbyConstants.ATTR_ROUTING;
20  import static org.seasar.cubby.internal.util.LogMessages.format;
21  
22  import java.io.IOException;
23  import java.lang.reflect.Method;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.regex.Pattern;
29  
30  import javax.servlet.RequestDispatcher;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.seasar.cubby.internal.util.MetaUtils;
36  import org.seasar.cubby.internal.util.QueryStringBuilder;
37  import org.seasar.cubby.internal.util.StringUtils;
38  import org.seasar.cubby.routing.PathResolver;
39  import org.seasar.cubby.routing.Routing;
40  import org.seasar.cubby.spi.PathResolverProvider;
41  import org.seasar.cubby.spi.ProviderFactory;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  /**
46   * 指定されたパスにフォワードする {@link ActionResult} です。
47   * <p>
48   * アクションメソッドの戻り値としてこのインスタンスを指定することで、指定されたパスにフォワードします。
49   * </p>
50   * <p>
51   * 使用例1 : フォワード先を相対パスで指定
52   * 
53   * <pre>
54   * return new Forward(&quot;list.jsp&quot;);
55   * </pre>
56   * 
57   * </p>
58   * <p>
59   * 使用例2 : フォワード先を絶対パスで指定
60   * 
61   * <pre>
62   * return new Forward(&quot;/todo/list.jsp&quot;);
63   * </pre>
64   * 
65   * </p>
66   * <p>
67   * 使用例2 : フォワード先をクラスとメソッド名で指定
68   * 
69   * <pre>
70   * return new Forward(TodoListAction.class, &quot;show&quot;);
71   * </pre>
72   * 
73   * <p>
74   * 使用例3 : フォワード先をクラスとメソッド名で指定(paramメソッドによるパラメータつき)
75   * 
76   * <pre>
77   * return new Forward(TodoListAction.class, &quot;show&quot;).param(&quot;value1&quot;, &quot;12345&quot;);
78   * </pre>
79   * 
80   * </p>
81   * <p>
82   * 使用例3 : フォワード先をクラスとメソッド名で指定(Mapによるパラメータつき)
83   * 
84   * <pre>
85   * Map&lt;String, String[]&gt; parameters = new HashMap();
86   * parameters.put(&quot;value1&quot;, new String[] { &quot;12345&quot; });
87   * return new Forward(TodoListAction.class, &quot;show&quot;, parameters);
88   * </pre>
89   * 
90   * </p>
91   * <p>
92   * フォワード前には {@link Action#invokePreRenderMethod(Method)} を実行します。 フォワード後には
93   * {@link Action#invokePostRenderMethod(Method)} を実行し、フラッシュメッセージをクリアします。
94   * </p>
95   * 
96   * @author baba
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 	/** {@link PathResolver} のプロバイダ。 */
108 	private final PathResolverProvider pathResolverProvider;
109 
110 	/** フォワード先のパス。 */
111 	private String path;
112 
113 	/** ルーティング。 */
114 	private final Routing routing;
115 
116 	/** フォワード先のアクションクラス */
117 	private Class<?> actionClass;
118 
119 	/** フォワード先のアクションクラスのメソッド名 */
120 	private String methodName;
121 
122 	/** フォワード時のパラメータ */
123 	private Map<String, String[]> parameters;
124 
125 	/**
126 	 * インスタンスを生成します。
127 	 * 
128 	 * @param path
129 	 *            フォワード先のパス
130 	 */
131 	public Forward(final String path) {
132 		this.pathResolverProvider = null;
133 		this.path = path;
134 		this.routing = null;
135 	}
136 
137 	/**
138 	 * インスタンスを生成します。
139 	 * 
140 	 * @param actionClass
141 	 *            アクションクラス
142 	 * @param methodName
143 	 *            アクションメソッド名
144 	 * @param parameters
145 	 *            パラメータ
146 	 */
147 	public Forward(final Class<?> actionClass, final String methodName,
148 			final Map<String, String[]> parameters) {
149 		this.pathResolverProvider = ProviderFactory
150 				.get(PathResolverProvider.class);
151 		this.actionClass = actionClass;
152 		this.methodName = methodName;
153 		this.parameters = parameters;
154 		try {
155 			final Method method = actionClass.getMethod(methodName);
156 			this.routing = new ForwardRouting(actionClass, method);
157 		} catch (final NoSuchMethodException e) {
158 			throw new IllegalArgumentException(e);
159 		}
160 	}
161 
162 	/**
163 	 * 指定されたアクションクラスのindexメソッドへフォワードするインスタンスを生成します。
164 	 * 
165 	 * @param actionClass
166 	 *            アクションクラス
167 	 */
168 	public Forward(final Class<?> actionClass) {
169 		this(actionClass, "index");
170 	}
171 
172 	/**
173 	 * 指定されたアクションメソッドへフォワードするインスタンスを生成します。
174 	 * 
175 	 * @param actionClass
176 	 *            アクションクラス
177 	 * @param methodName
178 	 *            アクションメソッド名
179 	 */
180 	public Forward(final Class<?> actionClass, final String methodName) {
181 		this(actionClass, methodName, EMPTY_PARAMETERS);
182 	}
183 
184 	/**
185 	 * パスを取得します。
186 	 * 
187 	 * @param characterEncoding
188 	 *            URI のエンコーディング
189 	 * @return パス
190 	 */
191 	public String getPath(final String characterEncoding) {
192 		if (isReverseLookupRedirect()) {
193 			final PathResolver pathResolver = this.pathResolverProvider
194 					.getPathResolver();
195 			final String forwardPath = pathResolver.reverseLookup(actionClass,
196 					methodName, parameters, characterEncoding);
197 			this.path = forwardPath;
198 		}
199 		return this.path;
200 	}
201 
202 	/**
203 	 * アクションクラスを指定したフォワードかどうかを判定します。
204 	 * 
205 	 * @return アクションクラスを指定したフォワードならtrue
206 	 */
207 	private boolean isReverseLookupRedirect() {
208 		return this.actionClass != null && this.methodName != null
209 				&& this.parameters != null;
210 	}
211 
212 	/**
213 	 * {@inheritDoc}
214 	 */
215 	public void execute(final ActionContext actionContext,
216 			final HttpServletRequest request, final HttpServletResponse response)
217 			throws ServletException, IOException {
218 		actionContext.invokePreRenderMethod();
219 
220 		final String forwardPath = calculateForwardPath(getPath(request
221 				.getCharacterEncoding()), actionContext.getActionClass(),
222 				request.getCharacterEncoding());
223 		if (this.routing != null) {
224 			request.setAttribute(ATTR_ROUTING, this.routing);
225 		}
226 		if (logger.isDebugEnabled()) {
227 			logger.debug(format("DCUB0001", forwardPath, routing));
228 		}
229 		final RequestDispatcher dispatcher = request
230 				.getRequestDispatcher(forwardPath);
231 		dispatcher.forward(request, response);
232 		if (logger.isDebugEnabled()) {
233 			logger.debug(format("DCUB0002", forwardPath));
234 		}
235 
236 		actionContext.invokePostRenderMethod();
237 		actionContext.clearFlash();
238 	}
239 
240 	/**
241 	 * フォワードするパスを計算します。
242 	 * 
243 	 * @param actionClass
244 	 *            アクションクラス
245 	 * @param characterEncoding
246 	 *            URI のエンコーディング
247 	 * @return フォワードするパス
248 	 */
249 	protected String calculateForwardPath(final String path,
250 			final Class<?> actionClass, final String characterEncoding) {
251 		final String absolutePath;
252 		if (getPath(characterEncoding).startsWith("/")) {
253 			absolutePath = path;
254 		} else {
255 			final String actionDirectory = MetaUtils
256 					.getActionDirectory(actionClass);
257 			if (StringUtils.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(final String paramName, final 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 			final 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 		final 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 	 * @author baba
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 }