/* * 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.configuration; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.io.WKTWriter; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.imageio.spi.ServiceRegistry; import javax.xml.bind.JAXBException; import org.apache.sis.storage.DataStoreException; import org.constellation.configuration.AcknowlegementType; import org.constellation.configuration.ConfigurationException; import org.constellation.configuration.DataSourceType; import org.constellation.configuration.Instance; import org.constellation.configuration.SOSConfiguration; import org.constellation.dto.SensorMLTree; import org.constellation.metadata.io.MetadataIoException; import org.constellation.ogc.configuration.OGCConfigurer; 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.SOSConstants; import org.constellation.sos.ws.SOSUtils; import org.constellation.ws.CstlServiceException; import org.geotoolkit.factory.FactoryNotFoundException; import org.geotoolkit.gml.xml.AbstractGeometry; import org.geotoolkit.gml.xml.v321.TimeInstantType; import org.geotoolkit.gml.xml.v321.TimePeriodType; import org.geotoolkit.observation.ObservationFilterReader; import org.constellation.sos.io.ObservationReader; import org.constellation.sos.io.ObservationWriter; import org.geotoolkit.observation.xml.AbstractObservation; import org.geotoolkit.sml.xml.AbstractSensorML; import static org.geotoolkit.sml.xml.SensorMLUtilities.getSensorMLType; import static org.geotoolkit.sml.xml.SensorMLUtilities.getSmlID; import org.geotoolkit.util.FileUtilities; import org.opengis.observation.Observation; import org.opengis.observation.ObservationCollection; import org.opengis.observation.Phenomenon; import org.opengis.referencing.operation.TransformException; import org.opengis.temporal.Instant; import org.opengis.temporal.Period; import org.opengis.temporal.TemporalGeometricPrimitive; import org.opengis.util.FactoryException; /** * {@link org.constellation.configuration.ServiceConfigurer} implementation for SOS service. * * TODO: implement specific configuration methods * * @author Fabien Bernard (Geomatys). * @version 0.9 * @since 0.9 */ public class SOSConfigurer extends OGCConfigurer { @Override public Instance getInstance(final String spec, final String identifier) throws ConfigurationException { final Instance instance = super.getInstance(spec, identifier); try { instance.setLayersNumber(getSensorIds(identifier).size()); } catch (ConfigurationException ex) { LOGGER.log(Level.WARNING, "Error while getting metadata count on CSW instance:" + identifier, ex); } return instance; } public AcknowlegementType importSensor(final String id, final File sensorFile, final String type) throws ConfigurationException { LOGGER.info("Importing sensor"); final SensorWriter writer = getSensorWriter(id); final List<File> files; switch (type) { case "zip": try { final FileInputStream fis = new FileInputStream(sensorFile); files = FileUtilities.unZipFileList(fis); fis.close(); } catch (IOException ex) { throw new ConfigurationException(ex); } break; case "xml": files = Arrays.asList(sensorFile); break; default: throw new ConfigurationException("Unexpected file extension, accepting zip or xml"); } try { for (File importedFile: files) { if (importedFile != null) { final AbstractSensorML sensor = SOSUtils.unmarshallSensor(importedFile); final String sensorID = getSmlID(sensor); writer.writeSensor(sensorID, sensor); } else { throw new ConfigurationException("An imported file is null"); } } return new AcknowlegementType("Success", "The specified sensor have been imported in the SOS"); } catch (JAXBException ex) { LOGGER.log(Level.WARNING, "Exception while unmarshalling imported file", ex); } catch (DataStoreException | CstlServiceException ex) { throw new ConfigurationException(ex); } return new AcknowlegementType("Error", "An error occurs during the process"); } public AcknowlegementType importSensor(final String id, final AbstractSensorML sensor, final String sensorID) throws ConfigurationException { LOGGER.info("Importing sensor"); final SensorWriter writer = getSensorWriter(id); try { writer.writeSensor(sensorID, sensor); return new AcknowlegementType("Success", "The specified sensor have been imported in the SOS"); } catch (CstlServiceException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType removeSensor(final String id, final String sensorID) throws ConfigurationException { final SensorWriter smlWriter = getSensorWriter(id); final SensorReader smlReader = getSensorReader(id); final ObservationWriter omWriter = getObservationWriter(id); try { final SensorMLTree root = getSensorTree(id); final SensorMLTree tree = root.find(sensorID); // for a System sensor, we delete also his components final List<String> toRemove = new ArrayList<>(); if (tree != null) { toRemove.addAll(tree.getAllChildrenIds()); } else { // tree should no be null toRemove.add(sensorID); } for (String sid : toRemove) { smlWriter.deleteSensor(sid); omWriter.removeProcedure(sid); } // if the sensor has a System parent, we must update his component list if (tree != null && tree.getParent() != null) { final String parentID = tree.getParent().getId(); if (!"root".equals(parentID)) { final AbstractSensorML sml = smlReader.getSensor(parentID); SOSUtils.removeComponent(sml, sensorID); smlWriter.replaceSensor(parentID, sml); } } return new AcknowlegementType("Success", "The specified sensor have been removed in the SOS"); } catch (CstlServiceException | DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType removeAllSensors(final String id) throws ConfigurationException { final SensorWriter smlWriter = getSensorWriter(id); final ObservationWriter omWriter = getObservationWriter(id); try { final Collection<String> sensorNames = getSensorIds(id); for (String sensorID : sensorNames) { boolean sucess = smlWriter.deleteSensor(sensorID); if (sucess) { omWriter.removeProcedure(sensorID); } else { return new AcknowlegementType("Error", "Unable to remove the sensor from SML datasource:" + sensorID); } } return new AcknowlegementType("Success", "The specified sensor have been removed in the SOS"); } catch (CstlServiceException | DataStoreException ex) { throw new ConfigurationException(ex); } } public SensorMLTree getSensorTree(String id) throws ConfigurationException { final SensorReader reader = getSensorReader(id); try { final Collection<String> sensorNames = reader.getSensorNames(); final List<SensorMLTree> values = new ArrayList<>(); for (String sensorID : sensorNames) { final AbstractSensorML sml = reader.getSensor(sensorID); final String smlType = getSensorMLType(sml); final String smlID = getSmlID(sml); final SensorMLTree t = new SensorMLTree(smlID, smlType, null, null); final List<SensorMLTree> children = SOSUtils.getChildren(sml); t.setChildren(children); values.add(t); } return SensorMLTree.buildTree(values); } catch (CstlServiceException ex) { throw new ConfigurationException(ex); } } public Object getSensor(final String id, final String sensorID) throws ConfigurationException { final SensorReader reader = getSensorReader(id); try { return reader.getSensor(sensorID); } catch (CstlServiceException ex) { throw new ConfigurationException(ex); } } public int getSensorCount(final String id) throws ConfigurationException { final SensorReader reader = getSensorReader(id); try { return reader.getSensorCount(); } catch (CstlServiceException ex) { throw new ConfigurationException(ex); } } public Collection<String> getSensorIds(final String id) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { return reader.getProcedureNames(); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public Collection<String> getSensorIdsForObservedProperty(final String id, final String observedProperty) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { return reader.getProceduresForPhenomenon(observedProperty); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public Collection<String> getObservedPropertiesForSensorId(final String id, final String sensorID) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { final SensorMLTree root = getSensorTree(id); final SensorMLTree current = root.find(sensorID); return SOSUtils.getPhenomenonFromSensor(current, reader); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public TemporalGeometricPrimitive getTimeForSensorId(final String id, final String sensorID) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { return reader.getTimeForProcedure("2.0.0", sensorID); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType importObservations(final String id, final File observationFile) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { final Object objectFile = SOSUtils.unmarshallObservationFile(observationFile); if (objectFile instanceof AbstractObservation) { writer.writeObservation((AbstractObservation)objectFile); } else if (objectFile instanceof ObservationCollection) { importObservations(id, (ObservationCollection)objectFile); } else { return new AcknowlegementType("Failure", "Unexpected object type for observation file"); } return new AcknowlegementType("Success", "The specified observation have been imported in the SOS"); } catch (JAXBException | DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType importObservations(final String id, final ObservationCollection collection) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { final long start = System.currentTimeMillis(); writer.writeObservations(collection.getMember()); LOGGER.log(Level.INFO, "observations imported in :{0} ms", (System.currentTimeMillis() - start)); return new AcknowlegementType("Success", "The specified observations have been imported in the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType importObservations(final String id, final List<Observation> observations, final List<Phenomenon> phenomenons) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { final long start = System.currentTimeMillis(); writer.writePhenomenons(phenomenons); writer.writeObservations(observations); LOGGER.log(Level.INFO, "observations imported in :{0} ms", (System.currentTimeMillis() - start)); return new AcknowlegementType("Success", "The specified observations have been imported in the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType removeSingleObservation(final String id, final String observationID) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { writer.removeObservation(observationID); return new AcknowlegementType("Success", "The specified observation have been removed from the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType removeObservationForProcedure(final String id, final String procedureID) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { writer.removeObservationForProcedure(procedureID); return new AcknowlegementType("Success", "The specified observations have been removed from the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public Collection<String> getObservedPropertiesIds(String id) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { return reader.getPhenomenonNames(); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType writeProcedure(final String id, final String sensorID, final AbstractGeometry location, final String parent, final String type) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { writer.writeProcedure(sensorID, location, parent, type); return new AcknowlegementType("Success", "The sensor have been recorded in the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public AcknowlegementType updateSensorLocation(final String id, final String sensorID, final AbstractGeometry location) throws ConfigurationException { final ObservationWriter writer = getObservationWriter(id); try { writer.recordProcedureLocation(sensorID, location); return new AcknowlegementType("Success", "The sensor location have been updated in the SOS"); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public String getWKTSensorLocation(final String id, final String sensorID) throws ConfigurationException { final ObservationReader reader = getObservationReader(id); try { final SensorMLTree root = getSensorTree(id); final SensorMLTree current = root.find(sensorID); final List<Geometry> jtsGeometries = SOSUtils.getJTSGeometryFromSensor(current, reader); if (jtsGeometries.size() == 1) { final WKTWriter writer = new WKTWriter(); return writer.write(jtsGeometries.get(0)); } else if (!jtsGeometries.isEmpty()) { final Geometry[] geometries = jtsGeometries.toArray(new Geometry[jtsGeometries.size()]); final GeometryCollection coll = new GeometryCollection(geometries, new GeometryFactory()); final WKTWriter writer = new WKTWriter(); return writer.write(coll); } return ""; } catch (DataStoreException | FactoryException | TransformException ex) { throw new ConfigurationException(ex); } } public String getObservationsCsv(final String id, final String sensorID, final List<String> observedProperties, final Date start, final Date end) throws ConfigurationException { final ObservationFilterReader filter = getObservationFilter(id); try { filter.initFilterGetResult(sensorID, SOSConstants.OBSERVATION_QNAME); if (observedProperties.isEmpty()) { observedProperties.addAll(getObservedPropertiesForSensorId(id, sensorID)); } filter.setObservedProperties(observedProperties); filter.setResponseFormat("text/csv"); if (start != null && end != null) { final Period period = new TimePeriodType(new Timestamp(start.getTime()), new Timestamp(end.getTime())); filter.setTimeDuring(period); } else if (start != null) { final Instant time = new TimeInstantType(new Timestamp(start.getTime())); filter.setTimeAfter(time); } else if (end != null) { final Instant time = new TimeInstantType(new Timestamp(end.getTime())); filter.setTimeBefore(time); } return filter.getResults(); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public String getDecimatedObservationsCsv(final String id, final String sensorID, final List<String> observedProperties, final Date start, final Date end, final int width) throws ConfigurationException { final ObservationFilterReader filter = getObservationFilter(id); try { filter.initFilterGetResult(sensorID, SOSConstants.OBSERVATION_QNAME); if (observedProperties.isEmpty()) { observedProperties.addAll(getObservedPropertiesForSensorId(id, sensorID)); } filter.setObservedProperties(observedProperties); filter.setResponseFormat("text/csv"); if (start != null && end != null) { final Period period = new TimePeriodType(new Timestamp(start.getTime()), new Timestamp(end.getTime())); filter.setTimeDuring(period); } else if (start != null) { final Instant time = new TimeInstantType(new Timestamp(start.getTime())); filter.setTimeAfter(time); } else if (end != null) { final Instant time = new TimeInstantType(new Timestamp(end.getTime())); filter.setTimeBefore(time); } return filter.getDecimatedResults(width); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } public boolean buildDatasource(final String serviceID) throws ConfigurationException { final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final OMFactory omfactory = getOMFactory(config.getObservationWriterType()); try { return omfactory.buildDatasource(config.getOMConfiguration(), new HashMap<String, Object>()); } catch (DataStoreException ex) { LOGGER.log(Level.WARNING, "Error while building O&M datasource", ex); } } return false; } /** * Build a new Sensor writer for the specified service ID. * * @param serviceID the service identifier (form multiple SOS) default: "" * * @return A sensor Writer. * @throws ConfigurationException */ protected SensorWriter getSensorWriter(final String serviceID) throws ConfigurationException { // we get the SOS configuration file final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final SMLFactory smlfactory = getSMLFactory(config.getSMLType()); try { return smlfactory.getSensorWriter(config.getSMLType(), config.getSMLConfiguration(), new HashMap<String, Object>()); } catch (MetadataIoException ex) { throw new ConfigurationException("JAXBException while initializing the writer!", ex); } } else { throw new ConfigurationException("there is no configuration file correspounding to this ID:" + serviceID); } } /** * Build a new Sensor reader for the specified service ID. * * @param serviceID the service identifier (form multiple SOS) default: "" * * @return A sensor reader. * @throws ConfigurationException */ protected SensorReader getSensorReader(final String serviceID) throws ConfigurationException { // we get the CSW configuration file final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final SMLFactory smlfactory = getSMLFactory(config.getSMLType()); try { return smlfactory.getSensorReader(config.getSMLType(), config.getSMLConfiguration(), getProperties(config)); } catch (MetadataIoException ex) { throw new ConfigurationException("MetadataIoException while initializing the reader:" + ex.getMessage(), ex); } } else { throw new ConfigurationException("there is no configuration file correspounding to this ID:" + serviceID); } } /** * Refresh the map of configuration object. * * @param id identifier of the CSW service. * @return * @throws ConfigurationException */ protected SOSConfiguration getServiceConfiguration(final String id) throws ConfigurationException { try { // we get the SOS configuration file final SOSConfiguration config = (SOSConfiguration) serviceBusiness.getConfiguration("SOS", id); return config; } catch (ConfigurationException ex) { throw new ConfigurationException("ConfigurationException while getting the SOS configuration for:" + id, ex.getMessage()); } catch (IllegalArgumentException ex) { throw new ConfigurationException("IllegalArgumentException: " + ex.getMessage()); } } /** * Select the good SML factory in the available ones in function of the dataSource type. * * @param type * @return */ private SMLFactory getSMLFactory(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); } /** * Select the good OM factory in the available ones in function of the dataSource type. * * @param type * @return */ private OMFactory getOMFactory(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); } /** * Build a new Observation writer for the specified service ID. * * @param serviceID the service identifier (form multiple SOS) default: "" * * @return An observation Writer. * @throws ConfigurationException */ protected ObservationWriter getObservationWriter(final String serviceID) throws ConfigurationException { // we get the SOS configuration file final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final OMFactory omfactory = getOMFactory(config.getObservationWriterType()); try { return omfactory.getObservationWriter(config.getObservationWriterType(), config.getOMConfiguration(), getProperties(config)); } catch (DataStoreException ex) { throw new ConfigurationException("JAXBException while initializing the writer!", ex); } } else { throw new ConfigurationException("there is no configuration file correspounding to this ID:" + serviceID); } } /** * Build a new Observation writer for the specified service ID. * * @param serviceID the service identifier (form multiple SOS) default: "" * * @return An observation Writer. * @throws ConfigurationException */ protected ObservationReader getObservationReader(final String serviceID) throws ConfigurationException { // we get the SOS configuration file final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final OMFactory omfactory = getOMFactory(config.getObservationWriterType()); try { return omfactory.getObservationReader(config.getObservationWriterType(), config.getOMConfiguration(), getProperties(config)); } catch (DataStoreException ex) { throw new ConfigurationException("JAXBException while initializing the writer!", ex); } } else { throw new ConfigurationException("there is no configuration file correspounding to this ID:" + serviceID); } } /** * Build a new Observation writer for the specified service ID. * * @param serviceID the service identifier (form multiple SOS) default: "" * * @return An observation Writer. * @throws ConfigurationException */ protected ObservationFilterReader getObservationFilter(final String serviceID) throws ConfigurationException { // we get the SOS configuration file final SOSConfiguration config = getServiceConfiguration(serviceID); if (config != null) { final OMFactory omfactory = getOMFactory(config.getObservationWriterType()); try { return (ObservationFilterReader) omfactory.getObservationFilter(DataSourceType.OM2, config.getOMConfiguration(), getProperties(config)); } catch (DataStoreException ex) { throw new ConfigurationException("JAXBException while initializing the filter reader!", ex); } } else { throw new ConfigurationException("there is no configuration file correspounding to this ID:" + serviceID); } } private Map<String, Object> getProperties(final SOSConfiguration configuration) { //we initialize the properties attribute final String observationIdBase = configuration.getObservationIdBase() != null ? configuration.getObservationIdBase() : "urn:ogc:object:observation:unknow:"; final String sensorIdBase = configuration.getSensorIdBase() != null ? configuration.getSensorIdBase() : "urn:ogc:object:sensor:unknow:"; final String phenomenonIdBase = configuration.getPhenomenonIdBase() != null ? configuration.getPhenomenonIdBase() : "urn:ogc:def:phenomenon:OGC:1.0.30:"; final String observationTemplateIdBase = configuration.getObservationTemplateIdBase() != null ? configuration.getObservationTemplateIdBase() : "urn:ogc:object:observationTemplate:unknow:"; final boolean alwaysFeatureCollection = configuration.getBooleanParameter(OMFactory.ALWAYS_FEATURE_COLLECTION); // 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(configuration.getOMConfiguration().getCustomparameters()); properties.putAll(configuration.getSMLConfiguration().getCustomparameters()); return properties; } }