001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata masks may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: FieldValueConvertor.java,v $
031: * Revision 1.12 2005/10/12 18:36:36 colinmacleod
032: * Standardized format of Logger declaration - to make it easier to find instances
033: * which are not both static and final.
034: *
035: * Revision 1.11 2005/10/11 18:55:29 colinmacleod
036: * Fixed some checkstyle and javadoc issues.
037: *
038: * Revision 1.10 2005/10/02 10:46:54 colinmacleod
039: * Improved logging.
040: * Added checking for primitive types - now initializes them with "0" string
041: * when the value is null or empty.
042: *
043: * Revision 1.9 2005/09/29 12:09:42 colinmacleod
044: * Fixed ValidatorError constructor parameters to include the field.
045: *
046: * Revision 1.8 2005/09/14 12:51:52 colinmacleod
047: * Added serialVersionUID.
048: *
049: * Revision 1.7 2005/04/27 14:28:08 colinmacleod
050: * Fixed so it returns null for a null or empty
051: * string.
052: *
053: * Revision 1.6 2005/04/11 12:27:02 colinmacleod
054: * Added preliminary support for filters.
055: * Added FieldValueConvertor factor interface
056: * to split off value convertors for reuse.
057: *
058: * Revision 1.5 2005/04/09 18:04:15 colinmacleod
059: * Changed copyright text to GPL v2 explicitly.
060: *
061: * Revision 1.4 2005/03/10 10:20:02 colinmacleod
062: * Now implements Serializable.
063: *
064: * Revision 1.3 2005/01/19 12:35:04 colinmacleod
065: * Added hidden fields.
066: *
067: * Revision 1.2 2005/01/06 22:13:21 colinmacleod
068: * Moved up a version number.
069: * Changed copyright notices to 2005.
070: * Updated the documentation:
071: * - started working on multiproject:site docu.
072: * - changed the logo.
073: * Added checkstyle and fixed LOADS of style issues.
074: * Added separate thirdparty subproject.
075: * Added struts (in web), util and webgui (in webtheme) from ivata op.
076: *
077: * Revision 1.1 2004/12/29 20:07:07 colinmacleod
078: * Renamed subproject masks to mask.
079: *
080: * Revision 1.2 2004/11/11 13:33:14 colinmacleod
081: * Bug fixes. Added log4j logging.
082: *
083: * Revision 1.1.1.1 2004/05/16 20:40:32 colinmacleod
084: * Ready for 0.1 release
085: * -----------------------------------------------------------------------------
086: */
087: package com.ivata.mask.field;
088:
089: import java.beans.PropertyDescriptor;
090: import java.io.Serializable;
091: import java.lang.reflect.Constructor;
092: import java.lang.reflect.InvocationTargetException;
093: import java.util.Arrays;
094:
095: import org.apache.commons.beanutils.PropertyUtils;
096: import org.apache.log4j.Logger;
097:
098: import com.ivata.mask.util.StringHandling;
099: import com.ivata.mask.util.SystemException;
100: import com.ivata.mask.validation.ValidationError;
101: import com.ivata.mask.validation.ValidationErrors;
102:
103: /**
104: * <p>
105: * Retrieve the value from a value object for a given field.
106: * </p>
107: *
108: * @since ivata masks 0.1 (2004-05-14)
109: * @author Colin MacLeod
110: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
111: * @version $Revision: 1.12 $
112: */
113: public class FieldValueConvertor implements Serializable {
114: /**
115: * Serialization version (for <code>Serializable</code> interface).
116: */
117: private static final long serialVersionUID = 1L;
118:
119: /**
120: * <p>
121: * Wraps any error encountered when trying retrieve a field value via
122: * reflection.
123: * </p>
124: *
125: * @since ivata masks 0.1 (2004-05-14)
126: * @author Colin MacLeod
127: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
128: * @version $Revision: 1.12 $
129: */
130: public static class FieldValueException extends RuntimeException {
131: /**
132: * Serialization version (for <code>Serializable</code> interface).
133: */
134: private static final long serialVersionUID = 1L;
135:
136: /**
137: * <p>
138: * Construct a field value exception from another throwable.
139: * </p>
140: *
141: * @param throwable
142: * cause of the exception.
143: */
144: public FieldValueException(final Throwable throwable) {
145: super ("ERROR (" + throwable.getClass().getName() + "): "
146: + throwable.getMessage());
147: }
148:
149: /**
150: * <p>
151: * Construct a field value exception from another throwable.
152: * </p>
153: *
154: * @param throwable
155: * cause of the exception.
156: * @param location
157: * brief text describing where the error ocurred
158: */
159: public FieldValueException(final Throwable throwable,
160: final String location) {
161: super ("ERROR (" + throwable.getClass().getName() + ") "
162: + location + ": " + throwable.getMessage());
163: }
164: }
165:
166: /**
167: * <p>
168: * This log provides tracing and debugging information.
169: * </p>
170: */
171: private static final Logger logger = Logger
172: .getLogger(FieldValueConvertor.class);
173:
174: /**
175: * <p>
176: * Convert a value from a string. Override this method to choose how your
177: * class converts string values to your object class values.
178: * </p>
179: *
180: * <p>
181: * This implementation attempts to instantiate an object of the desired
182: * class by locating a constructor which takes a single string as an
183: * argument.
184: * </p>
185: *
186: * @param propertyClassParam
187: * exact class to be converted to.
188: * @param stringValueParam
189: * value to be converted.
190: * @return valid object value converted from a string.
191: */
192: public Object convertFromString(final Class propertyClassParam,
193: final String stringValueParam) {
194: if (logger.isDebugEnabled()) {
195: logger
196: .debug("convertFromString(Class propertyClassParam = "
197: + propertyClassParam
198: + ", String stringValueParam = "
199: + stringValueParam + ") - start");
200: }
201:
202: String stringValue = stringValueParam;
203: if (StringHandling.isNullOrEmpty(stringValue)) {
204: // if the property class is primitive, and we have a null or empty
205: // string, set the string to '0'
206: if (propertyClassParam.isPrimitive()) {
207: stringValue = "0";
208: } else {
209: // if we got nothing in for any non-primitive type, assume
210: // null means 'an empty result' for this type.
211:
212: if (logger.isDebugEnabled()) {
213: logger
214: .debug("convertFromString - end - return value = "
215: + null);
216: }
217: return null;
218: }
219: }
220: Constructor stringConstructor;
221: Class propertyClass;
222: if (propertyClassParam.isPrimitive()) {
223: try {
224: propertyClass = DefaultFieldValueConvertorFactory
225: .convertPrimitiveType(propertyClassParam);
226: } catch (SystemException e) {
227: logger.error("convertFromString(Class, String)", e);
228:
229: throw new RuntimeException(e);
230: }
231: } else {
232: propertyClass = propertyClassParam;
233: }
234: try {
235: stringConstructor = propertyClass
236: .getConstructor(new Class[] { String.class });
237: } catch (SecurityException e) {
238: logger.error("convertFromString(Class, String)", e);
239:
240: throw new FieldValueException(e, "constructing '"
241: + propertyClass.getName() + "' from string value '"
242: + stringValue + "'");
243: } catch (NoSuchMethodException e) {
244: logger.error("convertFromString(Class, String)", e);
245:
246: throw new FieldValueException(e, "constructing '"
247: + propertyClass.getName() + "' from string value '"
248: + stringValue + "'");
249: }
250: Object value;
251: try {
252: value = stringConstructor
253: .newInstance(new Object[] { stringValue });
254: } catch (IllegalArgumentException e) {
255: logger.error("convertFromString(Class, String)", e);
256:
257: throw new FieldValueException(e, "constructing '"
258: + propertyClass.getName() + "' from string value '"
259: + stringValue + "'");
260: } catch (InstantiationException e) {
261: logger.error("convertFromString(Class, String)", e);
262:
263: throw new FieldValueException(e, "constructing '"
264: + propertyClass.getName() + "' from string value '"
265: + stringValue + "'");
266: } catch (IllegalAccessException e) {
267: logger.error("convertFromString(Class, String)", e);
268:
269: throw new FieldValueException(e, "constructing '"
270: + propertyClass.getName() + "' from string value '"
271: + stringValue + "'");
272: } catch (InvocationTargetException e) {
273: logger.error("convertFromString(Class, String)", e);
274:
275: throw new FieldValueException(e, "constructing '"
276: + propertyClass.getName() + "' from string value '"
277: + stringValue + "'");
278: }
279:
280: if (logger.isDebugEnabled()) {
281: logger.debug("convertFromString - end - return value = "
282: + value);
283: }
284: return value;
285: }
286:
287: /**
288: * <p>
289: * Get the value of a named property within an object.
290: * </p>
291: *
292: * @param object
293: * POJO for which to return the field value.
294: * @param propertyName
295: * name of the property/field to return the value for.
296: * @param defaultValue
297: * value to use if none is set.
298: * @return the value of the named property.
299: */
300: protected final Object getObjectValue(final Object object,
301: final String propertyName, final Object defaultValue) {
302: if (logger.isDebugEnabled()) {
303: logger.debug("getObjectValue(Object object = " + object
304: + ", String propertyName = " + propertyName
305: + ", Object defaultValue = " + defaultValue
306: + ") - start");
307: }
308:
309: Object objectValue;
310: try {
311: objectValue = PropertyUtils.getProperty(object,
312: propertyName);
313: } catch (IllegalAccessException e) {
314: logger.error("getObjectValue(Object, String, Object)", e);
315:
316: throw new FieldValueException(e);
317: } catch (InvocationTargetException e) {
318: logger.error("getObjectValue(Object, String, Object)", e);
319:
320: throw new FieldValueException(e);
321: } catch (NoSuchMethodException e) {
322: logger.error("getObjectValue(Object, String, Object)", e);
323:
324: // if there is no method, just set the value to null
325: objectValue = null;
326: }
327: if (objectValue == null) {
328: objectValue = defaultValue;
329: }
330:
331: if (logger.isDebugEnabled()) {
332: logger.debug("getObjectValue - end - return value = "
333: + objectValue);
334: }
335: return objectValue;
336: }
337:
338: /**
339: * <p>
340: * Get the value of the field provided, in the value object supplied, and
341: * return the string equivalent.
342: * </p>
343: *
344: * @param object
345: * POJO for which to return the field value.
346: * @param propertyName
347: * name of the property/field to return the value for.
348: * @param defaultValue
349: * value to use if none is set.
350: * @return the value of the named property, as a string.
351: */
352: public final String getStringValue(final Object object,
353: final String propertyName, final String defaultValue) {
354: if (logger.isDebugEnabled()) {
355: logger.debug("getStringValue(Object object = " + object
356: + ", String propertyName = " + propertyName
357: + ", String defaultValue = " + defaultValue
358: + ") - start");
359: }
360:
361: Object objectValue = getObjectValue(object, propertyName,
362: defaultValue);
363: String returnString = toString(objectValue);
364: if (logger.isDebugEnabled()) {
365: logger.debug("getStringValue - end - return value = "
366: + returnString);
367: }
368: return returnString;
369: }
370:
371: /**
372: * <p>
373: * Set the value of the field provided, in the value object supplied.
374: * </p>
375: *
376: * <p>
377: * This implementation attempts to instantiate an object of the desired
378: * class by locating a constructor which takes a single string as an
379: * argument.
380: * </p>
381: *
382: * @param object
383: * POJO for which to set the field value.
384: * @param field
385: * field to be set.
386: * @param stringValue
387: * new string equivalent value of this field.
388: * @return errors, if there are any errors with the field values, otherwise
389: * an empty collection.
390: */
391: public final ValidationErrors setStringValue(final Object object,
392: final Field field, final String stringValue) {
393: if (logger.isDebugEnabled()) {
394: logger.debug("setStringValue(Object object = " + object
395: + ", Field field = " + field
396: + ", String stringValue = " + stringValue
397: + ") - start");
398: }
399:
400: ValidationErrors validationErrors = new ValidationErrors();
401: PropertyDescriptor descriptor;
402: try {
403: descriptor = PropertyUtils.getPropertyDescriptor(object,
404: field.getName());
405: } catch (IllegalAccessException e) {
406: logger.error("setStringValue(Object, Field, String)", e);
407:
408: throw new FieldValueException(e);
409: } catch (InvocationTargetException e) {
410: logger.error("setStringValue(Object, Field, String)", e);
411:
412: throw new FieldValueException(e);
413: } catch (NoSuchMethodException e) {
414: logger.error("setStringValue(Object, Field, String)", e);
415:
416: throw new FieldValueException(e);
417: }
418: // if there is no setter, just get out.
419: if (descriptor == null) {
420: if (logger.isDebugEnabled()) {
421: logger.debug("setStringValue - end - return value = "
422: + validationErrors);
423: }
424: return validationErrors;
425: }
426: Class propertyClass = descriptor.getPropertyType();
427: Object value;
428: try {
429: // work around for primitive types
430: if ("int".equals(propertyClass.getName())) {
431: propertyClass = Integer.class;
432: } else if ("short".equals(propertyClass.getName())) {
433: propertyClass = Short.class;
434: } else if ("long".equals(propertyClass.getName())) {
435: propertyClass = Long.class;
436: } else if ("float".equals(propertyClass.getName())) {
437: propertyClass = Float.class;
438: } else if ("double".equals(propertyClass.getName())) {
439: propertyClass = Double.class;
440: }
441: value = convertFromString(propertyClass, stringValue);
442: } catch (FieldValueException e) {
443: logger.error("setStringValue(Object, Field, String)", e);
444:
445: validationErrors.add(new ValidationError(null, field,
446: "errors.field.invalidValue", Arrays
447: .asList(new Object[] { stringValue })));
448:
449: if (logger.isDebugEnabled()) {
450: logger.debug("setStringValue - end - return value = "
451: + validationErrors);
452: }
453: return validationErrors;
454: }
455: if (validationErrors.isEmpty()) {
456: try {
457: PropertyUtils.setProperty(object, field.getName(),
458: value);
459: } catch (IllegalAccessException e) {
460: logger
461: .error("setStringValue(Object, Field, String)",
462: e);
463:
464: throw new FieldValueException(e);
465: } catch (InvocationTargetException e) {
466: logger
467: .error("setStringValue(Object, Field, String)",
468: e);
469:
470: throw new FieldValueException(e);
471: } catch (NoSuchMethodException e) {
472: // this means there is no setter - that's probably ok.
473: if (!("idString".equals(field.getName()) || "class"
474: .equals(field.getName()))) {
475: logger.warn("Warning (" + e.getClass().getName()
476: + ": setting value '" + value
477: + "' to field '" + field + " on object "
478: + object + ": " + e.getMessage());
479: }
480: }
481: }
482:
483: if (logger.isDebugEnabled()) {
484: logger.debug("setStringValue - end - return value = "
485: + validationErrors);
486: }
487: return validationErrors;
488: }
489:
490: /**
491: * <p>
492: * Convert a field object value into a string. Override this method to
493: * convert for a specific type.
494: * </p>
495: *
496: * @param objectValue
497: * object to be converted.
498: * @return string equivalent.
499: */
500: protected String toString(final Object objectValue) {
501: if (logger.isDebugEnabled()) {
502: logger.debug("toString(Object objectValue = " + objectValue
503: + ") - start");
504: }
505:
506: // default implementation simply uses toString...
507: if (objectValue == null) {
508: if (logger.isDebugEnabled()) {
509: logger
510: .debug("toString(Object) - end - return value = ");
511: }
512: return "";
513: } else {
514: String returnString = objectValue.toString();
515: if (logger.isDebugEnabled()) {
516: logger.debug("toString(Object) - end - return value = "
517: + returnString);
518: }
519: return returnString;
520: }
521: }
522: }
|