package org.jboss.seam.remoting.wrapper; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.List; import java.util.Set; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import org.dom4j.Element; import org.jboss.seam.remoting.MetadataCache; /** * @author Shane Bryzak */ public class BeanWrapper extends BaseWrapper implements Wrapper { private static final byte[] REF_START_TAG_OPEN = "<ref id=\"".getBytes(); private static final byte[] REF_START_TAG_END = "\"/>".getBytes(); private static final byte[] BEAN_START_TAG_OPEN = "<bean type=\"".getBytes(); private static final byte[] BEAN_START_TAG_CLOSE = "\">".getBytes(); private static final byte[] BEAN_CLOSE_TAG = "</bean>".getBytes(); private static final byte[] MEMBER_START_TAG_OPEN = "<member name=\"" .getBytes(); private static final byte[] MEMBER_START_TAG_CLOSE = "\">".getBytes(); private static final byte[] MEMBER_CLOSE_TAG = "</member>".getBytes(); public BeanWrapper(BeanManager beanManager) { super(beanManager); } private MetadataCache metadataCache; @SuppressWarnings("unchecked") private MetadataCache getMetadataCache() { if (metadataCache == null) { Bean<MetadataCache> bean = (Bean<MetadataCache>) beanManager.getBeans( MetadataCache.class).iterator().next(); metadataCache = bean.create(beanManager.createCreationalContext(bean)); } return metadataCache; } @Override @SuppressWarnings("unchecked") public void setElement(Element element) { super.setElement(element); String beanType = element.attributeValue("type"); Set<Bean<?>> beans = beanManager.getBeans(beanType); if (beans.size() > 0) { Bean bean = beans.iterator().next(); value = bean.create(beanManager.createCreationalContext(bean)); } else { try { value = Class.forName(beanType).newInstance(); } catch (Exception ex) { throw new RuntimeException("Could not unmarshal bean element: " + element.getText(), ex); } } } public Type getBeanPropertyType(String propertyName) { Class<?> cls = value.getClass(); String getter = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); for (Method m : cls.getMethods()) { if (getter.equals(m.getName())) return m.getGenericReturnType(); } Field field = null; while (field == null && !cls.equals(Object.class)) { try { field = cls.getDeclaredField(propertyName); } catch (NoSuchFieldException e) { cls = cls.getSuperclass(); } } if (field == null) { throw new IllegalArgumentException("Invalid property name [" + propertyName + "] specified for target class [" + value.getClass() + "]"); } return field.getGenericType(); } public Wrapper getBeanProperty(String propertyName) { Class<?> cls = value.getClass(); Field f = null; try { f = cls.getField(propertyName); } catch (NoSuchFieldException ex) { } boolean accessible = false; try { // Temporarily set the field's accessibility so we can read it if (f != null) { accessible = f.isAccessible(); f.setAccessible(true); return context.createWrapperFromObject(f.get(value), null); } else { Method accessor = null; try { accessor = cls.getMethod(String.format("get%s%s", Character.toUpperCase(propertyName.charAt(0)), propertyName.substring(1))); } catch (NoSuchMethodException ex) { try { accessor = cls.getMethod(String.format("is%s%s", Character.toUpperCase(propertyName.charAt(0)), propertyName.substring(1))); } catch (NoSuchMethodException ex2) { // uh oh... continue with the next one return null; } } try { return context.createWrapperFromObject(accessor.invoke(value), null); } catch (InvocationTargetException ex) { throw new RuntimeException(String.format( "Failed to read property [%s] for object [%s]", propertyName, value)); } } } catch (IllegalAccessException ex) { throw new RuntimeException("Error reading value from field."); } finally { if (f != null) f.setAccessible(accessible); } } public void setBeanProperty(String propertyName, Wrapper valueWrapper) { Class<?> cls = value.getClass(); // We're going to try a combination of ways to set the property value Method method = null; Field field = null; // First try to find the best matching method String setter = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); ConversionScore score = ConversionScore.nomatch; for (Method m : cls.getMethods()) { if (setter.equals(m.getName()) && m.getParameterTypes().length == 1) { ConversionScore s = valueWrapper.conversionScore(m.getParameterTypes()[0]); if (s.getScore() > score.getScore()) { method = m; score = s; } } } // If we can't find a method, look for a matching field name if (method == null) { while (field == null && !cls.equals(Object.class)) { try { // First check the declared fields field = cls.getDeclaredField(propertyName); } catch (NoSuchFieldException ex) { // Couldn't find the field.. try the superclass cls = cls.getSuperclass(); } } if (field == null) { throw new RuntimeException(String.format( "Error while unmarshalling - property [%s] not found in class [%s]", propertyName, value.getClass().getName())); } } // Now convert the field value to the correct target type Object fieldValue = null; try { fieldValue = valueWrapper.convert(method != null ? method .getGenericParameterTypes()[0] : field.getGenericType()); } catch (ConversionException ex) { throw new RuntimeException( "Could not convert value while unmarshaling", ex); } // If we have a setter method, invoke it if (method != null) { try { method.invoke(value, fieldValue); } catch (Exception e) { throw new RuntimeException(String.format( "Could not invoke setter method [%s]", method.getName())); } } else { // Otherwise try to set the field value directly boolean accessible = field.isAccessible(); try { if (!accessible) field.setAccessible(true); field.set(value, fieldValue); } catch (Exception ex) { throw new RuntimeException("Could not set field value.", ex); } finally { field.setAccessible(accessible); } } } @SuppressWarnings("unchecked") @Override public void unmarshal() { List<Element> members = element.elements("member"); for (Element member : members) { String name = member.attributeValue("name"); Wrapper w = context.createWrapperFromElement((Element) member .elementIterator().next()); setBeanProperty(name, w); } } public Object convert(Type type) throws ConversionException { if (type instanceof Class<?> && ((Class<?>) type).isAssignableFrom(value.getClass())) { return value; } else { throw new ConversionException(String.format( "Value [%s] cannot be converted to type [%s].", value, type)); } } /** * Writes the object reference ID to the specified OutputStream */ public void marshal(OutputStream out) throws IOException { context.addOutRef(this); out.write(REF_START_TAG_OPEN); out.write(Integer.toString(context.getOutRefs().indexOf(this)).getBytes()); out.write(REF_START_TAG_END); } @Override public void serialize(OutputStream out) throws IOException { serialize(out, null); } /** * Writes a serialized representation of the object's properties to the specified * OutputStream. * * @param out * @param constraints * @throws IOException */ public void serialize(OutputStream out, List<String> constraints) throws IOException { out.write(BEAN_START_TAG_OPEN); Class<?> cls = value.getClass(); /** * @todo This is a hack to get the "real" class - find out if there is an * API method in CGLIB that can be used instead */ if (cls.getName().contains("EnhancerByCGLIB")) { cls = cls.getSuperclass(); } if (cls.getName().contains("_$$_javassist_")) { cls = cls.getSuperclass(); } // FIXME work out a better way to do this - perhaps walk up the class hierarchy and test against beanManager.getBeans() ? if (cls.getName().contains("_$$_WeldClientProxy")) { cls = cls.getSuperclass(); } String componentName = cls.getName(); Set<Bean<?>> beans = beanManager.getBeans(cls); if (beans.size() > 0) { Bean<?> bean = beanManager.getBeans(cls).iterator().next(); if (bean.getName() != null) { componentName = bean.getName(); } } out.write(componentName.getBytes()); out.write(BEAN_START_TAG_CLOSE); for (String propertyName : getMetadataCache().getAccessibleProperties(cls).keySet()) { String fieldPath = path != null && path.length() > 0 ? String.format( "%s.%s", path, propertyName) : propertyName; // Also exclude fields listed using wildcard notation: // [componentName].fieldName String wildCard = String.format("[%s].%s", componentName != null ? componentName : cls.getName(), propertyName); if (constraints == null || (!constraints.contains(fieldPath) && !constraints.contains(wildCard))) { out.write(MEMBER_START_TAG_OPEN); out.write(propertyName.getBytes()); out.write(MEMBER_START_TAG_CLOSE); Wrapper w = getBeanProperty(propertyName); if (w != null) { w.setPath(fieldPath); w.marshal(out); } out.write(MEMBER_CLOSE_TAG); } } out.write(BEAN_CLOSE_TAG); } public ConversionScore conversionScore(Class<?> cls) { if (cls.equals(value.getClass())) { return ConversionScore.exact; } else if (cls.isAssignableFrom(value.getClass()) || cls.equals(Object.class)) { return ConversionScore.compatible; } else { return ConversionScore.nomatch; } } }