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 org.dom4j.Element; import org.jboss.seam.Component; import org.jboss.seam.Seam; import org.jboss.seam.remoting.InterfaceGenerator; import org.jboss.seam.util.Reflections; /** * @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(); @Override public void setElement(Element element) { super.setElement(element); String beanType = element.attributeValue("type"); Component component = Component.forName(beanType); if (component != null) { value = component.newInstance(); } else { try { value = Reflections.classForName(beanType).newInstance(); } catch (Exception ex) { throw new RuntimeException("Could not unmarshal bean element: " + element.getText(), ex); } } } @Override public void unmarshal() { List members = element.elements("member"); for (Element member : (List<Element>) members) { String name = member.attributeValue("name"); Wrapper w = context.createWrapperFromElement((Element) member.elementIterator().next()); Class cls = value.getClass(); // We're going to try a combination of ways to set the property value here Method method = null; Field field = null; // First try to find the best matching method String setter = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1); ConversionScore score = ConversionScore.nomatch; for (Method m : cls.getMethods()) { if (setter.equals(m.getName()) && m.getParameterTypes().length == 1) { ConversionScore s = w.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(name); } 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]", name, value.getClass().getName())); } } // Now convert the field value to the correct target type Object fieldValue = null; try { fieldValue = w.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); } } } } 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)); } 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); } 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(); } String componentName = Seam.getComponentName(cls); Component component = componentName != null ? Component.forName(componentName) : null; if (component != null) cls = component.getBeanClass(); if (componentName != null) out.write(componentName.getBytes()); else out.write(cls.getName().getBytes()); out.write(BEAN_START_TAG_CLOSE); for (String propertyName : InterfaceGenerator.getAccessibleProperties(cls)) { 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); 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); context.createWrapperFromObject(f.get(value), fieldPath).marshal(out); } 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 continue; } } try { context.createWrapperFromObject(accessor.invoke(value), fieldPath). marshal(out); } 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); } 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; } }