/** * 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.streaming; import java.io.OutputStream; import java.util.EnumMap; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import net.opengis.om.x20.OMObservationType; import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlObject; import org.joda.time.DateTime; import org.n52.sos.coding.CodingRepository; import org.n52.sos.convert.Converter; import org.n52.sos.convert.ConverterException; import org.n52.sos.convert.ConverterRepository; import org.n52.sos.encode.AbstractOmEncoderv20; import org.n52.sos.encode.Encoder; import org.n52.sos.encode.EncoderKey; import org.n52.sos.encode.EncodingValues; import org.n52.sos.encode.ObservationEncoder; import org.n52.sos.encode.XmlEncoderKey; import org.n52.sos.encode.XmlStreamWriter; 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.CodeWithAuthority; import org.n52.sos.ogc.gml.GmlConstants; 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.AbstractObservationValue; import org.n52.sos.ogc.om.NamedValue; import org.n52.sos.ogc.om.OmConstants; import org.n52.sos.ogc.om.OmObservation; import org.n52.sos.ogc.ows.OwsExceptionReport; import org.n52.sos.ogc.sos.SosConstants; import org.n52.sos.ogc.sos.SosProcedureDescription; import org.n52.sos.ogc.sos.SosConstants.HelperValues; import org.n52.sos.service.Configurator; import org.n52.sos.service.ServiceConfiguration; import org.n52.sos.service.profile.Profile; import org.n52.sos.util.CodingHelper; import org.n52.sos.util.Constants; import org.n52.sos.util.DateTimeHelper; import org.n52.sos.util.GmlHelper; import org.n52.sos.util.JavaHelper; import org.n52.sos.util.StringHelper; import org.n52.sos.util.XmlOptionsHelper; import org.n52.sos.w3c.W3CConstants; import com.google.common.base.Strings; import com.google.common.collect.Maps; /** * Abstract implementation of {@link XmlStreamWriter} for writing * {@link OmObservation}s to stream * * @author Carsten Hollmann <c.hollmann@52north.org> * @since 4.1.0 * */ public abstract class AbstractOmV20XmlStreamWriter extends XmlStreamWriter<OmObservation> { private OmObservation observation; /** * constructor */ public AbstractOmV20XmlStreamWriter() { } /** * constructor * * @param observation * {@link OmObservation} to write to stream */ public AbstractOmV20XmlStreamWriter(OmObservation observation) { setOmObservation(observation); } @Override public void write(OutputStream out) throws XMLStreamException, OwsExceptionReport { write(getOmObservation(), out); } @Override public void write(OutputStream out, EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { write(getOmObservation(), out, encodingValues); } @Override public void write(OmObservation response, OutputStream out) throws XMLStreamException, OwsExceptionReport { write(response, out, new EncodingValues()); } @Override public void write(OmObservation observation, OutputStream out, EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { try { setOmObservation(observation); init(out, encodingValues); start(encodingValues.isEmbedded()); writeOmObservationDoc(encodingValues); end(); finish(); } catch (XMLStreamException xmlse) { throw new NoApplicableCodeException().causedBy(xmlse); } } /** * Write {@link OmObservation} XML encoded to stream * * @param encodingValues * {@link EncodingValues} contains additional information for the * encoding * @throws XMLStreamException * If an error occurs when writing to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written If an * error occurs when creating elements to be written */ protected void writeOmObservationDoc(EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { start(OmConstants.QN_OM_20_OBSERVATION); namespace(W3CConstants.NS_XLINK_PREFIX, W3CConstants.NS_XLINK); namespace(OmConstants.NS_OM_PREFIX, OmConstants.NS_OM_2); namespace(GmlConstants.NS_GML_PREFIX, GmlConstants.NS_GML_32); String observationID = addGmlId(observation); writeNewLine(); if (observation.isSetIdentifier()) { writeIdentifier(observation.getIdentifierCodeWithAuthority()); writeNewLine(); } if (observation.isSetDescription()) { writeDescription(observation.getDescription()); writeNewLine(); } if (observation.getObservationConstellation().isSetObservationType()) { writeObservationType(observation.getObservationConstellation().getObservationType()); writeNewLine(); } Time phenomenonTime = observation.getPhenomenonTime(); if (phenomenonTime.getGmlId() == null) { phenomenonTime.setGmlId(OmConstants.PHENOMENON_TIME_NAME + "_" + observationID); } writePhenomenonTime(phenomenonTime); writeNewLine(); writeResultTime(); writeNewLine(); writeProcedure(encodingValues); writeNewLine(); if (observation.isSetParameter()) { writeParameter(encodingValues); } writeObservableProperty(); writeNewLine(); writeFeatureOfIntererst(encodingValues); writeNewLine(); writeResult(observation, encodingValues); writeNewLine(); indent--; end(OmConstants.QN_OM_20_OBSERVATION); indent++; } /** * Write {@link CodeWithAuthority} as gml:identifier to stream * * @param identifier * {@link CodeWithAuthority} to write * @throws OwsExceptionReport * If an error occurs when creating elements to be written * @throws XMLStreamException * If an error occurs when writing to stream */ protected void writeIdentifier(CodeWithAuthority identifier) throws OwsExceptionReport, XMLStreamException { Encoder<?, CodeWithAuthority> encoder = CodingRepository.getInstance().getEncoder( CodingHelper.getEncoderKey(GmlConstants.NS_GML_32, identifier)); if (encoder != null) { writeXmlObject((XmlObject) encoder.encode(identifier), GmlConstants.QN_IDENTIFIER_32); } else { throw new NoApplicableCodeException() .withMessage("Error while encoding geometry value, needed encoder is missing!"); } } /** * Write description as gml:descritpion to stream * * @param description * Description to write * @throws XMLStreamException * If an error occurs when writing to stream */ protected void writeDescription(String description) throws XMLStreamException { start(GmlConstants.QN_DESCRIPTION_32); chars(description); endInline(GmlConstants.QN_DESCRIPTION_32); } /** * Write observation typ as om:type to stream * * @param observationType * Observation type to write * @throws XMLStreamException * If an error occurs when writing to stream */ protected void writeObservationType(String observationType) throws XMLStreamException { empty(OmConstants.QN_OM_20_OBSERVATION_TYPE); addXlinkHrefAttr(observationType); } /** * Write {@link Time} as om:phenomenonTime to stream * * @param time * {@link Time} to write as om:phenomenonTime to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written * @throws XMLStreamException * If an error occurs when writing to stream */ protected void writePhenomenonTime(Time time) throws OwsExceptionReport, XMLStreamException { start(OmConstants.QN_OM_20_PHENOMENON_TIME); writeNewLine(); writePhenomenonTimeContent(time); writeNewLine(); indent--; end(OmConstants.QN_OM_20_PHENOMENON_TIME); indent++; } /** * Write om:resultTime to stream * * @throws XMLStreamException * If an error occurs when writing to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written */ protected void writeResultTime() throws XMLStreamException, OwsExceptionReport { TimeInstant resultTime = observation.getResultTime(); Time phenomenonTime = observation.getPhenomenonTime(); // get result time from SOS result time representation if (observation.getResultTime() != null) { if (resultTime.equals(phenomenonTime)) { empty(OmConstants.QN_OM_20_RESULT_TIME); addXlinkHrefAttr(Constants.NUMBER_SIGN_STRING + phenomenonTime.getGmlId()); } else { addResultTime(resultTime); } } // if result time is not set, get result time from phenomenon time // representation else { if (phenomenonTime instanceof TimeInstant) { empty(OmConstants.QN_OM_20_RESULT_TIME); addXlinkHrefAttr(Constants.NUMBER_SIGN_STRING + phenomenonTime.getGmlId()); } else if (phenomenonTime instanceof TimePeriod) { TimeInstant rsTime = new TimeInstant(((TimePeriod) observation.getPhenomenonTime()).getEnd()); addResultTime(rsTime); } } } /** * Write om:procedure encoded or as xlink:href to stream * * @param encodingValues * {@link EncodingValues} contains the required encoder * @throws XMLStreamException * If an error occurs when writing to stream * @throws UnsupportedEncoderInputException * If the procedure could not be encoded * @throws OwsExceptionReport * If an error occurs when creating elements to be written */ @SuppressWarnings("unchecked") protected void writeProcedure(EncodingValues encodingValues) throws XMLStreamException, UnsupportedEncoderInputException, OwsExceptionReport { // if (encodingValues.isSetEncoder() && checkEncodProcedureForEncoderKeys(encodingValues.getEncoder())) { // SosProcedureDescription procedureToEncode = observation // .getObservationConstellation().getProcedure(); // // should the procedure be converted // if (procedureToEncode.getDescriptionFormat().equals(anObject)) { // Converter<SosProcedureDescription, SosProcedureDescription> converter = // ConverterRepository.getInstance().getConverter(procedureDescription.getDescriptionFormat(), // getDefaultProcedureEncodingNamspace()); // if (converter != null) { // try { // procedureToEncode = converter.convert(procedureDescription); // } catch (ConverterException e) { // throw new NoApplicableCodeException().causedBy(e).withMessage( // "Error while converting procedureDescription!"); // } // } else { // throw new NoApplicableCodeException().withMessage("No converter (%s -> %s) found!", // procedureDescription.getDescriptionFormat(), getDefaultProcedureEncodingNamspace()); // } // } else { // procedureToEncode = procedureDescription; // } // // encode procedure or add reference // XmlObject encodedProcedure = // CodingHelper.encodeObjectToXml(procedureToEncode.getDescriptionFormat(), procedureToEncode); // if (encodedProcedure != null) { // writeXmlObject(encodedProcedure, OmConstants.QN_OM_20_PROCEDURE); // } else { // empty(OmConstants.QN_OM_20_PROCEDURE); // addXlinkHrefAttr(observation.getObservationConstellation().getProcedure().getIdentifier()); // } // } else { empty(OmConstants.QN_OM_20_PROCEDURE); addXlinkHrefAttr(observation.getObservationConstellation().getProcedure().getIdentifier()); if (observation.getObservationConstellation().getProcedure().isSetName() && observation.getObservationConstellation().getProcedure().getFirstName().isSetValue()) { addXlinkTitleAttr(observation.getObservationConstellation().getProcedure().getFirstName().getValue()); } // } // if (encodingValues.isSetEncoder() && encodingValues.getEncoder() instanceof ObservationEncoder) { // XmlObject xmlObject = // ((ObservationEncoder<XmlObject, Object>) encodingValues.getEncoder()).encode(observation // .getObservationConstellation().getProcedure(), null); // writeXmlObject(xmlObject, OmConstants.QN_OM_20_PROCEDURE); // } else { // empty(OmConstants.QN_OM_20_PROCEDURE); // addXlinkHrefAttr(observation.getObservationConstellation().getProcedure().getIdentifier()); // } } /** * Write om:parameter to stream * * @param encodingValues * {@link EncodingValues} contains the required encoder * @throws XMLStreamException * If an error occurs when writing to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written */ @SuppressWarnings("unchecked") protected void writeParameter(EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { if (encodingValues.isSetEncoder() && encodingValues.getEncoder() instanceof ObservationEncoder) { for (NamedValue<?> namedValue : observation.getParameter()) { start(OmConstants.QN_OM_20_PARAMETER); writeNewLine(); XmlObject xmlObject = ((ObservationEncoder<XmlObject, Object>) encodingValues.getEncoder()).encode(namedValue); writeXmlObject(xmlObject, OmConstants.QN_OM_20_NAMED_VALUE); writeNewLine(); indent--; end(OmConstants.QN_OM_20_PARAMETER); writeNewLine(); indent++; } } } /** * Write om:observedProperty to stream * * @throws XMLStreamException * If an error occurs when writing to stream */ protected void writeObservableProperty() throws XMLStreamException { empty(OmConstants.QN_OM_20_OBSERVED_PROPERTY); addXlinkHrefAttr(observation.getObservationConstellation().getObservableProperty().getIdentifier()); if (observation.getObservationConstellation().getObservableProperty().isSetName() && observation.getObservationConstellation().getObservableProperty().getFirstName().isSetValue()) { addXlinkTitleAttr(observation.getObservationConstellation().getObservableProperty().getFirstName() .getValue()); } } /** * Write om:featureOfInterest encoded or as xlink:href to stream * * @param encodingValues * {@link EncodingValues} contains the required encoder * @throws XMLStreamException * If an error occurs when writing to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written */ protected void writeFeatureOfIntererst(EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { if (encodingValues.isSetEncoder() && encodingValues.getEncoder() instanceof AbstractOmEncoderv20) { AbstractOmEncoderv20 encoder = (AbstractOmEncoderv20) encodingValues.getEncoder(); Map<HelperValues, String> additionalValues = new EnumMap<SosConstants.HelperValues, String>(HelperValues.class); Profile activeProfile = Configurator.getInstance().getProfileHandler().getActiveProfile(); additionalValues.put(HelperValues.ENCODE, Boolean.toString(activeProfile.isEncodeFeatureOfInterestInObservations())); if (StringHelper.isNotEmpty(activeProfile.getEncodingNamespaceForFeatureOfInterest())) { additionalValues.put(HelperValues.ENCODE_NAMESPACE, activeProfile.getEncodingNamespaceForFeatureOfInterest()); } else { additionalValues.put(HelperValues.ENCODE_NAMESPACE, encoder.getDefaultFeatureEncodingNamespace()); } XmlObject xmlObject = CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, observation.getObservationConstellation() .getFeatureOfInterest(), additionalValues); writeXmlObject(xmlObject, OmConstants.QN_OM_20_FEATURE_OF_INTEREST); } else { empty(OmConstants.QN_OM_20_FEATURE_OF_INTEREST); addXlinkHrefAttr(observation.getObservationConstellation().getFeatureOfInterest().getIdentifier()); if (observation.getObservationConstellation().getFeatureOfInterest().isSetName() && observation.getObservationConstellation().getFeatureOfInterest().getFirstName().isSetValue()) { addXlinkTitleAttr(observation.getObservationConstellation().getFeatureOfInterest().getFirstName() .getValue()); } } } /** * write om:result to stream * * @param observation * {@link OmObservation} with the result to write * @param encodingValues * {@link EncodingValues} contains the result element namespace * @throws XMLStreamException * If an error occurs when writing to stream * @throws OwsExceptionReport * If an error occurs when creating elements to be written */ protected void writeResult(OmObservation observation, EncodingValues encodingValues) throws XMLStreamException, OwsExceptionReport { if (observation.getValue() instanceof AbstractObservationValue<?>) { ((AbstractObservationValue<?>) observation.getValue()).setValuesForResultEncoding(observation); } XmlObject createResult = CodingHelper.encodeObjectToXml(encodingValues.getEncodingNamespace(), observation.getValue()); if (createResult != null) { if (createResult.xmlText().contains(XML_FRAGMENT)) { XmlObject set = OMObservationType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()) .addNewResult().set(createResult); writeXmlObject(set, OmConstants.QN_OM_20_RESULT); } else { if (checkResult(createResult)) { QName name = createResult.schemaType().getName(); String prefix = name.getPrefix(); if (Strings.isNullOrEmpty(prefix)) { XmlCursor newCursor = createResult.newCursor(); prefix = newCursor.prefixForNamespace(name.getNamespaceURI()); newCursor.setAttributeText(W3CConstants.QN_XSI_TYPE, prefix + Constants.COLON_STRING + name.getLocalPart()); newCursor.dispose(); } writeXmlObject(createResult, OmConstants.QN_OM_20_RESULT); } else { start(OmConstants.QN_OM_20_RESULT); writeNewLine(); writeXmlObject(createResult, OmConstants.QN_OM_20_RESULT); writeNewLine(); indent--; end(OmConstants.QN_OM_20_RESULT); indent++; } } } else { empty(OmConstants.QN_OM_20_RESULT); } } /** * Get additional values map with document helper value * * @return */ protected Map<HelperValues, String> getDocumentAdditionalHelperValues() { Map<HelperValues, String> additionalValues = Maps.newHashMap(); additionalValues.put(HelperValues.DOCUMENT, null); return additionalValues; } /** * 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()); } /** * Check the encoded om:result content for ...PropertyType * * @param result * Encoded om:result content to check * @return <code>true</code>, if content contains ...PropertyType */ private boolean checkResult(XmlObject result) { if (result.schemaType() != null) { SchemaType schemaType = result.schemaType(); if (schemaType.getName() != null) { QName name = schemaType.getName(); if (name.getLocalPart() != null && name.getLocalPart().toLowerCase().contains("propertytype")) { return true; } } } return false; } /** * Add gml:id to om:OM_Observation element * * @param observation * {@link OmObservation} with the GML id * @return observation id * @throws XMLStreamException * If an error occurs when writing to stream */ private String addGmlId(OmObservation observation) throws XMLStreamException { String observationID = JavaHelper.generateID(Double.toString(System.currentTimeMillis() * Math.random())); if (observation.isSetObservationID()) { observationID = observation.getObservationID(); } else { observation.setObservationID(observationID); } attr(GmlConstants.QN_ID_32, "o_" + observationID); return observationID; } /** * Write encoded om:phenomenonTime to stream * * @param time * {@link Time} to encode and write * @throws OwsExceptionReport * If an error occurs when creating elements to be written * @throws XMLStreamException * If an error occurs when writing to stream */ private void writePhenomenonTimeContent(Time time) throws OwsExceptionReport, XMLStreamException { XmlObject xmlObject = CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, time, getDocumentAdditionalHelperValues()); writeXmlObject(xmlObject, GmlHelper.getGml321QnameForITime(time)); } /** * Write encoded om:resultTime to stream * * @param time * {@link Time} to encode and write * @throws OwsExceptionReport * If an error occurs when creating elements to be written * @throws XMLStreamException * If an error occurs when writing to stream */ private void addResultTime(TimeInstant time) throws OwsExceptionReport, XMLStreamException { start(OmConstants.QN_OM_20_RESULT_TIME); writeNewLine(); XmlObject xmlObject = CodingHelper.encodeObjectToXml(GmlConstants.NS_GML_32, time, getDocumentAdditionalHelperValues()); writeXmlObject(xmlObject, GmlConstants.QN_TIME_INSTANT_32); writeNewLine(); indent--; end(OmConstants.QN_OM_20_RESULT_TIME); indent++; } /** * 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); } /** * Set {@link OmObservation} which should be written * * @param observation * the {@link OmObservation} */ private void setOmObservation(OmObservation observation) { this.observation = observation; } /** * Get the {@link OmObservation} which should be written * * @return the {@link OmObservation} */ private OmObservation getOmObservation() { return observation; } /** * Method to check whether the procedure should be encoded * * @return True or false */ private boolean checkEncodProcedureForEncoderKeys(Encoder<?, ?> encoder) { Set<EncoderKey> encoderKeyType = encoder.getEncoderKeyType(); for (EncoderKey encoderKey : encoderKeyType) { if (encoderKey instanceof XmlEncoderKey) { if (Configurator.getInstance().getProfileHandler().getActiveProfile() .isEncodeProcedureInObservation(((XmlEncoderKey) encoderKey).getNamespace())) { return true; } } } return false; } }