/**
* Copyright (c) Codice Foundation
*
* 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.
*
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants;
import org.codice.ddf.spatial.ogc.csw.catalog.transformer.TransformerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.MarshallingContext;
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;
import com.thoughtworks.xstream.io.xml.xppdom.XppFactory;
import ddf.catalog.data.BinaryContent;
import ddf.catalog.data.Metacard;
import ddf.catalog.transform.CatalogTransformerException;
import ddf.catalog.transform.InputTransformer;
import ddf.catalog.transform.MetacardTransformer;
/**
* Class to determine what transformer to use based on the schema and transforms the data appropriately.
*/
public class CswTransformProvider implements Converter {
private static final Logger LOGGER = LoggerFactory.getLogger(CswTransformProvider.class);
private TransformerManager metacardTransformerManager;
private TransformerManager inputTransformerManager;
public CswTransformProvider(TransformerManager metacardTransformers,
TransformerManager inputTransformers) {
this.metacardTransformerManager = metacardTransformers;
this.inputTransformerManager = inputTransformers;
}
/**
* Marshals Metacards to an xml. This method is not typically be called directly, instead it is
* called by another XStream Converter using MarshallingContext.convertAnother();
*
* @param o - metacard to transform.
* @param writer - writes the XML.
* @param context - the marshalling context. Should contain a map entry for {@link
* org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants.OUTPUT_SCHEMA_PARAMETER}
* to identify which transformer to use. Also contains properties for any
* arguments to provide the transformer.
*/
@Override
public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext context) {
if (o == null) {
return;
}
Metacard metacard = (Metacard) o;
Object arg = context.get(CswConstants.OUTPUT_SCHEMA_PARAMETER);
MetacardTransformer transformer = null;
if (arg != null && StringUtils.isNotBlank((String) arg)) {
String outputSchema = (String) arg;
transformer = metacardTransformerManager.getTransformerBySchema(outputSchema);
} else {
transformer = metacardTransformerManager
.getTransformerBySchema(CswConstants.CSW_OUTPUT_SCHEMA);
}
if (transformer == null) {
throw new ConversionException(
"Unable to locate a transformer for output schema: " + arg);
}
BinaryContent content = null;
try {
content = transformer.transform(metacard, getArguments(context));
} catch (CatalogTransformerException e) {
throw new ConversionException("Unable to transform Metacard", e);
}
writeXml(content, writer);
}
private void writeXml(BinaryContent content, HierarchicalStreamWriter writer) {
try {
XmlPullParser parser = XppFactory.createDefaultParser();
new HierarchicalStreamCopier()
.copy(new XppReader(new InputStreamReader(content.getInputStream()), parser),
writer);
} catch (XmlPullParserException e) {
throw new ConversionException("Unable to copy metadata to XML Output.", e);
}
}
private Map<String, Serializable> getArguments(DataHolder holder) {
Map<String, Serializable> arguments = new HashMap<>();
Iterator<Object> contextIterator = holder.keys();
while (contextIterator.hasNext()) {
Object key = contextIterator.next();
if (key instanceof String) {
Object value = holder.get(key);
if (value instanceof Serializable) {
arguments.put((String) key, (Serializable) value);
}
}
}
return arguments;
}
/**
* Creates a Metacard from the given XML. This method is not typically be called directly, instead it is
* called by another XStream Converter using UnmarshallingContext.convertAnother();
*
* @param reader
* @param context
* @return
*/
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Object arg = context.get(CswConstants.OUTPUT_SCHEMA_PARAMETER);
InputTransformer transformer = null;
if (arg == null || CswConstants.CSW_OUTPUT_SCHEMA.equals((String) arg)) {
transformer = inputTransformerManager
.<InputTransformer>getTransformerBySchema(CswConstants.CSW_OUTPUT_SCHEMA);
if (transformer != null) {
return ((CswRecordConverter) transformer).unmarshal(reader, context);
}
} else {
String outputSchema = (String) arg;
transformer = inputTransformerManager
.<InputTransformer>getTransformerBySchema(outputSchema);
}
if (transformer == null) {
throw new ConversionException(
"Unable to locate a transformer for output schema: " + arg);
}
Metacard metacard = null;
try (InputStream is = readXml(reader, context)) {
InputStream inputStream = is;
if (LOGGER.isDebugEnabled()) {
String originalInputStream = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
LOGGER.debug("About to transform\n{}", originalInputStream);
inputStream = new ByteArrayInputStream(originalInputStream.getBytes(StandardCharsets.UTF_8.name()));
}
metacard = transformer.transform(inputStream);
} catch (IOException | CatalogTransformerException e) {
throw new ConversionException("Unable to transform Metacard", e);
}
return metacard;
}
private InputStream readXml(HierarchicalStreamReader reader, UnmarshallingContext context)
throws IOException {
InputStream is = null;
Map<String, String> namespaces = null;
Object namespaceObj = context.get(CswConstants.WRITE_NAMESPACES);
if (namespaceObj instanceof Map<?, ?>) {
namespaces = (Map<String, String>) namespaceObj;
}
StringWriter writer = new StringWriter();
XStreamAttributeCopier copier = new XStreamAttributeCopier();
NoNameCoder noNameCoder = new NoNameCoder();
copier.copyAttributes(reader, new CompactWriter(writer, noNameCoder), namespaces);
return IOUtils.toInputStream(writer.toString(), StandardCharsets.UTF_8.name());
}
@Override
public boolean canConvert(Class clazz) {
return Metacard.class.isAssignableFrom(clazz);
}
}