/**
* 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.source.reader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.spatial.ogc.wfs.catalog.converter.FeatureConverter;
import org.codice.ddf.spatial.ogc.wfs.catalog.converter.impl.GmlEnvelopeConverter;
import org.codice.ddf.spatial.ogc.wfs.catalog.converter.impl.GmlGeometryConverter;
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.codice.ddf.spatial.ogc.wfs.v2_0_0.catalog.converter.impl.FeatureCollectionConverterWfs20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.io.xml.WstxDriver;
import ddf.catalog.data.Metacard;
import net.opengis.wfs.v_2_0_0.FeatureCollectionType;
@Consumes({MediaType.TEXT_XML, MediaType.APPLICATION_XML})
@Provider
public class FeatureCollectionMessageBodyReaderWfs20
implements MessageBodyReader<Wfs20FeatureCollection> {
private static final Logger LOGGER = LoggerFactory.getLogger(
FeatureCollectionMessageBodyReaderWfs20.class);
private static final JAXBContext JAXB_CONTEXT = initJaxbContext();
protected XStream xstream;
protected FeatureCollectionConverterWfs20 featureCollectionConverter;
protected Map<String, FeatureConverter> featureConverterMap =
new HashMap<String, FeatureConverter>();
public FeatureCollectionMessageBodyReaderWfs20() {
xstream = new XStream(new WstxDriver());
xstream.setClassLoader(this.getClass()
.getClassLoader());
xstream.registerConverter(new GmlGeometryConverter());
xstream.registerConverter(new GmlEnvelopeConverter());
featureCollectionConverter = new FeatureCollectionConverterWfs20();
featureCollectionConverter.setFeatureConverterMap(featureConverterMap);
xstream.registerConverter(featureCollectionConverter);
xstream.alias("FeatureCollection", Wfs20FeatureCollection.class);
}
private static JAXBContext initJaxbContext() {
JAXBContext jaxbContext = null;
// JAXB context path
String contextPath = StringUtils.join(new String[] {Wfs20Constants.OGC_WFS_PACKAGE,
Wfs20Constants.OGC_FILTER_PACKAGE, Wfs20Constants.OGC_GML_PACKAGE,
Wfs20Constants.OGC_OWS_PACKAGE}, ":");
try {
jaxbContext = JAXBContext.newInstance(contextPath,
FeatureCollectionMessageBodyReaderWfs20.class.getClassLoader());
} catch (JAXBException e) {
LOGGER.info("Unable to create JAXB context using contextPath: {}.", contextPath, e);
}
return jaxbContext;
}
@Override
public boolean isReadable(Class<?> clazz, Type type, Annotation[] annotations,
MediaType mediaType) {
if (!Wfs20FeatureCollection.class.isAssignableFrom(clazz)) {
LOGGER.debug("{} class is not readable.", clazz);
}
return Wfs20FeatureCollection.class.isAssignableFrom(clazz);
}
@SuppressWarnings("unchecked")
@Override
public Wfs20FeatureCollection readFrom(Class<Wfs20FeatureCollection> clazz, Type type,
Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> headers,
InputStream inStream) throws IOException, WebApplicationException {
// Save original input stream for any exception message that might need to be
// created and additional attributes
String originalInputStream = IOUtils.toString(inStream, "UTF-8");
LOGGER.debug("{}", originalInputStream);
ClassLoader ccl = Thread.currentThread()
.getContextClassLoader();
try {
Thread.currentThread()
.setContextClassLoader(FeatureCollectionMessageBodyReaderWfs20.class.getClassLoader());
//Fetch FeatureCollection attributes
Unmarshaller unmarshaller = null;
JAXBElement<FeatureCollectionType> wfsFeatureCollectionType = null;
try {
unmarshaller = JAXB_CONTEXT.createUnmarshaller();
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
false);
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,
false);
XMLStreamReader xmlStreamReader =
xmlInputFactory.createXMLStreamReader(new StringReader(originalInputStream));
wfsFeatureCollectionType =
(JAXBElement<FeatureCollectionType>) unmarshaller.unmarshal(xmlStreamReader);
} catch (ClassCastException e1) {
LOGGER.debug(
"Exception unmarshalling {}, could be an OWS Exception Report from server.",
e1.getMessage());
// If an ExceptionReport is sent from the remote WFS site it will be sent with an
// JAX-RS "OK" status, hence the ErrorResponse exception mapper will not fire.
// Instead the ServiceExceptionReport will come here and be treated like a GetFeature
// response, resulting in an XStreamException since ExceptionReport cannot be
// unmarshalled. So this catch clause is responsible for catching that XStream
// exception and creating a JAX-RS response containing the original stream
// (with the ExceptionReport) and rethrowing it as a WebApplicationException,
// which CXF will wrap as a ClientException that the WfsSource catches, converts
// to a WfsException, and logs.
ByteArrayInputStream bis = new ByteArrayInputStream(originalInputStream.getBytes(
StandardCharsets.UTF_8));
ResponseBuilder responseBuilder = Response.ok(bis);
responseBuilder.type("text/xml");
Response response = responseBuilder.build();
throw new WebApplicationException(e1, response);
} catch (JAXBException | XMLStreamException e1) {
LOGGER.debug("Error in retrieving feature collection.", e1);
} catch (RuntimeException | Error e) {
LOGGER.debug("Error processing collection", e);
throw e;
}
Wfs20FeatureCollection featureCollection = null;
if (null != wfsFeatureCollectionType && null != wfsFeatureCollectionType.getValue()) {
BigInteger numberReturned = wfsFeatureCollectionType.getValue()
.getNumberReturned();
String numberMatched = wfsFeatureCollectionType.getValue()
.getNumberMatched();
// Re-create the input stream (since it has already been read for potential
// exception message creation)
inStream = new ByteArrayInputStream(originalInputStream.getBytes("UTF-8"));
try {
featureCollection = (Wfs20FeatureCollection) xstream.fromXML(inStream);
featureCollection.setNumberMatched(numberMatched);
featureCollection.setNumberReturned(numberReturned);
} catch (XStreamException e) {
LOGGER.debug("Exception unmarshalling {}", e);
} finally {
IOUtils.closeQuietly(inStream);
}
}
return featureCollection;
} finally {
Thread.currentThread()
.setContextClassLoader(ccl);
}
}
public void registerConverter(FeatureConverter converter) {
featureConverterMap.put(converter.getMetacardType()
.getName(), converter);
xstream.registerConverter(converter);
xstream.alias(converter.getMetacardType()
.getName(), Metacard.class);
}
}