/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * 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.constellation.sos.ws; // JDK dependencies import java.io.File; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.logging.FileHandler; import java.util.logging.Level; import javax.imageio.spi.ServiceRegistry; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.namespace.QName; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.logging.MonolineFormatter; import org.apache.sis.xml.MarshallerPool; import org.constellation.ServiceDef; import static org.constellation.api.QueryConstants.SERVICE_PARAMETER_LC; import org.constellation.configuration.ConfigDirectory; import org.constellation.configuration.ConfigurationException; import org.constellation.configuration.DataSourceType; import org.constellation.configuration.SOSConfiguration; import org.constellation.dto.Details; import org.constellation.generic.database.Automatic; import org.constellation.metadata.io.MetadataIoException; import org.constellation.security.SecurityManagerHolder; import org.constellation.sos.factory.OMFactory; import org.constellation.sos.factory.SMLFactory; import org.constellation.sos.io.SensorReader; import org.constellation.sos.io.SensorWriter; import org.constellation.sos.ws.DatablockParser.Values; import static org.constellation.sos.ws.DatablockParser.getResultValues; import static org.constellation.sos.ws.Normalizer.normalizeDocument; import static org.constellation.sos.ws.Normalizer.regroupObservation; import static org.constellation.sos.ws.SOSConstants.ACCEPTED_OUTPUT_FORMATS; import static org.constellation.sos.ws.SOSConstants.EVENT_TIME; import static org.constellation.sos.ws.SOSConstants.INSERTION_CAPABILITIES; import static org.constellation.sos.ws.SOSConstants.MEASUREMENT_QNAME; import static org.constellation.sos.ws.SOSConstants.NOT_SUPPORTED; import static org.constellation.sos.ws.SOSConstants.OBSERVATION_MODEL; import static org.constellation.sos.ws.SOSConstants.OBSERVATION_QNAME; import static org.constellation.sos.ws.SOSConstants.OBSERVATION_TEMPLATE; import static org.constellation.sos.ws.SOSConstants.OFFERING; import static org.constellation.sos.ws.SOSConstants.OPERATIONS_METADATA_100; import static org.constellation.sos.ws.SOSConstants.OPERATIONS_METADATA_200; import static org.constellation.sos.ws.SOSConstants.OUTPUT_FORMAT; import static org.constellation.sos.ws.SOSConstants.PROCEDURE; import static org.constellation.sos.ws.SOSConstants.PROCEDURE_DESCRIPTION_FORMAT; import static org.constellation.sos.ws.SOSConstants.PROFILES_V200; import static org.constellation.sos.ws.SOSConstants.RESPONSE_MODE; import static org.constellation.sos.ws.SOSConstants.SENSORML_101_FORMAT_V100; import static org.constellation.sos.ws.SOSConstants.SENSORML_101_FORMAT_V200; import static org.constellation.sos.ws.SOSConstants.SOS; import static org.constellation.sos.ws.SOSConstants.SOS_FILTER_CAPABILITIES_V100; import static org.constellation.sos.ws.SOSConstants.SOS_FILTER_CAPABILITIES_V200; import static org.constellation.sos.ws.SOSConstants.SUPPORTED_FOI_TYPES; import static org.constellation.sos.ws.SOSConstants.SUPPORTED_OBS_TYPES; import static org.constellation.sos.ws.SOSUtils.BoundMatchEnvelope; import static org.constellation.sos.ws.SOSUtils.extractTimeBounds; import static org.constellation.sos.ws.SOSUtils.getCollectionBound; import static org.constellation.sos.ws.SOSUtils.getIDFromObject; import static org.constellation.sos.ws.SOSUtils.getSensorPosition; import static org.constellation.sos.ws.SOSUtils.isCompleteEnvelope3D; import static org.constellation.sos.ws.SOSUtils.samplingPointMatchEnvelope; import org.constellation.ws.AbstractWorker; import org.constellation.ws.CstlServiceException; import org.constellation.ws.UnauthorizedException; import org.geotoolkit.factory.FactoryNotFoundException; import org.geotoolkit.gml.GmlInstant; import org.geotoolkit.gml.xml.AbstractFeature; import org.geotoolkit.gml.xml.AbstractGeometry; import org.geotoolkit.gml.xml.Envelope; import org.geotoolkit.gml.xml.FeatureCollection; import org.geotoolkit.gml.xml.FeatureProperty; import org.geotoolkit.gml.xml.TimeIndeterminateValueType; import org.geotoolkit.observation.ObservationFilter; import org.geotoolkit.observation.ObservationFilterReader; import org.constellation.sos.io.ObservationReader; import org.geotoolkit.observation.ObservationResult; import org.geotoolkit.observation.ObservationStoreException; import org.constellation.sos.io.ObservationWriter; import org.geotoolkit.observation.xml.AbstractObservation; import org.geotoolkit.observation.xml.OMXmlFactory; import org.geotoolkit.observation.xml.ObservationComparator; import org.geotoolkit.observation.xml.Process; import org.geotoolkit.ogc.xml.XMLLiteral; import org.geotoolkit.ows.xml.AbstractCapabilitiesCore; import org.geotoolkit.ows.xml.AbstractOperation; import org.geotoolkit.ows.xml.AbstractOperationsMetadata; import org.geotoolkit.ows.xml.AbstractServiceIdentification; import org.geotoolkit.ows.xml.AbstractServiceProvider; import org.geotoolkit.ows.xml.AcceptFormats; import static org.geotoolkit.ows.xml.OWSExceptionCode.INVALID_PARAMETER_VALUE; import static org.geotoolkit.ows.xml.OWSExceptionCode.MISSING_PARAMETER_VALUE; import static org.geotoolkit.ows.xml.OWSExceptionCode.NO_APPLICABLE_CODE; import static org.geotoolkit.ows.xml.OWSExceptionCode.OPERATION_NOT_SUPPORTED; import static org.geotoolkit.ows.xml.OWSExceptionCode.VERSION_NEGOTIATION_FAILED; import org.geotoolkit.ows.xml.OWSXmlFactory; import org.geotoolkit.ows.xml.Range; import org.geotoolkit.ows.xml.RequestBase; import org.geotoolkit.ows.xml.Sections; import org.geotoolkit.sml.xml.AbstractSensorML; import org.geotoolkit.sml.xml.SensorMLUtilities; import org.geotoolkit.sml.xml.SmlFactory; import org.geotoolkit.sml.xml.v100.SensorML; import org.geotoolkit.sos.xml.Capabilities; import org.geotoolkit.sos.xml.Contents; import org.geotoolkit.sos.xml.FilterCapabilities; import org.geotoolkit.sos.xml.GetCapabilities; import org.geotoolkit.sos.xml.GetFeatureOfInterest; import org.geotoolkit.sos.xml.GetObservation; import org.geotoolkit.sos.xml.GetObservationById; import org.geotoolkit.sos.xml.GetResult; import org.geotoolkit.sos.xml.GetResultResponse; import org.geotoolkit.sos.xml.GetResultTemplate; import org.geotoolkit.sos.xml.GetResultTemplateResponse; import org.geotoolkit.sos.xml.InsertObservation; import org.geotoolkit.sos.xml.InsertObservationResponse; import org.geotoolkit.sos.xml.InsertResult; import org.geotoolkit.sos.xml.InsertResultResponse; import org.geotoolkit.sos.xml.InsertResultTemplate; import org.geotoolkit.sos.xml.InsertResultTemplateResponse; import org.geotoolkit.sos.xml.ObservationOffering; import org.geotoolkit.sos.xml.ResponseModeType; import static org.geotoolkit.sos.xml.ResponseModeType.INLINE; import static org.geotoolkit.sos.xml.ResponseModeType.OUT_OF_BAND; import static org.geotoolkit.sos.xml.ResponseModeType.RESULT_TEMPLATE; import org.geotoolkit.sos.xml.ResultTemplate; import org.geotoolkit.sos.xml.SOSMarshallerPool; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildCapabilities; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildContents; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildDataArrayProperty; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildDeleteSensorResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildEnvelope; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildFeatureCollection; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildFeatureProperty; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildGetObservationByIdResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildGetObservationResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildGetResultResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildGetResultTemplateResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildInsertObservationResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildInsertResultResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildInsertResultTemplateResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildInsertSensorResponse; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildObservationCollection; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildOffering; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildRange; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildTimeAfter; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildTimeBefore; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildTimeDuring; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildTimeEquals; import static org.geotoolkit.sos.xml.SOSXmlFactory.buildTimePeriod; import org.geotoolkit.sos.xml.SosInsertionMetadata; import org.geotoolkit.sos.xml.GetFeatureOfInterestTime; import org.geotoolkit.swe.xml.AbstractDataComponent; import org.geotoolkit.swe.xml.AbstractEncoding; import org.geotoolkit.swe.xml.DataArray; import org.geotoolkit.swe.xml.DataArrayProperty; import org.geotoolkit.swe.xml.DataRecord; import org.geotoolkit.swe.xml.PhenomenonProperty; import org.geotoolkit.swe.xml.TextBlock; import org.geotoolkit.swes.xml.DeleteSensor; import org.geotoolkit.swes.xml.DeleteSensorResponse; import org.geotoolkit.swes.xml.DescribeSensor; import org.geotoolkit.swes.xml.InsertSensor; import org.geotoolkit.swes.xml.InsertSensorResponse; import org.geotoolkit.swes.xml.ObservationTemplate; import org.geotoolkit.temporal.object.ISODateParser; import org.geotoolkit.temporal.object.TemporalUtilities; import org.geotoolkit.util.StringUtilities; import org.opengis.filter.Filter; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLike; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.temporal.After; import org.opengis.filter.temporal.Before; import org.opengis.filter.temporal.Begins; import org.opengis.filter.temporal.BegunBy; import org.opengis.filter.temporal.During; import org.opengis.filter.temporal.EndedBy; import org.opengis.filter.temporal.Ends; import org.opengis.filter.temporal.Meets; import org.opengis.filter.temporal.OverlappedBy; import org.opengis.filter.temporal.TContains; import org.opengis.filter.temporal.TEquals; import org.opengis.filter.temporal.TOverlaps; import org.opengis.geometry.primitive.Point; import org.opengis.observation.Measure; import org.opengis.observation.Measurement; import org.opengis.observation.Observation; import org.opengis.observation.ObservationCollection; import org.opengis.observation.sampling.SamplingFeature; import org.opengis.temporal.Instant; import org.opengis.temporal.Period; import org.opengis.temporal.TemporalGeometricPrimitive; import org.opengis.temporal.TemporalObject; import org.opengis.temporal.TemporalPrimitive; import org.opengis.util.CodeList; /** * * @author Guilhem Legal (Geomatys). */ public class SOSworker extends AbstractWorker { public static final int DISCOVERY = 0; public static final int TRANSACTIONAL = 1; /** * A list of temporary ObservationTemplate */ private final Map<String, Observation> templates = new HashMap<>(); /** * A list of temporary resultTemplate */ private final Map<String, ResultTemplate> resultTemplates = new HashMap<>(); /** * The base for sensor id. */ private String sensorIdBase; /** * The base for observation id. */ private String observationTemplateIdBase; /** * The base for offering id. */ private static final String OFFERING_ID_BASE = "offering-"; /** * The base for phenomenon id. */ private String phenomenonIdBase; /** * The valid time for a getObservation template (in ms). */ private long templateValidTime; /** * A list of schreduled Task (used in close method). */ private final List<Timer> schreduledTask = new ArrayList<>(); /** * A list of supported SensorML version */ private Map<String, List<String>> acceptedSensorMLFormats; /** * The profile of the SOS service (transational/discovery). */ private int profile; /** * The Observation database reader */ private ObservationReader omReader; /** * The Observation database writer */ private ObservationWriter omWriter; /** * The observation filter */ private ObservationFilter omFilter; /** * The sensorML database reader */ private SensorReader smlReader; /** * The sensorML database writer */ private SensorWriter smlWriter; /** * The factory to instanciate the O&M Readers / Writers / Filters */ private OMFactory omFactory; /** * The factory to instanciate the SensorML Readers / Writers */ private SMLFactory smlFactory; /** * The supported Response Mode for GetObservation request (depends on reader capabilities) */ private List<ResponseModeType> acceptedResponseMode; /** * The supported Response Format for GetObservation request (depends on reader capabilities) */ private List<String> acceptedResponseFormat; /** * A debug flag. * If true the server will verify the gap between a the samplingTime of an observation and the time of insertion. */ private boolean verifySynchronization; /** * A flag indicating if we have to store in cache the capabilities document. */ private boolean keepCapabilities; /** * if the flag keepCapabilities is set to true, this attribute will be fill with the reponse of a getCapabilities. */ private Capabilities loadedCapabilities; private boolean alwaysFeatureCollection; private String sensorTypeFilter; private SOSConfiguration configuration; /** * Initialize the database connection. * * @param id identifier of the worker instance. */ public SOSworker(final String id) { super(id, ServiceDef.Specification.SOS); isStarted = true; // Database configuration try { final Object object = serviceBusiness.getConfiguration("sos", id); if (object instanceof SOSConfiguration) { configuration = (SOSConfiguration) object; } else { startError("The configuration object is malformed or null.", null); return; } final String logFolder = configuration.getLogFolder(); if (logFolder != null) { initLogger("", logFolder); LOGGER.log(Level.INFO, "Redirecting the log to: {0}", logFolder); } this.profile = configuration.getProfile(); this.verifySynchronization = configuration.isVerifySynchronization(); this.keepCapabilities = configuration.isKeepCapabilities(); if (keepCapabilities) { loadCachedCapabilities(); } //we get the O&M filter Type final DataSourceType omFilterType = configuration.getObservationFilterType(); //we get the O&M reader Type final DataSourceType omReaderType = configuration.getObservationReaderType(); //we get the O&M writer Type final DataSourceType omWriterType = configuration.getObservationWriterType(); //we get the Sensor reader type final DataSourceType smlType = configuration.getSMLType(); final File configurationDirectory = ConfigDirectory.getInstanceDirectory("SOS", id); final Automatic smlConfiguration = configuration.getSMLConfiguration(); if (smlConfiguration == null) { startError("The configuration file does not contains a SML configuration.", null); return; } smlConfiguration.setConfigurationDirectory(configurationDirectory); final Automatic omConfiguration = configuration.getOMConfiguration(); if (omConfiguration == null) { startError ("The configuration file does not contains a O&M configuration.", null); return; } omConfiguration.setConfigurationDirectory(configurationDirectory); //we initialize the properties attribute final String observationIdBase = configuration.getObservationIdBase() != null ? configuration.getObservationIdBase() : "urn:ogc:object:observation:unknow:"; sensorIdBase = configuration.getSensorIdBase() != null ? configuration.getSensorIdBase() : "urn:ogc:object:sensor:unknow:"; phenomenonIdBase = configuration.getPhenomenonIdBase() != null ? configuration.getPhenomenonIdBase() : "urn:ogc:def:phenomenon:OGC:1.0.30:"; observationTemplateIdBase = configuration.getObservationTemplateIdBase() != null ? configuration.getObservationTemplateIdBase() : "urn:ogc:object:observationTemplate:unknow:"; alwaysFeatureCollection = configuration.getBooleanParameter(OMFactory.ALWAYS_FEATURE_COLLECTION); sensorTypeFilter = configuration.getParameter(OMFactory.SENSOR_TYPE_FILTER); applySupportedVersion(); // we fill a map of properties to sent to the reader/writer/filter final Map<String, Object> properties = new HashMap<>(); properties.put(OMFactory.OBSERVATION_ID_BASE, observationIdBase); properties.put(OMFactory.OBSERVATION_TEMPLATE_ID_BASE, observationTemplateIdBase); properties.put(OMFactory.SENSOR_ID_BASE, sensorIdBase); properties.put(OMFactory.PHENOMENON_ID_BASE, phenomenonIdBase); // we add the general parameters to the properties properties.putAll(configuration.getParameters()); // we add the custom parameters to the properties properties.putAll(omConfiguration.getCustomparameters()); properties.putAll(smlConfiguration.getCustomparameters()); // look for template life limit int h, m; try { String validTime = configuration.getTemplateValidTime(); if (validTime == null || validTime.isEmpty() || validTime.indexOf(':') == -1) { validTime = "1:00"; LOGGER.info("using default template valid time: one hour.\n"); } h = Integer.parseInt(validTime.substring(0, validTime.indexOf(':'))); m = Integer.parseInt(validTime.substring(validTime.indexOf(':') + 1)); } catch (NumberFormatException ex) { LOGGER.info("using default template valid time: one hour.\n"); h = 1; m = 0; } templateValidTime = (h * 3600000) + (m * 60000); // we initialize the reader/writer if (!DataSourceType.NONE.equals(smlType)) { smlFactory = getSMLFactory(smlType); smlReader = smlFactory.getSensorReader(smlType, smlConfiguration, properties); smlWriter = smlFactory.getSensorWriter(smlType, smlConfiguration, properties); this.acceptedSensorMLFormats = smlReader.getAcceptedSensorMLFormats(); } else { this.acceptedSensorMLFormats = new HashMap<>(); } // we initialize the O&M reader/writer/filter if (!DataSourceType.NONE.equals(omReaderType)) { omFactory = getOMFactory(omReaderType); omReader = omFactory.getObservationReader(omReaderType, omConfiguration, properties); //we initialize the variables depending on the Reader capabilities this.acceptedResponseMode = omReader.getResponseModes(); this.acceptedResponseFormat = omReader.getResponseFormats(); } else { this.acceptedResponseMode = new ArrayList<>(); this.acceptedResponseFormat = new ArrayList<>(); } if (!DataSourceType.NONE.equals(omWriterType)) { omFactory = getOMFactory(omWriterType); omWriter = omFactory.getObservationWriter(omWriterType, omConfiguration, properties); } if (!DataSourceType.NONE.equals(omFilterType)) { omFactory = getOMFactory(omFilterType); omFilter = omFactory.getObservationFilter(omFilterType, omConfiguration, properties); } setLogLevel(configuration.getLogLevel()); // we log some implementation informations logInfos(); } catch (JAXBException ex) { LOGGER.log(Level.FINER, ex.getMessage(), ex); String msg; if (ex.getMessage() != null) { msg = ex.getMessage(); } else { if (ex.getLinkedException() != null) { msg = ex.getLinkedException().getMessage(); } else { msg = "no message"; } } startError("JAXBException:" + msg, ex); } catch (FactoryNotFoundException ex) { startError("Unable to find a SOS Factory." + ex.getMessage(), ex); } catch (MetadataIoException ex) { startError("MetadataIOException while initializing the sensor reader/writer:\n" + ex.getMessage(), ex); } catch (CstlServiceException | DataStoreException ex) { startError(ex.getMessage(), ex); } catch (ConfigurationException ex) { startError("The configuration file can't be found.", ex); } } private void startError(final String msg, final Exception ex) { startError = msg; isStarted = false; LOGGER.log(Level.WARNING, "\nThe SOS worker is not running!\ncause: {0}", startError); if (ex != null) { LOGGER.log(Level.FINER, "\nThe SOS worker is not running!", ex); } } /** * Select the good O&M factory in the available ones in function of the dataSource type. * * @param type * @return */ private OMFactory getOMFactory(final DataSourceType type) { final Iterator<OMFactory> ite = ServiceRegistry.lookupProviders(OMFactory.class); while (ite.hasNext()) { OMFactory currentFactory = ite.next(); if (currentFactory.factoryMatchType(type)) { return currentFactory; } } throw new FactoryNotFoundException("No OM factory has been found for type:" + type); } /** * Select the good O&M factory in the available ones in function of the dataSource type. * * @param type * @return */ private SMLFactory getSMLFactory(final DataSourceType type) { final Iterator<SMLFactory> ite = ServiceRegistry.lookupProviders(SMLFactory.class); while (ite.hasNext()) { SMLFactory currentFactory = ite.next(); if (currentFactory.factoryMatchType(type)) { return currentFactory; } } throw new FactoryNotFoundException("No SML factory has been found for type:" + type); } /** * Log some informations about the implementations classes for reader / writer / filter object. */ private void logInfos() { final String loaded = " loaded.\n"; final StringBuilder infos = new StringBuilder(); if (this.profile == DISCOVERY) { infos.append("Discovery profile loaded.\n"); } else { infos.append("Transactional profile loaded.\n"); } if (smlReader != null) { infos.append('\n').append(smlReader.getInfos()).append(loaded).append('\n'); } else { infos.append("No SensorML reader loaded.\n"); } if ( profile == TRANSACTIONAL) { if (smlWriter != null) { infos.append(smlWriter.getInfos()).append(loaded).append('\n'); } else { infos.append("No SensorML writer loaded.\n"); } } if (omReader != null) { infos.append(omReader.getInfos()).append(loaded).append('\n'); } else { infos.append("No O&M reader loaded.\n"); } if (omFilter != null) { infos.append(omFilter.getInfos()).append(loaded).append('\n'); } else { infos.append("No O&M filter loaded.\n"); } if ( profile == TRANSACTIONAL) { if (omWriter != null) { infos.append(omWriter.getInfos()).append(loaded).append('\n'); } else { infos.append("No O&M writer loaded.\n"); } } infos.append("SOS worker \"").append(getId()).append("\" running\n"); LOGGER.info(infos.toString()); } /** * Load the Capabilites document from a configuration file if its present. * * @param configurationDirectory * @throws JAXBException */ private void loadCachedCapabilities() throws JAXBException { //we fill the cachedCapabilities if we have to LOGGER.info("adding capabilities document in cache"); try { Object object = serviceBusiness.getExtraConfiguration("SOS", getId(), "cached-offerings.xml", SOSMarshallerPool.getInstance()); if (object instanceof JAXBElement) { object = ((JAXBElement)object).getValue(); } if (object instanceof Capabilities) { loadedCapabilities = (Capabilities) object; } else { LOGGER.severe("cached capabilities file does not contains Capablities object."); } } catch (ConfigurationException ex) { // file can be missing } } /** * Web service operation describing the service and its capabilities. * * @param request A document specifying the section you would obtain like : * ServiceIdentification, ServiceProvider, Contents, operationMetadata. */ public Capabilities getCapabilities(final GetCapabilities request) throws CstlServiceException { isWorking(); LOGGER.log(logLevel, "getCapabilities request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(request, false, true); final String currentVersion = request.getVersion().toString(); final AcceptFormats formats = request.getAcceptFormats(); if (formats != null && formats.getOutputFormat().size() > 0 ) { boolean found = false; for (String form: formats.getOutputFormat()) { if (ACCEPTED_OUTPUT_FORMATS.contains(form)) { found = true; } } if (!found) { throw new CstlServiceException("accepted format : text/xml, application/xml", INVALID_PARAMETER_VALUE, "acceptFormats"); } } //set the current updateSequence parameter final boolean returnUS = returnUpdateSequenceDocument(request.getUpdateSequence()); if (returnUS) { return buildCapabilities(currentVersion, getCurrentUpdateSequence()); } Sections sections = request.getSections(); if (sections == null) { sections = OWSXmlFactory.buildSections("1.1.0", Arrays.asList("All")); } else if (!request.isValidSections()){ throw new CstlServiceException("Invalid sections values", INVALID_PARAMETER_VALUE, "section"); } // If the getCapabilities response is in cache, we just return it. final AbstractCapabilitiesCore cachedCapabilities = getCapabilitiesFromCache(currentVersion, null); if (cachedCapabilities != null) { return (Capabilities) cachedCapabilities.applySections(sections); } // we load the skeleton capabilities final Details skeleton = getStaticCapabilitiesObject("sos", null); final Capabilities skeletonCapabilities = SOSConstants.createCapabilities(currentVersion, skeleton); final Capabilities localCapabilities; if (keepCapabilities) { localCapabilities = loadedCapabilities; } else { localCapabilities = skeletonCapabilities; } //we prepare the different parts response document final AbstractServiceIdentification si = localCapabilities.getServiceIdentification(); final AbstractServiceProvider sp = localCapabilities.getServiceProvider(); final FilterCapabilities fc; final AbstractOperationsMetadata om; if (currentVersion.equals("2.0.0")) { fc = SOS_FILTER_CAPABILITIES_V200; om = OPERATIONS_METADATA_200.clone(); si.setProfile(PROFILES_V200); } else { fc = SOS_FILTER_CAPABILITIES_V100; om = OPERATIONS_METADATA_100.clone(); } //we remove the operation not supported in this profile (transactional/discovery) if (profile == DISCOVERY) { om.removeOperation("InsertObservation"); om.removeOperation("RegisterSensor"); om.removeOperation("InsertSensor"); om.removeOperation("DeleteSensor"); } //we update the URL om.updateURL(getServiceUrl()); final Capabilities c; try { if (!keepCapabilities) { //we update the parameter in operation metadata. final AbstractOperation go = om.getOperation("GetObservation"); final Collection<String> foiNames; final Collection<String> procNames; final Collection<String> phenNames; final Collection<String> offNames; final List<String> eventTime; final List<String> queryableResultProperties; if (omReader != null) { foiNames = omReader.getFeatureOfInterestNames(); procNames = omReader.getProcedureNames(sensorTypeFilter); phenNames = omReader.getPhenomenonNames(); offNames = omReader.getOfferingNames(currentVersion, sensorTypeFilter); eventTime = omReader.getEventTime(); } else { foiNames = new ArrayList<>(); procNames = new ArrayList<>(); phenNames = new ArrayList<>(); offNames = new ArrayList<>(); eventTime = new ArrayList<>(); } if (omFilter != null) { queryableResultProperties = omFilter.supportedQueryableResultProperties(); } else { queryableResultProperties = new ArrayList<>(); } // the list of offering names go.updateParameter(OFFERING, offNames); // the event time range if (eventTime != null && eventTime.size() == 1) { final Range range = buildRange(currentVersion, eventTime.get(0), "now"); go.updateParameter(EVENT_TIME, range); } else if (eventTime != null && eventTime.size() == 2) { final Range range = buildRange(currentVersion, eventTime.get(0), eventTime.get(1)); go.updateParameter(EVENT_TIME, range); } //the process list go.updateParameter(PROCEDURE, procNames); //the phenomenon list go.updateParameter("observedProperty", phenNames); //the feature of interest list go.updateParameter("featureOfInterest", foiNames); // the different responseMode available final List<String> arm = new ArrayList<>(); for (ResponseModeType rm: acceptedResponseMode) { arm.add(rm.value()); } go.updateParameter(RESPONSE_MODE, arm); // the different responseFormat available go.updateParameter("responseFormat", acceptedResponseFormat); // the result filtrable part if (!queryableResultProperties.isEmpty()) { go.updateParameter("result", queryableResultProperties); } /** * Because sometimes there is some sensor that are queryable in DescribeSensor but not in GetObservation */ final AbstractOperation ds = om.getOperation("DescribeSensor"); if (smlReader != null) { final List<String> sensorNames = new ArrayList<>(smlReader.getSensorNames(sensorTypeFilter)); Collections.sort(sensorNames); ds.updateParameter(PROCEDURE, sensorNames); } else { ds.updateParameter(PROCEDURE, procNames); } final List<String> smlformats = acceptedSensorMLFormats.get(currentVersion); if (smlformats != null) { ds.updateParameter("outputFormat", smlformats); } final AbstractOperation gfoi = om.getOperation("GetFeatureOfInterest"); if (gfoi != null) { //the feature of interest list gfoi.updateParameter("featureOfInterestId", foiNames); } final AbstractOperation gfoit = om.getOperation("GetFeatureOfInterestTime"); if (gfoit != null) { //the feature of interest list gfoit.updateParameter("featureOfInterestId", foiNames); } } final Contents cont; if (keepCapabilities) { cont = loadedCapabilities.getContents(); } else { // we add the list of observation offerings final List<ObservationOffering> offerings; if (omReader != null) { offerings = omReader.getObservationOfferings(currentVersion, sensorTypeFilter); } else { offerings = new ArrayList<>(); } cont = buildContents(currentVersion, offerings); } // we build and normalize the document final Capabilities temp = buildCapabilities(currentVersion, si, sp, om, getCurrentUpdateSequence(), fc, cont, Arrays.asList((Object)INSERTION_CAPABILITIES)); c = normalizeDocument(temp); LOGGER.log(logLevel, "getCapabilities processed in {0} ms.\n", (System.currentTimeMillis() - start)); putCapabilitiesInCache(currentVersion, null, c); } catch (DataStoreException ex) { throw new CstlServiceException(ex); } return (Capabilities) c.applySections(sections); } /** * Web service operation which return an sml description of the specified sensor. * * @param request A document specifying the id of the sensor that we want the description. * * @throws org.constellation.ws.CstlServiceException */ public AbstractSensorML describeSensor(final DescribeSensor request) throws CstlServiceException { LOGGER.log(logLevel, "DescribeSensor request processing\n"); final long start = System.currentTimeMillis(); // we get the form verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); //we verify that the output format is good. final String locator; if (currentVersion.equals("2.0.0")) { locator = PROCEDURE_DESCRIPTION_FORMAT; } else { locator = OUTPUT_FORMAT; } final String out = request.getOutputFormat(); if (out != null && !out.isEmpty()) { if (!StringUtilities.containsIgnoreCase(acceptedSensorMLFormats.get(currentVersion), request.getOutputFormat())) { final StringBuilder msg = new StringBuilder("Accepted values for outputFormat:"); for (String s : acceptedSensorMLFormats.get(currentVersion)) { msg.append('\n').append(s); } throw new CstlServiceException(msg.toString(), INVALID_PARAMETER_VALUE, locator); } } else { final StringBuilder msg = new StringBuilder("output format must be specify, accepted value are:"); for (String s : acceptedSensorMLFormats.get(currentVersion)) { msg.append('\n').append(s); } throw new CstlServiceException(msg.toString(), MISSING_PARAMETER_VALUE, locator); } // we verify that we have a sensor ID. final String sensorId = request.getProcedure(); if (sensorId == null || sensorId.isEmpty()) { throw new CstlServiceException("You must specify the sensor ID!", MISSING_PARAMETER_VALUE, PROCEDURE); } AbstractSensorML result = smlReader.getSensor(sensorId); if (result instanceof SensorML && (out.equalsIgnoreCase(SENSORML_101_FORMAT_V100) || out.equalsIgnoreCase(SENSORML_101_FORMAT_V200))) { result = SmlFactory.convertTo101((SensorML)result); } LOGGER.log(logLevel, "describeSensor processed in {0} ms.\n", (System.currentTimeMillis() - start)); return result; } public DeleteSensorResponse deleteSensor(final DeleteSensor request) throws CstlServiceException { LOGGER.log(logLevel, "DescribeSensor request processing\n"); final long start = System.currentTimeMillis(); // we get the form verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); // we verify that we have a sensor ID. final String sensorId = request.getProcedure(); if (sensorId == null || sensorId.isEmpty()) { throw new CstlServiceException("You must specify the sensor ID!", MISSING_PARAMETER_VALUE, PROCEDURE); } final boolean result = smlWriter.deleteSensor(sensorId); if (result) { smlReader.removeFromCache(sensorId); LOGGER.log(logLevel, "describeSensor processed in {0} ms.\n", (System.currentTimeMillis() - start)); return buildDeleteSensorResponse(currentVersion, sensorId); } else { throw new CstlServiceException("unable to delete sensor:" + sensorId); } } public Object getObservationById(final GetObservationById request) throws CstlServiceException { LOGGER.log(logLevel, "getObservation request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); final List<Observation> observation = new ArrayList<>(); try { for (String oid : request.getObservation()) { if (oid.isEmpty()) { final String locator; if (currentVersion.equals("2.0.0")) { locator = "observation"; } else { locator = "observationId"; } throw new CstlServiceException("Empty observation id", MISSING_PARAMETER_VALUE, locator); } observation.add(omReader.getObservation(oid, request.getResultModel(), INLINE, currentVersion)); } } catch (DataStoreException ex) { throw new CstlServiceException(ex); } final ObservationCollection response = buildGetObservationByIdResponse(currentVersion, "collection-1", null, observation); LOGGER.log(logLevel, "getObservationById processed in {0}ms.\n", (System.currentTimeMillis() - start)); return response; } /** * Web service operation which respond a collection of observation satisfying * the restriction specified in the query. * * @param requestObservation a document specifying the parameter of the request. * * @throws org.constellation.ws.CstlServiceException */ public Object getObservation(final GetObservation requestObservation) throws CstlServiceException { LOGGER.log(logLevel, "getObservation request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(requestObservation, true, false); final String currentVersion = requestObservation.getVersion().toString(); //we verify that the output format is good. final String responseFormat = requestObservation.getResponseFormat(); if (responseFormat != null && !responseFormat.isEmpty()) { if (!acceptedResponseFormat.contains(responseFormat)) { final StringBuilder arf = new StringBuilder(); for (String s : acceptedResponseFormat) { arf.append(s).append('\n'); } throw new CstlServiceException(responseFormat + " is not accepted for responseFormat.\n" + "Accepted values are:\n" + arf.toString(), INVALID_PARAMETER_VALUE, "responseFormat"); } } else if (currentVersion.equals("1.0.0") || (responseFormat != null && responseFormat.isEmpty())) { final StringBuilder arf = new StringBuilder(); for (String s : acceptedResponseFormat) { arf.append(s).append('\n'); } throw new CstlServiceException("Response format must be specify.\nAccepted values are:\n" + arf.toString(), MISSING_PARAMETER_VALUE, "responseFormat"); } QName resultModel = requestObservation.getResultModel(); if (resultModel == null) { resultModel = OBSERVATION_QNAME; } //we get the mode of result boolean template = false; boolean outOfBand = false; ResponseModeType mode; if (requestObservation.getResponseMode() == null) { mode = INLINE; } else { try { mode = ResponseModeType.fromValue(requestObservation.getResponseMode()); } catch (IllegalArgumentException e) { final StringBuilder arm = new StringBuilder(); for (ResponseModeType s : acceptedResponseMode) { arm.append(s.value()).append('\n'); } throw new CstlServiceException("The response Mode: " + requestObservation.getResponseMode() + " is not supported by the service." + "Supported Values are:\n" + arm.toString(), INVALID_PARAMETER_VALUE, RESPONSE_MODE); } } if (mode == OUT_OF_BAND) { outOfBand = true; } else if (mode == RESULT_TEMPLATE) { template = true; } else if (!acceptedResponseMode.contains(mode)) { final StringBuilder arm = new StringBuilder(); for (ResponseModeType s : acceptedResponseMode) { arm.append(s.value()).append('\n'); } throw new CstlServiceException("This response Mode is not supported by the service" + "Supported Values are:\n" + arm.toString(), OPERATION_NOT_SUPPORTED, RESPONSE_MODE); } final Object response; try { // we clone the filter for this request final ObservationFilter localOmFilter = omFactory.cloneObservationFilter(omFilter); // we set the response format on the filter reader if (localOmFilter instanceof ObservationFilterReader) { ((ObservationFilterReader)localOmFilter).setResponseFormat(responseFormat); } localOmFilter.initFilterObservation(mode, resultModel); //we verify that there is an offering (mandatory in 1.0.0, optional in 2.0.0) final List<ObservationOffering> offerings = new ArrayList<>(); final List<String> offeringNames = requestObservation.getOfferings(); if (currentVersion.equals("1.0.0") && (offeringNames == null || offeringNames.isEmpty())) { throw new CstlServiceException("Offering must be specify!", MISSING_PARAMETER_VALUE, OFFERING); } else { for (String offeringName : offeringNames) { // CITE if (offeringName.isEmpty()) { throw new CstlServiceException("This offering name is empty", MISSING_PARAMETER_VALUE, OFFERING); } final ObservationOffering offering = omReader.getObservationOffering(offeringName, currentVersion); if (offering == null) { throw new CstlServiceException("This offering is not registered in the service", INVALID_PARAMETER_VALUE, OFFERING); } offerings.add(offering); } } localOmFilter.setOfferings(offerings); //we verify that the srsName (if there is one) is advertised in the offering if (requestObservation.getSrsName() != null) { for (ObservationOffering off : offerings) { if (!off.getSrsName().contains(requestObservation.getSrsName())) { final StringBuilder availableSrs = new StringBuilder(); for (String s : off.getSrsName()) { availableSrs.append(s).append('\n'); } throw new CstlServiceException("This srs name is not advertised in the offering.\n" + "Available srs name are:\n" + availableSrs.toString(), INVALID_PARAMETER_VALUE, "srsName"); } } } //we verify that the resultModel (if there is one) is advertised in the offering if (requestObservation.getResultModel() != null) { for (ObservationOffering off : offerings) { if (!off.getResultModel().contains(requestObservation.getResultModel())) { final StringBuilder availableRM = new StringBuilder(); for (QName s : off.getResultModel()) { availableRM.append(s).append('\n'); } throw new CstlServiceException("This result model is not advertised in the offering:" + requestObservation.getResultModel() + '\n' + "Available result model for this offering are:", INVALID_PARAMETER_VALUE, "resultModel"); } } } //we get the list of process final List<String> procedures = requestObservation.getProcedure(); for (String procedure : procedures) { if (procedure != null) { LOGGER.log(logLevel, "process ID: {0}", procedure); // CITE if (procedure.isEmpty()) { throw new CstlServiceException(" the procedure parameter is empty", MISSING_PARAMETER_VALUE, PROCEDURE); } if (!omReader.existProcedure(procedure)) { throw new CstlServiceException(" this process is not registred in the table", INVALID_PARAMETER_VALUE, PROCEDURE); } if (!offerings.isEmpty()) { boolean found = false; for (ObservationOffering off : offerings) { if (!found && off.getProcedures().contains(procedure)) { found = true; } } if (!found) { throw new CstlServiceException(" this process is not registred in the offerings", INVALID_PARAMETER_VALUE, PROCEDURE); } } } else { //if there is only one proccess null we return error (we'll see) if (procedures.size() == 1) { throw new CstlServiceException("the procedure is null", INVALID_PARAMETER_VALUE, PROCEDURE); } } } localOmFilter.setProcedure(procedures, offerings); //we get the list of phenomenon //TODO verifier que les pheno appartiennent a l'offering final List<String> observedProperties = requestObservation.getObservedProperty(); if (observedProperties != null && !observedProperties.isEmpty()) { final List<String> phenomenons = new ArrayList<>(); for (String phenomenonName : observedProperties) { if (!phenomenonName.equals(phenomenonIdBase + "ALL")) { if (!omReader.existPhenomenon(phenomenonName)) { throw new CstlServiceException(" this phenomenon " + phenomenonName + " is not registred in the database!", INVALID_PARAMETER_VALUE, "observedProperty"); } phenomenons.add(phenomenonName); } } if (!phenomenons.isEmpty()) { localOmFilter.setObservedProperties(phenomenons); } } else if (currentVersion.equals("1.0.0")){ throw new CstlServiceException("You must specify at least One phenomenon", MISSING_PARAMETER_VALUE, "observedProperty"); } //we treat the time restriction final List<Filter> times = requestObservation.getTemporalFilter(); final TemporalGeometricPrimitive templateTime = treatEventTimeRequest(currentVersion, times, template, localOmFilter); //we treat the restriction on the feature of interest // if the request is a list of station if (!requestObservation.getFeatureIds().isEmpty()) { //verify that the station is registred in the DB. final Collection<String> fois = omReader.getFeatureOfInterestNames(); for (final String samplingFeatureName : requestObservation.getFeatureIds()) { if (!fois.contains(samplingFeatureName)) { throw new CstlServiceException("the feature of interest "+ samplingFeatureName + " is not registered", INVALID_PARAMETER_VALUE, "featureOfInterest"); } } localOmFilter.setFeatureOfInterest(requestObservation.getFeatureIds()); } // if the request is a spatial operator if (requestObservation.getSpatialFilter() != null) { // for a BBOX Spatial ops if (requestObservation.getSpatialFilter() instanceof BBOX) { final Envelope e = getEnvelopeFromBBOX(currentVersion, (BBOX)requestObservation.getSpatialFilter()); if (e != null && e.isCompleteEnvelope2D() || isCompleteEnvelope3D(e)) { boolean add = false; final List<String> matchingFeatureOfInterest = new ArrayList<>(); if (localOmFilter.isBoundedObservation()) { localOmFilter.setBoundingBox(e); } else { for (ObservationOffering off : offerings) { for (String refStation : off.getFeatureOfInterestIds()) { // TODO for SOS 2.0 use observed area final org.geotoolkit.sampling.xml.SamplingFeature station = (org.geotoolkit.sampling.xml.SamplingFeature) omReader.getFeatureOfInterest(refStation, currentVersion); if (station == null) { throw new CstlServiceException("the feature of interest is not registered", INVALID_PARAMETER_VALUE); } if (station.getGeometry() instanceof Point) { if (samplingPointMatchEnvelope((Point)station.getGeometry(), e)) { matchingFeatureOfInterest.add(getIDFromObject(station)); add = true; } else { LOGGER.log(Level.FINER, " the feature of interest {0} is not in the BBOX", getIDFromObject(station)); } } else if (station instanceof AbstractFeature) { final AbstractFeature sc = (AbstractFeature) station; if (BoundMatchEnvelope(sc, e)) { matchingFeatureOfInterest.add(sc.getId()); add = true; } } else { LOGGER.log(Level.WARNING, "unknow implementation:{0}", station.getClass().getName()); } } } if (add) { localOmFilter.setFeatureOfInterest(matchingFeatureOfInterest); // if there is no matching FOI we must return an empty result } else { return buildObservationCollection(currentVersion, "urn:ogc:def:nil:OGC:inapplicable"); } } } else { throw new CstlServiceException("the envelope is not build correctly", INVALID_PARAMETER_VALUE); } } else { throw new CstlServiceException(NOT_SUPPORTED, OPERATION_NOT_SUPPORTED); } } //TODO we treat the restriction on the result if (requestObservation.getComparisonFilter() != null) { final Filter filter = requestObservation.getComparisonFilter(); //we treat the different operation if (filter instanceof PropertyIsLessThan) { final Expression propertyName = ((PropertyIsLessThan)filter).getExpression1(); final Expression literal = ((PropertyIsLessThan)filter).getExpression2(); if (literal == null || propertyName == null) { throw new CstlServiceException(" to use the operation Less Than you must specify the propertyName and the litteral", MISSING_PARAMETER_VALUE, "lessThan"); } } else if (filter instanceof PropertyIsGreaterThan) { final Expression propertyName = ((PropertyIsGreaterThan)filter).getExpression1(); final Expression literal = ((PropertyIsGreaterThan)filter).getExpression2(); if (propertyName == null || literal == null) { throw new CstlServiceException(" to use the operation Greater Than you must specify the propertyName and the litteral", MISSING_PARAMETER_VALUE, "greaterThan"); } } else if (filter instanceof PropertyIsEqualTo) { final PropertyName propertyName = (PropertyName)((PropertyIsEqualTo)filter).getExpression1(); final Expression literal = ((PropertyIsEqualTo)filter).getExpression2(); if (propertyName == null || propertyName.getPropertyName() == null || propertyName.getPropertyName().isEmpty() || literal == null) { throw new CstlServiceException(" to use the operation Equal you must specify the propertyName and the litteral", INVALID_PARAMETER_VALUE, "propertyIsEqualTo"); // cite test } if (!localOmFilter.supportedQueryableResultProperties().isEmpty()) { localOmFilter.setResultEquals(propertyName.getPropertyName(), literal.toString()); } } else if (filter instanceof PropertyIsLike) { throw new CstlServiceException(NOT_SUPPORTED, OPERATION_NOT_SUPPORTED, "propertyIsLike"); } else if (filter instanceof PropertyIsBetween) { final PropertyIsBetween pib = (PropertyIsBetween) filter; if (pib.getExpression() == null) { throw new CstlServiceException("To use the operation Between you must specify the propertyName and the litteral", MISSING_PARAMETER_VALUE, "propertyIsBetween"); } final String propertyName = pib.getExpression().toString(); final XMLLiteral lowerLiteral = (XMLLiteral) pib.getLowerBoundary(); final XMLLiteral upperLiteral = (XMLLiteral) pib.getUpperBoundary(); if (propertyName == null || propertyName.isEmpty() || lowerLiteral == null || upperLiteral == null) { throw new CstlServiceException("This property name, lower and upper literal must be specify", INVALID_PARAMETER_VALUE, "result"); } } else { throw new CstlServiceException(NOT_SUPPORTED,OPERATION_NOT_SUPPORTED); } } if (!outOfBand) { /* * here we can have 2 different behaviour : * * (1) - We have separate observation filter and reader : * - The filter execute a request and return a list of identifiers. * - The reader retrieve each observation from the list of identifiers * * (2) - We have mixed observation filter and reader : * - The filterReader execute a request and return directly the observations * */ final List<Observation> matchingResult; final Envelope computedBounds; // case (1) if (!(localOmFilter instanceof ObservationFilterReader)) { matchingResult = new ArrayList<>(); final Set<String> observationIDs = localOmFilter.filterObservation(); for (String observationID : observationIDs) { final Observation obs = OMXmlFactory.cloneObervation(currentVersion, omReader.getObservation(observationID, resultModel, mode, currentVersion)); // parse result values to eliminate wrong results if (obs.getSamplingTime() instanceof Period) { final Timestamp tbegin; final Timestamp tend; final Period p = (Period)obs.getSamplingTime(); if (p.getBeginning() != null && p.getBeginning().getDate() != null) { tbegin = new Timestamp(p.getBeginning().getDate().getTime()); } else { tbegin = null; } if (p.getEnding() != null && p.getEnding().getDate() != null) { tend = new Timestamp(p.getEnding().getDate().getTime()); } else { tend = null; } if (obs.getResult() instanceof DataArrayProperty) { final DataArray array = ((DataArrayProperty)obs.getResult()).getDataArray(); final Values result = getResultValues(tbegin, tend, array, times); array.setValues(result.values.toString()); array.setElementCount(result.nbBlock); } } matchingResult.add(obs); } Collections.sort(matchingResult, new ObservationComparator()); computedBounds = null; // case (2) } else { final ObservationFilterReader omFR = (ObservationFilterReader) localOmFilter; if (template) { matchingResult = omFR.getObservationTemplates(currentVersion); } else { matchingResult = omFR.getObservations(currentVersion); } if (omFR.computeCollectionBound()) { computedBounds = omFR.getCollectionBoundingShape(); } else { computedBounds = null; } } final List<Observation> observations = new ArrayList<>(); for (Observation o : matchingResult) { if (template) { final String temporaryTemplateId = o.getName().getCode() + '-' + getTemplateSuffix(o.getName().getCode()); final AbstractObservation temporaryTemplate = ((AbstractObservation) o).getTemporaryTemplate(temporaryTemplateId, templateTime); // Remove the default templateTime if (!localOmFilter.isDefaultTemplateTime() && templateTime == null) { temporaryTemplate.emptySamplingTime(); } templates.put(temporaryTemplateId, temporaryTemplate); // we launch a timer which will destroy the template in one hours final Timer t = new Timer(); //we get the date and time for now final Date d = new Date(System.currentTimeMillis() + templateValidTime); LOGGER.log(logLevel, "this template will be destroyed at:{0}", d.toString()); t.schedule(new DestroyTemplateTask(temporaryTemplateId), d); schreduledTask.add(t); observations.add(temporaryTemplate); } else { observations.add(o); } } // this is a little hack for cite test dummy srsName comparaison String srsName = "urn:ogc:def:crs:EPSG::4326"; if ("EPSG:4326".equals(requestObservation.getSrsName())) { srsName ="EPSG:4326"; } final Envelope envelope; if (computedBounds == null) { envelope = getCollectionBound(currentVersion, observations, srsName); } else { LOGGER.log(Level.FINER, "Using computed bounds:{0}", computedBounds); envelope = computedBounds; } ObservationCollection ocResponse = buildGetObservationResponse(currentVersion, "collection-1", envelope, observations); ocResponse = regroupObservation(currentVersion, envelope, ocResponse); ocResponse = normalizeDocument(currentVersion, ocResponse); response = ocResponse; } else { final Object sReponse; if (localOmFilter instanceof ObservationFilterReader) { sReponse = ((ObservationFilterReader)localOmFilter).getOutOfBandResults(); } else { throw new CstlServiceException("Out of band response mode has been implemented only for ObservationFilterReader for now", NO_APPLICABLE_CODE, RESPONSE_MODE); } response = sReponse; } } catch (DataStoreException ex) { if (ex instanceof ObservationStoreException) { final ObservationStoreException oex = (ObservationStoreException) ex; throw new CstlServiceException(ex, oex.getExceptionCode(), oex.getLocator()); } else { throw new CstlServiceException(ex); } } LOGGER.log(logLevel, "getObservation processed in {0}ms.\n", (System.currentTimeMillis() - start)); return response; } private Envelope getEnvelopeFromBBOX(final String version, final BBOX bbox) { return buildEnvelope(version, null, bbox.getMinX(), bbox.getMinY(), bbox.getMaxX(), bbox.getMaxY(), bbox.getSRS()); } /** * Web service operation * @param request * @throws org.constellation.ws.CstlServiceException */ public GetResultResponse getResult(final GetResult request) throws CstlServiceException { LOGGER.log(logLevel, "getResult request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); final String observationTemplateID = request.getObservationTemplateId(); final List<String> fois = new ArrayList<>(); ObservationOffering offering = null; final String procedure; final String observedProperty; final TemporalObject time; final QName resultModel; final String values; try { // we clone the filter for this request final ObservationFilter localOmFilter = omFactory.cloneObservationFilter(omFilter); if (observationTemplateID != null) { final Observation template = templates.get(observationTemplateID); if (template == null) { throw new CstlServiceException("this template does not exist or is no longer usable", INVALID_PARAMETER_VALUE, "ObservationTemplateId"); } procedure = ((Process) template.getProcedure()).getHref(); time = template.getSamplingTime(); observedProperty = null; final String foi = SOSUtils.extractFOID(template); if (foi != null) { fois.add(foi); } if (template instanceof Measurement) { resultModel = MEASUREMENT_QNAME; } else { resultModel = OBSERVATION_QNAME; } } else if (currentVersion.equals("1.0.0")){ throw new CstlServiceException("ObservationTemplateID must be specified", MISSING_PARAMETER_VALUE, "ObservationTemplateId"); } else { if (request.getOffering() == null || request.getOffering().isEmpty()) { throw new CstlServiceException("The offering parameter must be specified", MISSING_PARAMETER_VALUE, "offering"); } else { offering = omReader.getObservationOffering(request.getOffering(), currentVersion); if (offering== null) { throw new CstlServiceException("The offering parameter is invalid", INVALID_PARAMETER_VALUE, "offering"); } procedure = offering.getProcedures().get(0); } if (request.getObservedProperty() == null || request.getObservedProperty().isEmpty()) { throw new CstlServiceException("The observedProperty parameter must be specified", MISSING_PARAMETER_VALUE, "observedProperty"); } else { if (!omReader.existPhenomenon(request.getObservedProperty())) { throw new CstlServiceException("The observedProperty parameter is invalid", INVALID_PARAMETER_VALUE, "observedProperty"); } observedProperty = request.getObservedProperty(); } time = null; resultModel = OBSERVATION_QNAME; fois.addAll(request.getFeatureOfInterest()); } //we begin to create the sql request localOmFilter.initFilterGetResult(procedure, resultModel); // phenomenon property if (observedProperty != null) { localOmFilter.setObservedProperties(Arrays.asList(observedProperty)); } // offering if (offering != null) { localOmFilter.setOfferings(Arrays.asList(offering)); } // spatial filter // if the request is a spatial operator if (request.getSpatialFilter() != null) { // for a BBOX Spatial ops if (request.getSpatialFilter() instanceof BBOX) { final Envelope e = getEnvelopeFromBBOX(currentVersion, (BBOX)request.getSpatialFilter()); if (e != null && e.isCompleteEnvelope2D()) { if (localOmFilter.isBoundedObservation()) { localOmFilter.setBoundingBox(e); } else { for (String refStation : offering.getFeatureOfInterestIds()) { // TODO for SOS 2.0 use observed area final org.geotoolkit.sampling.xml.SamplingFeature station = (org.geotoolkit.sampling.xml.SamplingFeature) omReader.getFeatureOfInterest(refStation, currentVersion); if (station == null) { throw new CstlServiceException("the feature of interest is not registered", INVALID_PARAMETER_VALUE); } if (station.getGeometry() instanceof Point) { if (samplingPointMatchEnvelope((Point)station.getGeometry(), e)) { fois.add(getIDFromObject(station)); } else { LOGGER.log(Level.FINER, " the feature of interest {0} is not in the BBOX", getIDFromObject(station)); } } else if (station instanceof AbstractFeature) { final AbstractFeature sc = (AbstractFeature) station; if (BoundMatchEnvelope(sc, e)) { fois.add(sc.getId()); } } else { LOGGER.log(Level.WARNING, "unknow implementation:{0}", station.getClass().getName()); } } } } else { throw new CstlServiceException("the envelope is not build correctly", INVALID_PARAMETER_VALUE); } } else { throw new CstlServiceException(NOT_SUPPORTED, OPERATION_NOT_SUPPORTED); } } //foi filter if (!fois.isEmpty()) { localOmFilter.setFeatureOfInterest(fois); } //we treat the time constraint final List<Filter> times = request.getTemporalFilter(); /** * The template time : */ // case TEquals with time instant if (time instanceof Instant) { final TEquals equals = buildTimeEquals(currentVersion, null, time); times.add(equals); } else if (time instanceof Period) { final Period tp = (Period) time; //case TBefore if (TimeIndeterminateValueType.BEFORE.equals(((GmlInstant)tp.getBeginning()).getTimePosition().getIndeterminatePosition())) { final Before before = buildTimeBefore(currentVersion, null, tp.getEnding()); times.add(before); //case TAfter } else if (TimeIndeterminateValueType.NOW.equals((((GmlInstant)tp.getEnding()).getTimePosition()).getIndeterminatePosition())) { final After after = buildTimeAfter(currentVersion, null, tp.getBeginning()); times.add(after); //case TDuring/TEquals (here the sense of T_Equals with timePeriod is lost but not very usefull) } else { final During during = buildTimeDuring(currentVersion, null, tp); times.add(during); } } //we treat the time constraint treatEventTimeRequest(currentVersion, times, false, localOmFilter); //we prepare the response document if (localOmFilter instanceof ObservationFilterReader) { values = ((ObservationFilterReader)localOmFilter).getResults(); } else { final List<ObservationResult> results = localOmFilter.filterResult(); final StringBuilder datablock = new StringBuilder(); for (ObservationResult result: results) { final Timestamp tBegin = result.beginTime; final Timestamp tEnd = result.endTime; final Object r = omReader.getResult(result.resultID, resultModel, currentVersion); if (r instanceof DataArray || r instanceof DataArrayProperty) { final DataArray array; if (r instanceof DataArrayProperty) { array = ((DataArrayProperty)r).getDataArray(); } else { array = (DataArray)r; } if (array != null) { final Values resultValues = getResultValues(tBegin, tEnd, array, times); final String brutValues = resultValues.values.toString(); if (!brutValues.isEmpty()) { datablock.append(brutValues); } } else { throw new IllegalArgumentException("Array is null"); } } else if (r instanceof Measure) { final Measure meas = (Measure) r; datablock.append(tBegin).append(',').append(meas.getValue()).append("@@"); } else { throw new IllegalArgumentException("Unexpected result type:" + r); } } values = datablock.toString(); } } catch (DataStoreException ex) { throw new CstlServiceException(ex); } final String url = getServiceUrl().substring(0, getServiceUrl().length() -1); final GetResultResponse response = buildGetResultResponse(currentVersion, values, url + '/' + observationTemplateID); LOGGER.log(logLevel, "GetResult processed in {0} ms", (System.currentTimeMillis() - start)); return response; } public AbstractFeature getFeatureOfInterest(final GetFeatureOfInterest request) throws CstlServiceException { verifyBaseRequest(request, true, false); LOGGER.log(logLevel, "GetFeatureOfInterest request processing\n"); final long start = System.currentTimeMillis(); final String currentVersion = request.getVersion().toString(); // if there is no filter we throw an exception v 1.0.0 if (currentVersion.equals("1.0.0") && request.getTemporalFilters().isEmpty() && request.getFeatureOfInterestId().isEmpty() && request.getSpatialFilters().isEmpty()) { throw new CstlServiceException("You must choose a filter parameter: eventTime, featureId or location", MISSING_PARAMETER_VALUE); } AbstractFeature result = null; try { // we clone the filter for this request final ObservationFilter localOmFilter = omFactory.cloneObservationFilter(omFilter); localOmFilter.initFilterGetFeatureOfInterest(); // filtering on time treatEventTimeRequest(currentVersion, request.getTemporalFilters(), false, localOmFilter); boolean ofilter = false; if (request.getObservedProperty() != null && !request.getObservedProperty().isEmpty()) { for (String observedProperty : request.getObservedProperty()) { // CITE if (observedProperty.isEmpty()) { throw new CstlServiceException("The observedProperty name is empty", MISSING_PARAMETER_VALUE, "observedProperty"); } else if (!omReader.existPhenomenon(observedProperty)){ throw new CstlServiceException("This observedProperty is not registered", INVALID_PARAMETER_VALUE, "observedProperty"); } } localOmFilter.setObservedProperties(request.getObservedProperty()); ofilter = true; } if (request.getProcedure() != null && !request.getProcedure().isEmpty()) { for (String procedure : request.getProcedure()) { // CITE if (procedure.isEmpty()) { throw new CstlServiceException("The procedure name is empty", MISSING_PARAMETER_VALUE, PROCEDURE); } else if (!omReader.existProcedure(procedure)){ throw new CstlServiceException("This procedure is not registered", INVALID_PARAMETER_VALUE, PROCEDURE); } } localOmFilter.setProcedure(request.getProcedure(), null); ofilter = true; } boolean filter = false; // we return a single result final String locatorFID; if (currentVersion.equals("2.0.0")) { locatorFID = "featureOfInterest"; } else { locatorFID = "featureOfInterestId"; } if (request.getFeatureOfInterestId().size() == 1) { // CITE if (request.getFeatureOfInterestId().get(0).isEmpty()) { throw new CstlServiceException("The foi name is empty", MISSING_PARAMETER_VALUE, locatorFID); } final SamplingFeature singleResult = omReader.getFeatureOfInterest(request.getFeatureOfInterestId().get(0), currentVersion); if (singleResult == null) { throw new CstlServiceException("There is no such Feature Of Interest", INVALID_PARAMETER_VALUE, locatorFID); } else { if (!alwaysFeatureCollection) { return (AbstractFeature) singleResult; } else { final List<FeatureProperty> features = new ArrayList<>(); features.add(buildFeatureProperty(currentVersion, singleResult)); final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; filter = true; } } // we return a featureCollection } else if (request.getFeatureOfInterestId().size() > 1) { final List<FeatureProperty> features = new ArrayList<>(); for (String featureID : request.getFeatureOfInterestId()) { final SamplingFeature feature = omReader.getFeatureOfInterest(featureID, currentVersion); if (feature == null) { throw new CstlServiceException("There is no such Feature Of Interest", INVALID_PARAMETER_VALUE, locatorFID); } else { features.add(buildFeatureProperty(currentVersion, feature)); } } final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; filter = true; } if (request.getSpatialFilters() != null && !request.getSpatialFilters().isEmpty()) { final Filter spatialFilter = request.getSpatialFilters().get(0); // TODO handle multiple filters (SOS 2.0.0) if (spatialFilter instanceof BBOX) { final BBOX bboxFilter = (BBOX) spatialFilter; // CITE if (bboxFilter.getPropertyName() == null || bboxFilter.getPropertyName().isEmpty()) { final String locator; if (currentVersion.equals("2.0.0")) { locator = "ValueReference"; } else { locator = "propertyName"; } throw new CstlServiceException("The spatial filter property name is empty", MISSING_PARAMETER_VALUE, locator); } final List<SamplingFeature> results = spatialFiltering(bboxFilter, currentVersion); // we return a single result if (results.size() == 1) { result = (AbstractFeature) results.get(0); // we return a feature collection } else if (results.size() > 1) { final List<FeatureProperty> features = new ArrayList<>(); for (SamplingFeature feature : results) { features.add(buildFeatureProperty(currentVersion, feature)); } final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; // if there is no response we send an error } else { //throw new CstlServiceException("There is no such Feature Of Interest", INVALID_PARAMETER_VALUE); result = buildFeatureCollection(currentVersion, "feature-collection-empty", null, null, null); } } else { throw new CstlServiceException("Only the filter BBOX is upported for now", OPERATION_NOT_SUPPORTED); } filter = true; } if (ofilter) { if (localOmFilter instanceof ObservationFilterReader) { final List<FeatureProperty> features = new ArrayList<>(); final List<SamplingFeature> sfeatures = ((ObservationFilterReader)localOmFilter).getFeatureOfInterests(currentVersion); for (SamplingFeature sf : sfeatures) { features.add(buildFeatureProperty(currentVersion, sf)); } final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; } else { final List<FeatureProperty> features = new ArrayList<>(); final Set<String> fid = localOmFilter.filterFeatureOfInterest(); for (String foid : fid) { final SamplingFeature feature = omReader.getFeatureOfInterest(foid, currentVersion); features.add(buildFeatureProperty(currentVersion, feature)); } final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; } // request for all foi } else if (!filter) { final List<FeatureProperty> features = new ArrayList<>(); for (String foid : omReader.getFeatureOfInterestNames()) { final SamplingFeature feature = omReader.getFeatureOfInterest(foid, currentVersion); features.add(buildFeatureProperty(currentVersion, feature)); } final FeatureCollection collection = buildFeatureCollection(currentVersion, "feature-collection-1", null, null, features); collection.computeBounds(); result = collection; } } catch (DataStoreException ex) { throw new CstlServiceException(ex); } LOGGER.log(logLevel, "GetFeatureOfInterest processed in {0}ms", (System.currentTimeMillis() - start)); return result; } public TemporalPrimitive getFeatureOfInterestTime(final GetFeatureOfInterestTime request) throws CstlServiceException { LOGGER.log(logLevel, "GetFeatureOfInterestTime request processing\n"); final long start = System.currentTimeMillis(); verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); final String fid = request.getFeatureOfInterestId(); // if there is no filter we throw an exception if (fid == null || fid.isEmpty()) { throw new CstlServiceException("You must specify a samplingFeatureId", MISSING_PARAMETER_VALUE); } final TemporalPrimitive result; try { if (omReader.getFeatureOfInterestNames().contains(fid)) { result = omReader.getFeatureOfInterestTime(fid, currentVersion); } else { throw new CstlServiceException("there is not such samplingFeature on the server", INVALID_PARAMETER_VALUE); } } catch (DataStoreException ex) { throw new CstlServiceException(ex); } LOGGER.log(logLevel, "GetFeatureOfInterestTime processed in {0} ms", (System.currentTimeMillis() - start)); return result; } public InsertResultTemplateResponse insertResultTemplate(final InsertResultTemplate request) throws CstlServiceException { LOGGER.log(logLevel, "InsertResultTemplate request processing\n"); final long start = System.currentTimeMillis(); verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); if (request.getTemplate() == null) { throw new CstlServiceException("ResultTemplate must be specified", MISSING_PARAMETER_VALUE, "proposedTemplate"); } // verify the validity of the template if (request.getTemplate().getObservationTemplate() == null) { throw new CstlServiceException("ResultTemplate must contains observationTemplate", MISSING_PARAMETER_VALUE, "observationTemplate"); } if (request.getTemplate().getOffering() == null) { throw new CstlServiceException("ResultTemplate must contains offering", MISSING_PARAMETER_VALUE, "offering"); } if (request.getTemplate().getResultEncoding() == null) { throw new CstlServiceException("ResultTemplate must contains resultEncoding", MISSING_PARAMETER_VALUE, "resultEncoding"); } if (request.getTemplate().getResultStructure() == null) { throw new CstlServiceException("ResultTemplate must contains resultStructure", MISSING_PARAMETER_VALUE, "resultStructure"); } final String templateID = observationTemplateIdBase + UUID.randomUUID(); resultTemplates.put(templateID, request.getTemplate()); final InsertResultTemplateResponse result = buildInsertResultTemplateResponse(currentVersion, templateID); LOGGER.log(logLevel, "InsertResultTemplate processed in {0} ms", (System.currentTimeMillis() - start)); return result; } public InsertResultResponse insertResult(final InsertResult request) throws CstlServiceException { LOGGER.log(logLevel, "InsertResult request processing\n"); final long start = System.currentTimeMillis(); verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); final String templateID = request.getTemplate(); if (templateID == null || templateID.isEmpty()) { throw new CstlServiceException("template ID missing.", MISSING_PARAMETER_VALUE, "template"); } final ResultTemplate template = resultTemplates.get(templateID); if (template == null) { throw new CstlServiceException("template ID is invalid:" + templateID, INVALID_PARAMETER_VALUE, "template"); } final AbstractObservation obs = (AbstractObservation) template.getObservationTemplate(); final AbstractEncoding encoding = template.getResultEncoding(); final String values = request.getResultValues(); if (values == null || values.isEmpty()) { throw new CstlServiceException("ResultValues is empty", MISSING_PARAMETER_VALUE, "resultValues"); } if (!(template.getResultStructure() instanceof DataRecord)) { throw new CstlServiceException("Only DataRecord is supported for a resultStructure"); } final DataRecord structure = (DataRecord) template.getResultStructure(); int count = 0; if (encoding instanceof TextBlock) { final TextBlock textEnc = ((TextBlock)encoding); final String separator = textEnc.getBlockSeparator(); count = values.split(separator).length; // verify the structure final StringTokenizer tokenizer = new StringTokenizer(values, textEnc.getBlockSeparator()); while (tokenizer.hasMoreTokens()) { final String block = tokenizer.nextToken(); final int nbToken = block.split(textEnc.getTokenSeparator()).length; if (nbToken != structure.getField().size()) { throw new CstlServiceException("ResultValues is empty", INVALID_PARAMETER_VALUE, "resultValues"); } } } final DataArrayProperty array = buildDataArrayProperty(currentVersion, null, count, null, structure, encoding, values); try { obs.setName(null);//omReader.getNewObservationId()); obs.setResult(array); obs.setSamplingTimePeriod(extractTimeBounds(currentVersion, values, encoding)); omWriter.writeObservation(obs); omFilter.refresh(); } catch (DataStoreException ex) { throw new CstlServiceException(ex); } final InsertResultResponse result = buildInsertResultResponse(currentVersion); LOGGER.log(logLevel, "InsertResult processed in {0} ms", (System.currentTimeMillis() - start)); return result; } public GetResultTemplateResponse getResultTemplate(final GetResultTemplate request) throws CstlServiceException { LOGGER.log(logLevel, "GetResultTemplate request processing\n"); final long start = System.currentTimeMillis(); verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); if (request.getOffering() == null || request.getOffering().isEmpty()) { throw new CstlServiceException("offering parameter is missing.", MISSING_PARAMETER_VALUE, "offering"); } final AbstractDataComponent structure; final AbstractEncoding encoding; try { final ObservationOffering offering = omReader.getObservationOffering(request.getOffering(), currentVersion); if (offering == null) { throw new CstlServiceException("offering parameter is invalid.", INVALID_PARAMETER_VALUE, "offering"); } // we clone the filter for this request final ObservationFilter localOmFilter = omFactory.cloneObservationFilter(omFilter); localOmFilter.initFilterObservation(RESULT_TEMPLATE, OBSERVATION_QNAME); localOmFilter.setProcedure(offering.getProcedures(), Arrays.asList(offering)); if (request.getObservedProperty() == null || request.getObservedProperty().isEmpty()) { throw new CstlServiceException("observedProperty parameter is missing.", MISSING_PARAMETER_VALUE, "observedProperty"); } if (!omReader.existPhenomenon(request.getObservedProperty())) { throw new CstlServiceException(" this phenomenon " + request.getObservedProperty() + " is not registred in the database!", INVALID_PARAMETER_VALUE, "observedProperty"); } localOmFilter.setObservedProperties(Arrays.asList(request.getObservedProperty())); final List<Observation> matchingResult; // case (1) if (!(localOmFilter instanceof ObservationFilterReader)) { matchingResult = new ArrayList<>(); final Set<String> observationIDs = localOmFilter.filterObservation(); for (String observationID : observationIDs) { matchingResult.add(omReader.getObservation(observationID, OBSERVATION_QNAME, RESULT_TEMPLATE, currentVersion)); } // case (2) } else { final ObservationFilterReader omFR = (ObservationFilterReader) localOmFilter; matchingResult = omFR.getObservationTemplates(currentVersion); } if (matchingResult.isEmpty()) { throw new CstlServiceException("there is no result template matching the arguments"); } else { if (matchingResult.size() > 1) { LOGGER.warning("more than one result for resultTemplate"); } final Object result = matchingResult.get(0).getResult(); if (result instanceof DataArrayProperty) { final DataArray array = ((DataArrayProperty)result).getDataArray(); structure = array.getPropertyElementType().getAbstractRecord(); encoding = array.getEncoding(); } else { throw new CstlServiceException("unable to extract structure and encoding for other result type than DataArrayProperty"); } } } catch (DataStoreException ex) { throw new CstlServiceException(ex); } final GetResultTemplateResponse result = buildGetResultTemplateResponse(currentVersion, structure, encoding); LOGGER.log(logLevel, "InsertResult processed in {0} ms", (System.currentTimeMillis() - start)); return result; } private List<SamplingFeature> spatialFiltering(final BBOX bbox, final String currentVersion) throws DataStoreException, CstlServiceException { final Envelope e = getEnvelopeFromBBOX(currentVersion, bbox); if (e != null && e.isCompleteEnvelope2D()) { final List<SamplingFeature> matchingFeatureOfInterest = new ArrayList<>(); final List<ObservationOffering> offerings = omReader.getObservationOfferings(currentVersion); for (ObservationOffering off : offerings) { // TODO for SOS 2.0 use observed area for (String refStation : off.getFeatureOfInterestIds()) { final org.geotoolkit.sampling.xml.SamplingFeature station = (org.geotoolkit.sampling.xml.SamplingFeature) omReader.getFeatureOfInterest(refStation, currentVersion); if (station == null) { LOGGER.log(Level.WARNING, "the feature of interest is not registered:{0}", refStation); continue; } if (station.getGeometry() instanceof Point) { if (samplingPointMatchEnvelope((Point)station.getGeometry(), e)) { matchingFeatureOfInterest.add(station); } else { LOGGER.log(Level.FINER, " the feature of interest {0} is not in the BBOX", getIDFromObject(station)); } } else if (station instanceof AbstractFeature) { if (BoundMatchEnvelope((AbstractFeature) station, e)) { matchingFeatureOfInterest.add(station); } else { LOGGER.log(Level.FINER, " the feature of interest {0} is not in the BBOX", getIDFromObject(station)); } } else { LOGGER.log(Level.WARNING, "unknow implementation:{0}", station.getClass().getName()); } } } return matchingFeatureOfInterest; } else { throw new CstlServiceException("the envelope is not build correctly", INVALID_PARAMETER_VALUE); } } /** * Web service operation which register a Sensor in the SensorML database, * and initialize its observation by adding an observation template in the O&M database. * * @param request A request containing a SensorML File describing a Sensor, * and an observation template for this sensor. */ public InsertSensorResponse registerSensor(final InsertSensor request) throws CstlServiceException { if (profile == DISCOVERY) { throw new CstlServiceException("The operation registerSensor is not supported by the service", INVALID_PARAMETER_VALUE, "request"); } if (isTransactionSecurized() && !SecurityManagerHolder.getInstance().isAuthenticated()) { throw new UnauthorizedException("You must be authentified to perform an registerSensor request."); } LOGGER.log(logLevel, "registerSensor request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); if (currentVersion.equals("2.0.0")) { if (request.getProcedureDescriptionFormat() == null) { throw new CstlServiceException("Procedure description format must be specified" , MISSING_PARAMETER_VALUE, PROCEDURE_DESCRIPTION_FORMAT); } else if (!acceptedSensorMLFormats.get("2.0.0").contains(request.getProcedureDescriptionFormat())){ throw new CstlServiceException("This procedure description format is not supported" , INVALID_PARAMETER_VALUE, PROCEDURE_DESCRIPTION_FORMAT); } } // verify the sensorMetadata if (request.getInsertionMetadata() instanceof SosInsertionMetadata) { final SosInsertionMetadata insMetadata = (SosInsertionMetadata) request.getInsertionMetadata(); for (String foiType : insMetadata.getFeatureOfInterestType()) { if (foiType == null || foiType.isEmpty()) { throw new CstlServiceException("The feature Of Interest type is missing.", MISSING_PARAMETER_VALUE, "featureOfInterestType"); } else if (!SUPPORTED_FOI_TYPES.contains(foiType)) { throw new CstlServiceException("The feature Of Interest type is not supported.", INVALID_PARAMETER_VALUE, "featureOfInterestType"); } } for (String obsType : insMetadata.getObservationType()) { if (obsType == null || obsType.isEmpty()) { throw new CstlServiceException("The observation type is missing.", MISSING_PARAMETER_VALUE, "observationType"); } else if (!SUPPORTED_OBS_TYPES.contains(obsType)) { throw new CstlServiceException("The observation type is not supported.", INVALID_PARAMETER_VALUE, "observationType"); } } } boolean success = false; String id = ""; String assignedOffering = null; try { //we begin a transaction smlWriter.startTransaction(); //we get the SensorML file who describe the Sensor to insert. final Object d = request.getSensorDescription(); AbstractSensorML process; if (d instanceof AbstractSensorML) { process = (AbstractSensorML) d; } else { String type = "null"; if (d != null) { type = d.getClass().getName(); } throw new CstlServiceException("unexpected type for process: " + type , INVALID_PARAMETER_VALUE, "sensorDescription"); } //we get the observation template provided with the sensor description. final ObservationTemplate temp = request.getObservationTemplate(); if (temp == null || !temp.isTemplateSpecified()) { throw new CstlServiceException("observation template must be specify", MISSING_PARAMETER_VALUE, OBSERVATION_TEMPLATE); } else if (!temp.isComplete()) { throw new CstlServiceException("observation template must specify at least the following fields: procedure ,observedProperty ,featureOfInterest, Result", INVALID_PARAMETER_VALUE, OBSERVATION_TEMPLATE); } //we create a new Identifier from the SensorML database final String smlExtractedIdentifier = SensorMLUtilities.getSmlID(process); String num = ""; if (temp.getProcedure() != null) { id = temp.getProcedure(); if (id.startsWith(sensorIdBase)) { num = id.substring(sensorIdBase.length()); } else { num = id; } LOGGER.log(logLevel, "using specified sensor ID:{0} num ={1}", new Object[]{id, num}); } else if (!smlExtractedIdentifier.equals("unknow_identifier")){ num = smlExtractedIdentifier; id = smlExtractedIdentifier; LOGGER.log(logLevel, "using extracted sensor ID:{0} num ={1}", new Object[]{id, num}); } else { num = Integer.toString(smlWriter.getNewSensorId()); id = sensorIdBase + num; } /* * @TODO * * here we affect the new Sensor id to the metatadata * does we have to keep the one of the metadata instead of generating one? */ if (process.getMember().size() == 1) { process.getMember().get(0).getRealProcess().setId(id); } else { LOGGER.warning("multiple SensorML member"); } //and we write it in the sensorML Database smlWriter.writeSensor(id, process); // and we record the position of the piezometer final AbstractGeometry position = getSensorPosition(process); if (omWriter != null) { //we assign the new capteur id to the observation template temp.setProcedure(id); temp.setName(observationTemplateIdBase + num); //we write the observation template in the O&M database omWriter.writeObservationTemplate(temp); omWriter.recordProcedureLocation(id, position); assignedOffering = addSensorToOffering(num, temp, currentVersion); } else { LOGGER.warning("unable to record Sensor template and location in O&M datasource: no O&M writer"); } success = true; } catch(DataStoreException ex) { throw new CstlServiceException(ex); } finally { if (!success) { smlWriter.abortTransaction(); LOGGER.warning("Transaction failed"); } else { smlWriter.endTransaction(); } } LOGGER.log(logLevel, "registerSensor processed in {0}ms", (System.currentTimeMillis() - start)); return buildInsertSensorResponse(currentVersion, id, assignedOffering); } /** * Web service operation which insert a new Observation for the specified sensor * in the O&M database. * * @param request an InsertObservation request containing an O&M object and a Sensor id. * @throws CstlServiceException */ public InsertObservationResponse insertObservation(final InsertObservation request) throws CstlServiceException { if (profile == DISCOVERY) { throw new CstlServiceException("The operation insertObservation is not supported by the service", INVALID_PARAMETER_VALUE, "request"); } if (isTransactionSecurized() && !SecurityManagerHolder.getInstance().isAuthenticated()) { throw new UnauthorizedException("You must be authentified to perform an insertObservation request."); } LOGGER.log(logLevel, "InsertObservation request processing\n"); final long start = System.currentTimeMillis(); //we verify the base request attribute verifyBaseRequest(request, true, false); final String currentVersion = request.getVersion().toString(); final List<String> ids = new ArrayList<>(); try { //we get the id of the sensor and we create a sensor object final String sensorId = request.getAssignedSensorId(); if (currentVersion.equals("1.0.0")) { if (sensorId == null) { throw new CstlServiceException("The sensor identifier is missing.", MISSING_PARAMETER_VALUE, "assignedSensorId"); } } else { final List<String> offeringNames = request.getOffering(); if (offeringNames == null || offeringNames.isEmpty()) { throw new CstlServiceException("The offering identifiers are missing.", MISSING_PARAMETER_VALUE, "offering"); } else { final ObservationOffering off = omReader.getObservationOffering(offeringNames.get(0), currentVersion); if (off != null) { // TODO } else { throw new CstlServiceException("The offering identifier is invalid.", INVALID_PARAMETER_VALUE, "offering"); } } } //we get the observation and we assign to it the sensor final List<? extends Observation> observations = request.getObservations(); for (Observation observation : observations) { final AbstractObservation obs = (AbstractObservation) observation; if (obs != null) { obs.setProcedure(sensorId); obs.setName(null);//omReader.getNewObservationId()); LOGGER.log(Level.FINER, "samplingTime received: {0}", obs.getSamplingTime()); LOGGER.log(Level.FINER, "template received:\n{0}", obs.toString()); } else { throw new CstlServiceException("The observation template must be specified", MISSING_PARAMETER_VALUE, OBSERVATION_TEMPLATE); } // Debug part if (verifySynchronization) { if (obs.getSamplingTime() instanceof Instant) { final Instant timeInstant = (Instant) obs.getSamplingTime(); try { final ISODateParser parser = new ISODateParser(); final Date d = parser.parseToDate(timeInstant.getDate().toString()); final long t = System.currentTimeMillis() - d.getTime(); LOGGER.info("gap between time of reception and time of sampling: " + t + " ms (" + TemporalUtilities.durationToString(t) + ')'); } catch (IllegalArgumentException ex) { LOGGER.warning("unable to parse the samplingTime"); } } } //we record the observation in the O&M database final String id; if (currentVersion.equals("2.0.0")) { id = omWriter.writeObservation(obs); } else { if (obs instanceof Measurement) { id = omWriter.writeObservation(obs); } else { //in first we verify that the observation is conform to the template final Observation template = (Observation) omReader.getTemplateForProcedure(sensorId, currentVersion); //if the observation to insert match the template we can insert it in the OM db if (obs.matchTemplate(template)) { if (obs.getSamplingTime() != null && obs.getResult() != null) { id = omWriter.writeObservation(obs); LOGGER.log(logLevel, "new observation inserted: id = " + id + " for the sensor " + obs.getProcedure()); } else { throw new CstlServiceException("The observation sampling time and the result must be specify", MISSING_PARAMETER_VALUE, "samplingTime"); } } else { throw new CstlServiceException(" The observation doesn't match with the template of the sensor", INVALID_PARAMETER_VALUE, "samplingTime"); } } } ids.add(id); } LOGGER.log(logLevel, "insertObservation processed in {0} ms", (System.currentTimeMillis() - start)); omFilter.refresh(); } catch (DataStoreException ex) { throw new CstlServiceException(ex); } return buildInsertObservationResponse(currentVersion, ids); } /** * * * @param times A list of time constraint. * @param SQLrequest A stringBuilder building the SQL request. * * @return true if there is no errors in the time constraint else return false. */ private TemporalGeometricPrimitive treatEventTimeRequest(final String version, final List<Filter> times, final boolean template, final ObservationFilter localOmFilter) throws CstlServiceException, DataStoreException { //In template mode his method return a temporal Object. TemporalGeometricPrimitive templateTime = null; for (Filter time: times) { // The operation Time Equals if (time instanceof TEquals) { final TEquals filter = (TEquals) time; // we get the property name (not used for now) //String propertyName = time.getTEquals().getPropertyName(); final Object timeFilter = filter.getExpression2(); /*look for "latest" or "getFirst" filter (52N compatibility) if (timeFilter instanceof Instant){ final Instant ti = (Instant) timeFilter; if (ti.getPosition() != null && ti.getPosition().getDateTime() != null && ti.getPosition().getDateTime().toString().equalsIgnoreCase("latest")) { if (!template) { localOmFilter.setTimeLatest(); continue; } else { LOGGER.warning("latest time are not handled with template mode"); } } if (ti.getPosition() != null && ti.getPosition().getDateTime() != null && ti.getPosition().getDateTime().toString().equalsIgnoreCase("getFirst")) { if (!template) { localOmFilter.setTimeFirst(); continue; } else { LOGGER.warning("getFirst time are not handled with template mode"); } } }*/ if (!template) { localOmFilter.setTimeEquals(timeFilter); } else if (timeFilter instanceof TemporalGeometricPrimitive) { templateTime = (TemporalGeometricPrimitive) timeFilter; } else { throw new CstlServiceException("TM_Equals operation require timeInstant or TimePeriod!", INVALID_PARAMETER_VALUE, EVENT_TIME); } // The operation Time before } else if (time instanceof Before) { final Before filter = (Before) time; // we get the property name (not used for now) // String propertyName = time.getTBefore().getPropertyName(); final Object timeFilter = filter.getExpression2(); if (!template) { localOmFilter.setTimeBefore(timeFilter); } else if (timeFilter instanceof Instant) { final Instant ti = (Instant)timeFilter; templateTime = buildTimePeriod(version, TimeIndeterminateValueType.BEFORE, ti.getDate()); } else { throw new CstlServiceException("TM_Before operation require timeInstant!", INVALID_PARAMETER_VALUE, EVENT_TIME); } // The operation Time after } else if (time instanceof After) { final After filter = (After) time; // we get the property name (not used for now) //String propertyName = time.getTAfter().getPropertyName(); final Object timeFilter = filter.getExpression2(); if (!template) { localOmFilter.setTimeAfter(timeFilter); } else if (timeFilter instanceof Instant) { final Instant ti = (Instant)timeFilter; templateTime = buildTimePeriod(version, ti.getDate(), TimeIndeterminateValueType.NOW); } else { throw new CstlServiceException("TM_After operation require timeInstant!", INVALID_PARAMETER_VALUE, EVENT_TIME); } // The time during operation } else if (time instanceof During) { final During filter = (During) time; // we get the property name (not used for now) //String propertyName = time.getTDuring().getPropertyName(); final Object timeFilter = filter.getExpression2(); if (!template) { localOmFilter.setTimeDuring(timeFilter); } if (timeFilter instanceof Period) { templateTime = (Period)timeFilter; } else { throw new CstlServiceException("TM_During operation require TimePeriod!", INVALID_PARAMETER_VALUE, EVENT_TIME); } } else if (time instanceof Begins|| time instanceof BegunBy || time instanceof TContains ||time instanceof EndedBy || time instanceof Ends || time instanceof Meets || time instanceof TOverlaps|| time instanceof OverlappedBy) { throw new CstlServiceException("This operation is not take in charge by the Web Service, supported one are: TM_Equals, TM_After, TM_Before, TM_During", OPERATION_NOT_SUPPORTED); } else { throw new CstlServiceException("Unknow time filter operation, supported one are: TM_Equals, TM_After, TM_Before, TM_During.\n" + "Another possibility is that the content of your time filter is empty or unrecognized.", OPERATION_NOT_SUPPORTED); } } return templateTime; } /** * Verify that the bases request attributes are correct. */ private void verifyBaseRequest(final RequestBase request, final boolean versionMandatory, final boolean getCapabilities) throws CstlServiceException { isWorking(); if (request != null) { if (request.getService() != null && !request.getService().isEmpty()) { if (!request.getService().equals(SOS)) { throw new CstlServiceException("service must be \"SOS\"!", INVALID_PARAMETER_VALUE, SERVICE_PARAMETER_LC); } } else { throw new CstlServiceException("service must be specified!", MISSING_PARAMETER_VALUE, SERVICE_PARAMETER_LC); } if (request.getVersion()!= null && !request.getVersion().toString().isEmpty()) { if (isSupportedVersion(request.getVersion().toString())) { request.setVersion(request.getVersion().toString()); } else { final CodeList code; final String locator; if (getCapabilities) { code = VERSION_NEGOTIATION_FAILED; locator = "acceptVersion"; } else { code = INVALID_PARAMETER_VALUE; locator = "version"; } final StringBuilder sb = new StringBuilder(); for (ServiceDef v : supportedVersions) { sb.append("\"").append(v.version.toString()).append("\""); } throw new CstlServiceException("version must be " + sb.toString() + "!", code, locator); } } else { if (versionMandatory) { throw new CstlServiceException("version must be specified!", MISSING_PARAMETER_VALUE, "version"); } else { request.setVersion(getBestVersion(null).version.toString()); } } } else { throw new CstlServiceException("The request is null!", NO_APPLICABLE_CODE); } } /** * Find a new suffix to obtain a unic temporary template id. * * @param templateName the full name of the sensor template. * * @return an integer to paste after the template name; */ private int getTemplateSuffix(final String templateName) { int i = 0; boolean notFound = true; while (notFound) { if (templates.containsKey(templateName + '-' + i)) { i++; } else { notFound = false; } } return i; } /** * Add the new Sensor to an offering specified in the network attribute of sensorML file. * if the offering doesn't yet exist in the database, it will be create. * * @param sensor A sensorML object describing the sensor. * @param template The observation template for this sensor. * * @throws CstlServiceException If an error occurs during the the storage of offering in the datasource. */ private String addSensorToOffering(final String num, final ObservationTemplate template, final String version) throws CstlServiceException, DataStoreException { final String offeringName = "offering-" + num; final ObservationOffering offering = omReader.getObservationOffering(offeringName, version); if (offering != null) { updateOffering(offering, template); } else { createOffering(version, offeringName, template); } /* * then we add the sensor to the global offering containing all the sensor (v 1.0.0) * TODO remove ? */ if (version.equals("1.0.0")) { final ObservationOffering offeringAll = omReader.getObservationOffering("offering-allSensor", version); if (offeringAll != null) { updateOffering(offeringAll, template); } else { createOffering(version, "allSensor", template); } } return offeringName; } /** * Update an offering by adding to it the phenomenons, procedures and features of interest. * * @param offering The offering to update * @param template An Observation template. * * @throws CstlServiceException If the service does not succeed to update the offering in the datasource. */ private void updateOffering(final ObservationOffering offering, final ObservationTemplate template) throws DataStoreException { //we add the new sensor to the offering String offProc = null; final String processID = template.getProcedure(); if (!offering.getProcedures().contains(processID)) { offProc = processID; } //we add the phenomenon to the offering List<String> offPheno = new ArrayList<>(); if (template.getObservedProperties() != null) { for (String observedProperty : template.getObservedProperties()) { if (!offering.getObservedProperties().contains(observedProperty)) { offPheno.add(observedProperty); } } } // we add the feature of interest (station) to the offering String offSF = null; if (template.getFeatureOfInterest() != null) { if (!offering.getFeatureOfInterestIds().contains(template.getFeatureOfInterest())) { offSF = template.getFeatureOfInterest(); } } omWriter.updateOffering(offering.getId(), offProc, offPheno, offSF); } /** * Create a new Offering with the specified observation template * * @param offeringName the name of the new offering (not including offering base name). * @param template An observation template used as a base for the offering. * * @throws CstlServiceException If the service does not succeed to store the offering in the datasource. */ private void createOffering(final String version, final String offeringName, final ObservationTemplate template) throws DataStoreException { LOGGER.log(logLevel, "offering {0} not present, first build", offeringName); // TODO bounded by??? station? // for the eventime of the offering we take the time of now. final Timestamp t = new Timestamp(System.currentTimeMillis()); final Period time = buildTimePeriod(version, null, t.toString(), null); //we add the template process final String process = template.getProcedure(); //we add the template phenomenon final List<String> observedProperties = template.getObservedProperties(); final List<PhenomenonProperty> observedPropertiesV100 = template.getFullObservedProperties(); //we add the template feature of interest final String featureOfInterest = template.getFeatureOfInterest(); //we create a list of accepted responseMode (fixed) final List<ResponseModeType> responses = Arrays.asList(RESULT_TEMPLATE, INLINE); final List<QName> resultModel = Arrays.asList(OBSERVATION_QNAME, MEASUREMENT_QNAME); final List<String> resultModelV200 = Arrays.asList(OBSERVATION_MODEL); final List<String> offeringOutputFormat = Arrays.asList("text/xml; subtype=\"om/1.0.0\""); final List<String> srsName = Arrays.asList("EPSG:4326"); String description = ""; if ("allSensor".equals(offeringName)) { description = "Base offering containing all the sensors."; } // we create a the new Offering omWriter.writeOffering(buildOffering(version, OFFERING_ID_BASE + offeringName, OFFERING_ID_BASE + offeringName, description, srsName, time, Arrays.asList(process), observedPropertiesV100, observedProperties, Arrays.asList(featureOfInterest), offeringOutputFormat, resultModel, resultModelV200, responses, acceptedSensorMLFormats.get(version))); } /** * Redirect the logs into the specified folder. * if the parameter ID is null or empty it create a file named "cstl-sos.log" * else the file is named "ID-cstl-sos.log" * * @param ID The ID of the service in a case of multiple sos server. * @param filePath The path to the log folder. */ private void initLogger(String id, final String filePath) { try { if (id != null && !id.isEmpty()) { id = id + '-'; } final FileHandler handler = new FileHandler(filePath + '/'+ id + "cstl-sos.log"); handler.setFormatter(new MonolineFormatter(handler)); LOGGER.addHandler(handler); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "IO exception while trying to separate CSW Logs:{0}", ex.getMessage()); } catch (SecurityException ex) { LOGGER.log(Level.SEVERE, "Security exception while trying to separate CSW Logs{0}", ex.getMessage()); } } /** * Destroy and free the resource used by the worker. */ @Override public void destroy() { super.destroy(); if (smlReader != null) {smlReader.destroy();} if (smlWriter != null) {smlWriter.destroy();} if (omReader != null) {omReader.destroy();} if (omWriter != null) {omWriter.destroy();} if (omFilter != null) {omFilter.destroy();} for (Timer t : schreduledTask) { t.cancel(); } startError = "The service has been shutdown"; isStarted = false; } @Override public final void setLogLevel(final Level logLevel) { this.logLevel = logLevel; if (omFilter != null) { omFilter.setLoglevel(logLevel); } } /** * {@inheritDoc} */ @Override protected MarshallerPool getMarshallerPool() { return SOSMarshallerPool.getInstance(); } @Override protected final String getProperty(final String propertyName) { if (configuration != null) { return configuration.getParameter(propertyName); } return null; } /** * A task destroying a observation template when the template validity period pass. */ class DestroyTemplateTask extends TimerTask { /** * The identifier of the temporary template. */ private final String templateId; /** * Build a new Timer which will destroy the temporaryTemplate * * @param templateId The identifier of the temporary template. */ public DestroyTemplateTask(final String templateId) { this.templateId = templateId; } /** * This method is launch when the timer expire. */ @Override public void run() { templates.remove(templateId); LOGGER.log(logLevel, "template:{0} destroyed", templateId); } } }