/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. **/ package org.codice.ddf.spatial.ogc.csw.catalog.converter; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.copy.HierarchicalStreamCopier; import com.thoughtworks.xstream.io.naming.NoNameCoder; import com.thoughtworks.xstream.io.xml.CompactWriter; import com.thoughtworks.xstream.io.xml.XppReader; /** * XStream tool to copy the contents of a HierarchicalStreamReader into another container. */ public class XStreamAttributeCopier { private static final Logger LOGGER = LoggerFactory.getLogger(XStreamAttributeCopier.class); private static final HierarchicalStreamCopier COPIER = new HierarchicalStreamCopier(); private static void copyElementWithAttributes(HierarchicalStreamReader source, HierarchicalStreamWriter destination, Map<String, String> namespaceMap) { destination.startNode(source.getNodeName()); int attributeCount = source.getAttributeCount(); for (int i = 0; i < attributeCount; i++) { destination.addAttribute(source.getAttributeName(i), source.getAttribute(i)); } if (namespaceMap != null && !namespaceMap.isEmpty()) { for (Entry<String, String> entry : namespaceMap.entrySet()) { if (StringUtils.isBlank(source.getAttribute(entry.getKey()))) { destination.addAttribute(entry.getKey(), entry.getValue()); } } } String value = source.getValue(); if (value != null && value.length() > 0) { destination.setValue(value); } while (source.hasMoreChildren()) { source.moveDown(); COPIER.copy(source, destination); source.moveUp(); } destination.endNode(); } /** * Copies the entire XML element {@code reader} is currently at into {@code writer} and returns * a new reader ready to read the copied element. After the call, {@code reader} will be at the * end of the element that was copied. * <p> * If {@code attributeMap} is provided, the attributes will be added to the copy. * * @param reader the reader currently at the XML element you want to copy * @param writer the writer that the element will be copied into * @param attributeMap the map of attribute names to values that will be added as attributes of * the copy, may be null * @return a new reader ready to read the copied element * @throws ConversionException if a parser to use for the new reader can't be created */ public static HierarchicalStreamReader copyXml(HierarchicalStreamReader reader, StringWriter writer, Map<String, String> attributeMap) { copyElementWithAttributes(reader, new CompactWriter(writer, new NoNameCoder()), attributeMap); XmlPullParser parser; try { parser = XmlPullParserFactory.newInstance() .newPullParser(); } catch (XmlPullParserException e) { throw new ConversionException("Unable to create XmlPullParser, cannot parse XML.", e); } try { // NOTE: must specify encoding here, otherwise the platform default // encoding will be used which will not always work return new XppReader(new InputStreamReader(IOUtils.toInputStream(writer.toString(), StandardCharsets.UTF_8.name())), parser); } catch (IOException e) { LOGGER.debug("Unable create reader with UTF-8 encoding, Exception {}", e); return new XppReader(new InputStreamReader(IOUtils.toInputStream(writer.toString())), parser); } } /** * Copies the namespace declarations on the XML element {@code reader} is currently at into * {@code context}. The namespace declarations will be available in {@code context} at the key * {@link CswConstants#NAMESPACE_DECLARATIONS}. The new namespace declarations will be added to any * existing ones already in {@code context}. * * @param reader the reader currently at the XML element with namespace declarations you want * to copy * @param context the {@link UnmarshallingContext} that the namespace declarations will be * copied to */ public static void copyXmlNamespaceDeclarationsIntoContext(HierarchicalStreamReader reader, UnmarshallingContext context) { @SuppressWarnings("unchecked") Map<String, String> namespaces = (Map<String, String>) context.get(CswConstants.NAMESPACE_DECLARATIONS); if (namespaces == null) { namespaces = new HashMap<>(); } @SuppressWarnings("unchecked") Iterator<String> attributeNames = reader.getAttributeNames(); while (attributeNames.hasNext()) { String name = attributeNames.next(); if (StringUtils.startsWith(name, CswConstants.XMLNS)) { String attributeValue = reader.getAttribute(name); namespaces.put(name, attributeValue); } } if (!namespaces.isEmpty()) { context.put(CswConstants.NAMESPACE_DECLARATIONS, namespaces); } } }