/** * 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.v2_0_0.catalog.converter.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import org.codice.ddf.spatial.ogc.catalog.common.converter.XmlNode; import org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsFeatureCollection; import org.codice.ddf.spatial.ogc.wfs.catalog.common.WfsQnameBuilder; import org.codice.ddf.spatial.ogc.wfs.catalog.converter.FeatureConverter; import org.codice.ddf.spatial.ogc.wfs.v2_0_0.catalog.common.Wfs20Constants; import org.codice.ddf.spatial.ogc.wfs.v2_0_0.catalog.common.Wfs20FeatureCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.converters.Converter; 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 com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.MetacardImpl; /** * This class works in conjunction with XStream to convert a {@link Wfs20FeatureCollection} to XML * according to the GML 3.2.1 spec. It will also convert respective XML into a * {@link Wfs20FeatureCollection}. * */ public class FeatureCollectionConverterWfs20 implements Converter { private static final String FEATURE_MEMBER = "member"; private static final String FEATURE_COLLECTION = "FeatureCollection"; private static final Logger LOGGER = LoggerFactory.getLogger(FeatureCollectionConverterWfs20.class); private String contextRoot; private Map<String, FeatureConverter> featureConverterMap = new HashMap<String, FeatureConverter>(); private Map<String, String> prefixToUriMapping = new HashMap<String, String>(); public FeatureCollectionConverterWfs20() { prefixToUriMapping.put(Wfs20Constants.WFS_NAMESPACE_PREFIX, Wfs20Constants.WFS_2_0_NAMESPACE); prefixToUriMapping.put(Wfs20Constants.GML_PREFIX, Wfs20Constants.GML_3_2_NAMESPACE); } @Override public boolean canConvert(Class clazz) { if (!WfsFeatureCollection.class.isAssignableFrom(clazz)) { LOGGER.debug("Cannot convert: {}", clazz.getName()); } return Wfs20FeatureCollection.class.isAssignableFrom(clazz); } @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { if (value != null) { Wfs20FeatureCollection wfc = (Wfs20FeatureCollection) value; String schemaLoc = generateSchemaLocationFromMetacards(wfc.getMembers(), prefixToUriMapping); for (Entry<String, String> entry : prefixToUriMapping.entrySet()) { writer.addAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + entry.getKey(), entry.getValue()); } writer.addAttribute(Wfs20Constants.ATTRIBUTE_SCHEMA_LOCATION, schemaLoc); Geometry allGeometry = getBounds(wfc.getMembers()); if (!allGeometry.isEmpty()) { XmlNode.writeEnvelope(Wfs20Constants.GML_PREFIX + ":" + "boundedBy", context, writer, allGeometry.getEnvelopeInternal()); } for (Metacard mc : wfc.getMembers()) { writer.startNode(Wfs20Constants.GML_PREFIX + ":" + FEATURE_MEMBER); context.convertAnother(mc); writer.endNode(); } } else { LOGGER.debug("Incoming value was null"); } } public void setContextRoot(String contextRoot) { if (null != contextRoot) { this.contextRoot = contextRoot; } } private String generateSchemaLocationFromMetacards(List<Metacard> metacards, Map<String, String> prefixToUriMapping) { if (metacards != null) { StringBuilder descFeatureService = new StringBuilder(); descFeatureService.append(contextRoot) .append("/wfs?service=wfs&request=DescribeFeatureType&version=2.0.0&typeName="); StringBuilder schemaLocation = new StringBuilder(); Set<QName> qnames = new HashSet<QName>(); for (Metacard metacard : metacards) { qnames.add(WfsQnameBuilder.buildQName(metacard.getMetacardType() .getName(), metacard.getContentTypeName())); } for (QName qname : qnames) { prefixToUriMapping.put(qname.getPrefix(), qname.getNamespaceURI()); schemaLocation.append(qname.getNamespaceURI()) .append(" ") .append(descFeatureService) .append(qname.getPrefix()) .append(":") .append(qname.getLocalPart()) .append(" "); } return schemaLocation.toString(); } else { LOGGER.debug("Metacard list is null"); return null; } } private Geometry getBounds(List<Metacard> metacards) { if (metacards != null) { List<Geometry> geometries = new ArrayList<Geometry>(); for (Metacard card : metacards) { if (null != card.getLocation()) { Geometry geo = XmlNode.readGeometry(card.getLocation()); if (null != geo) { geometries.add(geo); } } } return new GeometryCollection(geometries.toArray(new Geometry[0]), new GeometryFactory()); } else { LOGGER.debug("Metacard list is null"); return null; } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Wfs20FeatureCollection featureCollection = new Wfs20FeatureCollection(); while (reader.hasMoreChildren()) { reader.moveDown(); String nodeName = reader.getNodeName(); // Its important to note that the reader appears to drop the // namespace. if (FEATURE_MEMBER.equals(nodeName)) { reader.moveDown(); String subNodeName = reader.getNodeName(); //If the member contains a sub feature collection, step in and get members if (subNodeName.equals(FEATURE_COLLECTION)) { while (reader.hasMoreChildren()) { reader.moveDown(); String subNodeName2 = reader.getNodeName(); if (FEATURE_MEMBER.equals(subNodeName2)) { reader.moveDown(); // lookup the converter for this featuretype featureCollection = addMetacardToFeatureCollection(featureCollection, context, reader); reader.moveUp(); } reader.moveUp(); } } else { // lookup the converter for this featuretype featureCollection = addMetacardToFeatureCollection(featureCollection, context, reader); } reader.moveUp(); } reader.moveUp(); } return featureCollection; } private Wfs20FeatureCollection addMetacardToFeatureCollection( Wfs20FeatureCollection featureCollection, UnmarshallingContext context, HierarchicalStreamReader reader) { featureCollection.getMembers() .add((Metacard) context.convertAnother(null, MetacardImpl.class, featureConverterMap.get(reader.getNodeName()))); return featureCollection; } public void setFeatureConverterMap(Map<String, FeatureConverter> featureConverterMap) { this.featureConverterMap = featureConverterMap; } }