String message = "Couldn't get bean info for " + instance.getClass();
throw new RuntimeException(message, ie);
}
// if there are any cast/parse exceptions, add them to this
ValidationException validationException = new ValidationException();
for (PropertyDescriptor property : descriptors)
{
// some properties might not be passed in
String propertyName = property.getName();
if (!properties.containsKey(propertyName))
{
continue;
}
// some properties don't have setters
Method setter = property.getWriteMethod();
if (setter == null)
{
continue;
}
Serializable valueObj = properties.get(propertyName);
// catch invocation-level exceptions at the same level
// so we only need to do exception handling once
try
{
// now you have a property, a setter, and a new value. Set it!
// to add property types, put another block here for that type
// cast or parse exceptions are added to the ValidationException
// and returned as part of the validation result set
// the reason you don't just call setter.invoke() is that
// when the map comes in as a String/String map from a form,
// the strings will need to be parsed before calling the setter
// if the property type is a Long/Int/Date/etc
if (property.getPropertyType().equals(String.class))
{
String value = (String) valueObj;
// if empty string, should set to null for database
// (optional=true)
if ("".equals(value))
{
value = null;
}
setter.invoke(instance, value);
continue;
}
if (property.getPropertyType().equals(List.class))
{
setter.invoke(instance, (List) valueObj);
continue;
}
if (property.getPropertyType().equals(HashMap.class))
{
setter.invoke(instance, (HashMap) valueObj);
continue;
}
if (property.getPropertyType().equals(Set.class))
{
setter.invoke(instance, (Set) valueObj);
continue;
}
if (property.getPropertyType().equals(boolean.class)
|| property.getPropertyType().equals(Boolean.class))
{
setter.invoke(instance, ((Boolean) valueObj).booleanValue());
continue;
}
if (property.getPropertyType().equals(Integer.class) || property.getPropertyType().equals(int.class))
{
setter.invoke(instance, (Integer) valueObj);
continue;
}
if (property.getPropertyType().equals(float.class))
{
setter.invoke(instance, ((Float) valueObj).floatValue());
continue;
}
if (property.getPropertyType().equals(Date.class))
{
setter.invoke(instance, (Date) valueObj);
continue;
}
// TODO add more property types here
// once our domain classes have more than just strings.
validationException.addError(propertyName, "Type not found.");
// TODO some of these exceptions I either can't get to
// (because they're programmatically prevented above)
// or I just plain don't know how to cause. These should be tested as well
}
catch (IllegalArgumentException e)
{
validationException.addError(propertyName, valueObj + " had an illegal argument");
}
catch (IllegalAccessException e)
{
validationException.addError(propertyName, valueObj + " couldn't be accessed");
}
catch (InvocationTargetException e)
{
validationException.addError(propertyName, valueObj + " couldn't be invoked");
}
}
// hibernate would throw an exception on writing,
// but if we do it manually we can re-wrap hibernate's validation info
// in a more concise and gwt-friendly way
ClassValidator validator = new ClassValidator(instance.getClass());
InvalidValue[] invalidValues = validator.getInvalidValues(instance);
for (InvalidValue invalidValue : invalidValues)
{
validationException.addError(invalidValue.getPropertyName(), invalidValue.getMessage());
}
// throw if we had any parse errors or constraints errors
if (validationException.getErrors().size() > 0)
{
throw validationException;
}
}