Package org.archive.crawler.restlet

Source Code of org.archive.crawler.restlet.XmlMarshaller

/*
*  This file is part of the Heritrix web crawler (crawler.archive.org).
*
*  Licensed to the Internet Archive (IA) by one or more individual
*  contributors.
*
*  The IA 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.archive.crawler.restlet;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.apache.commons.lang.StringUtils;
import org.restlet.util.XmlWriter;
import org.xml.sax.SAXException;

/**
* XmlMarshaller can be used to write data structures as simple xml. See
* {@link #marshalDocument(Writer, String, Object)} for more information.
*
* @contributor nlevitt
*/
public class XmlMarshaller {
   
    protected static XmlWriter getXmlWriter(Writer writer) {
        XmlWriter xmlWriter = new XmlWriter(writer);

        // https://webarchive.jira.com/browse/HER-1603?focusedCommentId=22558#action_22558
        xmlWriter.setDataFormat(true);
        xmlWriter.setIndentStep(2);

        return xmlWriter;
    }

    /**
     * Writes {@code content} as xml to {@code writer}. Recursively descends
     * into Maps, using keys as tag names. Iterates over items in arrays and
     * Iterables, using "value" as the tag name. Marshals simple object values
     * with {@link #toString()}. The result looks something like this:
     *
     * <pre>
     * {@literal
     * <rootTag> <!-- root object is a Map -->
     *   <key1>simpleObjectValue1</key1>
     *   <key2>  <!-- /rootTag/key2 is another Map -->
     *     <subkey1>subvalue1</subkey1>
     *     <subkey2> <!-- an array or Iterable-->
     *       <value>item1Value</value>
     *       <value>item2Value</value>
     *     </subkey2>
     *     <subkey3>subvalue3</subkey3>
     *   </key2>
     * </rootTag>
     * }
     * </pre>
     *
     * @param writer
     *            output writer
     * @param rootTag
     *            xml document root tag name
     * @param content
     *            data structure to marshal
     * @throws IOException
     */
    public static void marshalDocument(Writer writer, String rootTag,
            Object content) throws IOException {
        XmlWriter xmlWriter = getXmlWriter(writer);
        try {
            xmlWriter.startDocument();
            marshal(xmlWriter, rootTag, content);
            xmlWriter.endDocument(); // calls flush()
        } catch (SAXException e) {
            if (e.getException() instanceof IOException) {
                // e.g. broken tcp connection
                throw (IOException) e.getException()
            } else {
                throw new RuntimeException(e);
            }
        }
    }
    /**
     * sort PropertyDescriptors according to propOrder.
     * properties listed in propOrder come first, in the order they are listed, and
     * then come remaining unlisted properties, in arbitrary order (likely alphabetical).
     * this semantics is rather relaxed compared to original JAXB semantics, where props
     * and propOrder must be the same set (except for those marked XmlTransient).
     * @param props PropertyDescriptor array to be sorted in-place.
     * @param propOrder list of property names in order of desired appearance.
     */
    protected static void orderProperties(PropertyDescriptor[] props, final String[] propOrder) {
        if (propOrder == null || propOrder.length == 0) return;
        final Map<String, Integer> order = new HashMap<String, Integer>();
        for (int i = 0; i < propOrder.length; i++) {
            order.put(propOrder[i], i);
        }
        final Integer LAST = Integer.valueOf(propOrder.length);
        Arrays.sort(props, new Comparator<PropertyDescriptor>() {
            @Override
            public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
                Integer c1 = order.get(o1.getName());
                Integer c2 = order.get(o2.getName());
                return (c1 != null ? c1 : LAST).compareTo(c2 != null ? c2 : LAST);
            }
        });
    }
    /**
     * test if {@code obj} has {@link XmlRootElement} annotation.
     * to avoid unexpected side effects, objects are mapped to nested XML structure
     * only when its class has {@link XmlRootElement} annotation.
     * note semantics is slightly different from JAXB - just borrowing XmlRootElement
     * as substitute of XmlElement, because Map entry cannot be annotated.
     * @param obj object to test
     * @return true if obj's class has XmlRootElement annotation.
     */
    protected static boolean marshalAsElement(Object obj) {
      XmlRootElement ann = obj.getClass().getAnnotation(XmlRootElement.class);
      return ann != null;
    }
    /**
     * generate nested XML structure for a bean {@code obj}. enclosing element
     * will not be generated if {@code key} is empty. each readable JavaBeans property
     * is mapped to an nested element named after its name. Those properties
     * annotated with {@link XmlTransient} are ignored.
     * @param xmlWriter XmlWriter
     * @param key name of enclosing element
     * @param obj bean
     * @throws SAXException
     */
    protected static void marshalBean(XmlWriter xmlWriter, String key, Object obj) throws SAXException {
        if (!StringUtils.isEmpty(key))
            xmlWriter.startElement(key);
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class);
            PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
            XmlType xmlType = obj.getClass().getAnnotation(XmlType.class);
            if (xmlType != null) {
                String[] propOrder = xmlType.propOrder();
                if (propOrder != null) {
                    // TODO: should cache this sorted version?
                    orderProperties(props, propOrder);
                }
            }
            for (PropertyDescriptor prop : props) {
                Method m = prop.getReadMethod();
                if (m == null || m.getAnnotation(XmlTransient.class) != null)
                    continue;
                try {
                    Object propValue = m.invoke(obj);
                    if (propValue != null && !"".equals(propValue)) {
                        marshal(xmlWriter, prop.getName(), propValue);
                    }
                } catch (Exception ex) {
                    // generate empty element, for now. generate comment?
                    xmlWriter.emptyElement(prop.getName());
                }
            }
        } catch (IntrospectionException ex) {
            // ignored, for now.
        }
        if (!StringUtils.isEmpty(key))
            xmlWriter.endElement(key);
    }

    protected static void marshal(XmlWriter xmlWriter, String key, Object value) throws SAXException {
        if (value == null) {
            xmlWriter.emptyElement(key);
        } else if (value instanceof Map<?,?>) {
            marshal(xmlWriter, key, (Map<?,?>) value);
        } else if (value instanceof Iterable<?>) {
            marshal(xmlWriter, key, (Iterable<?>) value);
        } else if (marshalAsElement(value)) {
            marshalBean(xmlWriter, key, value);
        } else {
            xmlWriter.dataElement(key, value.toString());
        }
    }

    protected static void marshal(XmlWriter xmlWriter, String key, Map<?,?> map) throws SAXException {
        xmlWriter.startElement(key);
        for (Map.Entry<?,?> entry: map.entrySet()) {
            marshal(xmlWriter, entry.getKey().toString(), entry.getValue());
        }
        xmlWriter.endElement(key);
    }

    protected static void marshal(XmlWriter xmlWriter, String key, Iterable<?> iterable) throws SAXException {
        xmlWriter.startElement(key);
        for (Object item: iterable) {
            marshal(xmlWriter, item);
        }
        xmlWriter.endElement(key);
    }

    // something we have no name for goes in a <value/> tag
    protected static void marshal(XmlWriter xmlWriter, Object item) throws SAXException {
        if (item instanceof Map.Entry<?, ?>) {
            // if it happens to have a name, use it
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) item;
            marshal(xmlWriter, entry.getKey().toString(), entry.getValue());
        } else {
            marshal(xmlWriter, "value", item);
        }
    }
}
TOP

Related Classes of org.archive.crawler.restlet.XmlMarshaller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.