/**
* 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.source.reader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
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 org.apache.commons.io.IOUtils;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordCollection;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswSourceConfiguration;
import org.codice.ddf.spatial.ogc.csw.catalog.converter.GetRecordsResponseConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.io.xml.XppReader;
import ddf.catalog.data.Metacard;
/**
* Custom JAX-RS MessageBodyReader for parsing a CSW GetRecords response, extracting the search
* results and CSW records.
*/
public class GetRecordsMessageBodyReader implements MessageBodyReader<CswRecordCollection> {
private static final Logger LOGGER = LoggerFactory.getLogger(GetRecordsMessageBodyReader.class);
private XStream xstream;
private DataHolder argumentHolder;
public GetRecordsMessageBodyReader(Converter provider, CswSourceConfiguration configuration) {
xstream = new XStream(new XppDriver());
xstream.setClassLoader(this.getClass().getClassLoader());
GetRecordsResponseConverter converter = new GetRecordsResponseConverter(provider);
xstream.registerConverter(converter);
xstream.alias(CswConstants.GET_RECORDS_RESPONSE, CswRecordCollection.class);
xstream.alias(CswConstants.CSW_NAMESPACE_PREFIX + CswConstants.NAMESPACE_DELIMITER
+ CswConstants.GET_RECORDS_RESPONSE, CswRecordCollection.class);
buildArguments(configuration);
}
private void buildArguments(CswSourceConfiguration configuration) {
argumentHolder = xstream.newDataHolder();
argumentHolder.put(CswConstants.OUTPUT_SCHEMA_PARAMETER, configuration.getOutputSchema());
argumentHolder.put(CswConstants.CSW_MAPPING, configuration.getMetacardCswMappings());
argumentHolder.put(CswConstants.IS_LON_LAT_ORDER_PROPERTY, configuration.isLonLatOrder());
argumentHolder.put(Metacard.RESOURCE_URI, configuration.getResourceUriMapping());
argumentHolder.put(Metacard.THUMBNAIL, configuration.getThumbnailMapping());
}
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
return CswRecordCollection.class.isAssignableFrom(type);
}
@Override
public CswRecordCollection readFrom(Class<CswRecordCollection> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream inStream) throws IOException,
WebApplicationException {
// Save original input stream for any exception message that might need to be
// created
String originalInputStream = IOUtils.toString(inStream, "UTF-8");
LOGGER.debug("Converting to CswRecordCollection: \n {}", originalInputStream);
// Re-create the input stream (since it has already been read for potential
// exception message creation)
inStream = new ByteArrayInputStream(originalInputStream.getBytes("UTF-8"));
CswRecordCollection cswRecords = null;
try {
HierarchicalStreamReader reader = new XppReader(new InputStreamReader(inStream),
XmlPullParserFactory.newInstance().newPullParser());
cswRecords = (CswRecordCollection) xstream.unmarshal(reader, null, argumentHolder);
} catch (XmlPullParserException e) {
LOGGER.error("Unable to create XmlPullParser, and cannot parse CSW Response.", e);
} catch (XStreamException e) {
// If an ExceptionReport is sent from the remote CSW site it will be sent with an
// JAX-RS "OK" status, hence the ErrorResponse exception mapper will not fire.
// Instead the ExceptionReport will come here and be treated like a GetRecords
// 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 CswSource catches, converts
// to a CswException, and logs.
ByteArrayInputStream bis = new ByteArrayInputStream(originalInputStream.getBytes());
ResponseBuilder responseBuilder = Response.ok(bis);
responseBuilder.type("text/xml");
Response response = responseBuilder.build();
throw new WebApplicationException(e, response);
} finally {
IOUtils.closeQuietly(inStream);
}
return cswRecords;
}
}