/*
* 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.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.datatype.XMLGregorianCalendar;
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.DataType;
import org.apache.tuscany.sca.interfacedef.util.XMLType;
/**
* Transformer to convert data from a JavaBean object to xml
*
* @version $Rev$ $Date$
*/
public abstract class JavaBean2XMLTransformer<T> extends BaseTransformer<Object, T> implements
PullTransformer<Object, T> {
public static final String GET = "get";
public static final String PREFIX = "n";
public static final String PERIOD = ".";
public static final String FWD_SLASH = "/";
public static final String HTTP = "http://";
private static int prefixCount = 1;
protected SimpleTypeMapperImpl mapper;
public JavaBean2XMLTransformer() {
this.mapper = new SimpleTypeMapperImpl();
}
public T transform(Object source, TransformationContext context) {
QName rootElement = null;
if (context != null) {
DataType<?> type = context.getTargetDataType();
if (type != null) {
Object logical = type.getLogical();
if (logical instanceof XMLType) {
rootElement = ((XMLType)logical).getElementName();
}
}
}
//FIXME See how/if we still need to get the metadata here
//QName rootElementName = (QName)context.getTargetDataType().getMetadata("RootElementName");
//if (rootElementName == null) {
QName rootElementName = new QName(resolveRootElementName(source.getClass()));
//}
T root = createElement(rootElementName);
appendChildElements(root, resolveElementName(source.getClass()), source.getClass(), source, context);
return root;
}
private void appendChildElements(T parent,
QName elementName,
Class javaType,
Object javaObject,
TransformationContext context) {
T element = null;
if (javaObject != null) {
if (javaType.isPrimitive() || isSimpleJavaType(javaObject)) {
appendText(parent, mapper.toXMLLiteral(null, javaObject, context));
} else if (javaType.isArray()) {
int size = Array.getLength(javaObject);
for (int count = 0; count < size; ++count) {
Object item = Array.get(javaObject, count);
element = createElement(elementName);
appendChild(parent, element);
appendChildElements(element, elementName, javaType.getComponentType(), item, context);
}
} else {
Field[] javaFields = javaType.getFields();
for (Field aField : javaFields) {
try {
QName fieldElementName = new QName(aField.getName());
if (!aField.getType().isArray()) {
element = createElement(fieldElementName);
appendChild(parent, element);
appendChildElements(element,
fieldElementName,
aField.getType(),
aField.get(javaObject),
context);
} else {
appendChildElements(parent,
fieldElementName,
aField.getType(),
aField.get(javaObject),
context);
}
} catch (IllegalAccessException e) {
Java2XMLMapperException java2xmlEx = new Java2XMLMapperException(e);
java2xmlEx.setJavaFieldName(aField.getName());
java2xmlEx.setJavaType(javaType);
throw java2xmlEx;
}
}
Method[] methods = javaType.getMethods();
String fieldName = null;
for (Method aMethod : methods) {
try {
if (Modifier.isPublic(aMethod.getModifiers()) && aMethod.getName().startsWith(GET)
&& aMethod.getParameterTypes().length == 0
&& isMappedGetter(aMethod.getName())) {
fieldName = resolveFieldFromMethod(aMethod.getName());
try {
javaType.getField(fieldName);
} catch (NoSuchFieldException e) {
QName fieldElementName = new QName(fieldName);
if (aMethod.getReturnType().isArray()) {
appendChildElements(parent, fieldElementName, aMethod.getReturnType(), aMethod
.invoke(javaObject, new Object[0]), context);
} else {
element = createElement(fieldElementName);
appendChild(parent, element);
appendChildElements(element, fieldElementName, aMethod.getReturnType(), aMethod
.invoke(javaObject, new Object[0]), context);
}
}
}
} catch (IllegalAccessException e) {
Java2XMLMapperException java2xmlEx = new Java2XMLMapperException(e);
java2xmlEx.setJavaFieldName(fieldName);
java2xmlEx.setJavaType(javaType);
throw java2xmlEx;
} catch (InvocationTargetException e) {
Java2XMLMapperException java2xmlEx = new Java2XMLMapperException(e);
java2xmlEx.setJavaFieldName(fieldName);
java2xmlEx.setJavaType(javaType);
throw java2xmlEx;
}
}
}
}
}
/*
* Subclasses can override this method to prevent some getter methods
* from being mapped. The default implementation provided by this class
* maps all getter methods.
*/
protected boolean isMappedGetter(String methodName) {
return true;
}
@Override
public String getSourceDataBinding() {
return JavaBeansDataBinding.NAME;
}
@Override
public Class<Object> getSourceType() {
return Object.class;
}
private boolean isSimpleJavaType(Object javaObject) {
if (javaObject instanceof String) {
return true;
}
if (javaObject instanceof Byte || javaObject instanceof Character
|| javaObject instanceof Short
|| javaObject instanceof Integer
|| javaObject instanceof Long
|| javaObject instanceof Float
|| javaObject instanceof Double
|| javaObject instanceof Boolean) {
return true;
}
if (javaObject instanceof GregorianCalendar || javaObject instanceof Date
|| javaObject instanceof XMLGregorianCalendar
|| javaObject instanceof byte[]
|| javaObject instanceof QName) {
return true;
}
return false;
}
private String resolveRootElementName(Class javaType) {
if (javaType.isArray()) {
return javaType.getComponentType().getSimpleName() + "_collection";
} else {
return javaType.getSimpleName() + "_instance";
}
}
private QName resolveElementName(Class javaType) {
if (javaType.isArray()) {
return new QName(javaType.getComponentType().getSimpleName());
} else {
return new QName(javaType.getSimpleName());
}
}
private String resolveFieldFromMethod(String methodName) {
StringBuffer fieldName = new StringBuffer();
fieldName.append(Character.toLowerCase(methodName.charAt(GET.length())));
fieldName.append(methodName.substring(GET.length() + 1));
return fieldName.toString();
}
public String getNexPrefix() {
return PREFIX + prefixCount++;
}
@Override
public int getWeight() {
return JavaBeansDataBinding.HEAVY_WEIGHT;
}
/**
* Create an element with the given name
* @param qName
* @return
* @throws Java2XMLMapperException
*/
public abstract T createElement(QName qName) throws Java2XMLMapperException;
/**
* Create a text node and add it to the parent
* @param parentElement
* @param textData
* @throws Java2XMLMapperException
*/
public abstract void appendText(T parentElement, String textData) throws Java2XMLMapperException;
/**
* Add the child element to the parent
* @param parentElement
* @param childElement
* @throws Java2XMLMapperException
*/
public abstract void appendChild(T parentElement, T childElement) throws Java2XMLMapperException;
}