/* * Copyright (c) 2009-2010 Lockheed Martin Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eurekastreams.server.service.actions.strategies; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eurekastreams.commons.exceptions.ValidationException; import org.hibernate.validator.ClassValidator; import org.hibernate.validator.InvalidValue; /** * uses reflection to update an object. */ public class ReflectiveUpdater implements UpdaterStrategy { /** * public constructor. * */ public ReflectiveUpdater() { } /** * suppressing warnings here because not using generics: this is class-non-specific code. * * @param instance * the object on which to set properties * @param properties * properties to set on the instance. */ public void setProperties(final Object instance, final Map<String, Serializable> properties) { PropertyDescriptor[] descriptors; try { descriptors = Introspector.getBeanInfo(instance.getClass()).getPropertyDescriptors(); } catch (IntrospectionException ie) { // (from the javadocs) // Typical causes of this exception include // not being able to map a string class name to a Class object, // not being able to resolve a string method name, // or specifying a method name that has the wrong type signature for its intended use 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; } } }