/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.tuscany.sca.databinding.javabeans; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import org.apache.tuscany.sca.databinding.BaseTransformer; import org.apache.tuscany.sca.databinding.PullTransformer; import org.apache.tuscany.sca.databinding.TransformationContext; import org.apache.tuscany.sca.databinding.impl.SimpleTypeMapperImpl; import org.apache.tuscany.sca.interfacedef.util.XMLType; /** * Transformer to convert data from XML to JavaBean * * @version $Rev$ $Date$ */ public abstract class XML2JavaBeanTransformer<T> extends BaseTransformer<T, Object> implements PullTransformer<T, Object> { public static final String SET = "set"; public static final QName QNAME_MESSAGE = new QName("message"); protected SimpleTypeMapperImpl mapper; public XML2JavaBeanTransformer() { this.mapper = new SimpleTypeMapperImpl(); } @Override public int getWeight() { return JavaBeansDataBinding.HEAVY_WEIGHT; } public Object transform(T source, TransformationContext context) { //FIXME why is the logical type sometimes a Class instead of an XMLType? if (context.getSourceDataType().getLogical() instanceof XMLType) { XMLType xmlType = (XMLType) context.getSourceDataType().getLogical(); return toJavaObject(xmlType.getTypeName(), getRootElement(source), context); } else { return toJavaObject(null, getRootElement(source), context); } } public Object toJavaObject(QName xmlType, T xmlElement, TransformationContext context) { if (xmlType != null && mapper.isSimpleXSDType(xmlType)) { return mapper.toJavaObject(xmlType, getText(xmlElement), context); } else { Class<?> javaType = (Class<?>)context.getTargetDataType().getPhysical(); return createJavaObject(xmlElement, javaType, context); } } @SuppressWarnings("unchecked") private <L> L createJavaObject(T element, Class<L> javaType, TransformationContext context) throws XML2JavaMapperException { if (isTextOnly(element)) { return (L) mapper.toJavaObject(mapper.getXMLType(javaType).getQName(), getText(element), context); } else { String fieldName = null; try { L javaInstance; T detailMsg = null; if (Throwable.class.isAssignableFrom(javaType)) { T msgElement = getFirstChildWithName(element, QNAME_MESSAGE); if (msgElement != null && isTextOnly(msgElement)) { detailMsg = msgElement; // skip this when handling child elements Constructor constructor = javaType.getConstructor(new Class[] {String.class}); javaInstance = (L)constructor.newInstance(new Object[] {getText(detailMsg)}); } else { javaInstance = javaType.newInstance(); } } else { javaInstance = javaType.newInstance(); } Map<Field, List<Object>> arrayFields = new Hashtable<Field, List<Object>>(); Map<Method, List<Object>> arraySetters = new Hashtable<Method, List<Object>>(); for (Iterator<T> childElements = getChildElements(element); childElements.hasNext(); ) { T childElement = childElements.next(); if (!isTextElement(childElement) && childElement != detailMsg) { fieldName = getElementName(childElement); try { Field javaField = javaType.getField(fieldName); setFieldValue(javaInstance, javaField, childElement, arrayFields, context); } catch (NoSuchFieldException e1) { setFieldValueUsingSetter(javaType, javaInstance, fieldName, childElement, arraySetters, context); } } } setArrayValues(javaInstance, arrayFields, arraySetters); return javaInstance; } catch (Exception e2) { XML2JavaMapperException xml2JavaEx = new XML2JavaMapperException(e2); xml2JavaEx.setJavaType(javaType); xml2JavaEx.setJavaFieldName(fieldName); throw xml2JavaEx; } } } private void setFieldValue(Object javaInstance, Field javaField, T fieldValue, Map<Field, List<Object>> arrayFields, TransformationContext context) throws IllegalAccessException { Class<?> javaFieldType = (Class<?>) javaField.getType(); if (javaFieldType.isArray()) { Class<?> componentType = javaFieldType.getComponentType(); List<Object> fldValueArray = arrayFields.get(javaField); if (fldValueArray == null) { fldValueArray = new ArrayList<Object>(); arrayFields.put(javaField, fldValueArray); } fldValueArray.add(createJavaObject(fieldValue, componentType, context)); } else { javaField.setAccessible(true); javaField.set(javaInstance, createJavaObject(fieldValue, javaFieldType, context)); } } private void setFieldValueUsingSetter(Class javaType, Object javaInstance, String fieldName, T fieldValue, Map<Method, List<Object>> arraySetters, TransformationContext context) throws IllegalAccessException, InvocationTargetException { char firstChar = Character.toUpperCase(fieldName.charAt(0)); StringBuilder methodName = new StringBuilder(SET + fieldName); methodName.setCharAt(SET.length(), firstChar); boolean methodNotFound = true; for (int methodCount = 0; methodNotFound && methodCount < javaType.getMethods().length; ++methodCount) { Method aMethod = javaType.getMethods()[methodCount]; if (aMethod.getName().equals(methodName.toString()) && aMethod.getParameterTypes().length == 1) { Class<?> paramType = aMethod.getParameterTypes()[0]; if (paramType.isArray()) { Class<?> componentType = paramType.getComponentType(); List<Object> setterValueArray = arraySetters.get(aMethod); if (setterValueArray == null) { setterValueArray = new ArrayList<Object>(); arraySetters.put(aMethod, setterValueArray); } setterValueArray.add(createJavaObject(fieldValue, componentType, context)); } else { aMethod.invoke(javaInstance, new Object[] {createJavaObject(fieldValue, paramType, context)}); } methodNotFound = false; } } if (methodNotFound) { XML2JavaMapperException xml2JavaEx = new XML2JavaMapperException("No field or setter method to configure xml data"); xml2JavaEx.setJavaFieldName(fieldName); xml2JavaEx.setJavaType(javaType); throw xml2JavaEx; } } private void setArrayValues(Object javaInstance, Map<Field, List<Object>> arrayFields, Map<Method, List<Object>> arraySetters) throws IllegalAccessException, InvocationTargetException { if (arrayFields.size() > 0) { for (Field javaField : arrayFields.keySet()) { javaField.setAccessible(true); if (javaField.getType().getComponentType().isPrimitive()) { javaField.set(javaInstance, createPrimitiveArray(javaField.getType() .getComponentType(), arrayFields.get(javaField))); } else { javaField.set(javaInstance, createNonPrimitiveArray(javaField.getType().getComponentType(), arrayFields.get(javaField))); } } } if (arraySetters.size() > 0) { for (Method aMethod : arraySetters.keySet()) { Class<?> paramType = aMethod.getParameterTypes()[0]; if (paramType.getComponentType().isPrimitive()) { aMethod.invoke(javaInstance, new Object[] {createPrimitiveArray(paramType.getComponentType(), arraySetters.get(aMethod))}); } else { aMethod.invoke(javaInstance, new Object[] {createNonPrimitiveArray(paramType.getComponentType(), arraySetters.get(aMethod))}); } } } } private Object createNonPrimitiveArray(Class fieldType, List values) { Object objectArray = Array.newInstance(fieldType, values.size()); for (int count = 0; count < values.size(); ++count) { Array.set(objectArray, count, values.get(count)); } return objectArray; } private Object createPrimitiveArray(Class fieldType, List values) { if (fieldType.isPrimitive()) { if (fieldType.getName().equals("int")) { int[] primitiveValues = new int[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Integer) values.get(count)).intValue(); } return primitiveValues; } else if (fieldType.getName().equals("float")) { float[] primitiveValues = new float[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Float) values.get(count)).floatValue(); } return primitiveValues; } else if (fieldType.getName().equals("boolean")) { boolean[] primitiveValues = new boolean[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Boolean) values.get(count)).booleanValue(); } return primitiveValues; } else if (fieldType.getName().equals("char")) { char[] primitiveValues = new char[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Character) values.get(count)).charValue(); } return primitiveValues; } else if (fieldType.getName().equals("byte")) { byte[] primitiveValues = new byte[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Byte) values.get(count)).byteValue(); } return primitiveValues; } else if (fieldType.getName().equals("short")) { short[] primitiveValues = new short[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Short) values.get(count)).shortValue(); } return primitiveValues; } else if (fieldType.getName().equals("long")) { long[] primitiveValues = new long[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Long) values.get(count)).longValue(); } return primitiveValues; } else if (fieldType.getName().equals("double")) { double[] primitiveValues = new double[values.size()]; for (int count = 0; count < values.size(); ++count) { primitiveValues[count] = ((Double) values.get(count)).doubleValue(); } return primitiveValues; } } return values; } public abstract String getText(T source) throws XML2JavaMapperException; public abstract Iterator<T> getChildElements(T parent) throws XML2JavaMapperException; public abstract String getElementName(T element) throws XML2JavaMapperException; public abstract boolean isTextElement(T element) throws XML2JavaMapperException; public abstract boolean isTextOnly(T element) throws XML2JavaMapperException; public abstract T getFirstChildWithName(T element, QName name) throws XML2JavaMapperException; public abstract T getRootElement(T element) throws XML2JavaMapperException; @Override public Class<Object> getTargetType() { return Object.class; } @Override public String getTargetDataBinding() { return JavaBeansDataBinding.NAME; } }