/* * 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.rest.api; import static org.constellation.utils.RESTfulUtilities.ok; import static org.geotoolkit.sml.xml.SensorMLUtilities.getChildrenIdentifiers; import static org.geotoolkit.sml.xml.SensorMLUtilities.getSensorMLType; import static org.geotoolkit.sml.xml.SensorMLUtilities.getSmlID; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.logging.Logging; import org.constellation.business.IProviderBusiness; import org.constellation.business.ISensorBusiness; import org.constellation.configuration.AcknowlegementType; import org.constellation.configuration.ConfigDirectory; import org.constellation.configuration.ConfigurationException; import org.constellation.configuration.StringList; import org.constellation.dto.ParameterValues; import org.constellation.dto.SensorMLTree; import org.constellation.dto.SimpleValue; import org.constellation.database.api.jooq.tables.pojos.CstlUser; import org.constellation.database.api.jooq.tables.pojos.Data; import org.constellation.database.api.jooq.tables.pojos.Provider; import org.constellation.database.api.jooq.tables.pojos.Sensor; import org.constellation.database.api.repository.UserRepository; import org.constellation.json.metadata.binding.RootObj; import org.constellation.json.metadata.v2.Template; import org.constellation.provider.DataProviders; import org.constellation.provider.coveragestore.CoverageStoreProvider; import org.constellation.provider.observationstore.ObservationStoreProvider; import org.constellation.sos.configuration.SensorMLGenerator; import org.constellation.sos.ws.SOSUtils; import org.constellation.util.Util; import org.geotoolkit.observation.ObservationReader; import org.geotoolkit.sml.xml.AbstractSensorML; import org.geotoolkit.sml.xml.SensorMLMarshallerPool; import org.geotoolkit.sos.netcdf.ExtractionResult.ProcedureTree; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; import org.opengis.referencing.operation.TransformException; import org.opengis.util.FactoryException; import org.springframework.stereotype.Component; import com.google.common.base.Optional; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.io.WKTWriter; /** * * @author Guilhem Legal (Geomatys) * @author Cédric Briançon (Geomatys) */ @Component @Path("/1/sensor/") public class SensorRest { private static final Logger LOGGER = Logging.getLogger("org.constellation.rest.api"); @Inject private ISensorBusiness sensorBusiness; @Inject private IProviderBusiness providerBusiness; /** * Injected user repository. */ @Inject private UserRepository userRepository; @GET @Path("list") @Produces({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON}) public Response getSensorList() { final SensorMLTree result = getFullSensorMLTree(); return Response.ok(result).build(); } private SensorMLTree getFullSensorMLTree() { final List<SensorMLTree> values = new ArrayList<>(); final List<Sensor> sensors = sensorBusiness.getAll(); for (final Sensor sensor : sensors) { final Optional<CstlUser> optUser = userRepository.findById(sensor.getOwner()); String owner = null; if(optUser!=null && optUser.isPresent()){ final CstlUser user = optUser.get(); if(user != null){ owner = user.getLogin(); } } final SensorMLTree t = new SensorMLTree(sensor.getIdentifier(), sensor.getType(), owner, sensor.getDate()); final List<SensorMLTree> children = new ArrayList<>(); final List<Sensor> records = sensorBusiness.getChildren(sensor); for (final Sensor record : records) { final Optional<CstlUser> optUserChild = userRepository.findById(sensor.getOwner()); String ownerChild = null; if(optUserChild!=null && optUserChild.isPresent()){ final CstlUser user = optUserChild.get(); if(user != null){ ownerChild = user.getLogin(); } } children.add(new SensorMLTree(record.getIdentifier(), record.getType(), ownerChild, record.getDate())); } t.setChildren(children); values.add(t); } return SensorMLTree.buildTree(values); } @DELETE @Path("{sensorid}") public Response deleteSensor(@PathParam("sensorid") String sensorid) { sensorBusiness.delete(sensorid); return Response.ok().type(MediaType.TEXT_PLAIN_TYPE).build(); } @GET @Path("{sensorid}") public Response getSensorMetadata(@PathParam("sensorid") String sensorid) { final Sensor record = sensorBusiness.getSensor(sensorid); if (record != null) { final AbstractSensorML sml; try { sml = SOSUtils.unmarshallSensor(record.getMetadata()); return ok(sml); } catch (JAXBException | DataStoreException ex) { LOGGER.log(Level.WARNING, "error while unmarshalling SensorML", ex); return Response.status(500).entity("failed").build(); } } else { return Response.status(404).build(); } } /** * Returns applied template for metadata sensorML for read mode only like metadata viewer. * for reference (consult) purposes only. * * @param sensorid given sensor identifier. * @param type sensor type system or component. * @param prune flag that indicates if template result will clean empty children/block. * @return {@code Response} */ @GET @Path("metadataJson/{sensorid}/{type}/{prune}") @Produces({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON}) public Response getSensorMetadataJson(final @PathParam("sensorid") String sensorid, final @PathParam("type") String type, final @PathParam("prune") boolean prune) { final Sensor record = sensorBusiness.getSensor(sensorid); if (record != null) { try { final AbstractSensorML sml = SOSUtils.unmarshallSensor(record.getMetadata()); final StringWriter buffer = new StringWriter(); if (sml != null) { //get template name final String templateName; if("system".equalsIgnoreCase(type)){ templateName="profile_sensorml_system"; }else if ("component".equalsIgnoreCase(type)){ templateName="profile_sensorml_component"; } else { templateName="profile_sensorml_system"; } final Template template = Template.getInstance(templateName); template.write(sml,buffer,prune, false); } return Response.ok(buffer.toString()).build(); } catch (Exception ex) { LOGGER.log(Level.WARNING, "error while writing metadata sensorML to json.", ex); return Response.status(500).entity("failed").build(); } } else { return Response.status(500).entity("There is no sensor for id "+sensorid).build(); } } /** * Proceed to save sensorML with given values from metadata editor. * * @param sensorid the data provider identifier * @param type the data type. * @param metadataValues the values of metadata editor. * @return {@code Response} */ @POST @Path("metadata/save/{sensorid}/{type}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response saveSensorML(@PathParam("sensorid") final String sensorid, @PathParam("type") final String type, final RootObj metadataValues) { try { final Sensor record = sensorBusiness.getSensor(sensorid); if (record != null) { final AbstractSensorML sml = SOSUtils.unmarshallSensor(record.getMetadata()); if (sml != null) { //get template name final String templateName; if("system".equalsIgnoreCase(type)){ templateName="profile_sensorml_system"; }else if ("component".equalsIgnoreCase(type)){ templateName="profile_sensorml_component"; } else { templateName="profile_sensorml_system"; } final Template template = Template.getInstance(templateName); template.read(metadataValues,sml,false); final String xml = marshallSensor(sml); record.setMetadata(xml); //Save sensorML sensorBusiness.update(record); } } else { return Response.status(500).entity("There is no sensor for id "+sensorid).build(); } } catch (Exception ex) { LOGGER.log(Level.WARNING,"Error while saving sensorML",ex); return Response.status(500).entity(ex.getLocalizedMessage()).build(); } return Response.ok().type(MediaType.TEXT_PLAIN_TYPE).build(); } /** * Return as an attachment file the metadata of sensor in xml format. * @param sensorid given sensor identifier. * @return the xml file */ @GET @Path("metadata/download/{sensorid}") @Produces(MediaType.APPLICATION_XML) public Response downloadMetadataForSensor(@PathParam("sensorid") final String sensorid) { try{ final Sensor record = sensorBusiness.getSensor(sensorid); if (record != null) { final String sensorML = record.getMetadata(); return Response.ok(sensorML, MediaType.APPLICATION_XML_TYPE) .header("Content-Disposition", "attachment; filename=\"" + sensorid + ".xml\"").build(); } }catch(Exception ex){ LOGGER.log(Level.WARNING, "Failed to get xml metadata for sensor with identifier "+sensorid,ex); return Response.status(500).entity("failed").build(); } return Response.status(500).entity("failed").build(); } @PUT @Path("generate") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response generateSensor(final ParameterValues pv) { final String providerId = pv.get("providerId"); final QName dataId = Util.parseQName(pv.get("dataId")); final org.constellation.provider.Provider provider = DataProviders.getInstance().getProvider(providerId); final List<ProcedureTree> procedures; try { if (provider instanceof ObservationStoreProvider) { final ObservationStoreProvider omProvider = (ObservationStoreProvider) provider; procedures = omProvider.getObservationStore().getProcedures(); } else if (provider instanceof CoverageStoreProvider) { final CoverageStoreProvider covProvider = (CoverageStoreProvider) provider; if (covProvider.isSensorAffectable()) { procedures = covProvider.getObservationStore().getProcedures(); } else { return ok(new AcknowlegementType("Failure", "Only available on netCDF file for coverage for now")); } } else { return ok(new AcknowlegementType("Failure", "Available only on Observation provider (and netCDF coverage) for now")); } } catch (DataStoreException ex) { LOGGER.log(Level.WARNING, "Error while reading netCDF", ex); return ok(new AcknowlegementType("Failure", "Error while reading netCDF")); } // SensorML generation try { for (ProcedureTree process : procedures) { generateSensorML(dataId, providerId, process, null); } } catch (SQLException ex) { LOGGER.log(Level.WARNING, "Error while writng sensorML", ex); return ok(new AcknowlegementType("Failure", "SQLException while writing sensorML")); } return ok(new AcknowlegementType("Success", "The sensors has been succesfully generated")); } private void generateSensorML(final QName dataID, final String providerID, final ProcedureTree process, final String parentID) throws SQLException { final Properties prop = new Properties(); prop.put("id", process.id); if (process.spatialBound.dateStart != null) { prop.put("beginTime", process.spatialBound.dateStart); } if (process.spatialBound.dateEnd != null) { prop.put("endTime", process.spatialBound.dateEnd); } if (process.spatialBound.minx != null) { prop.put("longitude", process.spatialBound.minx); } if (process.spatialBound.miny != null) { prop.put("latitude", process.spatialBound.miny); } prop.put("phenomenon", process.fields); Sensor sensor = sensorBusiness.getSensor(process.id); if (sensor == null) { sensor = sensorBusiness.create(process.id, process.type, parentID, null, System.currentTimeMillis()); } final List<String> component = new ArrayList<>(); for (ProcedureTree child : process.children) { component.add(child.id); generateSensorML(dataID, providerID, child, process.id); } prop.put("component", component); final String sml = SensorMLGenerator.getTemplateSensorMLString(prop, process.type); // update sml sensor.setMetadata(sml); sensorBusiness.update(sensor); sensorBusiness.linkDataToSensor(dataID, providerID, sensor.getIdentifier()); } /** * Receive a {@link org.glassfish.jersey.media.multipart.MultiPart} which contain a file need to be save on server. * Then proceed to import sensor file into database. * * @param fileIs * @param fileDetail * @param request * @return A {@link Response} with 200 code if upload work, 500 if not work. */ @POST @Path("upload") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response uploadSensor(@FormDataParam("data") InputStream fileIs, @FormDataParam("data") FormDataContentDisposition fileDetail, @Context HttpServletRequest request) { final String sessionId = request.getSession().getId(); final File uploadDirectory = ConfigDirectory.getUploadDirectory(sessionId); final String fileName = fileDetail.getFileName(); final File newFileData = new File(uploadDirectory, fileName); try { if (fileIs != null) { if (!uploadDirectory.exists()) { uploadDirectory.mkdir(); } Files.copy(fileIs, newFileData.toPath(), StandardCopyOption.REPLACE_EXISTING); fileIs.close(); } } catch (IOException ex) { LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex); return Response.status(500).entity("failed").build(); } //proceed to import sensor final List<Sensor> sensorsImported; try { sensorsImported = proceedToImportSensor(newFileData.getAbsolutePath()); }catch (JAXBException ex) { LOGGER.log(Level.WARNING, "Error while reading sensorML file", ex); return Response.status(500).entity("fail to read sensorML file").build(); } return Response.ok(sensorsImported).build(); } @PUT @Path("add") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response importSensor(final ParameterValues pv) { final String path = pv.get("path"); final List<Sensor> sensorsImported; try{ sensorsImported = proceedToImportSensor(path); } catch (JAXBException ex) { LOGGER.log(Level.WARNING, "Error while reading sensorML file", ex); return Response.status(500).entity("fail to read sensorML file").build(); } return Response.ok(sensorsImported).build(); } /** * Import sensorML file and Returns a list of sensor described into this sensorML. * * @param path to the file sensorML * @return list of Sensor * @throws JAXBException */ private List<Sensor> proceedToImportSensor(final String path) throws JAXBException { final List<Sensor> sensorsImported = new ArrayList<>(); final File imported = new File(path); //@FIXME treat zip case if (imported.isDirectory()) { final Map<String, List<String>> parents = new HashMap<>(); final List<File> files = getFiles(imported); for (final File f : files) { final AbstractSensorML sml = unmarshallSensor(f); final String type = getSensorMLType(sml); final String sensorID = getSmlID(sml); final List<String> children = getChildrenIdentifiers(sml); final Sensor sensor = sensorBusiness.create(sensorID, type, null, marshallSensor(sml), System.currentTimeMillis()); sensorsImported.add(sensor); parents.put(sensorID, children); } // update dependencies for (final Entry<String, List<String>> entry : parents.entrySet()) { for (final String child : entry.getValue()) { final Sensor childRecord = sensorBusiness.getSensor(child);//ConfigurationEngine.getSensor(child); childRecord.setParent(entry.getKey()); sensorBusiness.update(childRecord); } } } else { final AbstractSensorML sml = unmarshallSensor(imported); final String type = getSensorMLType(sml); final String sensorID = getSmlID(sml); final Sensor sensor = sensorBusiness.create(sensorID, type, null, marshallSensor(sml), System.currentTimeMillis()); sensorsImported.add(sensor); } return sensorsImported; } @GET @Path("observedProperties/identifiers") public Response getObservedPropertiesIds() throws Exception { try { final Set<String> phenomenons = new HashSet<>(); final List<Sensor> records = sensorBusiness.getAll(); for (Sensor record : records) { final List<Data> datas = sensorBusiness.getLinkedData(record); // look for provider ids final Set<String> providerIDs = new HashSet<>(); for (Data data : datas) { final Provider provider = providerBusiness.getProvider(data.getProvider()); providerIDs.add(provider.getIdentifier()); } for (String providerId : providerIDs) { final ObservationReader reader = getObservationReader(providerId); phenomenons.addAll(reader.getPhenomenonNames()); } } return ok(new StringList(phenomenons)); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } @GET @Path("sensors/identifiers/{observedProperty}") public Response getSensorIdsForObservedProperty(final @PathParam("observedProperty") String observedProperty) throws Exception { try { final Set<String> sensorIDS = new HashSet<>(); final List<Sensor> records = sensorBusiness.getAll(); for (Sensor record : records) { final List<Data> datas = sensorBusiness.getLinkedData(record); // look for provider ids final Set<String> providerIDs = new HashSet<>(); for (Data data : datas) { final Provider provider = providerBusiness.getProvider(data.getProvider()); providerIDs.add(provider.getIdentifier()); } for (String providerId : providerIDs) { final ObservationReader reader = getObservationReader(providerId); if (reader.existPhenomenon(observedProperty)) { sensorIDS.addAll(reader.getProcedureNames()); } } } return ok(new StringList(sensorIDS)); } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } @GET @Path("observedProperty/identifiers/{sensorID}") public Response getObservedPropertiesForSensor(final @PathParam("sensorID") String sensorID) throws ConfigurationException { try { final Sensor sensor = sensorBusiness.getSensor(sensorID); if (sensor != null) { final Set<String> phenomenons = new HashSet<>(); final List<Data> datas = sensorBusiness.getLinkedData(sensor); // look for provider ids final Set<String> providerIDs = new HashSet<>(); for (Data data : datas) { final Provider provider = providerBusiness.getProvider(data.getProvider()); providerIDs.add(provider.getIdentifier()); } for (String providerId : providerIDs) { final ObservationReader reader = getObservationReader(providerId); phenomenons.addAll(reader.getPhenomenonNames()); } return ok(new StringList(phenomenons)); } else { return Response.status(404).build(); } } catch (DataStoreException ex) { throw new ConfigurationException(ex); } } @GET @Path("location/{sensorID}") public Response getWKTSensorLocation(final @PathParam("sensorID") String sensorID) throws ConfigurationException { try { final Sensor sensor = sensorBusiness.getSensor(sensorID); if (sensor != null) { final List<Data> datas = sensorBusiness.getLinkedData(sensor); // look for provider ids final Set<String> providerIDs = new HashSet<>(); for (Data data : datas) { final Provider provider = providerBusiness.getProvider(data.getProvider()); providerIDs.add(provider.getIdentifier()); } final List<Geometry> jtsGeometries = new ArrayList<>(); final SensorMLTree root = getFullSensorMLTree(); final SensorMLTree current = root.find(sensorID); for (String providerId : providerIDs) { final ObservationReader reader = getObservationReader(providerId); jtsGeometries.addAll(SOSUtils.getJTSGeometryFromSensor(current, reader)); } if (jtsGeometries.size() == 1) { final WKTWriter writer = new WKTWriter(); return ok(new SimpleValue(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 ok(new SimpleValue(writer.write(coll))); } return ok(new SimpleValue("")); } else { return Response.status(404).build(); } } catch (DataStoreException | FactoryException | TransformException ex) { throw new ConfigurationException(ex); } } private ObservationReader getObservationReader(final String providerID) throws ConfigurationException { final org.constellation.provider.Provider provider = DataProviders.getInstance().getProvider(providerID); if (provider instanceof ObservationStoreProvider) { final ObservationStoreProvider omProvider = (ObservationStoreProvider) provider; return omProvider.getObservationStore().getReader(); } else if (provider instanceof CoverageStoreProvider) { final CoverageStoreProvider covProvider = (CoverageStoreProvider) provider; if (covProvider.isSensorAffectable()) { return covProvider.getObservationStore().getReader(); } else { throw new ConfigurationException("Only available on netCDF file for coverage for now"); } } else { throw new ConfigurationException("Available only on Observation provider (and netCDF coverage) for now"); } } private List<File> getFiles(final File directory) { final List<File> results = new ArrayList<>(); if (directory.isDirectory()) { for (File f : directory.listFiles()) { if (f.isDirectory()) { results.addAll(getFiles(f)); } else { results.add(f); } } } else { results.add(directory); } return results; } private static AbstractSensorML unmarshallSensor(final File f) throws JAXBException { final Unmarshaller um = SensorMLMarshallerPool.getInstance().acquireUnmarshaller(); Object obj = um.unmarshal(f); SensorMLMarshallerPool.getInstance().recycle(um); if (obj instanceof JAXBElement) { obj = ((JAXBElement)obj).getValue(); } if (obj instanceof AbstractSensorML) { return (AbstractSensorML)obj; } return null; } private static String marshallSensor(final AbstractSensorML f) throws JAXBException { final Marshaller m = SensorMLMarshallerPool.getInstance().acquireMarshaller(); final StringWriter sw = new StringWriter(); m.marshal(f, sw); SensorMLMarshallerPool.getInstance().recycle(m); return sw.toString(); } }