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