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