blob: 49057c01f6fd175adf26d7a9e8d1ae5b73503846 [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 org.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.ofbiz.base.util.Debug;
import org.ofbiz.base.util.StringUtil;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilObject;
import org.ofbiz.base.util.UtilXml;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericPK;
import org.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;
}
}