/**
* 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.encode;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.opengis.om.x20.OMObservationDocument;
import net.opengis.om.x20.OMObservationType;
import net.opengis.samplingSpatial.x20.ShapeType;
import net.opengis.waterml.x20.CollectionDocument;
import net.opengis.waterml.x20.CollectionType;
import net.opengis.waterml.x20.MonitoringPointDocument;
import net.opengis.waterml.x20.MonitoringPointType;
import net.opengis.waterml.x20.ObservationProcessDocument;
import net.opengis.waterml.x20.ObservationProcessType;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.joda.time.DateTime;
import org.n52.sos.coding.CodingRepository;
import org.n52.sos.exception.ows.NoApplicableCodeException;
import org.n52.sos.exception.ows.concrete.DateTimeFormatException;
import org.n52.sos.exception.ows.concrete.UnsupportedEncoderInputException;
import org.n52.sos.ogc.gml.AbstractFeature;
import org.n52.sos.ogc.gml.CodeType;
import org.n52.sos.ogc.gml.CodeWithAuthority;
import org.n52.sos.ogc.gml.GmlConstants;
import org.n52.sos.ogc.gml.ReferenceType;
import org.n52.sos.ogc.gml.time.Time;
import org.n52.sos.ogc.gml.time.TimeInstant;
import org.n52.sos.ogc.gml.time.TimePeriod;
import org.n52.sos.ogc.om.NamedValue;
import org.n52.sos.ogc.om.OmObservation;
import org.n52.sos.ogc.om.features.FeatureCollection;
import org.n52.sos.ogc.om.features.samplingFeatures.SamplingFeature;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.ogc.sos.Sos2Constants;
import org.n52.sos.ogc.sos.SosConstants;
import org.n52.sos.ogc.sos.SosConstants.HelperValues;
import org.n52.sos.ogc.wml.ObservationProcess;
import org.n52.sos.ogc.wml.WaterMLConstants;
import org.n52.sos.response.GetObservationResponse;
import org.n52.sos.util.CodingHelper;
import org.n52.sos.util.CollectionHelper;
import org.n52.sos.util.DateTimeHelper;
import org.n52.sos.util.JavaHelper;
import org.n52.sos.util.SosHelper;
import org.n52.sos.util.XmlHelper;
import org.n52.sos.util.XmlOptionsHelper;
import org.n52.sos.util.http.MediaType;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.vividsolutions.jts.geom.Geometry;
/**
* Abstract encoder class for WaterML 2.0
*
* @since 4.0.0
*/
public abstract class AbstractWmlEncoderv20 extends AbstractOmEncoderv20 implements ProcedureEncoder<XmlObject, Object> {
@SuppressWarnings("unchecked")
protected static final Set<EncoderKey> DEFAULT_ENCODER_KEYS = CollectionHelper.union(CodingHelper.encoderKeysForElements(
WaterMLConstants.NS_WML_20, AbstractFeature.class), CodingHelper.encoderKeysForElements(
WaterMLConstants.NS_WML_20_PROCEDURE_ENCODING, ObservationProcess.class));
private static final Map<String, ImmutableMap<String, Set<String>>> SUPPORTED_PROCEDURE_DESCRIPTION_FORMATS =
ImmutableMap.of(
SosConstants.SOS,
ImmutableMap
.<String, Set<String>> builder()
.put(Sos2Constants.SERVICEVERSION,
ImmutableSet.of(WaterMLConstants.NS_WML_20_PROCEDURE_ENCODING)).build());
protected static Set<EncoderKey> getDefaultEncoderKeys() {
return Collections.unmodifiableSet(DEFAULT_ENCODER_KEYS);
}
@Override
protected boolean convertEncodedProcedure() {
return true;
}
@Override
public boolean isObservationAndMeasurmentV20Type() {
return true;
}
@Override
public boolean shouldObservationsWithSameXBeMerged() {
return true;
}
@Override
public Set<String> getSupportedProcedureDescriptionFormats(String service, String version) {
if (SUPPORTED_PROCEDURE_DESCRIPTION_FORMATS.containsKey(service)
&& SUPPORTED_PROCEDURE_DESCRIPTION_FORMATS.get(service).containsKey(version)) {
return SUPPORTED_PROCEDURE_DESCRIPTION_FORMATS.get(service).get(version);
}
return Collections.emptySet();
}
@Override
public MediaType getContentType() {
return WaterMLConstants.WML_CONTENT_TYPE;
}
@Override
public XmlObject encode(Object element, Map<HelperValues, String> additionalValues) throws OwsExceptionReport {
if (element instanceof ObservationProcess) {
return createObservationProcess((ObservationProcess) element, additionalValues);
} else if (element instanceof AbstractFeature) {
return encodeAbstractFeature((AbstractFeature) element, additionalValues);
} else {
return super.encode(element, additionalValues);
}
}
private XmlObject encodeAbstractFeature(AbstractFeature abstractFeature, Map<HelperValues, String> additionalValues)
throws UnsupportedEncoderInputException, OwsExceptionReport {
if (abstractFeature instanceof OmObservation) {
return super.encode(abstractFeature, additionalValues);
} else {
return createMonitoringPoint(abstractFeature);
}
}
@Override
public String getDefaultFeatureEncodingNamespace() {
return WaterMLConstants.NS_WML_20;
}
@Override
protected String getDefaultProcedureEncodingNamspace() {
return WaterMLConstants.NS_WML_20_PROCEDURE_ENCODING;
}
@Override
public void addNamespacePrefixToMap(Map<String, String> nameSpacePrefixMap) {
super.addNamespacePrefixToMap(nameSpacePrefixMap);
nameSpacePrefixMap.put(WaterMLConstants.NS_WML_20, WaterMLConstants.NS_WML_20_PREFIX);
}
/**
* Encodes a SOS GetObservationResponse to a single WaterML 2.0 observation
* or to a WaterML 1.0 ObservationCollection
*
* @param getObservationResonse
* SOS GetObservationResponse
* @return Encoded response
* @throws OwsExceptionReport
* If an error occurs
*/
protected XmlObject createWmlGetObservationResponse(GetObservationResponse getObservationResonse)
throws OwsExceptionReport {
// TODO: set schemaLocation if final
Map<String, String> gmlID4sfIdentifier = Maps.newHashMap();
int sfIdCounter = 1;
if (getObservationResonse.getObservationCollection() != null
&& !getObservationResonse.getObservationCollection().isEmpty()) {
Collection<OmObservation> sosObservations = getObservationResonse.getObservationCollection();
if (sosObservations.size() == 1) {
OMObservationDocument omObservationDoc =
OMObservationDocument.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions());
for (OmObservation sosObservation : sosObservations) {
Map<HelperValues, String> foiHelper =
new EnumMap<SosConstants.HelperValues, String>(SosConstants.HelperValues.class);
String gmlId = "sf_" + sfIdCounter;
foiHelper.put(HelperValues.GMLID, gmlId);
omObservationDoc.setOMObservation((OMObservationType) createOmObservation(sosObservation,
foiHelper));
}
return omObservationDoc;
} else {
CollectionDocument xmlCollectionDoc =
CollectionDocument.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions());
CollectionType wmlCollection = xmlCollectionDoc.addNewCollection();
for (OmObservation sosObservation : sosObservations) {
Map<HelperValues, String> foiHelper =
new EnumMap<SosConstants.HelperValues, String>(SosConstants.HelperValues.class);
String gmlId;
// FIXME CodeWithAuthority VS. String keys
if (gmlID4sfIdentifier.containsKey(sosObservation.getObservationConstellation()
.getFeatureOfInterest().getIdentifierCodeWithAuthority())) {
gmlId =
gmlID4sfIdentifier.get(sosObservation.getObservationConstellation()
.getFeatureOfInterest().getIdentifierCodeWithAuthority());
foiHelper.put(HelperValues.EXIST_FOI_IN_DOC, Boolean.toString(true));
} else {
gmlId = "sf_" + sfIdCounter;
gmlID4sfIdentifier.put(sosObservation.getObservationConstellation().getFeatureOfInterest()
.getIdentifierCodeWithAuthority().getValue(), gmlId);
foiHelper.put(HelperValues.EXIST_FOI_IN_DOC, Boolean.toString(false));
}
foiHelper.put(HelperValues.GMLID, gmlId);
wmlCollection.addNewObservationMember().setOMObservation(
(OMObservationType) createOmObservation(sosObservation, foiHelper));
}
return xmlCollectionDoc;
}
} else {
// TODO: HydrologieProfile-Exception
throw new NoApplicableCodeException().withMessage("Combination does not exists!");
}
}
/**
* Creates a WaterML 2.0 MonitoringPoint XML object from SOS feature object
*
* @param absFeature
* SOS feature
* @return WaterML 2.0 MonitoringPoint XML object
* @throws OwsExceptionReport
* If an error occurs
*/
protected XmlObject createMonitoringPoint(AbstractFeature absFeature) throws OwsExceptionReport {
if (absFeature instanceof SamplingFeature) {
SamplingFeature sampFeat = (SamplingFeature) absFeature;
StringBuilder builder = new StringBuilder();
builder.append("mp_");
builder.append(JavaHelper.generateID(absFeature.getIdentifierCodeWithAuthority().getValue()));
absFeature.setGmlId(builder.toString());
MonitoringPointDocument monitoringPointDoc =
MonitoringPointDocument.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions());
if (sampFeat.getXmlDescription() != null) {
try {
XmlObject feature = XmlObject.Factory.parse(sampFeat.getXmlDescription());
if (XmlHelper.getNamespace(feature).equals(WaterMLConstants.NS_WML_20)) {
if (feature instanceof MonitoringPointDocument) {
monitoringPointDoc = (MonitoringPointDocument) feature;
} else if (feature instanceof MonitoringPointType) {
monitoringPointDoc.setSFSpatialSamplingFeature((MonitoringPointType) feature);
}
XmlHelper.updateGmlIDs(monitoringPointDoc.getDomNode(), absFeature.getGmlId(), null);
return monitoringPointDoc;
}
} catch (XmlException xmle) {
throw new NoApplicableCodeException()
.causedBy(xmle)
.withMessage(
"Error while encoding GetFeatureOfInterest response, invalid samplingFeature description!");
}
}
MonitoringPointType monitoringPoint = monitoringPointDoc.addNewMonitoringPoint();
// set gml:id
monitoringPoint.setId(absFeature.getGmlId());
if (sampFeat.isSetIdentifier()
&& SosHelper.checkFeatureOfInterestIdentifierForSosV2(sampFeat.getIdentifierCodeWithAuthority().getValue(),
Sos2Constants.SERVICEVERSION)) {
XmlObject xmlObject = CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, sampFeat.getIdentifierCodeWithAuthority());
if (xmlObject != null) {
monitoringPoint.addNewIdentifier().set(xmlObject);
}
}
if (sampFeat.isSetName()) {
for (CodeType sosName : sampFeat.getName()) {
monitoringPoint.addNewName().set(CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, sosName));
}
}
// set type
// TODO: check if special definition
// monitoringPoint.addNewType().setHref(sampFeat.getFeatureType());
// set sampledFeatures
// TODO: CHECK
if (sampFeat.getSampledFeatures() != null && !sampFeat.getSampledFeatures().isEmpty()) {
if (sampFeat.getSampledFeatures().size() == 1) {
XmlObject encodeObjectToXml =
CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, sampFeat.getSampledFeatures()
.get(0));
monitoringPoint.addNewSampledFeature().set(encodeObjectToXml);
} else {
FeatureCollection featureCollection = new FeatureCollection();
featureCollection.setGmlId("sampledFeatures_" + absFeature.getGmlId());
for (AbstractFeature sampledFeature : sampFeat.getSampledFeatures()) {
featureCollection.addMember(sampledFeature);
}
XmlObject encodeObjectToXml =
CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, featureCollection);
monitoringPoint.addNewSampledFeature().set(encodeObjectToXml);
}
} else {
monitoringPoint.addNewSampledFeature().setHref(GmlConstants.NIL_UNKNOWN);
}
if (sampFeat.isSetParameter()) {
addParameter(monitoringPoint, sampFeat);
}
// set position
ShapeType xbShape = monitoringPoint.addNewShape();
Encoder<XmlObject, Geometry> encoder =
CodingRepository.getInstance().getEncoder(
CodingHelper.getEncoderKey(GmlConstants.NS_GML_32, sampFeat.getGeometry()));
if (encoder != null) {
Map<HelperValues, String> gmlAdditionalValues = new EnumMap<HelperValues, String>(HelperValues.class);
gmlAdditionalValues.put(HelperValues.GMLID, absFeature.getGmlId());
XmlObject xmlObject = encoder.encode(sampFeat.getGeometry(), gmlAdditionalValues);
xbShape.addNewAbstractGeometry().set(xmlObject);
XmlHelper.substituteElement(xbShape.getAbstractGeometry(), xmlObject);
} else {
throw new NoApplicableCodeException()
.withMessage("Error while encoding geometry for feature, needed encoder is missing!");
}
return monitoringPointDoc;
}
throw new UnsupportedEncoderInputException(this, absFeature);
}
/**
* Creates an WaterML 2.0 ObservationProcess XML object from SOS
* ObservationProcess object
*
* @param procedure
* SOS ObservationProcess
* @param additionalValues
* Additional values
* @return WaterML 2.0 ObservationProcess XML object
* @throws OwsExceptionReport
* If an error occurs
*/
protected ObservationProcessDocument createObservationProcess(ObservationProcess procedure,
Map<HelperValues, String> additionalValues) throws OwsExceptionReport {
ObservationProcessDocument observationProcessDoc = ObservationProcessDocument.Factory.newInstance();
ObservationProcessType observationProcess = observationProcessDoc.addNewObservationProcess();
if (additionalValues.containsKey(HelperValues.GMLID)) {
observationProcess.setId("process." + additionalValues.get(HelperValues.GMLID));
} else {
observationProcess.setId("process." + JavaHelper.generateID(procedure.toString()));
}
if (procedure.isSetIdentifier()) {
CodeWithAuthority codeWithAuthority = procedure.getIdentifierCodeWithAuthority();
Encoder<?, CodeWithAuthority> encoder =
CodingRepository.getInstance().getEncoder(
CodingHelper.getEncoderKey(GmlConstants.NS_GML_32,codeWithAuthority));
if (encoder != null) {
XmlObject xmlObject = (XmlObject) encoder.encode(codeWithAuthority);
observationProcess.addNewIdentifier().set(xmlObject);
} else {
throw new NoApplicableCodeException()
.withMessage("Error while encoding geometry value, needed encoder is missing!");
}
}
if (procedure.isSetName()) {
for (final CodeType sosName : procedure.getName()) {
observationProcess.addNewName().set(CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, sosName));
}
}
addProcessType(observationProcess, procedure);
addOriginatingProcess(observationProcess, procedure);
addAggregatingDuration(observationProcess, procedure);
addVerticalDatum(observationProcess, procedure);
addComment(observationProcess, procedure);
addProcessReference(observationProcess, procedure);
addInput(observationProcess, procedure);
addParameter(observationProcess, procedure);
return observationProcessDoc;
}
/**
* Adds processType value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addProcessType(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetProcessType() && procedure.getProcessType().isSetHref()) {
XmlObject referenceType = encodeReferenceType(procedure.getProcessType());
if (referenceType != null) {
observationProcess.addNewProcessType().set(referenceType);
}
} else {
throw new NoApplicableCodeException().withMessage("Missing processType definition");
}
}
/**
* Adds OriginatingProcess value to WaterML 2.0 ObservationProcess XML
* object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addOriginatingProcess(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetOriginatingProcess()) {
XmlObject referenceType = encodeReferenceType(procedure.getOriginatingProcess());
if (referenceType != null) {
observationProcess.addNewOriginatingProcess().set(referenceType);
}
}
}
/**
* Adds AggregatingDuration value to WaterML 2.0 ObservationProcess XML
* object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
*/
private void addAggregatingDuration(ObservationProcessType observationProcess, ObservationProcess procedure) {
}
/**
* Adds VerticalDatum value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addVerticalDatum(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetVerticalDatum()) {
XmlObject referenceType = encodeReferenceType(procedure.getVerticalDatum());
if (referenceType != null) {
observationProcess.addNewVerticalDatum().set(referenceType);
}
}
}
/**
* Adds Comment value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
*/
private void addComment(ObservationProcessType observationProcess, ObservationProcess procedure) {
if (procedure.isSetComments()) {
for (String comment : procedure.getComments()) {
if (comment != null && !comment.isEmpty()) {
observationProcess.addComment(comment);
}
}
}
}
/**
* Adds ProcessReference value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addProcessReference(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetProcessReference()) {
XmlObject referenceType = encodeReferenceType(procedure.getProcessReference());
if (referenceType != null) {
observationProcess.addNewProcessReference().set(referenceType);
}
}
}
/**
* Adds Input value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addInput(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetInputs()) {
for (org.n52.sos.ogc.gml.ReferenceType sosReferenceType : procedure.getInputs()) {
XmlObject referenceType = encodeReferenceType(sosReferenceType);
if (referenceType != null) {
observationProcess.addNewInput().set(referenceType);
}
}
}
}
/**
* Adds Parameter value to WaterML 2.0 ObservationProcess XML object
*
* @param observationProcess
* WaterML 2.0 ObservationProcess XML object
* @param procedure
* SOS ObservationProcess
* @throws OwsExceptionReport
* If an error occurs
*/
private void addParameter(ObservationProcessType observationProcess, ObservationProcess procedure)
throws OwsExceptionReport {
if (procedure.isSetParameters()) {
List<NamedValue<?>> parameters = procedure.getParameters();
for (NamedValue<?> sosNamedValue : parameters) {
XmlObject namedValue = createNamedValue(sosNamedValue);
if (namedValue != null) {
observationProcess.addNewParameter().addNewNamedValue().set(namedValue);
}
}
}
}
/**
* Creates a XML ReferenceType object from SOS ReferenceType object
*
* @param sosReferenceType
* SOS ReferenceType object
* @return XML ReferenceType object
* @throws OwsExceptionReport
* If an error occurs
*/
private XmlObject encodeReferenceType(ReferenceType sosReferenceType) throws OwsExceptionReport {
Encoder<XmlObject, ReferenceType> encoder =
CodingRepository.getInstance().getEncoder(
CodingHelper.getEncoderKey(GmlConstants.NS_GML_32, sosReferenceType));
if (encoder != null) {
return encoder.encode(sosReferenceType);
} else {
throw new NoApplicableCodeException()
.withMessage("Error while encoding referenceType, needed encoder is missing!");
}
}
/**
* Adds parameter values to WaterML 2.0 XML MonitoringPoint object from
* SosSamplingFeature
*
* @param monitoringPoint
* WaterML 2.0 XML MonitoringPoint object
* @param sampFeat
* SosSamplingFeature
* @throws OwsExceptionReport
* If an error occurs
*/
private void addParameter(MonitoringPointType monitoringPoint, SamplingFeature sampFeat) throws OwsExceptionReport {
for (NamedValue<?> namedValue : sampFeat.getParameters()) {
XmlObject encodeObjectToXml = createNamedValue(namedValue);
if (encodeObjectToXml != null) {
monitoringPoint.addNewParameter().addNewNamedValue().set(encodeObjectToXml);
}
}
}
/**
* Parses the ITime object to a time representation as String
*
* @param time
* SOS ITime object
* @return Time as String
* @throws DateTimeFormatException
* If a formatting error occurs
*/
protected String getTimeString(Time time) throws DateTimeFormatException {
DateTime dateTime = getTime(time);
return DateTimeHelper.formatDateTime2String(dateTime, time.getTimeFormat());
}
/**
* Get the time representation from ITime object
*
* @param time
* ITime object
* @return Time as DateTime
*/
private DateTime getTime(Time time) {
if (time instanceof TimeInstant) {
return ((TimeInstant) time).getValue();
} else if (time instanceof TimePeriod) {
TimePeriod timePeriod = (TimePeriod) time;
if (timePeriod.getEnd() != null) {
return timePeriod.getEnd();
} else {
return timePeriod.getStart();
}
}
return new DateTime().minusYears(1000);
}
}