| /* | |
| * 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; | |
| } | |
| } |