All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.archive.crawler.restlet.XmlMarshaller Maven / Gradle / Ivy

The newest version!
/*
 *  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.ext.xml.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.
 * 
 * @author 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:
     * 
     * 
     * {@literal
     *  
     *   simpleObjectValue1
     *     
     *     subvalue1
     *      
     *       item1Value
     *       item2Value
     *     
     *     subvalue3
     *   
     * 
     * }
     * 
* * @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 order = new HashMap(); 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() { @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 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy