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.text.ChoiceFormat;
22 import java.text.MessageFormat;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.MissingResourceException;
27 import java.util.ResourceBundle;
28
29 import ognl.OgnlContext;
30 import ognl.OgnlException;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 import de.smartics.exceptions.i18n.ConfigurationException;
36 import de.smartics.exceptions.i18n.MessageComposer;
37 import de.smartics.exceptions.i18n.MethodAccessConfigurationException;
38 import de.smartics.exceptions.i18n.PropertyAccessConfigurationException;
39 import de.smartics.exceptions.i18n.app.ConfigurationExceptionCode;
40 import de.smartics.exceptions.i18n.message.MessageParamParser.MessageParamInfo;
41 import de.smartics.exceptions.ognl.OgnlExpression;
42
43
44
45
46
47
48
49
50
51
52
53 public class DefaultMessageComposer implements MessageComposer
54 {
55
56
57
58
59
60
61
62
63
64 private final Log log = LogFactory.getLog(DefaultMessageComposer.class);
65
66
67
68
69
70
71
72
73 public DefaultMessageComposer()
74 {
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 public String composeMessage(final Throwable exception, final Locale locale,
94 final ResourceBundle bundle, final String keyPrefix,
95 final MessageType messageType)
96 {
97 if (messageType == null)
98 {
99 throw new NullPointerException(
100 "Message type to compose a message must not be 'null'.");
101 }
102
103 final String key = messageType.createKey(keyPrefix);
104 final String messageTemplate = bundle.getString(key);
105
106 final byte maxIndex = MessageTemplateAnalyser.maxIndex(messageTemplate);
107 if (maxIndex > -1)
108 {
109 final Object[] messageArguments = new Object[maxIndex + 1];
110
111 final MessageFormat formatter =
112 new MessageFormat(messageTemplate, locale);
113 Class<?> currentClass = exception.getClass();
114 do
115 {
116 supplyParentPropertyValues(exception, key, bundle, formatter,
117 messageType, messageArguments);
118
119 final Field[] declaredFields = currentClass.getDeclaredFields();
120 supplyPropertyValues(exception, key, bundle, formatter, messageType,
121 messageArguments, declaredFields);
122 currentClass = currentClass.getSuperclass();
123 }
124 while (currentClass != null && currentClass != Exception.class
125 && !isAllInformationProvided(messageArguments));
126
127 final String output = formatter.format(messageArguments);
128 return output;
129 }
130 else
131 {
132 return messageTemplate;
133 }
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147 private void supplyParentPropertyValues(final Throwable exception,
148 final String key, final ResourceBundle bundle,
149 final MessageFormat formatter, final MessageType messageType,
150 final Object[] messageArguments)
151 {
152 final Class<?> clazz = exception.getClass();
153 final ParentMessageParam annotation =
154 clazz.getAnnotation(ParentMessageParam.class);
155 if (annotation != null)
156 {
157 final Map<String, List<MessageParamInfo>> map =
158 messageType.getParentMessageParamInfos(annotation);
159 for (Map.Entry<String, List<MessageParamInfo>> entry : map.entrySet())
160 {
161 final String attribute = entry.getKey();
162 final List<MessageParamInfo> infos = entry.getValue();
163
164 for (MessageParamInfo info : infos)
165 {
166 final int index = info.getIndex();
167 if (index < messageArguments.length)
168 {
169 Object value = getProperty(exception, attribute);
170 if (value != null)
171 {
172 value = applyOgnl(exception, attribute, info, value);
173 applyCompoundMessageInfo(exception, key, bundle, formatter,
174 attribute, index);
175 messageArguments[index] = value;
176 }
177 }
178 }
179 }
180 }
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198 private Object applyOgnl(final Throwable exception, final String attribute,
199 final MessageParamInfo info, final Object value)
200 throws PropertyAccessConfigurationException
201 {
202 final Object newValue;
203 final String ognlPath = info.getOgnlPath();
204 if (ognlPath != null)
205 {
206 newValue = evaluateOgnl(exception, attribute, value, ognlPath);
207 }
208 else
209 {
210 newValue = value;
211 }
212
213 return newValue;
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 private void applyCompoundMessageInfo(final Throwable exception,
230 final String key, final ResourceBundle bundle,
231 final MessageFormat formatter, final String attribute, final int index)
232 throws PropertyAccessConfigurationException
233 {
234 final String[] limitStrings =
235 createLimitStrings(exception, attribute, key, index, bundle);
236 if (limitStrings != null)
237 {
238 final ChoiceFormat choiceFormat = new ChoiceFormat(new double[]
239 { 0d, 1d, 2d }, limitStrings);
240 formatter.setFormat(index, choiceFormat);
241 }
242 }
243
244
245
246
247
248
249
250
251 private static boolean isAllInformationProvided(
252 final Object[] messageArguments)
253 {
254 for (Object element : messageArguments)
255 {
256 if (element == null)
257 {
258 return false;
259 }
260 }
261 return true;
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278 private void supplyPropertyValues(final Throwable exception,
279 final String key, final ResourceBundle bundle,
280 final MessageFormat formatter, final MessageType messageType,
281 final Object[] messageArguments, final Field[] fields)
282 throws PropertyAccessConfigurationException
283 {
284 String fieldName;
285 for (Field field : fields)
286 {
287 fieldName = field.getName();
288 if (log.isTraceEnabled())
289 {
290 log.trace("Processing annotations for field '" + fieldName + "'...");
291 }
292
293 final MessageParam messageParam = field.getAnnotation(MessageParam.class);
294 if (messageParam != null)
295 {
296 final List<MessageParamInfo> infos =
297 messageType.getMessageParamInfos(messageParam);
298 for (MessageParamInfo info : infos)
299 {
300 final int index = info.getIndex();
301 if (index < messageArguments.length)
302 {
303 Object value = getValue(exception, field);
304 if (value != null)
305 {
306 value = applyOgnl(exception, fieldName, info, value);
307 applyCompoundMessageInfo(exception, key, bundle, formatter,
308 fieldName, index);
309 messageArguments[index] = value;
310 }
311 }
312 }
313 }
314 }
315 }
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332 private static String[] createLimitStrings(final Throwable exception,
333 final String fieldName, final String key, final int index,
334 final ResourceBundle bundle) throws PropertyAccessConfigurationException
335 {
336 try
337 {
338 final String compoundKeyPrefix = key + '.' + index;
339
340 final String key0 = compoundKeyPrefix + ".0";
341 final String key1 = compoundKeyPrefix + ".1";
342 final String keyM = compoundKeyPrefix + ".m";
343
344
345
346
347 final boolean isCompoundMessage =
348 containsKey(bundle, key0) || containsKey(bundle, key1)
349 || containsKey(bundle, keyM);
350 final String[] limitStrings;
351 if (isCompoundMessage)
352 {
353 limitStrings =
354 new String[]
355 { bundle.getString(key0), bundle.getString(key1),
356 bundle.getString(keyM) };
357 }
358 else
359 {
360 limitStrings = null;
361 }
362 return limitStrings;
363 }
364 catch (final MissingResourceException e)
365 {
366 throw new PropertyAccessConfigurationException(e,
367 ConfigurationExceptionCode.COMPOUND_MESSAGE_MISSING, fieldName,
368 exception.getClass());
369 }
370 }
371
372
373
374
375
376
377
378
379
380 private static boolean containsKey(final ResourceBundle bundle,
381 final String key)
382 {
383 try
384 {
385 bundle.getString(key);
386 return true;
387 }
388 catch (final Exception e)
389 {
390 return false;
391 }
392 }
393
394
395
396
397
398
399
400
401
402
403
404
405
406 private Object evaluateOgnl(final Throwable exception,
407 final String fieldName, final Object value, final String ognlPath)
408 throws PropertyAccessConfigurationException
409 {
410 try
411 {
412 final OgnlExpression expression = new OgnlExpression(ognlPath);
413 final OgnlContext context = new OgnlContext();
414 return expression.getValue(context, value);
415 }
416 catch (final OgnlException e)
417 {
418 throw new PropertyAccessConfigurationException(e,
419 ConfigurationExceptionCode.CONFIGURATION_OGNL_SYNTAX_ERROR,
420 fieldName, exception.getClass());
421 }
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436 private static Object getValue(final Object instance, final Field field)
437 throws ConfigurationException
438 {
439 final PropertyInfo propertyInfo = field.getAnnotation(PropertyInfo.class);
440 final Object value;
441 if (propertyInfo != null && !"".equals(propertyInfo.getter()))
442 {
443 final String getter = propertyInfo.getter();
444 value = getValue(instance, field.getName(), getter);
445 }
446 else
447 {
448 if (field.isAccessible())
449 {
450 value = getFieldValue(instance, field);
451 }
452 else
453 {
454 final String fieldName = field.getName();
455 value = getProperty(instance, fieldName);
456 }
457 }
458
459 return value;
460 }
461
462
463
464
465
466
467
468
469
470
471
472 private static Object getFieldValue(final Object instance, final Field field)
473 {
474 try
475 {
476 final Object value = field.get(instance);
477 return value;
478 }
479 catch (final IllegalArgumentException e)
480 {
481 assert false : "The field '" + field.getName()
482 + "' is not a member of the class '" + instance.getClass()
483 + "': " + e.getMessage();
484 }
485 catch (final IllegalAccessException e)
486 {
487 assert false : "The field '" + field.getName() + "' in class '"
488 + instance.getClass() + "' is not accessible: "
489 + e.getMessage();
490 }
491
492
493 assert false : "The system should never reach this point.";
494 return null;
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508
509 private static Object getValue(final Object instance,
510 final String propertyName, final String methodName)
511 throws MethodAccessConfigurationException
512 {
513 try
514 {
515 final Method method =
516 instance.getClass().getMethod(methodName, new Class[0]);
517 final Object value = method.invoke(instance, new Object[0]);
518 return value;
519 }
520 catch (final SecurityException e)
521 {
522 throw new MethodAccessConfigurationException(e,
523 ConfigurationExceptionCode.CONFIGURATION_SECURITY, propertyName,
524 instance.getClass(), methodName);
525 }
526 catch (final NoSuchMethodException e)
527 {
528 throw new MethodAccessConfigurationException(e,
529 ConfigurationExceptionCode.CONFIGURATION_MISSING_GETTER,
530 propertyName, instance.getClass(), methodName);
531 }
532 catch (final IllegalArgumentException e)
533 {
534 throw new MethodAccessConfigurationException(e,
535 ConfigurationExceptionCode.CONFIGURATION_NOARG, propertyName,
536 instance.getClass(), methodName);
537 }
538 catch (final IllegalAccessException e)
539 {
540 throw new MethodAccessConfigurationException(e,
541 ConfigurationExceptionCode.CONFIGURATION_MISSING_GETTER,
542 propertyName, instance.getClass(), methodName);
543 }
544 catch (final InvocationTargetException e)
545 {
546 throw new MethodAccessConfigurationException(e,
547 ConfigurationExceptionCode.CONFIGURATION_RUNTIME_ACCESS,
548 propertyName, instance.getClass(), methodName);
549 }
550 }
551
552
553
554
555
556
557
558
559
560
561
562 private static Object getProperty(final Object instance,
563 final String propertyName) throws PropertyAccessConfigurationException
564 {
565 try
566 {
567
568 final String methodName = "get" + Helper.capitalize(propertyName);
569 final Method method =
570 instance.getClass().getMethod(methodName, new Class[0]);
571 final Object value = method.invoke(instance, new Object[0]);
572 return value;
573 }
574 catch (final IllegalAccessException e)
575 {
576 throw new PropertyAccessConfigurationException(e,
577 ConfigurationExceptionCode.CONFIGURATION_INACCESSIBLE_PROPERTY,
578 propertyName, instance.getClass());
579 }
580 catch (final InvocationTargetException e)
581 {
582 throw new PropertyAccessConfigurationException(e,
583 ConfigurationExceptionCode.CONFIGURATION_PROPERTY_RUNTIME_ACCESS,
584 propertyName, instance.getClass());
585 }
586 catch (final NoSuchMethodException e)
587 {
588 throw new PropertyAccessConfigurationException(e,
589 ConfigurationExceptionCode.CONFIGURATION_NO_GETTER_FOR_PROPERTY,
590 propertyName, instance.getClass());
591 }
592 }
593
594
595 }