/**
* 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.wfs.catalog.converter.impl;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.B;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.BYTES_PER_GB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.BYTES_PER_KB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.BYTES_PER_MB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.BYTES_PER_PB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.BYTES_PER_TB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.GB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.KB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.MB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.PB;
import static org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants.TB;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.libs.geo.GeoFormatException;
import org.codice.ddf.libs.geo.util.GeospatialUtil;
import org.codice.ddf.spatial.ogc.catalog.common.converter.XmlNode;
import org.codice.ddf.spatial.ogc.wfs.catalog.common.FeatureMetacardType;
import org.codice.ddf.spatial.ogc.wfs.catalog.converter.FeatureConverter;
import org.codice.ddf.spatial.ogc.wfs.catalog.mapper.MetacardMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
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.StaxDriver;
import com.thoughtworks.xstream.io.xml.WstxDriver;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTWriter;
import com.vividsolutions.jts.io.gml2.GMLReader;
import ddf.catalog.data.AttributeDescriptor;
import ddf.catalog.data.AttributeType.AttributeFormat;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.MetacardType;
import ddf.catalog.data.impl.BasicTypes;
import ddf.catalog.data.impl.MetacardImpl;
import ddf.catalog.data.types.Core;
public abstract class AbstractFeatureConverter implements FeatureConverter {
protected static final String FID = "fid";
protected static final String ERROR_PARSING_MESSAGE =
"Error parsing Geometry from feature xml.";
protected static final String UTF8_ENCODING = "UTF-8";
protected static final String EXT_PREFIX = "ext.";
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFeatureConverter.class);
private final Set<String> basicAttributeNames = getBasicAttributeNames();
protected String sourceId;
protected String wfsUrl;
protected String prefix;
protected MetacardType metacardType;
protected String coordinateOrder;
private HierarchicalStreamCopier copier = new HierarchicalStreamCopier();
private NoNameCoder noNameCoder = new NoNameCoder();
private MetacardMapper metacardMapper = null;
private String srs = GeospatialUtil.EPSG_4326;
public AbstractFeatureConverter() {
}
public AbstractFeatureConverter(MetacardMapper metacardMapper) {
this.metacardMapper = metacardMapper;
}
@Override
public boolean canConvert(Class clazz) {
return Metacard.class.isAssignableFrom(clazz);
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public void setWfsUrl(String url) {
this.wfsUrl = url;
}
public MetacardType getMetacardType() {
return this.metacardType;
}
public void setMetacardType(MetacardType metacardType) {
this.metacardType = metacardType;
this.prefix = EXT_PREFIX + metacardType.getName() + ".";
}
public void setCoordinateOrder(String coordinateOrder) {
this.coordinateOrder = coordinateOrder;
}
protected HierarchicalStreamReader copyXml(HierarchicalStreamReader hreader,
StringWriter writer) {
copier.copy(hreader, new CompactWriter(writer, noNameCoder));
StaxDriver driver = new WstxDriver();
return driver.createReader(new ByteArrayInputStream(writer.toString()
.getBytes(StandardCharsets.UTF_8)));
}
protected Metacard createMetacardFromFeature(HierarchicalStreamReader hreader,
MetacardType metacardType) {
StringWriter metadataWriter = new StringWriter();
HierarchicalStreamReader reader = copyXml(hreader, metadataWriter);
MetacardImpl mc = new MetacardImpl(metacardType);
mc.setContentTypeName(metacardType.getName());
while (reader.hasMoreChildren()) {
reader.moveDown();
String featureProperty = prefix + reader.getNodeName();
AttributeDescriptor attributeDescriptor = metacardType.getAttributeDescriptor(
featureProperty);
//Check MetacardMapper for mappings of incoming values
String mappedMetacardAttribute = null;
if (metacardMapper != null) {
LOGGER.debug(
"Looking up metacard attribute for feature property {} using metacard mapper",
featureProperty);
mappedMetacardAttribute = metacardMapper.getMetacardAttribute(featureProperty);
LOGGER.debug("Found metacard attribute {} for feature property {}",
mappedMetacardAttribute,
featureProperty);
}
Serializable value = null;
if (attributeDescriptor != null && (StringUtils.isNotBlank(reader.getValue())
|| BasicTypes.GEO_TYPE.getAttributeFormat()
.equals(attributeDescriptor.getType()
.getAttributeFormat()) || BasicTypes.DATE_TYPE.getAttributeFormat()
.equals(attributeDescriptor.getType()
.getAttributeFormat()))) {
if (StringUtils.isNotBlank(mappedMetacardAttribute)) {
if (StringUtils.equals(mappedMetacardAttribute, Core.RESOURCE_SIZE)) {
String sizeBeforeConversion = reader.getValue();
String bytes = convertToBytes(reader, metacardMapper.getDataUnit());
if (StringUtils.isNotBlank(bytes)) {
LOGGER.debug("Setting mapped metacard attribute {} with value {}",
mappedMetacardAttribute,
bytes);
mc.setAttribute(mappedMetacardAttribute, bytes);
}
if (StringUtils.isNotBlank(sizeBeforeConversion)) {
LOGGER.debug("Setting metacard attribute {} with value {}",
featureProperty,
sizeBeforeConversion);
mc.setAttribute(featureProperty, sizeBeforeConversion);
}
} else {
value = getValueForMetacardAttribute(attributeDescriptor.getType()
.getAttributeFormat(), reader);
if (value != null) {
LOGGER.debug("Setting mapped metacard attribute {} with value {}",
mappedMetacardAttribute,
value);
mc.setAttribute(mappedMetacardAttribute, value);
mc.setAttribute(featureProperty, value);
}
}
} else {
value = getValueForMetacardAttribute(attributeDescriptor.getType()
.getAttributeFormat(), reader);
if (value != null) {
LOGGER.debug("Setting metacard attribute {} with value {}",
featureProperty,
value);
mc.setAttribute(featureProperty, value);
}
}
if (BasicTypes.GEO_TYPE.getAttributeFormat()
.equals(attributeDescriptor.getType()
.getAttributeFormat())) {
mc.setLocation((String) value);
}
// if this node matches a basic metacard attribute name,
// populate that field as well
if (isBasicMetacardAttribute(reader.getNodeName())) {
LOGGER.debug("Setting metacard basic attribute: {} = {}",
reader.getNodeName(),
value);
mc.setAttribute(reader.getNodeName(), value);
}
}
reader.moveUp();
}
mc.setMetadata(metadataWriter.toString());
try {
if (metacardType instanceof FeatureMetacardType) {
URI namespaceUri = new URI(((FeatureMetacardType) metacardType).getNamespaceURI());
mc.setTargetNamespace(namespaceUri);
}
} catch (URISyntaxException e) {
LOGGER.debug("Error setting target namespace uri on metacard.", e);
}
return mc;
}
protected Serializable getValueForMetacardAttribute(AttributeFormat attributeFormat,
HierarchicalStreamReader reader) {
Serializable ser = null;
switch (attributeFormat) {
case BOOLEAN:
ser = Boolean.valueOf(reader.getValue());
break;
case DOUBLE:
ser = Double.valueOf(reader.getValue());
break;
case FLOAT:
ser = Float.valueOf(reader.getValue());
break;
case INTEGER:
ser = Integer.valueOf(reader.getValue());
break;
case LONG:
ser = Long.valueOf(reader.getValue());
break;
case SHORT:
ser = Short.valueOf(reader.getValue());
break;
case XML:
case STRING:
ser = reader.getValue();
break;
case GEOMETRY:
XmlNode node = new XmlNode(reader);
String xml = node.toString();
GMLReader gmlReader = new GMLReader();
Geometry geo = null;
try {
geo = gmlReader.read(xml, null);
if (StringUtils.isNotBlank(srs) && !srs.equals(GeospatialUtil.EPSG_4326)) {
geo = GeospatialUtil.transformToEPSG4326LonLatFormat(geo, srs);
}
} catch (SAXException | IOException | ParserConfigurationException | GeoFormatException e) {
geo = null;
LOGGER.debug(ERROR_PARSING_MESSAGE, e);
}
if (geo != null) {
WKTWriter wktWriter = new WKTWriter();
ser = wktWriter.write(geo);
}
break;
case BINARY:
try {
ser = reader.getValue()
.getBytes(UTF8_ENCODING);
} catch (UnsupportedEncodingException e) {
LOGGER.debug("Error encoding the binary value into the metacard.", e);
}
break;
case DATE:
ser = parseDateFromXml(reader);
break;
default:
break;
}
return ser;
}
private String convertToBytes(HierarchicalStreamReader reader, String unit) {
BigDecimal resourceSize = new BigDecimal(reader.getValue());
resourceSize = resourceSize.setScale(1, BigDecimal.ROUND_HALF_UP);
switch (unit) {
case B:
break;
case KB:
resourceSize = resourceSize.multiply(new BigDecimal(BYTES_PER_KB));
break;
case MB:
resourceSize = resourceSize.multiply(new BigDecimal(BYTES_PER_MB));
break;
case GB:
resourceSize = resourceSize.multiply(new BigDecimal(BYTES_PER_GB));
break;
case TB:
resourceSize = resourceSize.multiply(new BigDecimal(BYTES_PER_TB));
break;
case PB:
resourceSize = resourceSize.multiply(new BigDecimal(BYTES_PER_PB));
break;
default:
break;
}
String resourceSizeAsString = resourceSize.toPlainString();
LOGGER.debug("resource size in bytes: {}", resourceSizeAsString);
return resourceSizeAsString;
}
protected Date parseDateFromXml(HierarchicalStreamReader reader) {
Date date = null;
boolean processingChildNode = false;
if (reader.hasMoreChildren()) {
reader.moveDown();
processingChildNode = true;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("node name: {}", reader.getNodeName());
LOGGER.debug("value: {}", reader.getValue());
}
if (StringUtils.isBlank(reader.getValue())) {
date = null;
if (processingChildNode) {
reader.moveUp();
}
return date;
}
try { // trying to parse xsd:date
date = DatatypeConverter.parseDate(reader.getValue())
.getTime();
} catch (IllegalArgumentException e) {
LOGGER.debug(
"Unable to parse date, attempting to parse as xsd:dateTime, Exception was {}",
e);
try { // try to parse it as a xsd:dateTime
date = DatatypeConverter.parseDateTime(reader.getValue())
.getTime();
} catch (IllegalArgumentException ie) {
LOGGER.debug(
"Unable to parse date from XML; defaulting \"{}\" to current datetime. Exception {}",
reader.getNodeName(),
ie);
date = new Date();
}
}
if (processingChildNode) {
reader.moveUp();
processingChildNode = false;
}
LOGGER.debug("node name: {}", reader.getNodeName());
return date;
}
protected Boolean isAttributeNotNull(final String attributeName, Metacard mc) {
return (mc.getAttribute(attributeName) != null && mc.getAttribute(attributeName)
.getValue() != null);
}
private Set<String> getBasicAttributeNames() {
Set<String> attrNames =
new HashSet<String>(BasicTypes.BASIC_METACARD.getAttributeDescriptors()
.size());
for (AttributeDescriptor ad : BasicTypes.BASIC_METACARD.getAttributeDescriptors()) {
attrNames.add(ad.getName());
}
return attrNames;
}
private boolean isBasicMetacardAttribute(String attrName) {
return basicAttributeNames.contains(attrName);
}
public void setSrs(String srs) {
this.srs = srs;
}
}