blob: d9923c85c3cd23f9c0e560d84383f0b2ddf8832e [file] [log] [blame]
// 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 com.cloud.bridge.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XSerializer {
protected final static Logger logger = Logger.getLogger(XSerializer.class);
private static Map<String, Class<?>> rootTypes = new HashMap<String, Class<?>>();
private XSerializerAdapter adapter;
private boolean flattenCollection;
private boolean omitNull;
public XSerializer(XSerializerAdapter adapter) {
this.adapter = adapter;
adapter.setSerializer(this);
// set default serialization options
flattenCollection = false;
omitNull = false;
}
public XSerializer(XSerializerAdapter adapter, boolean flattenCollection, boolean omitNull) {
this.adapter = adapter;
adapter.setSerializer(this);
this.flattenCollection = flattenCollection;
this.omitNull = omitNull;
}
public boolean getFlattenCollection() {
return flattenCollection;
}
public void setFlattenCollection(boolean value) {
flattenCollection = value;
}
public boolean flattenField(Field f) {
XFlatten flatten = f.getAnnotation(XFlatten.class);
if (flatten != null)
return flatten.value();
return flattenCollection;
}
public boolean omitNullField(Field f) {
XOmitNull omit = f.getAnnotation(XOmitNull.class);
if (omit != null)
return omit.value();
return omitNull;
}
public boolean getOmitNull() {
return omitNull;
}
public void setOmitNull(boolean value) {
omitNull = value;
}
public static void registerRootType(String elementName, Class<?> clz) {
rootTypes.put(elementName, clz);
}
public XSerializerAdapter getAdapter() {
return adapter;
}
public static Object mapElement(String elementName) {
Class<?> clz = rootTypes.get(elementName);
if (clz == null) {
logger.error("Object class is not registered for root element " + elementName);
throw new IllegalArgumentException("Object class is not registered for root element " + elementName);
}
try {
return clz.newInstance();
} catch (InstantiationException e) {
logger.error("Unable to instantiate object for root element due to InstantiationException, XML element: " + elementName);
throw new IllegalArgumentException("Unable to instantiate object for root element " + elementName);
} catch (IllegalAccessException e) {
logger.error("Unable to instantiate object for root element due to IllegalAccessException, XML element: " + elementName);
throw new IllegalArgumentException("Unable to instantiate object for root element due to IllegalAccessException, XML element: " + elementName);
}
}
public Object serializeFrom(String xmlString) {
try {
Document doc = XmlHelper.parse(xmlString);
Node node = XmlHelper.getRootNode(doc);
if (node == null) {
logger.error("Invalid XML document, no root element");
return null;
}
Object object = mapElement(node.getNodeName());
if (object == null) {
logger.error("Unable to map root element. Please remember to use XSerializer.registerRootType() to register the root object type");
return null;
}
if (object instanceof XSerializable)
((XSerializable)object).serializeFrom(this, object, node);
else
serializeFrom(object, object.getClass(), node);
return object;
} catch (IOException e) {
logger.error("Unable to parse XML input due to " + e.getMessage(), e);
}
return null;
}
private void serializeFrom(Object object, Class<?> clz, Node node) {
if (clz.getSuperclass() != null)
serializeFrom(object, clz.getSuperclass(), node);
Field[] fields = clz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if ((f.getModifiers() & Modifier.STATIC) == 0) {
f.setAccessible(true);
Class<?> fieldType = f.getType();
XElement elem = f.getAnnotation(XElement.class);
if (elem == null)
continue;
try {
if (fieldType.isPrimitive()) {
setPrimitiveField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if (fieldType.getSuperclass() == Number.class) {
setNumberField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if (fieldType == String.class) {
f.set(object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if (fieldType == Date.class) {
setDateField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if (fieldType == Calendar.class) {
setCalendarField(f, object, XmlHelper.getChildNodeTextContent(node, elem.name()));
} else if (fieldType.isArray()) {
if (flattenField(f))
setArrayField(f, object, node, elem.item(), elem.itemClass());
else
setArrayField(f, object, XmlHelper.getChildNode(node, elem.name()), elem.item(), elem.itemClass());
} else if (Collection.class.isAssignableFrom(fieldType)) {
if (flattenField(f))
setCollectionField(f, object, node, elem.item(), elem.itemClass());
else
setCollectionField(f, object, XmlHelper.getChildNode(node, elem.name()), elem.item(), elem.itemClass());
} else {
Node childNode = XmlHelper.getChildNode(node, elem.name());
Object fieldObject = f.get(object);
if (fieldObject == null) {
try {
fieldObject = fieldType.newInstance();
} catch (InstantiationException e) {
logger.error("Unable to instantiate " + fieldType.getName() + " object, please make sure it has public constructor");
assert (false);
}
f.set(object, fieldObject);
}
serializeFrom(fieldObject, fieldType, childNode);
}
} catch (IllegalArgumentException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
} catch (IllegalAccessException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
}
}
}
}
private void setPrimitiveField(Field f, Object object, String valueContent) throws IllegalArgumentException, IllegalAccessException {
String clzName = f.getType().getName();
if (clzName.equals("boolean")) {
if (valueContent != null && valueContent.equalsIgnoreCase("true"))
f.setBoolean(object, true);
else
f.setBoolean(object, false);
} else if (clzName.equals("byte")) {
byte value = 0;
if (valueContent != null)
value = Byte.parseByte(valueContent);
f.setByte(object, value);
} else if (clzName.equals("char")) {
char value = '\0';
if (valueContent != null) {
if (valueContent.charAt(0) == '\'')
value = valueContent.charAt(1);
else
value = valueContent.charAt(0);
}
f.setChar(object, value);
} else if (clzName.equals("short")) {
short value = 0;
if (valueContent != null)
value = Short.parseShort(valueContent);
f.setShort(object, value);
} else if (clzName.equals("int")) {
int value = 0;
if (valueContent != null)
value = Integer.parseInt(valueContent);
f.setInt(object, value);
} else if (clzName.equals("long")) {
long value = 0;
if (valueContent != null)
value = Long.parseLong(valueContent);
f.setLong(object, value);
} else if (clzName.equals("float")) {
float value = 0;
if (valueContent != null)
value = Float.parseFloat(valueContent);
f.setFloat(object, value);
} else if (clzName.equals("double")) {
double value = 0;
if (valueContent != null)
value = Double.parseDouble(valueContent);
f.setDouble(object, value);
} else {
logger.error("Assertion failed at setPrimitiveFiled");
assert (false);
}
}
private void setNumberField(Field f, Object object, String valueContent) throws IllegalArgumentException, IllegalAccessException {
String clzName = f.getType().getName();
if (clzName.equals("Byte")) {
byte value = 0;
if (valueContent != null)
value = Byte.parseByte(valueContent);
f.set(object, new Byte(value));
} else if (clzName.equals("Short")) {
short value = 0;
if (valueContent != null)
value = Short.parseShort(valueContent);
f.set(object, new Short(value));
} else if (clzName.equals("Integer")) {
int value = 0;
if (valueContent != null)
value = Integer.parseInt(valueContent);
f.set(object, new Integer(value));
} else if (clzName.equals("Long")) {
long value = 0;
if (valueContent != null)
value = Long.parseLong(valueContent);
f.set(object, new Long(value));
} else if (clzName.equals("Float")) {
float value = 0;
if (valueContent != null)
value = Float.parseFloat(valueContent);
f.set(object, new Float(value));
} else if (clzName.equals("Double")) {
double value = 0;
if (valueContent != null)
value = Double.parseDouble(valueContent);
f.setDouble(object, new Double(value));
} else if (clzName.equals("AtomicInteger")) {
int value = 0;
if (valueContent != null)
value = Integer.parseInt(valueContent);
f.set(object, new AtomicInteger(value));
} else if (clzName.equals("AtomicLong")) {
long value = 0;
if (valueContent != null)
value = Long.parseLong(valueContent);
f.set(object, new AtomicLong(value));
} else if (clzName.equals("BigInteger")) {
logger.error("we don't support BigInteger for now");
assert (false);
} else if (clzName.equals("BigDecimal")) {
logger.error("we don't support BigInteger for now");
assert (false);
} else {
logger.error("Assertion failed at setPrimitiveFiled");
assert (false);
}
}
private void setDateField(Field f, Object object, String valueContent) throws IllegalArgumentException, IllegalAccessException {
if (valueContent != null) {
valueContent = valueContent.replace('T', ' ');
valueContent = valueContent.replace('.', '\0');
SimpleDateFormat df = DateHelper.getGMTDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date value = df.parse(valueContent);
f.set(object, value);
} catch (ParseException e) {
logger.error("Unrecognized date/time format " + valueContent);
}
}
}
private void setCalendarField(Field f, Object object, String valueContent) throws IllegalArgumentException, IllegalAccessException {
if (valueContent != null) {
valueContent = valueContent.replace('T', ' ');
valueContent = valueContent.replace('.', '\0');
SimpleDateFormat df = DateHelper.getGMTDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date value = df.parse(valueContent);
f.set(object, DateHelper.toCalendar(value));
} catch (ParseException e) {
logger.error("Unrecognized date/time format " + valueContent);
}
}
}
private void setArrayField(Field f, Object object, Node node, String itemElementName, String itemClass) throws IllegalArgumentException, IllegalAccessException {
List<Object> arrayList = new ArrayList<Object>();
Class<?> itemClz = null;
try {
itemClz = this.getClass().forName(itemClass);
} catch (ClassNotFoundException e) {
logger.error("Unable to find class " + itemClass);
return;
}
if (node != null) {
NodeList l = node.getChildNodes();
if (l != null && l.getLength() > 0) {
for (int i = 0; i < l.getLength(); i++) {
try {
Node itemNode = l.item(i);
if (itemNode.getNodeName().equals(itemElementName)) {
Object item = itemClz.newInstance();
serializeFrom(item, itemClz, l.item(i));
arrayList.add(item);
}
} catch (InstantiationException e) {
logger.error("Unable to initiate object instance for class " + itemClass + ", make sure it has public constructor");
break;
}
}
}
}
Object arrary = Array.newInstance(f.getType().getComponentType(), arrayList.size());
arrayList.toArray((Object[])arrary);
f.set(object, arrary);
}
private void setCollectionField(Field f, Object object, Node node, String itemElementName, String itemClass) throws IllegalArgumentException, IllegalAccessException {
Object fieldObject = f.get(object);
if (fieldObject == null) {
logger.error("Please initialize collection field " + f.getName() + " in class " + object.getClass().getName() + "'s constructor");
return;
}
Class<?> itemClz = null;
try {
itemClz = this.getClass().forName(itemClass);
} catch (ClassNotFoundException e) {
logger.error("Unable to find class " + itemClass);
return;
}
NodeList l = node.getChildNodes();
if (l != null && l.getLength() > 0) {
for (int i = 0; i < l.getLength(); i++) {
try {
Node itemNode = l.item(i);
if (itemNode.getNodeName().equals(itemElementName)) {
Object item = itemClz.newInstance();
serializeFrom(item, itemClz, l.item(i));
((Collection)fieldObject).add(item);
}
} catch (InstantiationException e) {
logger.error("Unable to initiate object instance for class " + itemClass + ", make sure it has public constructor");
break;
}
}
}
}
public void serializeTo(Object obj, String startElement, String namespace, int indentLevel, PrintWriter writer) {
if (startElement != null) {
adapter.beginElement(startElement, namespace, indentLevel, writer);
indentLevel++;
}
if (obj instanceof XSerializable) {
((XSerializable)obj).serializeTo(this, indentLevel, writer);
} else {
Class<?> clz = obj.getClass();
serializeTo(obj, clz, indentLevel, writer);
}
if (startElement != null) {
indentLevel--;
adapter.endElement(startElement, indentLevel, writer);
}
}
public String serializeTo(Object obj, String startElement, String namespace, int indentLevel) {
StringWriter writer = new StringWriter();
serializeTo(obj, startElement, namespace, indentLevel, new PrintWriter(writer));
return writer.toString();
}
private void serializeTo(Object obj, Class<?> clz, int indentLevel, PrintWriter writer) {
if (clz.getSuperclass() != null)
serializeTo(obj, clz.getSuperclass(), indentLevel, writer);
Field[] fields = clz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if ((f.getModifiers() & Modifier.STATIC) == 0) {
f.setAccessible(true);
Class<?> fieldType = f.getType();
XElement elem = f.getAnnotation(XElement.class);
if (elem == null)
continue;
Object fieldValue = null;
try {
fieldValue = f.get(obj);
} catch (IllegalArgumentException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
} catch (IllegalAccessException e) {
logger.error("Unexpected exception " + e.getMessage(), e);
}
adapter.writeElement(elem.name(), elem.item(), fieldValue, f, indentLevel, writer);
if (i < fields.length - 1) {
Field next = fields[i + 1];
if ((next.getModifiers() & Modifier.STATIC) == 0 && next.getAnnotation(XElement.class) != null) {
adapter.writeSeparator(indentLevel, writer);
}
}
}
}
}
public boolean isComposite(Class<?> clz) {
if (clz.isPrimitive() || clz.getSuperclass() == Number.class || clz == String.class || clz == Date.class || clz == Calendar.class) {
return false;
}
return true;
}
}