/******************************************************************************* * 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.ofbiz.entity.serialize; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; import java.lang.ref.WeakReference; import java.math.BigDecimal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; import java.util.WeakHashMap; import javax.xml.bind.DatatypeConverter; import javax.xml.parsers.ParserConfigurationException; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilObject; import org.apache.ofbiz.base.util.UtilXml; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericPK; import org.apache.ofbiz.entity.GenericValue; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; /** * XmlSerializer class. This class is deprecated - new code should use the * Java object marshalling/unmarshalling methods in <code>UtilXml.java</code>. * */ public class XmlSerializer { public static final String module = XmlSerializer.class.getName(); private static WeakReference<DateFormat> simpleDateFormatter; public static String serialize(Object object) throws SerializeException, FileNotFoundException, IOException { Document document = UtilXml.makeEmptyXmlDocument("ofbiz-ser"); Element rootElement = document.getDocumentElement(); rootElement.appendChild(serializeSingle(object, document)); return UtilXml.writeXmlDocument(document); } /** Deserialize a Java object from an XML string. <p>This method should be used with caution. * If the XML string contains a serialized <code>GenericValue</code> or <code>GenericPK</code> * then it is possible to unintentionally corrupt the database.</p> * * @param content the content * @param delegator the delegator * @return return a deserialized object from XML string * @throws SerializeException * @throws SAXException * @throws ParserConfigurationException * @throws IOException */ public static Object deserialize(String content, Delegator delegator) throws SerializeException, SAXException, ParserConfigurationException, IOException { // readXmlDocument with false second parameter to disable validation Document document = UtilXml.readXmlDocument(content, false); if (document != null) { if (!"ofbiz-ser".equals(document.getDocumentElement().getTagName())) { return UtilXml.fromXml(content); } return deserialize(document, delegator); } else { Debug.logWarning("Serialized document came back null", module); return null; } } /** Deserialize a Java object from a DOM <code>Document</code>. * <p>This method should be used with caution. If the DOM <code>Document</code> * contains a serialized <code>GenericValue</code> or <code>GenericPK</code> * then it is possible to unintentionally corrupt the database.</p> * * @param document the document * @param delegator the delegator * @return returns a deserialized object from a DOM document * @throws SerializeException */ public static Object deserialize(Document document, Delegator delegator) throws SerializeException { Element rootElement = document.getDocumentElement(); // find the first element below the root element, that should be the object Node curChild = rootElement.getFirstChild(); while (curChild != null && curChild.getNodeType() != Node.ELEMENT_NODE) { curChild = curChild.getNextSibling(); } if (curChild == null) { return null; } return deserializeSingle((Element) curChild, delegator); } public static Element serializeSingle(Object object, Document document) throws SerializeException { if (document == null) return null; if (object == null) return makeElement("null", object, document); // - Standard Objects - if (object instanceof String) { return makeElement("std-String", object, document); } else if (object instanceof Integer) { return makeElement("std-Integer", object, document); } else if (object instanceof Long) { return makeElement("std-Long", object, document); } else if (object instanceof Float) { return makeElement("std-Float", object, document); } else if (object instanceof Double) { return makeElement("std-Double", object, document); } else if (object instanceof Boolean) { return makeElement("std-Boolean", object, document); } else if (object instanceof Locale) { return makeElement("std-Locale", object, document); } else if (object instanceof BigDecimal) { String stringValue = ((BigDecimal) object).setScale(10, BigDecimal.ROUND_HALF_UP).toString(); return makeElement("std-BigDecimal", stringValue, document); // - SQL Objects - } else if (object instanceof java.sql.Timestamp) { String stringValue = object.toString().replace(' ', 'T'); return makeElement("sql-Timestamp", stringValue, document); } else if (object instanceof java.sql.Date) { return makeElement("sql-Date", object, document); } else if (object instanceof java.sql.Time) { return makeElement("sql-Time", object, document); } else if (object instanceof java.util.Date) { // NOTE: make sure this is AFTER the java.sql date/time objects since they inherit from java.util.Date DateFormat formatter = getDateFormat(); String stringValue = null; synchronized (formatter) { stringValue = formatter.format((java.util.Date) object); } return makeElement("std-Date", stringValue, document); // return makeElement("std-Date", object, document); } else if (object instanceof Collection<?>) { // - Collections - String elementName = null; // these ARE order sensitive; for instance Stack extends Vector, so if Vector were first we would lose the stack part if (object instanceof ArrayList<?>) { elementName = "col-ArrayList"; } else if (object instanceof LinkedList<?>) { elementName = "col-LinkedList"; } else if (object instanceof Stack<?>) { elementName = "col-Stack"; } else if (object instanceof Vector<?>) { elementName = "col-Vector"; } else if (object instanceof TreeSet<?>) { elementName = "col-TreeSet"; } else if (object instanceof HashSet<?>) { elementName = "col-HashSet"; } else { // no specific type found, do general Collection, will deserialize as LinkedList elementName = "col-Collection"; } // if (elementName == null) return serializeCustom(object, document); Collection<?> value = UtilGenerics.cast(object); Element element = document.createElement(elementName); Iterator<?> iter = value.iterator(); while (iter.hasNext()) { element.appendChild(serializeSingle(iter.next(), document)); } return element; } else if (object instanceof GenericPK) { // Do GenericEntity objects as a special case, use std XML import/export routines GenericPK value = (GenericPK) object; return value.makeXmlElement(document, "eepk-"); } else if (object instanceof GenericValue) { GenericValue value = (GenericValue) object; return value.makeXmlElement(document, "eeval-"); } else if (object instanceof Map<?, ?>) { // - Maps - String elementName = null; // these ARE order sensitive; for instance Properties extends Hashtable, so if Hashtable were first we would lose the Properties part if (object instanceof HashMap<?, ?>) { elementName = "map-HashMap"; } else if (object instanceof Properties) { elementName = "map-Properties"; } else if (object instanceof Hashtable<?, ?>) { elementName = "map-Hashtable"; } else if (object instanceof WeakHashMap<?, ?>) { elementName = "map-WeakHashMap"; } else if (object instanceof TreeMap<?, ?>) { elementName = "map-TreeMap"; } else { // serialize as a simple Map implementation if nothing else applies, these will deserialize as a HashMap elementName = "map-Map"; } Element element = document.createElement(elementName); Map<?,?> value = UtilGenerics.cast(object); Iterator<Map.Entry<?, ?>> iter = UtilGenerics.cast(value.entrySet().iterator()); while (iter.hasNext()) { Map.Entry<?,?> entry = iter.next(); Element entryElement = document.createElement("map-Entry"); element.appendChild(entryElement); Element key = document.createElement("map-Key"); entryElement.appendChild(key); key.appendChild(serializeSingle(entry.getKey(), document)); Element mapValue = document.createElement("map-Value"); entryElement.appendChild(mapValue); mapValue.appendChild(serializeSingle(entry.getValue(), document)); } return element; } return serializeCustom(object, document); } public static Element serializeCustom(Object object, Document document) throws SerializeException { if (object instanceof Serializable) { byte[] objBytes = UtilObject.getBytes(object); if (objBytes == null) { throw new SerializeException("Unable to serialize object; null byte array returned"); } else { String byteHex = StringUtil.toHexString(objBytes); Element element = document.createElement("cus-obj"); // this is hex encoded so does not need to be in a CDATA block element.appendChild(document.createTextNode(byteHex)); return element; } } else { throw new SerializeException("Cannot serialize object of class " + object.getClass().getName()); } } public static Element makeElement(String elementName, Object value, Document document) { if (value == null) { Element element = document.createElement("null"); element.setAttribute("xsi:nil", "true"); // I tried to put the schema in the envelope header (in createAndSendSOAPResponse) // resEnv.declareNamespace("http://www.w3.org/2001/XMLSchema-instance", null); // But it gets prefixed and that does not work. So adding in each instance element.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); return element; } Element element = document.createElement(elementName); element.setAttribute("value", value.toString()); return element; } public static Object deserializeSingle(Element element, Delegator delegator) throws SerializeException { String tagName = element.getLocalName(); if (tagName.equals("null")) return null; if (tagName.startsWith("std-")) { // - Standard Objects - if ("std-String".equals(tagName)) { return element.getAttribute("value"); } else if ("std-Integer".equals(tagName)) { String valStr = element.getAttribute("value"); return Integer.valueOf(valStr); } else if ("std-Long".equals(tagName)) { String valStr = element.getAttribute("value"); return Long.valueOf(valStr); } else if ("std-Float".equals(tagName)) { String valStr = element.getAttribute("value"); return Float.valueOf(valStr); } else if ("std-Double".equals(tagName)) { String valStr = element.getAttribute("value"); return Double.valueOf(valStr); } else if ("std-BigDecimal".equals(tagName)) { String valStr = element.getAttribute("value"); return new BigDecimal(valStr); } else if ("std-Boolean".equals(tagName)) { String valStr = element.getAttribute("value"); return Boolean.valueOf(valStr); } else if ("std-Locale".equals(tagName)) { String valStr = element.getAttribute("value"); return UtilMisc.parseLocale(valStr); } else if ("std-Date".equals(tagName)) { String valStr = element.getAttribute("value"); DateFormat formatter = getDateFormat(); java.util.Date value = null; try { synchronized (formatter) { value = formatter.parse(valStr); } } catch (ParseException e) { throw new SerializeException("Could not parse date String: " + valStr, e); } return value; } } else if (tagName.startsWith("sql-")) { // - SQL Objects - if ("sql-Timestamp".equals(tagName)) { String valStr = element.getAttribute("value"); /* * sql-Timestamp is defined as xsd:dateTime in ModelService.getTypes(), * so try to parse the value as xsd:dateTime first. * Fallback is java.sql.Timestamp because it has been this way all the time. */ try { Calendar cal = DatatypeConverter.parseDate(valStr); return new java.sql.Timestamp(cal.getTimeInMillis()); } catch (Exception e) { Debug.logWarning("sql-Timestamp does not conform to XML Schema definition, try java.sql.Timestamp format", module); return java.sql.Timestamp.valueOf(valStr); } } else if ("sql-Date".equals(tagName)) { String valStr = element.getAttribute("value"); return java.sql.Date.valueOf(valStr); } else if ("sql-Time".equals(tagName)) { String valStr = element.getAttribute("value"); return java.sql.Time.valueOf(valStr); } } else if (tagName.startsWith("col-")) { // - Collections - Collection<Object> value = null; if ("col-ArrayList".equals(tagName)) { value = new ArrayList<Object>(); } else if ("col-LinkedList".equals(tagName)) { value = new LinkedList<Object>(); } else if ("col-Stack".equals(tagName)) { value = new Stack<Object>(); } else if ("col-Vector".equals(tagName)) { value = new Vector<Object>(); } else if ("col-TreeSet".equals(tagName)) { value = new TreeSet<Object>(); } else if ("col-HashSet".equals(tagName)) { value = new HashSet<Object>(); } else if ("col-Collection".equals(tagName)) { value = new LinkedList<Object>(); } if (value == null) { return deserializeCustom(element); } else { Node curChild = element.getFirstChild(); while (curChild != null) { if (curChild.getNodeType() == Node.ELEMENT_NODE) { value.add(deserializeSingle((Element) curChild, delegator)); } curChild = curChild.getNextSibling(); } return value; } } else if (tagName.startsWith("map-")) { // - Maps - Map<Object, Object> value = null; if ("map-HashMap".equals(tagName)) { value = new HashMap<Object, Object>(); } else if ("map-Properties".equals(tagName)) { value = new Properties(); } else if ("map-Hashtable".equals(tagName)) { value = new Hashtable<Object, Object>(); } else if ("map-WeakHashMap".equals(tagName)) { value = new WeakHashMap<Object, Object>(); } else if ("map-TreeMap".equals(tagName)) { value = new TreeMap<Object, Object>(); } else if ("map-Map".equals(tagName)) { value = new HashMap<Object, Object>(); } if (value == null) { return deserializeCustom(element); } else { Node curChild = element.getFirstChild(); while (curChild != null) { if (curChild.getNodeType() == Node.ELEMENT_NODE) { Element curElement = (Element) curChild; if ("map-Entry".equals(curElement.getLocalName())) { Element mapKeyElement = UtilXml.firstChildElement(curElement, "map-Key"); Element keyElement = null; Node tempNode = mapKeyElement.getFirstChild(); while (tempNode != null) { if (tempNode.getNodeType() == Node.ELEMENT_NODE) { keyElement = (Element) tempNode; break; } tempNode = tempNode.getNextSibling(); } if (keyElement == null) throw new SerializeException("Could not find an element under the map-Key"); Element mapValueElement = UtilXml.firstChildElement(curElement, "map-Value"); Element valueElement = null; tempNode = mapValueElement.getFirstChild(); while (tempNode != null) { if (tempNode.getNodeType() == Node.ELEMENT_NODE) { valueElement = (Element) tempNode; break; } tempNode = tempNode.getNextSibling(); } if (valueElement == null) throw new SerializeException("Could not find an element under the map-Value"); value.put(deserializeSingle(keyElement, delegator), deserializeSingle(valueElement, delegator)); } } curChild = curChild.getNextSibling(); } return value; } } else if (tagName.startsWith("eepk-")) { return delegator.makePK(element); } else if (tagName.startsWith("eeval-")) { return delegator.makeValue(element); } return deserializeCustom(element); } public static Object deserializeCustom(Element element) throws SerializeException { String tagName = element.getLocalName(); if ("cus-obj".equals(tagName)) { String value = UtilXml.elementValue(element); if (value != null) { byte[] valueBytes = StringUtil.fromHexString(value); if (valueBytes != null) { Object obj = UtilObject.getObject(valueBytes); if (obj != null) { return obj; } } } throw new SerializeException("Problem deserializing object from byte array + " + element.getLocalName()); } else { throw new SerializeException("Cannot deserialize element named " + element.getLocalName()); } } /** * Returns the DateFormat used to serialize and deserialize <code>java.util.Date</code> objects. * This format is NOT used to format any of the java.sql subtypes of java.util.Date. * A <code>WeakReference</code> is used to maintain a reference to the DateFormat object * so that it can be created and garbage collected as needed. * * @return the DateFormat used to serialize and deserialize <code>java.util.Date</code> objects. */ private static DateFormat getDateFormat() { DateFormat formatter = null; if (simpleDateFormatter != null) { formatter = simpleDateFormatter.get(); } if (formatter == null) { formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); simpleDateFormatter = new WeakReference<DateFormat>(formatter); } return formatter; } }