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.util;
17  
18  import java.lang.reflect.Method;
19  import java.util.Collection;
20  
21  import static org.seasar.cubby.action.RequestParameterBindingType.*;
22  import javax.servlet.http.HttpServletRequest;
23  
24  import org.seasar.cubby.action.Accept;
25  import org.seasar.cubby.action.Action;
26  import org.seasar.cubby.action.ActionResult;
27  import org.seasar.cubby.action.Form;
28  import org.seasar.cubby.action.OnSubmit;
29  import org.seasar.cubby.action.Path;
30  import org.seasar.cubby.action.RequestMethod;
31  import org.seasar.cubby.exception.ActionRuntimeException;
32  import org.seasar.framework.beans.BeanDesc;
33  import org.seasar.framework.beans.PropertyDesc;
34  import org.seasar.framework.beans.factory.BeanDescFactory;
35  import org.seasar.framework.util.StringUtil;
36  
37  /**
38   * Cubby内部で使用するユーティリティクラスです。
39   * 
40   * @author baba
41   * @since 1.0.0
42   */
43  public class CubbyUtils {
44  
45  	/** インデックスのメソッド名。 */
46  	private static final String INDEX_METHOD_NAME = "index";
47  
48  	/** デフォルトの{@link Accept}アノテーション。 */
49  	public static Accept DEFAULT_ACCEPT_ANNOTATION;
50  	static {
51  		@Accept
52  		class AcceptDummy {
53  		}
54  		DEFAULT_ACCEPT_ANNOTATION = AcceptDummy.class
55  				.getAnnotation(Accept.class);
56  	}
57  
58  	/**
59  	 * 指定されたアクションクラスに対応するディレクトリを取得します。
60  	 * 
61  	 * @param actionClass
62  	 *            アクションクラス
63  	 * @return アクションクラスに対応するディレクトリ
64  	 */
65  	public static String getActionDirectory(
66  			final Class<? extends Action> actionClass) {
67  		final String actionName;
68  		final Path path = actionClass.getAnnotation(Path.class);
69  		if (path != null && !StringUtil.isEmpty(path.value())) {
70  			actionName = path.value();
71  		} else {
72  			final String name = left(actionClass.getSimpleName(), "$");
73  			actionName = toFirstLower(name.replaceAll(
74  					"(.*[.])*([^.]+)(Action$)", "$2"));
75  		}
76  		return actionName;
77  	}
78  
79  	/**
80  	 * 指定された文字列をセパレータで区切った左側の文字列を返します。
81  	 * 
82  	 * @param text
83  	 *            文字列
84  	 * @param sep
85  	 *            セパレータ
86  	 * @return セパレータで区切った左側の文字列
87  	 */
88  	private static String left(final String text, final String sep) {
89  		final int pos = text.indexOf(sep);
90  		if (pos != -1) {
91  			return text.substring(0, pos);
92  		}
93  		return text;
94  	}
95  
96  	/**
97  	 * 指定された文字列の先頭1文字を小文字に変換します。
98  	 * 
99  	 * @param text
100 	 *            変換する文字列
101 	 * @return 先頭1文字を小文字にした文字列
102 	 */
103 	private static String toFirstLower(final String text) {
104 		if (StringUtil.isEmpty(text)) {
105 			throw new IllegalArgumentException("text is empty.");
106 		}
107 		final StringBuilder sb = new StringBuilder();
108 		sb.append(text.substring(0, 1).toLowerCase());
109 		if (text.length() > 1) {
110 			sb.append(text.substring(1));
111 		}
112 		return sb.toString();
113 	}
114 
115 	/**
116 	 * 指定されたアクションメソッドのパスを取得します。
117 	 * 
118 	 * @param actionClass
119 	 *            アクションクラス
120 	 * @param method
121 	 *            アクションメソッド
122 	 * @return アクションメソッドのパス
123 	 */
124 	public static String getActionPath(
125 			final Class<? extends Action> actionClass, final Method method) {
126 		final String path;
127 		final String actionMethodName = getActionMethodName(method);
128 		if (actionMethodName.startsWith("/")) {
129 			return path = actionMethodName;
130 		} else {
131 			final String actionDirectory = CubbyUtils
132 					.getActionDirectory(actionClass);
133 			if ("/".equals(actionDirectory)) {
134 				path = "/" + actionMethodName;
135 			} else {
136 				path = "/" + actionDirectory + "/" + actionMethodName;
137 			}
138 		}
139 		return path;
140 	}
141 
142 	/**
143 	 * 指定されたアクションメソッドのアクションメソッド名を取得します。
144 	 * 
145 	 * @param method
146 	 *            アクションメソッド
147 	 * @return アクションメソッド名
148 	 */
149 	private static String getActionMethodName(final Method method) {
150 		final String actionName;
151 		final Path path = method.getAnnotation(Path.class);
152 		if (path != null && !StringUtil.isEmpty(path.value())) {
153 			actionName = path.value();
154 		} else {
155 			final String methodName = method.getName();
156 			if (INDEX_METHOD_NAME.equals(methodName)) {
157 				actionName = "";
158 			} else {
159 				actionName = methodName;
160 			}
161 		}
162 		return actionName;
163 	}
164 
165 	/**
166 	 * 指定されたアクションメソッドが受付可能なリクエストメソッドを取得します。
167 	 * 
168 	 * @param actionClass
169 	 *            アクションクラス
170 	 * @param method
171 	 *            アクションメソッド
172 	 * @return 受付可能なリクエストメソッド
173 	 */
174 	public static RequestMethod[] getAcceptableRequestMethods(
175 			final Class<?> actionClass, final Method method) {
176 		final Accept accept;
177 		if (method.isAnnotationPresent(Accept.class)) {
178 			accept = method.getAnnotation(Accept.class);
179 		} else if (actionClass.isAnnotationPresent(Accept.class)) {
180 			accept = actionClass.getAnnotation(Accept.class);
181 		} else {
182 			accept = DEFAULT_ACCEPT_ANNOTATION;
183 		}
184 		return accept.value();
185 	}
186 
187 	/**
188 	 * 指定されたオブジェクトのサイズを取得します。
189 	 * 
190 	 * @param value
191 	 *            オブジェクト
192 	 * @return オブジェクトのサイズ
193 	 */
194 	public static int getObjectSize(final Object value) {
195 		final int size;
196 		if (value == null) {
197 			size = 0;
198 		} else if (value.getClass().isArray()) {
199 			final Object[] array = (Object[]) value;
200 			size = array.length;
201 		} else if (value instanceof Collection) {
202 			final Collection<?> collection = (Collection<?>) value;
203 			size = collection.size();
204 		} else {
205 			size = 1;
206 		}
207 		return size;
208 	}
209 
210 	/**
211 	 * リクエストの URI からコンテキストパスを除いたパスを返します。
212 	 * 
213 	 * @param request
214 	 *            リクエスト
215 	 * @return コンテキストパスを除いたパス
216 	 */
217 	public static String getPath(final HttpServletRequest request) {
218 		final StringBuilder builder = new StringBuilder();
219 		builder.append(request.getServletPath());
220 		final String pathInfo = request.getPathInfo();
221 		if (pathInfo != null) {
222 			builder.append(pathInfo);
223 		}
224 		return builder.toString();
225 	}
226 
227 	/**
228 	 * 指定されたクラスがアクションメソッドかを示します。
229 	 * 
230 	 * @param clazz
231 	 *            クラス
232 	 * @return 指定されたクラスがアクションクラスの場合は <code>true</code>、そうでない場合は
233 	 *         <code>false</code>
234 	 */
235 	public static boolean isActionClass(final Class<?> clazz) {
236 		return Action.class.isAssignableFrom(clazz);
237 	}
238 
239 	/**
240 	 * 指定されたメソッドがアクションメソッドかを示します。
241 	 * 
242 	 * @param method
243 	 *            メソッド
244 	 * @return 指定されたメソッドがアクションメソッドの場合は <code>true</code>、そうでない場合は
245 	 *         <code>false</code>
246 	 */
247 	public static boolean isActionMethod(final Method method) {
248 		return method.getReturnType().isAssignableFrom(ActionResult.class)
249 				&& method.getParameterTypes().length == 0;
250 	}
251 
252 	/**
253 	 * 指定された文字列のなかで、最初に出現した置換対象を置換文字列で置き換えます。
254 	 * 
255 	 * @param text
256 	 *            対象の文字列
257 	 * @param replace
258 	 *            置換対象
259 	 * @param with
260 	 *            置換文字列
261 	 * @return 最初に出現した置換対象を置換文字列で置き換えた文字列
262 	 */
263 	public static String replaceFirst(final String text, final String replace,
264 			final String with) {
265 		if (text == null || replace == null || with == null) {
266 			return text;
267 		}
268 		final int index = text.indexOf(replace);
269 		if (index == -1) {
270 			return text;
271 		}
272 		final StringBuilder builder = new StringBuilder(100);
273 		builder.append(text.substring(0, index));
274 		builder.append(with);
275 		builder.append(text.substring(index + replace.length()));
276 		return builder.toString();
277 	}
278 
279 	/**
280 	 * 指定された文字列を区切り文字で区切った文字列の配列に変換します。
281 	 * 
282 	 * @param text
283 	 *            対象の文字列
284 	 * @param delim
285 	 *            区切り文字
286 	 * @return 指定された文字列を区切り文字で区切った文字列の配列
287 	 */
288 	public static String[] split2(final String text, final char delim) {
289 		if (text == null) {
290 			return null;
291 		}
292 		final int index = text.indexOf(delim);
293 		if (index == -1) {
294 			return new String[] { text };
295 		}
296 		final String[] tokens = new String[2];
297 		tokens[0] = text.substring(0, index);
298 		tokens[1] = text.substring(index + 1);
299 		return tokens;
300 	}
301 
302 	/**
303 	 * 指定された文字列をHTMLとしてエスケープします。
304 	 * <p>
305 	 * <table> <thead>
306 	 * <tr>
307 	 * <th>変換前</th>
308 	 * <th>変換後</th>
309 	 * </tr>
310 	 * </thead> <tbody>
311 	 * <tr>
312 	 * <td>&amp;</td>
313 	 * <td>&amp;amp;</td>
314 	 * </tr>
315 	 * <tr>
316 	 * <td>&lt;</td>
317 	 * <td>&amp;lt;</td>
318 	 * </tr>
319 	 * <tr>
320 	 * <td>&gt;</td>
321 	 * <td>&amp;gt;</td>
322 	 * </tr>
323 	 * <tr>
324 	 * <td>&quot;</td>
325 	 * <td>&amp;quot;</td>
326 	 * </tr>
327 	 * <tr>
328 	 * <td>&#39</td>
329 	 * <td>&amp;#39</td>
330 	 * </tr>
331 	 * </tbody> </table>
332 	 * </p>
333 	 * 
334 	 * @param str
335 	 * @return エスケープされた文字列
336 	 */
337 	public static String escapeHtml(final Object str) {
338 		if (str == null) {
339 			return "";
340 		}
341 		String text;
342 		if (str instanceof String) {
343 			text = (String) str;
344 		} else {
345 			text = str.toString();
346 		}
347 		text = StringUtil.replace(text, "&", "&amp;");
348 		text = StringUtil.replace(text, "<", "&lt;");
349 		text = StringUtil.replace(text, ">", "&gt;");
350 		text = StringUtil.replace(text, "\"", "&quot;");
351 		text = StringUtil.replace(text, "'", "&#39;");
352 		return text;
353 	}
354 
355 	/**
356 	 * アクションメソッドの{@link Path}アノテーションから優先度を取得します。
357 	 * 
358 	 * @param method
359 	 *            アクションメソッド
360 	 * @return 優先度。メソッドに{@link Path}アノテーションが設定されていない場合{@link Integer#MAX_VALUE}
361 	 */
362 	public static int getPriority(final Method method) {
363 		final Path path = method.getAnnotation(Path.class);
364 		return path != null ? path.priority() : Integer.MAX_VALUE;
365 	}
366 
367 	/**
368 	 * 指定されたアクションからアクションメソッドに対応するフォームオブジェクトを取得します。
369 	 * 
370 	 * @param action
371 	 *            アクション
372 	 * @param actionClass
373 	 *            アクションクラス
374 	 * @param method
375 	 *            アクションメソッド
376 	 * @return フォームオブジェクト
377 	 * @throws ActionRuntimeException
378 	 *             &#064;Formでフォームオブジェクトとなるプロパティを指定しているが、そのプロパティが
379 	 *             <code>null</code> だった場合
380 	 * @since 1.0.2
381 	 */
382 	@SuppressWarnings("deprecation")
383 	public static Object getFormBean(final Action action,
384 			final Class<?> actionClass, final Method method) {
385 		final Form form = getForm(actionClass, method);
386 		if (form == null) {
387 			return action;
388 		}
389 		if (!form.binding()) {
390 			return null;
391 		}
392 		if (form.bindingType() == NONE) {
393 			return null;
394 		}
395 		if (Form.THIS.equals(form.value())) {
396 			return action;
397 		}
398 
399 		final String propertyName = form.value();
400 		final BeanDesc beanDesc = BeanDescFactory
401 				.getBeanDesc(action.getClass());
402 		final PropertyDesc propertyDesc = beanDesc
403 				.getPropertyDesc(propertyName);
404 		final Object formBean = propertyDesc.getValue(action);
405 		if (formBean == null) {
406 			throw new ActionRuntimeException("ECUB0102",
407 					new Object[] { propertyName });
408 		}
409 		return formBean;
410 	}
411 
412 	/**
413 	 * 指定されたアクションメソッドを修飾する {@link Form} を取得します。
414 	 * 
415 	 * @param actionClass
416 	 *            アクションクラス
417 	 * @param method
418 	 *            アクションメソッド
419 	 * @return {@link Form}、修飾されていない場合はメソッドが定義されたクラスを修飾する {@link Form}、クラスも修飾されていない場合は
420 	 *         <code>null</code>
421 	 * @since 1.0.2
422 	 */
423 	public static Form getForm(final Class<?> actionClass, final Method method) {
424 		final Form form;
425 		if (method.isAnnotationPresent(Form.class)) {
426 			form = method.getAnnotation(Form.class);
427 		} else {
428 			form = actionClass.getAnnotation(Form.class);
429 		}
430 		return form;
431 	}
432 
433 	/**
434 	 * 指定されたアクションメソッドを使用することを判断するためのパラメータ名を取得します。
435 	 * <p>
436 	 * パラメータ名によらずに実行する場合は <code>null</code> を返します。
437 	 * </p>
438 	 * 
439 	 * @param method
440 	 *            アクションメソッド
441 	 * @return パラメータ名
442 	 */
443 	public static String getOnSubmit(final Method method) {
444 		final OnSubmit onSubmit = method.getAnnotation(OnSubmit.class);
445 		final String parameterName;
446 		if (onSubmit == null) {
447 			parameterName = null;
448 		} else {
449 			parameterName = onSubmit.value();
450 		}
451 		return parameterName;
452 	}
453 
454 	/**
455 	 * リクエストから属性を取得します。
456 	 * 
457 	 * @param <T>
458 	 *            取得する属性の型
459 	 * @param request
460 	 *            リクエスト
461 	 * @param name
462 	 *            属性名
463 	 * @return 属性
464 	 * @since 1.1.0
465 	 */
466 	@SuppressWarnings("unchecked")
467 	public static <T> T getAttribute(final HttpServletRequest request,
468 			final String name) {
469 		return (T) request.getAttribute(name);
470 	}
471 
472 }