1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package de.smartics.exceptions.i18n.message;
17
18 import java.lang.reflect.Field;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.HashMap;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.ResourceBundle;
27
28 import ognl.OgnlContext;
29 import ognl.OgnlException;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34 import com.ibm.icu.text.MessageFormat;
35
36 import de.smartics.exceptions.i18n.ConfigurationException;
37 import de.smartics.exceptions.i18n.LocalizedException;
38 import de.smartics.exceptions.i18n.MessageComposer;
39 import de.smartics.exceptions.i18n.MethodAccessConfigurationException;
40 import de.smartics.exceptions.i18n.PropertyAccessConfigurationException;
41 import de.smartics.exceptions.i18n.app.ConfigurationExceptionCode;
42 import de.smartics.exceptions.i18n.message.MessageParamParser.MessageParamInfo;
43 import de.smartics.exceptions.ognl.OgnlExpression;
44 import de.smartics.util.lang.Arg;
45
46
47
48
49
50
51 public final class IcuMessageComposer implements MessageComposer
52 {
53
54
55
56
57
58
59
60
61
62 private final Log log = LogFactory.getLog(IcuMessageComposer.class);
63
64
65
66
67
68
69
70
71 public IcuMessageComposer()
72 {
73 }
74
75
76
77
78
79
80
81
82
83
84
85 @Override
86 public String composeMessage(final Object bean, final Locale locale,
87 final ResourceBundle bundle, final String keyPrefix,
88 final MessageType messageType) throws NullPointerException,
89 IllegalArgumentException
90 {
91 Arg.checkNotNull("messageType", messageType);
92
93 final String key = messageType.createKey(keyPrefix);
94 final String messageTemplate = bundle.getString(key);
95
96 final Map<String, Object> messageArgs =
97 createMessageArgs(bean, locale, messageType);
98
99 final MessageFormat messageFormat =
100 new MessageFormat(messageTemplate, locale);
101
102 final String message = messageFormat.format(messageArgs);
103
104 return message;
105 }
106
107 private Map<String, Object> createMessageArgs(final Object bean,
108 final Locale locale, final MessageType messageType)
109 {
110 final List<Class<?>> types = collectParents(bean);
111 final Map<String, Object> args = new HashMap<String, Object>();
112
113 for (final Class<?> type : types)
114 {
115 addParentMessageParamArgs(args, bean, locale, messageType, type);
116 addMessageParamFieldArgs(args, type, bean, locale, messageType);
117 addMessageParamMethodArgs(args, type, bean, locale, messageType);
118 }
119 return args;
120 }
121
122 private static List<Class<?>> collectParents(final Object bean)
123 {
124 Class<?> clazz = bean.getClass();
125 final LinkedList<Class<?>> classes = new LinkedList<Class<?>>();
126
127 do
128 {
129 classes.addFirst(clazz);
130 clazz = clazz.getSuperclass();
131 }
132 while (clazz != Object.class);
133
134 return classes;
135 }
136
137 private static void addParentMessageParamArgs(final Map<String, Object> args,
138 final Object bean, final Locale locale, final MessageType messageType,
139 final Class<?> clazz)
140 {
141 final ParentMessageParam annotation =
142 clazz.getAnnotation(ParentMessageParam.class);
143 if (annotation != null)
144 {
145 final Map<String, List<MessageParamInfo>> map =
146 messageType.getParentMessageParamInfos(annotation);
147 for (final Map.Entry<String, List<MessageParamInfo>> entry : map
148 .entrySet())
149 {
150 final String attribute = entry.getKey();
151 final List<MessageParamInfo> infos = entry.getValue();
152
153 for (final MessageParamInfo info : infos)
154 {
155 final String placeholderId = info.getPlaceholderId();
156 Object value = getProperty(bean, attribute);
157 value = normalizeValue(bean, locale, attribute, info, value);
158 args.put(placeholderId, value);
159 }
160 }
161 }
162 }
163
164 private static Object normalizeValue(final Object bean, final Locale locale,
165 final String attribute, final MessageParamInfo info, final Object value)
166 {
167 final Object norm;
168 if (value != null)
169 {
170 norm = applyOgnl(bean, locale, attribute, info, value);
171 }
172 else
173 {
174 norm = "-/-";
175 }
176 return norm;
177 }
178
179 private void addMessageParamFieldArgs(final Map<String, Object> args,
180 final Class<?> type, final Object bean, final Locale locale,
181 final MessageType messageType)
182 {
183 String fieldName;
184 final Field[] fields = type.getDeclaredFields();
185
186 for (final Field field : fields)
187 {
188 fieldName = field.getName();
189 if (log.isTraceEnabled())
190 {
191 log.trace(String.format("Processing annotations for field '%s'...",
192 fieldName));
193 }
194
195 final MessageParam messageParam = field.getAnnotation(MessageParam.class);
196 if (messageParam != null)
197 {
198 final List<MessageParamInfo> infos =
199 messageType.getMessageParamInfos(fieldName, messageParam);
200 for (final MessageParamInfo info : infos)
201 {
202 final String placeholderId = info.getPlaceholderId();
203 Object value = getValue(bean, field);
204 value = normalizeValue(bean, locale, fieldName, info, value);
205 args.put(placeholderId, value);
206 }
207 }
208 }
209 }
210
211 private void addMessageParamMethodArgs(final Map<String, Object> args,
212 final Class<?> type, final Object bean, final Locale locale,
213 final MessageType messageType)
214 {
215 String methodName;
216 final Object[] localeArg = new Object[] { locale };
217 final Method[] methods = type.getDeclaredMethods();
218 for (final Method method : methods)
219 {
220 methodName = method.getName();
221 if (log.isTraceEnabled())
222 {
223 log.trace("Processing annotations for method '" + methodName + "'...");
224 }
225
226 final MessageParam messageParam =
227 method.getAnnotation(MessageParam.class);
228 if (messageParam != null)
229 {
230 final String propertyName = calcPropertyName(methodName);
231 final List<MessageParamInfo> infos =
232 messageType.getMessageParamInfos(propertyName, messageParam);
233 for (final MessageParamInfo info : infos)
234 {
235 final String placeholderId = info.getPlaceholderId();
236 Object value = getValue(bean, method, localeArg);
237 value = normalizeValue(bean, locale, propertyName, info, value);
238 args.put(placeholderId, value);
239 }
240 }
241 }
242 }
243
244 private static Object getProperty(final Object bean, final String propertyName)
245 throws PropertyAccessConfigurationException
246 {
247 try
248 {
249
250 final String methodName = "get" + Helper.capitalize(propertyName);
251 final Method method = bean.getClass().getMethod(methodName, new Class[0]);
252 final Object value = method.invoke(bean, new Object[0]);
253 return value;
254 }
255 catch (final IllegalAccessException e)
256 {
257 throw new PropertyAccessConfigurationException(e,
258 ConfigurationExceptionCode.CONFIGURATION_INACCESSIBLE_PROPERTY,
259 propertyName, bean.getClass());
260 }
261 catch (final InvocationTargetException e)
262 {
263 throw new PropertyAccessConfigurationException(e,
264 ConfigurationExceptionCode.CONFIGURATION_PROPERTY_RUNTIME_ACCESS,
265 propertyName, bean.getClass());
266 }
267 catch (final NoSuchMethodException e)
268 {
269 throw new PropertyAccessConfigurationException(e,
270 ConfigurationExceptionCode.CONFIGURATION_NO_GETTER_FOR_PROPERTY,
271 propertyName, bean.getClass());
272 }
273 }
274
275 private static Object applyOgnl(final Object exception, final Locale locale,
276 final String attribute, final MessageParamInfo info, final Object value)
277 throws PropertyAccessConfigurationException
278 {
279 final Object newValue;
280 final String ognlPath = info.getOgnlPath();
281 if (ognlPath != null)
282 {
283 if (value instanceof Throwable && "cause".equals(attribute)
284 && "message".equals(ognlPath))
285 {
286 final Throwable cause = ((Throwable) value).getCause();
287 if(cause instanceof LocalizedException)
288 {
289 newValue = ((LocalizedException) cause).getMessages(locale);
290 }
291 else
292 {
293 newValue = evaluateOgnl(exception, attribute, value, ognlPath);
294 }
295 }
296 else
297 {
298 newValue = evaluateOgnl(exception, attribute, value, ognlPath);
299 }
300 }
301 else
302 {
303 newValue = value;
304 }
305
306 return newValue;
307 }
308
309 private static Object evaluateOgnl(final Object bean, final String fieldName,
310 final Object value, final String ognlPath)
311 throws PropertyAccessConfigurationException
312 {
313 try
314 {
315 final OgnlExpression expression = new OgnlExpression(ognlPath);
316 final OgnlContext context = new OgnlContext();
317 return expression.getValue(context, value);
318 }
319 catch (final OgnlException e)
320 {
321 throw new PropertyAccessConfigurationException(e,
322 ConfigurationExceptionCode.CONFIGURATION_OGNL_SYNTAX_ERROR,
323 fieldName, bean.getClass());
324 }
325 }
326
327 private static Object getValue(final Object bean, final Field field)
328 throws ConfigurationException
329 {
330 final PropertyInfo propertyInfo = field.getAnnotation(PropertyInfo.class);
331 final Object value;
332 if (propertyInfo != null && !"".equals(propertyInfo.getter()))
333 {
334 final String getter = propertyInfo.getter();
335 value = getValue(bean, field.getName(), getter);
336 }
337 else
338 {
339 final boolean accessible = field.isAccessible();
340 try
341 {
342 field.setAccessible(true);
343 value = getFieldValue(bean, field);
344 }
345 finally
346 {
347 field.setAccessible(accessible);
348 }
349 }
350
351 return value;
352 }
353
354 private static Object getValue(final Object bean, final String propertyName,
355 final String methodName) throws MethodAccessConfigurationException
356 {
357 try
358 {
359 final Method method = bean.getClass().getMethod(methodName, new Class[0]);
360 final Object value = method.invoke(bean, new Object[0]);
361 return value;
362 }
363 catch (final SecurityException e)
364 {
365 throw new MethodAccessConfigurationException(e,
366 ConfigurationExceptionCode.CONFIGURATION_SECURITY, propertyName,
367 bean.getClass(), methodName);
368 }
369 catch (final NoSuchMethodException e)
370 {
371 throw new MethodAccessConfigurationException(e,
372 ConfigurationExceptionCode.CONFIGURATION_MISSING_GETTER,
373 propertyName, bean.getClass(), methodName);
374 }
375 catch (final IllegalArgumentException e)
376 {
377 throw new MethodAccessConfigurationException(e,
378 ConfigurationExceptionCode.CONFIGURATION_NOARG, propertyName,
379 bean.getClass(), methodName);
380 }
381 catch (final IllegalAccessException e)
382 {
383 throw new MethodAccessConfigurationException(e,
384 ConfigurationExceptionCode.CONFIGURATION_MISSING_GETTER,
385 propertyName, bean.getClass(), methodName);
386 }
387 catch (final InvocationTargetException e)
388 {
389 throw new MethodAccessConfigurationException(e,
390 ConfigurationExceptionCode.CONFIGURATION_RUNTIME_ACCESS,
391 propertyName, bean.getClass(), methodName);
392 }
393 }
394
395 private static Object getValue(final Object bean, final Method method,
396 final Object[] localeArg) throws ConfigurationException
397 {
398 try
399 {
400 final Object value;
401 if (method.getParameterTypes().length == 0)
402 {
403 value = method.invoke(bean, new Object[0]);
404 }
405 else
406 {
407 value = method.invoke(bean, localeArg);
408 }
409 return value;
410 }
411 catch (final SecurityException e)
412 {
413 throw new MethodAccessConfigurationException(e,
414 ConfigurationExceptionCode.CONFIGURATION_SECURITY,
415 calcPropertyName(method.getName()), bean.getClass(), method.getName());
416 }
417 catch (final IllegalArgumentException e)
418 {
419 throw new MethodAccessConfigurationException(e,
420 ConfigurationExceptionCode.CONFIGURATION_NOARG,
421 calcPropertyName(method.getName()), bean.getClass(), method.getName());
422 }
423 catch (final IllegalAccessException e)
424 {
425 throw new MethodAccessConfigurationException(e,
426 ConfigurationExceptionCode.CONFIGURATION_MISSING_GETTER,
427 calcPropertyName(method.getName()), bean.getClass(), method.getName());
428 }
429 catch (final InvocationTargetException e)
430 {
431 throw new MethodAccessConfigurationException(e,
432 ConfigurationExceptionCode.CONFIGURATION_RUNTIME_ACCESS,
433 calcPropertyName(method.getName()), bean.getClass(), method.getName());
434 }
435 }
436
437 private static String calcPropertyName(final String methodName)
438 {
439 return methodName.startsWith("get") && methodName.length() > 3 ? methodName
440 .substring(3) : methodName;
441 }
442
443 private static Object getFieldValue(final Object bean, final Field field)
444 {
445 try
446 {
447 final Object value = field.get(bean);
448 return value;
449 }
450 catch (final IllegalArgumentException e)
451 {
452 assert false : "The field '" + field.getName()
453 + "' is not a member of the class '" + bean.getClass()
454 + "': " + e.getMessage();
455 }
456 catch (final IllegalAccessException e)
457 {
458 assert false : "The field '" + field.getName() + "' in class '"
459 + bean.getClass() + "' is not accessible: "
460 + e.getMessage();
461 }
462
463
464 assert false : "The system should never reach this point.";
465 return null;
466 }
467
468
469
470 }