/**
* Copyright (C) 2012-2017 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* License version 2 and the aforementioned licenses.
*
* 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 General
* Public License for more details.
*/
package org.n52.sos.decode;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.n52.sos.exception.ows.NoApplicableCodeException;
import org.n52.sos.exception.ows.concrete.NotYetSupportedException;
import org.n52.sos.exception.ows.concrete.UnsupportedDecoderInputException;
import org.n52.sos.ogc.filter.SpatialFilter;
import org.n52.sos.ogc.filter.TemporalFilter;
import org.n52.sos.ogc.om.OmConstants;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.ogc.sos.Sos1Constants;
import org.n52.sos.ogc.sos.SosConstants;
import org.n52.sos.request.AbstractServiceRequest;
import org.n52.sos.request.DescribeSensorRequest;
import org.n52.sos.request.GetCapabilitiesRequest;
import org.n52.sos.request.GetFeatureOfInterestRequest;
import org.n52.sos.request.GetObservationByIdRequest;
import org.n52.sos.request.GetObservationRequest;
import org.n52.sos.service.AbstractServiceCommunicationObject;
import org.n52.sos.service.ServiceConstants.SupportedTypeKey;
import org.n52.sos.util.CodingHelper;
import org.n52.sos.util.CollectionHelper;
import org.n52.sos.util.OMHelper;
import org.n52.sos.util.XmlHelper;
import org.n52.sos.util.http.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.opengis.ogc.SpatialOpsType;
import net.opengis.sos.x10.DescribeSensorDocument;
import net.opengis.sos.x10.DescribeSensorDocument.DescribeSensor;
import net.opengis.sos.x10.GetCapabilitiesDocument;
import net.opengis.sos.x10.GetCapabilitiesDocument.GetCapabilities;
import net.opengis.sos.x10.GetFeatureOfInterestDocument;
import net.opengis.sos.x10.GetFeatureOfInterestDocument.GetFeatureOfInterest;
import net.opengis.sos.x10.GetFeatureOfInterestDocument.GetFeatureOfInterest.Location;
import net.opengis.sos.x10.GetObservationByIdDocument;
import net.opengis.sos.x10.GetObservationByIdDocument.GetObservationById;
import net.opengis.sos.x10.GetObservationDocument;
import net.opengis.sos.x10.GetObservationDocument.GetObservation;
import net.opengis.sos.x10.GetObservationDocument.GetObservation.FeatureOfInterest;
import net.opengis.sos.x10.ResponseModeType.Enum;
/**
* @since 4.0.0
*
*/
public class SosDecoderv100 implements Decoder<AbstractServiceCommunicationObject, XmlObject> {
private static final Logger LOGGER = LoggerFactory.getLogger(SosDecoderv100.class);
@SuppressWarnings("unchecked")
private static final Set<DecoderKey> DECODER_KEYS = CollectionHelper.union(CodingHelper.decoderKeysForElements(
Sos1Constants.NS_SOS, GetCapabilitiesDocument.class, DescribeSensorDocument.class,
GetObservationDocument.class, GetFeatureOfInterestDocument.class, GetObservationByIdDocument.class),
CodingHelper.xmlDecoderKeysForOperation(SosConstants.SOS, Sos1Constants.SERVICEVERSION,
SosConstants.Operations.GetCapabilities, SosConstants.Operations.GetObservation,
SosConstants.Operations.GetFeatureOfInterest, SosConstants.Operations.GetObservationById,
SosConstants.Operations.DescribeSensor));
public SosDecoderv100() {
LOGGER.debug("Decoder for the following namespaces initialized successfully: {}!",
Joiner.on(", ").join(DECODER_KEYS));
}
@Override
public Set<DecoderKey> getDecoderKeyTypes() {
return Collections.unmodifiableSet(DECODER_KEYS);
}
@Override
public Map<SupportedTypeKey, Set<String>> getSupportedTypes() {
return Collections.emptyMap();
}
@Override
public Set<String> getConformanceClasses() {
return Collections.emptySet();
}
@Override
public AbstractServiceCommunicationObject decode(XmlObject xmlObject) throws OwsExceptionReport {
AbstractServiceCommunicationObject request = null;
LOGGER.debug("REQUESTTYPE:" + xmlObject.getClass());
/*
* Add O&M 1.0.0 namespace to GetObservation document. XmlBeans removes
* the namespace from the document because there are no om:... elements
* in the document. But the validation fails if the <resultModel>
* element is set with e.g. om:Measurement.
*/
if (xmlObject instanceof GetObservationDocument) {
XmlCursor cursor = xmlObject.newCursor();
cursor.toFirstChild();
cursor.insertNamespace(OmConstants.NS_OM_PREFIX, OmConstants.NS_OM);
cursor.dispose();
}
// validate document
XmlHelper.validateDocument(xmlObject);
// getCapabilities request
if (xmlObject instanceof GetCapabilitiesDocument) {
GetCapabilitiesDocument getCapsDoc = (GetCapabilitiesDocument) xmlObject;
request = parseGetCapabilities(getCapsDoc);
}
// DescribeSensor request (still SOS 1.0 NS_URI
else if (xmlObject instanceof DescribeSensorDocument) {
DescribeSensorDocument descSensorDoc = (DescribeSensorDocument) xmlObject;
request = parseDescribeSensor(descSensorDoc);
}
// getObservation request
else if (xmlObject instanceof GetObservationDocument) {
GetObservationDocument getObsDoc = (GetObservationDocument) xmlObject;
request = parseGetObservation(getObsDoc);
}
// getFeatureOfInterest request
else if (xmlObject instanceof GetFeatureOfInterestDocument) {
GetFeatureOfInterestDocument getFoiDoc = (GetFeatureOfInterestDocument) xmlObject;
request = parseGetFeatureOfInterest(getFoiDoc);
}
// getObservationById request
else if (xmlObject instanceof GetObservationByIdDocument) {
GetObservationByIdDocument getObsByIdDoc = (GetObservationByIdDocument) xmlObject;
request = parseGetObservationById(getObsByIdDoc);
}
else {
throw new UnsupportedDecoderInputException(this, xmlObject);
}
return request;
}
/**
* parses the XmlBean representing the getCapabilities request and creates a
* SosGetCapabilities request
*
* @param getCapsDoc
* XmlBean created from the incoming request stream
* @return Returns SosGetCapabilitiesRequest representing the request
*
*
* @throws OwsExceptionReport
* * If parsing the XmlBean failed
*/
private AbstractServiceRequest<?> parseGetCapabilities(GetCapabilitiesDocument getCapsDoc) throws OwsExceptionReport {
GetCapabilitiesRequest request = new GetCapabilitiesRequest();
GetCapabilities getCaps = getCapsDoc.getGetCapabilities();
request.setService(getCaps.getService());
if (getCaps.getAcceptFormats() != null && getCaps.getAcceptFormats().sizeOfOutputFormatArray() != 0) {
request.setAcceptFormats(Arrays.asList(getCaps.getAcceptFormats().getOutputFormatArray()));
}
if (getCaps.getAcceptVersions() != null && getCaps.getAcceptVersions().sizeOfVersionArray() != 0) {
request.setAcceptVersions(Arrays.asList(getCaps.getAcceptVersions().getVersionArray()));
}
if (getCaps.getSections() != null && getCaps.getSections().getSectionArray().length != 0) {
request.setSections(Arrays.asList(getCaps.getSections().getSectionArray()));
}
return request;
}
/**
* parses the XmlBean representing the describeSensor request and creates a
* DescribeSensor request
*
* @param descSensorDoc
* XmlBean created from the incoming request stream
* @return Returns SosDescribeSensorRequest representing the request
*
*
* @throws OwsExceptionReport
* * If parsing the XmlBean failed
*/
private AbstractServiceCommunicationObject parseDescribeSensor(DescribeSensorDocument descSensorDoc) {
DescribeSensorRequest request = new DescribeSensorRequest();
DescribeSensor descSensor = descSensorDoc.getDescribeSensor();
request.setService(descSensor.getService());
request.setVersion(descSensor.getVersion());
//parse outputFormat through MediaType to ensure it's a mime type and eliminate whitespace variations
request.setProcedureDescriptionFormat(MediaType.normalizeString(descSensor.getOutputFormat()));
request.setProcedure(descSensor.getProcedure());
return request;
}
/**
* parses the XmlBean representing the getObservation request and creates a
* SoSGetObservation request
*
* @param getObsDoc
* XmlBean created from the incoming request stream
* @return Returns SosGetObservationRequest representing the request
*
*
* @throws OwsExceptionReport
* * If parsing the XmlBean failed
*/
private AbstractServiceRequest<?> parseGetObservation(GetObservationDocument getObsDoc) throws OwsExceptionReport {
GetObservationRequest getObsRequest = new GetObservationRequest();
GetObservation getObs = getObsDoc.getGetObservation();
getObsRequest.setService(getObs.getService());
getObsRequest.setVersion(getObs.getVersion());
getObsRequest.setOfferings(Arrays.asList(getObs.getOffering()));
getObsRequest.setObservedProperties(Arrays.asList(getObs.getObservedPropertyArray()));
getObsRequest.setProcedures(Arrays.asList(getObs.getProcedureArray()));
getObsRequest.setTemporalFilters(parseTemporalFilters4GetObservation(getObs.getEventTimeArray()));
getObsRequest.setSrsName(getObs.getSrsName());
if (getObs.isSetFeatureOfInterest()) {
FeatureOfInterest featureOfInterest = getObs.getFeatureOfInterest();
if (featureOfInterest.isSetSpatialOps()) {
Object filter = CodingHelper.decodeXmlElement(featureOfInterest.getSpatialOps());
if (filter instanceof SpatialFilter) {
getObsRequest.setSpatialFilter((SpatialFilter) filter);
}
} else if (featureOfInterest.getObjectIDArray() != null) {
Set<String> featureIdentifiers = Sets.newHashSet();
for (String string : featureOfInterest.getObjectIDArray()) {
featureIdentifiers.add(string);
}
getObsRequest.setFeatureIdentifiers(Lists.newArrayList(featureIdentifiers));
}
}
// TODO implement result filtering
if (getObs.isSetResult()) {
throw new NotYetSupportedException("Result filtering");
}
// return error message
if (getObs.isSetResponseFormat()) {
try {
String responseFormat = URLDecoder.decode(getObs.getResponseFormat(), "UTF-8");
// parse responseFormat through MediaType to ensure it's a mime type and eliminate whitespace variations
getObsRequest.setResponseFormat(MediaType.normalizeString(responseFormat));
} catch (UnsupportedEncodingException e) {
throw new NoApplicableCodeException().causedBy(e).withMessage("Error while decoding response format!");
}
} else {
getObsRequest.setResponseFormat(OmConstants.CONTENT_TYPE_OM.toString());
}
if (getObs.isSetResultModel()) {
getObsRequest.setResultModel(OMHelper.getObservationTypeFor(getObs.getResultModel()));
}
return getObsRequest;
}
/**
* parses the passes XmlBeans document and creates a SOS
* getFeatureOfInterest request
*
* @param getFoiDoc
* XmlBeans document representing the getFeatureOfInterest
* request
* @return Returns SOS getFeatureOfInterest request
*
*
* @throws OwsExceptionReport
* * if validation of the request failed
*/
private AbstractServiceRequest<?> parseGetFeatureOfInterest(GetFeatureOfInterestDocument getFoiDoc)
throws OwsExceptionReport {
GetFeatureOfInterestRequest getFoiRequest = new GetFeatureOfInterestRequest();
GetFeatureOfInterest getFoi = getFoiDoc.getGetFeatureOfInterest();
getFoiRequest.setService(getFoi.getService());
getFoiRequest.setVersion(getFoi.getVersion());
getFoiRequest.setFeatureIdentifiers(Arrays.asList(getFoi.getFeatureOfInterestIdArray()));
getFoiRequest.setSpatialFilters(parseSpatialFilters4GetFeatureOfInterest(getFoi.getLocation()));
return getFoiRequest;
}
private AbstractServiceRequest<?> parseGetObservationById(GetObservationByIdDocument getObsByIdDoc)
throws OwsExceptionReport {
GetObservationByIdRequest getObsByIdRequest = new GetObservationByIdRequest();
GetObservationById getObsById = getObsByIdDoc.getGetObservationById();
getObsByIdRequest.setService(getObsById.getService());
getObsByIdRequest.setVersion(getObsById.getVersion());
if (getObsById.isSetResponseFormat()) {
try {
String responseFormat = URLDecoder.decode(getObsById.getResponseFormat(), "UTF-8");
// parse responseFormat through MediaType to ensure it's a mime type and eliminate whitespace variations
getObsByIdRequest.setResponseFormat(MediaType.normalizeString(responseFormat));
} catch (UnsupportedEncodingException e) {
throw new NoApplicableCodeException().causedBy(e).withMessage("Error while decoding response format!");
}
} else {
getObsByIdRequest.setResponseFormat(OmConstants.CONTENT_TYPE_OM.toString());
}
Enum responseMode = getObsById.getResponseMode();
if (responseMode != null && responseMode.toString().equalsIgnoreCase(SosConstants.RESPONSE_MODE_INLINE)) {
getObsByIdRequest.setResponseMode(SosConstants.RESPONSE_MODE_INLINE);
}
if (getObsById.isSetResultModel()) {
getObsByIdRequest.setResultModel(OMHelper.getObservationTypeFor(getObsById.getResultModel()));
}
getObsByIdRequest.setObservationIdentifier(Arrays.asList(getObsById.getObservationId()));
return getObsByIdRequest;
}
/**
* Parses the spatial filter of a GetObservation request.
*
* @param spatialOpsType
* XmlBean representing the spatial filter parameter of the
* request
* @return Returns SpatialFilter created from the passed foi request
* parameter
*
*
* @throws OwsExceptionReport
* * if creation of the SpatialFilter failed
*/
@Deprecated
private SpatialFilter parseSpatialFilter4GetObservation(SpatialOpsType spatialOpsType)
throws OwsExceptionReport {
// if (spatialOpsType != null) {
// if (spatialOpsType.getObjectIDArray() != null && spatialOpsType.getObjectIDArray().length > 0) {
// throw new NoApplicableCodeException().withMessage("ObjectID filtering in featureOfInterest is "
// + "not supported. Only spatial filters are allowed.");
// }
// if (spatialOpsType.getSpatialOps() != null) {
// Object filter = CodingHelper.decodeXmlElement(spatialOpsType.getSpatialOps());
// if (filter instanceof SpatialFilter) {
// return (SpatialFilter) filter;
// }
// }
// }
return null;
}
/**
* Parses the spatial filters of a GetFeatureOfInterest request.
*
* @param location
* XmlBean representing the spatial filter parameter of the
* request
* @return Returns SpatialFilter created from the passed foi request
* parameter
*
*
* @throws OwsExceptionReport
* * if creation of the SpatialFilter failed
*/
private List<SpatialFilter> parseSpatialFilters4GetFeatureOfInterest(Location location) throws OwsExceptionReport {
List<SpatialFilter> sosSpatialFilters = new LinkedList<SpatialFilter>();
if (location != null && location.getSpatialOps() != null) {
Object filter = CodingHelper.decodeXmlElement(location.getSpatialOps());
if (filter instanceof SpatialFilter) {
sosSpatialFilters.add((SpatialFilter) filter);
}
}
return sosSpatialFilters;
}
/**
* parses the Time of the requests and returns an array representing the
* temporal filters
*
* @param temporalFilters
* array of XmlObjects representing the Time element in the
* request
* @return Returns array representing the temporal filters
*
*
* @throws OwsExceptionReport
* * if parsing of the element failed
*/
private List<TemporalFilter> parseTemporalFilters4GetObservation(GetObservation.EventTime[] temporalFilters)
throws OwsExceptionReport {
List<TemporalFilter> sosTemporalFilters = new LinkedList<TemporalFilter>();
for (GetObservation.EventTime temporalFilter : temporalFilters) {
Object filter = CodingHelper.decodeXmlElement(temporalFilter.getTemporalOps());
if (filter instanceof TemporalFilter) {
sosTemporalFilters.add((TemporalFilter) filter);
}
}
return sosTemporalFilters;
}
}