/** * 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.wfs.v2_0_0.catalog.converter.impl; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.Set; import java.util.TreeSet; import javax.xml.namespace.QName; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateFormatUtils; import org.codice.ddf.spatial.ogc.catalog.common.converter.XmlNode; import org.codice.ddf.spatial.ogc.wfs.catalog.common.AttributeDescriptorComparator; import org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsConstants; import org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsQnameBuilder; import org.codice.ddf.spatial.ogc.wfs.catalog.converter.impl.EnhancedStaxWriter; import org.codice.ddf.spatial.ogc.wfs.catalog.mapper.MetacardMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.vividsolutions.jts.geom.Geometry; import ddf.catalog.data.Attribute; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType.AttributeFormat; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.MetacardImpl; /** * This class works in conjunction with XStream to convert a {@link Metacard} to XML according to * the GML 3.2.1 spec. It will also convert respective XML into a Metacard. * */ public class GenericFeatureConverterWfs20 extends AbstractFeatureConverterWfs20 { private static final String ID = "id"; private static final Logger LOGGER = LoggerFactory .getLogger(GenericFeatureConverterWfs20.class); private String sourceId = null; public GenericFeatureConverterWfs20() { } public GenericFeatureConverterWfs20(MetacardMapper metacardMapper) { super(metacardMapper); } /** * Method to determine if this converter knows how to convert the specified Class. * * @param clazz * the class to check */ @Override public boolean canConvert(Class clazz) { return Metacard.class.isAssignableFrom(clazz); } /** * This method will convert a {@link Metacard} instance into xml that will validate against the * GML 2.1.2 AbstractFeatureType. * * @param value * the {@link Metacard} to convert * @param writer * the stream writer responsible for writing this xml doc * @param context * a reference back to the Xstream marshalling context. Allows you to call * "convertAnother" which will lookup other registered converters. */ @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Metacard metacard = (Metacard) value; // TODO when we have a reference to the MCT we can get the namespace too QName qname = WfsQnameBuilder .buildQName(metacard.getMetacardType().getName(), metacard.getContentTypeName()); writer.startNode(qname.getPrefix() + ":" + qname.getLocalPart()); // Add the "id" attribute if we have an ID if (metacard.getAttribute(Metacard.ID).getValue() != null) { String id = (String) metacard.getAttribute(Metacard.ID).getValue(); writer.addAttribute(ID, id); } if (null != metacard.getLocation()) { Geometry geo = XmlNode.readGeometry(metacard.getLocation()); if (geo != null && !geo.isEmpty()) { XmlNode.writeEnvelope(WfsConstants.GML_PREFIX + ":" + "boundedBy", context, writer, geo.getEnvelopeInternal()); } } Set<AttributeDescriptor> descriptors = new TreeSet<AttributeDescriptor>( new AttributeDescriptorComparator()); descriptors.addAll(metacard.getMetacardType().getAttributeDescriptors()); for (AttributeDescriptor attributeDescriptor : descriptors) { Attribute attribute = metacard.getAttribute(attributeDescriptor.getName()); if (attribute != null) { writeAttributeToXml(attribute, qname, attributeDescriptor.getType().getAttributeFormat(), context, writer); } } writer.endNode(); } /* Helper method to convert these types to a String representation */ private void writeAttributeToXml(Attribute attribute, QName qname, AttributeFormat format, MarshallingContext context, HierarchicalStreamWriter writer) { // Loop to handle multi-valued attributes String name = qname.getPrefix() + ":" + attribute.getName(); for (Serializable value : attribute.getValues()) { String xmlValue = null; switch (format) { case XML: String cdata = (String) value; if (cdata != null && (writer.underlyingWriter() instanceof EnhancedStaxWriter)) { writer.startNode(name); EnhancedStaxWriter eWriter = (EnhancedStaxWriter) writer.underlyingWriter(); eWriter.writeCdata(cdata); writer.endNode(); } break; case GEOMETRY: XmlNode.writeGeometry(name, context, writer, XmlNode.readGeometry((String) value)); break; case BINARY: xmlValue = Base64.encodeBase64String((byte[]) value); break; case DATE: Date date = (Date) value; xmlValue = DateFormatUtils.formatUTC(date, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern()); break; case OBJECT: // Probably won't translate at all. break; default: xmlValue = value.toString(); break; } // Write the node if we were able to convert it. if (xmlValue != null) { writer.startNode(name); writer.setValue(xmlValue); writer.endNode(); } } } /** * This method will unmarshal an XML instance of a "gml:member" to a {@link Metacard}. * * @param hreader * the stream reader responsible for reading this xml doc * @param context * a reference back to the Xstream unmarshalling context. Allows you to call * "convertAnother" which will lookup other registered converters. */ @Override public Object unmarshal(HierarchicalStreamReader hreader, UnmarshallingContext context) { LOGGER.debug("Entering: {} : unmarshal", this.getClass().getName()); //Workaround for Xstream which seems to be having issues involving attributes with namespaces, //in that it cannot fetch the attributes value directly by name. String id = null; int count = hreader.getAttributeCount(); for (int i = 0; i < count; ++i) { if (hreader.getAttributeName(i).equals(ID)) { id = hreader.getAttribute(i); } } MetacardImpl mc; if (metacardType != null) { mc = (MetacardImpl) createMetacardFromFeature(hreader, metacardType); } else { throw new IllegalArgumentException( "No MetacardType registered on the FeatureConverter. Unable to to convert features to metacards."); } if (StringUtils.isNotBlank(id)) { mc.setId(id); } else { LOGGER.warn("Feature id is blank. Unable to set metacard id."); } mc.setSourceId(sourceId); // set some default values that we can't get from a generic // featureCollection if they are not already set Date genericDate = new Date(); if (mc.getEffectiveDate() == null) { mc.setEffectiveDate(genericDate); } if (mc.getCreatedDate() == null) { mc.setCreatedDate(genericDate); } if (mc.getModifiedDate() == null) { mc.setModifiedDate(genericDate); } if (StringUtils.isBlank(mc.getTitle())) { mc.setTitle(id); } mc.setContentTypeName(metacardType.getName()); try { mc.setTargetNamespace( new URI(WfsConstants.NAMESPACE_URN_ROOT + metacardType.getName())); } catch (URISyntaxException e) { LOGGER.warn("Unable to set Target Namespace on metacard: {}, Exception {}", WfsConstants.NAMESPACE_URN_ROOT + metacardType.getName(), e); } return mc; } public void setSourceId(final String sourceId) { this.sourceId = sourceId; } }