/* * Copyright (C) 2011 Citrix Systems, Inc. All rights reserved. * * Licensed 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; /** * @author Kelven Yang */ 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; } }