/* * Copyright 2015-2020 52°North Initiative for Geospatial Open Source * Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.n52.svalbard.encode; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import javax.inject.Inject; import javax.xml.stream.XMLStreamException; import org.apache.xmlbeans.XmlBoolean; import org.apache.xmlbeans.XmlInteger; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.XmlString; import org.n52.janmayen.http.MediaType; import org.n52.shetland.ogc.SupportedType; import org.n52.shetland.ogc.gml.AbstractFeature; import org.n52.shetland.ogc.om.AbstractObservationValue; import org.n52.shetland.ogc.om.MultiObservationValues; import org.n52.shetland.ogc.om.NamedValue; import org.n52.shetland.ogc.om.ObservationValue; import org.n52.shetland.ogc.om.OmConstants; import org.n52.shetland.ogc.om.OmObservation; import org.n52.shetland.ogc.om.OmObservationConstellation; import org.n52.shetland.ogc.om.SingleObservationValue; import org.n52.shetland.ogc.om.series.wml.WaterMLConstants; import org.n52.shetland.ogc.om.values.BooleanValue; import org.n52.shetland.ogc.om.values.CategoryValue; import org.n52.shetland.ogc.om.values.ComplexValue; import org.n52.shetland.ogc.om.values.CountValue; import org.n52.shetland.ogc.om.values.CvDiscretePointCoverage; import org.n52.shetland.ogc.om.values.GeometryValue; import org.n52.shetland.ogc.om.values.HrefAttributeValue; import org.n52.shetland.ogc.om.values.MultiPointCoverage; import org.n52.shetland.ogc.om.values.NilTemplateValue; import org.n52.shetland.ogc.om.values.ProfileValue; import org.n52.shetland.ogc.om.values.QuantityRangeValue; import org.n52.shetland.ogc.om.values.QuantityValue; import org.n52.shetland.ogc.om.values.RectifiedGridCoverage; import org.n52.shetland.ogc.om.values.ReferenceValue; import org.n52.shetland.ogc.om.values.SweDataArrayValue; import org.n52.shetland.ogc.om.values.TLVTValue; import org.n52.shetland.ogc.om.values.TVPValue; import org.n52.shetland.ogc.om.values.TextValue; import org.n52.shetland.ogc.om.values.TimeRangeValue; import org.n52.shetland.ogc.om.values.UnknownValue; import org.n52.shetland.ogc.om.values.XmlValue; import org.n52.shetland.ogc.om.values.visitor.ValueVisitor; import org.n52.shetland.ogc.sensorML.SensorMLConstants; import org.n52.shetland.ogc.sos.Sos2Constants; import org.n52.shetland.ogc.sos.SosConstants; import org.n52.shetland.ogc.swe.SweConstants; import org.n52.shetland.ogc.swe.SweDataArray; import org.n52.shetland.util.OMHelper; import org.n52.shetland.w3c.Nillable; import org.n52.shetland.w3c.SchemaLocation; import org.n52.svalbard.ConformanceClasses; import org.n52.svalbard.encode.exception.EncodingException; import org.n52.svalbard.encode.exception.UnsupportedEncoderInputException; import org.n52.svalbard.util.CodingHelper; import org.n52.svalbard.util.SweHelper; import org.n52.svalbard.write.OmV20XmlStreamWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Sets; import net.opengis.gml.x32.FeaturePropertyType; import net.opengis.gml.x32.TimeInstantPropertyType; import net.opengis.om.x20.OMObservationType; import net.opengis.om.x20.OMProcessPropertyType; import net.opengis.om.x20.TimeObjectPropertyType; /** * @since 1.0.0 * */ public class OmEncoderv20 extends AbstractOmEncoderv20 { private static final String NIL_REASON_TEMPLATE = "template"; private static final String OBSERVATION_GML_ID_TEMPLATE = NIL_REASON_TEMPLATE; private static final Logger LOGGER = LoggerFactory.getLogger(OmEncoderv20.class); private static final Set<EncoderKey> ENCODER_KEYS = CodingHelper.encoderKeysForElements(OmConstants.NS_OM_2, MultiObservationValues.class, NamedValue.class, SingleObservationValue.class, OmObservation.class, OmObservationConstellation.class); // TODO: change to correct conformance class private static final Set<String> CONFORMANCE_CLASSES = new HashSet<>(Arrays .asList(ConformanceClasses.OM_V2_MEASUREMENT, ConformanceClasses.OM_V2_CATEGORY_OBSERVATION, ConformanceClasses.OM_V2_COUNT_OBSERVATION, ConformanceClasses.OM_V2_TRUTH_OBSERVATION, ConformanceClasses.OM_V2_GEOMETRY_OBSERVATION, ConformanceClasses.OM_V2_TEXT_OBSERVATION, ConformanceClasses.OM_V2_COMPLEX_OBSERVATION)); private static final Set<SupportedType> SUPPORTED_TYPES = new HashSet<>(Arrays .asList(OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION_TYPE, OmConstants.OBS_TYPE_COMPLEX_OBSERVATION_TYPE, OmConstants.OBS_TYPE_GEOMETRY_OBSERVATION_TYPE, OmConstants.OBS_TYPE_CATEGORY_OBSERVATION_TYPE, OmConstants.OBS_TYPE_COUNT_OBSERVATION_TYPE, OmConstants.OBS_TYPE_MEASUREMENT_TYPE, OmConstants.OBS_TYPE_TEXT_OBSERVATION_TYPE, OmConstants.OBS_TYPE_TRUTH_OBSERVATION_TYPE, OmConstants.OBS_TYPE_REFERENCE_OBSERVATION_TYPE)); private static final Map<String, Map<String, Set<String>>> SUPPORTED_RESPONSE_FORMATS = Collections.singletonMap( SosConstants.SOS, Collections.singletonMap(Sos2Constants.SERVICEVERSION, Collections.singleton(OmConstants.NS_OM_2))); private SweHelper sweHelper; public OmEncoderv20() { LOGGER.debug("Encoder for the following keys initialized successfully: {}!", Joiner.on(", ").join(ENCODER_KEYS)); } @Inject public void setSweHelper(SweHelper sweHelper) { this.sweHelper = sweHelper; } @Override public Set<EncoderKey> getKeys() { return Collections.unmodifiableSet(ENCODER_KEYS); } @Override public Set<SupportedType> getSupportedTypes() { return Collections.unmodifiableSet(SUPPORTED_TYPES); } @Override public Map<String, Set<SupportedType>> getSupportedResponseFormatObservationTypes() { return Collections.singletonMap(OmConstants.NS_OM_2, getSupportedTypes()); } @Override public Set<String> getConformanceClasses(String service, String version) { if (SosConstants.SOS.equals(service) && Sos2Constants.SERVICEVERSION.equals(version)) { return Collections.unmodifiableSet(CONFORMANCE_CLASSES); } return Collections.emptySet(); } @Override public boolean isObservationAndMeasurmentV20Type() { return true; } @Override public Set<String> getSupportedResponseFormats(String service, String version) { if (SUPPORTED_RESPONSE_FORMATS.get(service) != null && SUPPORTED_RESPONSE_FORMATS.get(service).get(version) != null) { return SUPPORTED_RESPONSE_FORMATS.get(service).get(version); } return Collections.emptySet(); } @Override public boolean shouldObservationsWithSameXBeMerged() { return false; } @Override public boolean supportsResultStreamingForMergedValues() { return false; } @Override public String getDefaultFeatureEncodingNamespace() { return null; } @Override public MediaType getContentType() { return OmConstants.CONTENT_TYPE_OM_2; } @Override public Set<SchemaLocation> getSchemaLocations() { return Sets.newHashSet(OmConstants.OM_20_SCHEMA_LOCATION); } @Override public XmlObject encode(Object element, EncodingContext additionalValues) throws EncodingException { if (element instanceof ObservationValue) { return encodeResult((ObservationValue<?>) element); } else if (element instanceof OmObservationConstellation) { return encodeObservationTemplate((OmObservationConstellation) element); } return super.encode(element, additionalValues); } @Override public void encode(Object objectToEncode, OutputStream outputStream, EncodingContext ctx) throws EncodingException { if (objectToEncode instanceof OmObservation) { try { new OmV20XmlStreamWriter( ctx.with(EncoderFlags.ENCODER_REPOSITORY, getEncoderRepository()) .with(XmlEncoderFlags.XML_OPTIONS, (Supplier<XmlOptions>) this::getXmlOptions), outputStream, (OmObservation) objectToEncode).write(); } catch (XMLStreamException xmlse) { throw new EncodingException("Error while writing element to stream!", xmlse); } } else { super.encode(objectToEncode, ctx); } } @Override protected OMObservationType createOmObservationType() { return OMObservationType.Factory.newInstance(getXmlOptions()); } @Override protected XmlObject createResult(OmObservation sosObservation) throws EncodingException { ObservationValue<?> value = sosObservation.getValue(); // TODO if OM_SWEArrayObservation and ResultEncoding and ResultStructure exists, if (value instanceof AbstractObservationValue) { AbstractObservationValue<?> abstractObservationValue = (AbstractObservationValue<?>) value; abstractObservationValue.setValuesForResultEncoding(sosObservation); return encodeResult(abstractObservationValue); } return null; } @Override protected XmlObject encodeResult(ObservationValue<?> observationValue) throws EncodingException { if (observationValue instanceof SingleObservationValue) { return createSingleObservationToResult((SingleObservationValue<?>) observationValue); } else if (observationValue instanceof MultiObservationValues) { return createMultiObservationValueToResult((MultiObservationValues<?>) observationValue); } return null; } @Override protected void addObservationType(OMObservationType xbObservation, String observationType) { if (!Strings.isNullOrEmpty(observationType)) { xbObservation.addNewType().setHref(observationType); } } // @Override // public String getDefaultFeatureEncodingNamespace() { // return SfConstants.NS_SAMS; // } @Override protected String getDefaultProcedureEncodingNamspace() { return SensorMLConstants.NS_SML; } @Override protected boolean convertEncodedProcedure() { return false; } @Override protected void addAddtitionalInformation(OMObservationType omot, OmObservation observation) throws EncodingException { // do nothing } private OMObservationType encodeObservationTemplate(OmObservationConstellation observationTemplate) throws EncodingException { validateInput(observationTemplate); OMObservationType xbObservationTemplate = createOmObservationType(); addGmlId(xbObservationTemplate); addObservationType(xbObservationTemplate, observationTemplate.getObservationType()); addNilPhenomenonTime(xbObservationTemplate); addNilResultTime(xbObservationTemplate); addProcedure(xbObservationTemplate, observationTemplate.getNillableProcedure()); addObservedProperty(xbObservationTemplate, observationTemplate.getObservablePropertyIdentifier()); addFeature(xbObservationTemplate, observationTemplate.getNillableFeatureOfInterest()); addResult(xbObservationTemplate); return xbObservationTemplate; } private void addResult(OMObservationType xbObservationTemplate) { xbObservationTemplate.addNewResult(); } private void addGmlId(OMObservationType xbObservationTemplate) { xbObservationTemplate.setId(OBSERVATION_GML_ID_TEMPLATE); } private void addObservedProperty(OMObservationType xbObservationTemplate, String observablePropertyIdentifier) { xbObservationTemplate.addNewObservedProperty().setHref(observablePropertyIdentifier); } private void addFeature(OMObservationType xbObservationTemplate, Nillable<AbstractFeature> featureOfInterest) throws EncodingException { FeaturePropertyType xbFeatureOfInterest = xbObservationTemplate.addNewFeatureOfInterest(); if (featureOfInterest.isNil() || featureOfInterest.isAbsent()) { xbFeatureOfInterest.setNilReason(NIL_REASON_TEMPLATE); } else { XmlObject xbEncodedFeature = encodeObjectToXmlPropertyType( featureOfInterest.get().getDefaultElementEncoding(), featureOfInterest.get(), EncodingContext.empty()); xbFeatureOfInterest.set(xbEncodedFeature); } } private void addProcedure(OMObservationType xbObservationTemplate, Nillable<AbstractFeature> procedure) { OMProcessPropertyType xbProcedure = xbObservationTemplate.addNewProcedure(); if (procedure.isNil() || procedure.isAbsent() || !procedure.get().isSetIdentifier()) { xbProcedure.setNil(); xbProcedure.setNilReason(NIL_REASON_TEMPLATE); } else { xbProcedure.setHref(procedure.get().getIdentifier()); } } private void validateInput(OmObservationConstellation observationTemplate) throws UnsupportedEncoderInputException { if (!observationTemplate.isSetObservationType() || observationTemplate.getObservationType().isEmpty()) { throw new UnsupportedEncoderInputException(this, "missing type in OM_Observation"); } } private void addNilResultTime(OMObservationType xbObservationTemplate) { TimeInstantPropertyType xbResultTime = xbObservationTemplate.addNewResultTime(); xbResultTime.setNilReason(NIL_REASON_TEMPLATE); } private void addNilPhenomenonTime(OMObservationType xbObservationTemplate) { TimeObjectPropertyType xbPhenomenonTime = xbObservationTemplate.addNewPhenomenonTime(); xbPhenomenonTime.setNilReason(NIL_REASON_TEMPLATE); } private XmlObject createSingleObservationToResult(final SingleObservationValue<?> observationValue) throws EncodingException { final String observationType; if (observationValue.isSetObservationType()) { observationType = observationValue.getObservationType(); } else { observationType = OMHelper.getObservationTypeFor(observationValue.getValue()); } if (observationType.equals(OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION)) { SweDataArray dataArray = sweHelper.createSosSweDataArray(observationValue); return encodeSWE(dataArray, EncodingContext.of(XmlBeansEncodingFlags.DOCUMENT)); } return observationValue.getValue() .accept(new ResultValueVisitor(observationType, observationValue.getObservationID())); } private XmlObject createMultiObservationValueToResult(MultiObservationValues<?> observationValue) throws EncodingException { SweDataArray dataArray = sweHelper.createSosSweDataArray(observationValue); return encodeObjectToXml(SweConstants.NS_SWE_20, dataArray, EncodingContext.of(XmlBeansEncodingFlags.DOCUMENT)); } protected XmlString createXmlString() { return XmlString.Factory.newInstance(getXmlOptions()); } protected XmlInteger createXmlInteger() { return XmlInteger.Factory.newInstance(getXmlOptions()); } protected XmlBoolean createXmlBoolean() { return XmlBoolean.Factory.newInstance(getXmlOptions()); } protected XmlObject encodeSWE(Object o) throws EncodingException { return encodeObjectToXml(SweConstants.NS_SWE_20, o); } protected XmlObject encodeSWE(Object o, EncodingContext additionalValues) throws EncodingException { return encodeObjectToXml(SweConstants.NS_SWE_20, o, additionalValues); } private class ResultValueVisitor implements ValueVisitor<XmlObject, EncodingException> { private final String observationType; private final String observationId; ResultValueVisitor(String observationType, String observationId) { this.observationType = observationType; this.observationId = observationId; } @Override public XmlObject visit(BooleanValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_TRUTH_OBSERVATION)) { XmlBoolean xbBoolean = createXmlBoolean(); if (value.isSetValue()) { xbBoolean.setBooleanValue(value.getValue()); } else { xbBoolean.setNil(); } return xbBoolean; } else { return null; } } @Override public XmlObject visit(CategoryValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_CATEGORY_OBSERVATION)) { if (value.isSetValue() && !value.getValue().isEmpty()) { return encodeGML(value, EncodingContext.of(XmlBeansEncodingFlags.GMLID, SosConstants.OBS_ID_PREFIX + observationId)); } } return null; } @Override public XmlObject visit(ComplexValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_COMPLEX_OBSERVATION)) { if (value.isSetValue()) { return encodeSWE(value.getValue(), EncodingContext.of(XmlBeansEncodingFlags.FOR_OBSERVATION)); } } return null; } @Override public XmlObject visit(CountValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_COUNT_OBSERVATION)) { XmlInteger xbInteger = createXmlInteger(); if (value.isSetValue() && value.getValue() != Integer.MIN_VALUE) { xbInteger.setBigIntegerValue(new BigInteger(value.getValue().toString())); } else { xbInteger.setNil(); } return xbInteger; } else { return null; } } @Override public XmlObject visit(GeometryValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_GEOMETRY_OBSERVATION)) { if (value.isSetValue()) { return encodeGML(value.getValue(), EncodingContext.empty() .with(XmlBeansEncodingFlags.GMLID, SosConstants.OBS_ID_PREFIX + observationId) .with(XmlBeansEncodingFlags.PROPERTY_TYPE)); } else { return null; } } else { return null; } } @Override public XmlObject visit(HrefAttributeValue value) throws EncodingException { return null; } @Override public XmlObject visit(NilTemplateValue value) throws EncodingException { return null; } @Override public XmlObject visit(QuantityValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_MEASUREMENT) || observationType.equals(WaterMLConstants.OBSERVATION_TYPE_MEASURMENT_TVP) || observationType.equals(WaterMLConstants.OBSERVATION_TYPE_MEASURMENT_TDR)) { if (value.isSetValue()) { return encodeGML(value); } } return null; } @Override public XmlObject visit(QuantityRangeValue value) throws EncodingException { return null; } @Override public XmlObject visit(ReferenceValue value) throws EncodingException { if (value.isSetValue()) { return encodeGML(value.getValue()); } return null; } @Override public XmlObject visit(SweDataArrayValue value) throws EncodingException { return encodeSWE(value.getValue(), EncodingContext.of(XmlBeansEncodingFlags.FOR_OBSERVATION)); } @Override public XmlObject visit(TVPValue value) throws EncodingException { return null; } @Override public XmlObject visit(TextValue value) throws EncodingException { if (observationType.equals(OmConstants.OBS_TYPE_TEXT_OBSERVATION)) { XmlString xbString = createXmlString(); if (value.isSetValue()) { xbString.setStringValue(value.getValue()); } else { xbString.setNil(); } return xbString; } else { return null; } } @Override public XmlObject visit(UnknownValue value) throws EncodingException { return null; } @Override public XmlObject visit(TLVTValue value) throws EncodingException { return null; } @Override public XmlObject visit(CvDiscretePointCoverage value) throws EncodingException { return null; } @Override public XmlObject visit(MultiPointCoverage value) throws EncodingException { return null; } @Override public XmlObject visit(RectifiedGridCoverage value) throws EncodingException { return null; } @Override public XmlObject visit(ProfileValue value) throws EncodingException { return encodeGWML(value, EncodingContext.of(XmlBeansEncodingFlags.FOR_OBSERVATION)); } @Override public XmlObject visit(TimeRangeValue value) throws EncodingException { return null; } @Override public XmlObject visit(XmlValue<?> value) throws EncodingException { if (value.getValue() instanceof XmlObject) { return (XmlObject) value.getValue(); } return null; } } }