/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you 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.openengsb.core.api.model; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.annotation.XmlRootElement; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; /** * Container class that is intended to ease serialization. It is especially useful when the Runtime-type of the class is * not known, or when doing Serialization on Object-hierarchies. * * Example: A {@link org.openengsb.core.api.security.model.SecureRequest} contains a field for * {@link org.openengsb.core.api.security.model.AuthenticationInfo}. There may exist many implementations of * {@link org.openengsb.core.api.security.model.AuthenticationInfo} which can be difficult to handle with some message * formats * * However this class has some limits. It only supports String and byte[] properties. If any other type is encountered * the "toString()"-method is invoked to transform it into the beandescription. To transform it back, the type is * searched for a constructor that only takes one String as argument. */ @XmlRootElement public class BeanDescription implements Serializable { private static final long serialVersionUID = -3027590994502598619L; private String className; private Map<String, String> data; private Map<String, byte[]> binaryData; protected BeanDescription(String className) { this.className = className; data = new HashMap<String, String>(); binaryData = new HashMap<String, byte[]>(); } public BeanDescription(String className, Map<String, String> data) { this.className = className; this.data = data; } public BeanDescription(String className, Map<String, String> data, Map<String, byte[]> binaryData) { this.className = className; this.data = data; this.binaryData = binaryData; } public static BeanDescription fromObject(Object bean) { String className = bean.getClass().getCanonicalName(); BeanDescription desc = new BeanDescription(className); populateData(desc, bean); return desc; } public BeanDescription() { } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } /** * reconstructs the original object the {@link BeanDescription} is representing. */ public Object toObject() { Class<?> beanType = getBeanType(); return toObject(beanType); } private void doSetPropertyOnBean(Object bean, PropertyDescriptor d) throws IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> propertyType = d.getPropertyType(); if (byte[].class.isAssignableFrom(propertyType)) { d.getWriteMethod().invoke(bean, binaryData.get(d.getName())); } else { Object value = getPropertyValueFromString(d, propertyType); d.getWriteMethod().invoke(bean, value); } } private Object getPropertyValueFromString(PropertyDescriptor d, Class<?> propertyType) throws InstantiationException, IllegalAccessException, InvocationTargetException { String string = data.get(d.getName()); if (propertyType.equals(String.class)) { return string; } else { Constructor<?> constructor; try { constructor = propertyType.getConstructor(String.class); } catch (NoSuchMethodException e) { throw new IllegalStateException(e); } return constructor.newInstance(string); } } private Class<?> getBeanType() { ClassLoader loader = this.getClass().getClassLoader(); try { return loader.loadClass(className); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } } /** * reconstructs the original object the {@link BeanDescription} is representing. */ public <T> T toObject(Class<T> type) { Collection<PropertyDescriptor> accessibleProperties = getAccessiblePropertiesFromBean(type); T bean; try { bean = type.newInstance(); for (PropertyDescriptor d : accessibleProperties) { doSetPropertyOnBean(bean, d); } return bean; } catch (InstantiationException e) { throw new IllegalStateException(e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } private static BeanDescription populateData(BeanDescription desc, Object bean) { Collection<PropertyDescriptor> relevantPropertyDescriptors = getAccessiblePropertiesFromBean(bean.getClass()); for (PropertyDescriptor propertyDescriptor : relevantPropertyDescriptors) { if (byte[].class.isAssignableFrom(propertyDescriptor.getPropertyType())) { desc.binaryData.put(propertyDescriptor.getName(), (byte[]) getPropertyValue(bean, propertyDescriptor)); } else if (!propertyDescriptor.getPropertyType().isArray()) { desc.data.put(propertyDescriptor.getName(), getPropertyValue(bean, propertyDescriptor).toString()); } } return desc; } private static Collection<PropertyDescriptor> getAccessiblePropertiesFromBean(Class<?> beanClass) { BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(beanClass); } catch (IntrospectionException e) { throw new IllegalArgumentException(e); } List<PropertyDescriptor> allPropertyDescriptors = Arrays.asList(beanInfo.getPropertyDescriptors()); Collection<PropertyDescriptor> relevantPropertyDescriptors = Collections2.filter(allPropertyDescriptors, new Predicate<PropertyDescriptor>() { @Override public boolean apply(PropertyDescriptor input) { Method writeMethod = input.getWriteMethod(); return writeMethod != null && Modifier.isPublic(writeMethod.getModifiers()); } }); return relevantPropertyDescriptors; } private static Object getPropertyValue(Object bean, PropertyDescriptor propertyDescriptor) { try { return propertyDescriptor.getReadMethod().invoke(bean); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw new IllegalStateException(e); } } public Map<String, String> getData() { return data; } public void setData(Map<String, String> data) { this.data = data; } public Map<String, byte[]> getBinaryData() { return binaryData; } public void setBinaryData(Map<String, byte[]> binaryData) { this.binaryData = binaryData; } }