diff --git a/pom.xml b/pom.xml index d6bb97210b869193e1649933490888507e068ebb..dc4ab6601e363e42382df5858cbb61401495ade9 100755 --- a/pom.xml +++ b/pom.xml @@ -52,12 +52,6 @@ <artifactId>flyway-core</artifactId> <version>4.2.0</version> </dependency> - <dependency> - <!--groupId>com.github.bjornharrtell</groupId--> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <version>0.12.0</version> - </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-spatial</artifactId> @@ -88,6 +82,12 @@ <version>3.6.3.Final</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.jboss.resteasy</groupId> + <artifactId>resteasy-jackson2-provider</artifactId> + <version>3.6.3.Final</version> + <scope>provided</scope> + </dependency> <dependency> <groupId>no.nibio.vips.common</groupId> <artifactId>VIPSCommon</artifactId> @@ -198,6 +198,11 @@ <version>1.16.1</version> <type>jar</type> </dependency> + <dependency> + <groupId>org.wololo</groupId> + <artifactId>jts2geojson</artifactId> + <version>0.14.1</version> + </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java index 755bc567f09b2ef4f9f034ed9bfb204a365f7438..84fc06f83aeb5251fc18679a1c41eaf6b9649afc 100755 --- a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java +++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java @@ -46,6 +46,7 @@ import no.nibio.vips.logic.entity.ObservationMethod; import no.nibio.vips.logic.entity.Organism; import no.nibio.vips.logic.entity.Organization; import no.nibio.vips.logic.entity.OrganizationGroup; +import no.nibio.vips.logic.entity.PolygonService; import no.nibio.vips.logic.entity.VipsLogicRole; import no.nibio.vips.logic.entity.VipsLogicUser; import no.nibio.vips.logic.i18n.SessionLocaleUtil; @@ -234,7 +235,7 @@ public class ObservationController extends HttpServlet { Collections.sort(observations); Collections.reverse(observations); - observations.forEach(o->System.out.println(o)); + //observations.forEach(o->System.out.println(o)); List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); request.setAttribute("shortcuts", shortcuts); request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); @@ -282,7 +283,12 @@ public class ObservationController extends HttpServlet { allCrops = em.createNamedQuery("Organism.findAllCrops").getResultList(); } + // Get the polygonServices + List<PolygonService> polygonServices = SessionControllerGetter.getObservationBean().getPolygonServicesForOrganization(user.getOrganization_id()); + request.setAttribute("observation", observation); + request.setAttribute("polygonServices", polygonServices); + request.setAttribute("locationVisibilityFormValue", ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PUBLIC); request.setAttribute("shortcut", request.getParameter("observationFormShortcutId") != null ? em.find(ObservationFormShortcut.class, Integer.valueOf(request.getParameter("observationFormShortcutId"))) : null @@ -328,7 +334,10 @@ public class ObservationController extends HttpServlet { { Integer observationId = Integer.valueOf(request.getParameter("observationId")); Observation observation = SessionControllerGetter.getObservationBean().getObservation(observationId);//em.find(Observation.class, observationId); + List<PolygonService> polygonServices = SessionControllerGetter.getObservationBean().getPolygonServicesForOrganization(user.getOrganization_id()); + request.setAttribute("locationVisibilityFormValue", this.getLocationVisibilityFormValue(observation)); request.setAttribute("observation", observation); + request.setAttribute("polygonServices", polygonServices); request.setAttribute("noBroadcast", request.getParameter("noBroadcast") != null); request.setAttribute("mapLayers", SessionControllerGetter.getUserBean().getMapLayerJSONForUser(user)); //System.out.println(observation.getGeoinfo()); @@ -442,7 +451,7 @@ public class ObservationController extends HttpServlet { : formValidation.getFormField("observationData").getWebValue() ); observation.setIsQuantified(formValidation.getFormField("isQuantified").getWebValue() != null); - observation.setLocationIsPrivate(formValidation.getFormField("locationIsPrivate").getWebValue() != null); + this.setObservationLocationVisibility(observation, formValidation.getFormField("locationVisibility").getWebValue()); observation.setBroadcastMessage(formValidation.getFormField("broadcastMessage").getWebValue() != null); boolean sendNotification = false; @@ -512,7 +521,9 @@ public class ObservationController extends HttpServlet { // All transactions finished, we can send notifications // if conditions are met - if(sendNotification) + if(sendNotification && ! + (System.getProperty("DISABLE_MESSAGING_SYSTEM") != null && System.getProperty("DISABLE_MESSAGING_SYSTEM").equals("true")) + ) { SessionControllerGetter.getMessagingBean().sendUniversalMessage(observation); } @@ -670,6 +681,39 @@ public class ObservationController extends HttpServlet { } } } + + public static final String LOCATION_VISIBILITY_FORM_VALUE_PRIVATE = "private"; + public static final String LOCATION_VISIBILITY_FORM_VALUE_PUBLIC = "public"; + public static final String LOCATION_VISIBILITY_FORM_VALUE_MASK_PREFIX = "mask_"; + + private String getLocationVisibilityFormValue(Observation observation) + { + // Private is private, no matter what + if(observation.getLocationIsPrivate()) + { + return ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PRIVATE; + } + // Public can either be completely public or masked by a polygon layer + // e.g. county borders + if(observation.getPolygonService() == null) + { + return ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PUBLIC; + } + return ObservationController.LOCATION_VISIBILITY_FORM_VALUE_MASK_PREFIX + observation.getPolygonService().getPolygonServiceId(); + } + + private void setObservationLocationVisibility(Observation observation, String formValue) + { + // Private is private, no matter what + observation.setLocationIsPrivate(formValue.equals(ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PRIVATE)); + observation.setPolygonService( + // If private or public, set no polygon service + formValue.equals(ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PRIVATE) || formValue.equals(ObservationController.LOCATION_VISIBILITY_FORM_VALUE_PUBLIC) + ? null + // Otherwise, set selected polygon service + : em.find(PolygonService.class, Integer.valueOf(formValue.split("_")[1])) + ); + } // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> /** diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java index eefdc2053b7d72cc9e604cb0dc37444e786e3d05..253d85b352e8efa899724c0fdc88e187ffe75bec 100755 --- a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java @@ -49,6 +49,7 @@ import no.nibio.vips.logic.entity.ObservationStatusType; import no.nibio.vips.logic.entity.Organism; import no.nibio.vips.logic.entity.Organization; import no.nibio.vips.logic.entity.PointOfInterest; +import no.nibio.vips.logic.entity.PolygonService; import no.nibio.vips.logic.entity.VipsLogicUser; import no.nibio.vips.logic.util.SessionControllerGetter; import no.nibio.vips.logic.util.SystemTime; @@ -464,7 +465,7 @@ public class ObservationBean { .getResultList(); } - private List<Observation> getObservationsWithLocations(List<Observation> observations) { + public List<Observation> getObservationsWithLocations(List<Observation> observations) { Set<Integer> locationPointOfInterestIds = new HashSet<>(); observations.stream().filter((o) -> (o.getLocationPointOfInterestId() != null)).forEach((o) -> { locationPointOfInterestIds.add(o.getLocationPointOfInterestId()); @@ -607,7 +608,7 @@ public class ObservationBean { List<Observation> retVal = new ArrayList<>(); if(! observations.isEmpty()) { - //start = new Date(); + //Date start = new Date(); retVal = this.getObservationsWithGeoInfo(observations); //System.out.println("Finding geoinfo took " + (new Date().getTime() - start.getTime()) + " milliseconds"); //start = new Date(); @@ -742,8 +743,10 @@ public class ObservationBean { } - + public List<PolygonService> getPolygonServicesForOrganization(Integer organizationId) { + return em.createNativeQuery("SELECT * FROM polygon_service p WHERE p.polygon_service_id IN (SELECT polygon_service_id FROM public.organization_polygon_service WHERE organization_id=:organizationId)", PolygonService.class) + .setParameter("organizationId", organizationId) + .getResultList(); + } - - } diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java index 314c78198eb9093acceebbd5588d5c23e868c32d..321dfbc3c0a652ff564c95b4651f7b11a7e977d9 100755 --- a/src/main/java/no/nibio/vips/logic/entity/Observation.java +++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java @@ -104,6 +104,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse private Boolean isQuantified; private Boolean broadcastMessage; private Boolean locationIsPrivate; + private PolygonService polygonService; private String observationDataSchema; @@ -306,6 +307,17 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse this.organism = organism; } + @JoinColumn(name = "polygon_service_id", referencedColumnName = "polygon_service_id") + @ManyToOne + public PolygonService getPolygonService(){ + return this.polygonService; + } + + public void setPolygonService(PolygonService polygonService) + { + this.polygonService = polygonService; + } + /* @Override @Transient @@ -610,6 +622,11 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse this.lastEditedByUser = lastEditedByUser; } + /** + * Simplifies the public JSON object + * @param locale + * @return + */ public ObservationListItem getListItem(String locale) { // If geoInfo is from POI, need to add observationId @@ -624,7 +641,10 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse this.getOrganism().getLocalName(locale), this.getCropOrganismId(), this.getCropOrganism().getLocalName(locale), - this.location != null ? this.location.getGeoJSON() : this.getGeoinfo(), + // Specific geoInfo trumps location. This is to be interpreted + // as that the observation has been geographically masked by + // choice of the observer + this.location != null && this.geoinfo == null ? this.location.getGeoJSON() : this.getGeoinfo(), this.getObservationHeading(), this.getBroadcastMessage(), this.getLocationIsPrivate() diff --git a/src/main/java/no/nibio/vips/logic/entity/PolygonService.java b/src/main/java/no/nibio/vips/logic/entity/PolygonService.java new file mode 100644 index 0000000000000000000000000000000000000000..609bd3a7d9bd124c2f8f088a41c5c0e582c0365b --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/entity/PolygonService.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.entity; + +import java.io.Serializable; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.validation.constraints.Size; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +@Entity +@Table(name = "polygon_service") +@XmlRootElement +@NamedQueries({ + @NamedQuery(name = "PolygonService.findAll", query = "SELECT p FROM PolygonService p"), + @NamedQuery(name = "PolygonService.findByPolygonServiceId", query = "SELECT p FROM PolygonService p WHERE p.polygonServiceId = :polygonServiceId"), + @NamedQuery(name = "PolygonService.findByPolygonServiceName", query = "SELECT p FROM PolygonService p WHERE p.polygonServiceName = :polygonServiceName"), + @NamedQuery(name = "PolygonService.findByDescription", query = "SELECT p FROM PolygonService p WHERE p.description = :description"), + @NamedQuery(name = "PolygonService.findByGisSearchUrlTemplate", query = "SELECT p FROM PolygonService p WHERE p.gisSearchUrlTemplate = :gisSearchUrlTemplate"), + @NamedQuery(name = "PolygonService.findByEpsg", query = "SELECT p FROM PolygonService p WHERE p.epsg = :epsg")}) +public class PolygonService implements Serializable { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic(optional = false) + @Column(name = "polygon_service_id") + private Integer polygonServiceId; + @Size(max = 255) + @Column(name = "polygon_service_name") + private String polygonServiceName; + @Size(max = 2147483647) + @Column(name = "description") + private String description; + @Size(max = 1024) + @Column(name = "gis_search_url_template") + private String gisSearchUrlTemplate; + @Column(name = "epsg") + private Integer epsg; + + public PolygonService() { + } + + public PolygonService(Integer polygonServiceId) { + this.polygonServiceId = polygonServiceId; + } + + public Integer getPolygonServiceId() { + return polygonServiceId; + } + + public void setPolygonServiceId(Integer polygonServiceId) { + this.polygonServiceId = polygonServiceId; + } + + public String getPolygonServiceName() { + return polygonServiceName; + } + + public void setPolygonServiceName(String polygonServiceName) { + this.polygonServiceName = polygonServiceName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getGisSearchUrlTemplate() { + return gisSearchUrlTemplate; + } + + public void setGisSearchUrlTemplate(String gisSearchUrlTemplate) { + this.gisSearchUrlTemplate = gisSearchUrlTemplate; + } + + public Integer getEpsg() { + return epsg; + } + + public void setEpsg(Integer epsg) { + this.epsg = epsg; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (polygonServiceId != null ? polygonServiceId.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof PolygonService)) { + return false; + } + PolygonService other = (PolygonService) object; + if ((this.polygonServiceId == null && other.polygonServiceId != null) || (this.polygonServiceId != null && !this.polygonServiceId.equals(other.polygonServiceId))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "no.nibio.vips.logic.entity.PolygonService[ polygonServiceId=" + polygonServiceId + " ]"; + } + +} diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java index 7193b73a1c70fb53d791206fec8a2dc9ca4c9e32..41e9b94094b7ab2998f1f77f5c7c8f120feb4df0 100644 --- a/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java +++ b/src/main/java/no/nibio/vips/logic/entity/rest/ObservationListItem.java @@ -22,6 +22,7 @@ package no.nibio.vips.logic.entity.rest; import java.util.Date; /** + * A JSON friendly, public representation of the Observation * @copyright 2018 <a href="http://www.nibio.no/">NIBIO</a> * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingRequest.java b/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..3a5750ad1179630147694f74dab86fd16739d9df --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingRequest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.entity.rest; + +import java.io.Serializable; +import java.util.List; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +@XmlRootElement +public class PointMappingRequest implements Serializable{ + private List<ReferencedPoint> points; + + /** + * @return the points + */ + public List<ReferencedPoint> getPoints() { + return points; + } + + /** + * @param points the points to set + */ + public void setPoints(List<ReferencedPoint> points) { + this.points = points; + } + + +} diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingResponse.java b/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..820b483570667d02feb0cb0a8824d97c3775f072 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/entity/rest/PointMappingResponse.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.entity.rest; + +import java.util.Map; +import org.wololo.geojson.FeatureCollection; + + + + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +public class PointMappingResponse { + private Map[] mapping; + private FeatureCollection featureCollection; + + /** + * @return the mapping + */ + public Map[] getMapping() { + return mapping; + } + + /** + * @param mapping the mapping to set + */ + public void setMapping(Map[] mapping) { + this.mapping = mapping; + } + + /** + * @return the featureCollection + */ + public FeatureCollection getFeatureCollection() { + return featureCollection; + } + + /** + * @param featureCollection the featureCollection to set + */ + public void setFeatureCollection(FeatureCollection featureCollection) { + this.featureCollection = featureCollection; + } +} diff --git a/src/main/java/no/nibio/vips/logic/entity/rest/ReferencedPoint.java b/src/main/java/no/nibio/vips/logic/entity/rest/ReferencedPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..0fb89cf6595d3cc7766ce7eb2ef149b204276582 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/entity/rest/ReferencedPoint.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.entity.rest; + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +public class ReferencedPoint { + private String id; + private Double lon; + private Double lat; + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the lon + */ + public Double getLon() { + return lon; + } + + /** + * @param lon the lon to set + */ + public void setLon(Double lon) { + this.lon = lon; + } + + /** + * @return the lat + */ + public Double getLat() { + return lat; + } + + /** + * @param lat the lat to set + */ + public void setLat(Double lat) { + this.lat = lat; + } +} diff --git a/src/main/java/no/nibio/vips/logic/service/ObservationService.java b/src/main/java/no/nibio/vips/logic/service/ObservationService.java index 7e84c3295bcc402fd6f6566800f4554e0f5bfe64..e2805e4cd64081db98f98e0fc8235a2015d6dbc4 100755 --- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java +++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java @@ -19,14 +19,22 @@ package no.nibio.vips.logic.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.webcohesion.enunciate.metadata.Facet; import java.io.IOException; import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -37,15 +45,28 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import no.nibio.vips.logic.entity.Gis; import no.nibio.vips.logic.entity.Observation; +import no.nibio.vips.logic.entity.PolygonService; import no.nibio.vips.logic.entity.VipsLogicRole; import no.nibio.vips.logic.entity.VipsLogicUser; +import no.nibio.vips.logic.entity.rest.PointMappingRequest; +import no.nibio.vips.logic.entity.rest.PointMappingResponse; +import no.nibio.vips.logic.entity.rest.ReferencedPoint; import no.nibio.vips.logic.util.GISEntityUtil; import no.nibio.vips.logic.util.Globals; import no.nibio.vips.logic.util.SessionControllerGetter; import org.jboss.resteasy.annotations.GZIP; +import org.wololo.geojson.Feature; +import org.wololo.geojson.FeatureCollection; +import org.wololo.geojson.Polygon; /** * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a> @@ -131,6 +152,7 @@ public class ObservationService { { user = SessionControllerGetter.getUserBean().findVipsLogicUser(UUID.fromString(userUUID)); } + //System.out.println("getFilteredObservationListItems"); return Response.ok().entity( getFilteredObservationsFromBackend( organizationId, @@ -154,6 +176,7 @@ public class ObservationService { String toStr ) { + //System.out.println("getFilteredObservationsFromBackend"); SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat); //TODO Set correct timeZone!!! Date from = null; @@ -306,8 +329,44 @@ public class ObservationService { @GET @Path("{observationId}") @Produces("application/json;charset=UTF-8") - public Response getObservation(@PathParam("observationId") Integer observationId){ - return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservation(observationId)).build(); + public Response getObservation( + @PathParam("observationId") Integer observationId, + @QueryParam("userUUID") String userUUID + ){ + // Observation needs to be masked here as well, or does it create trouble for VIPSLogic observation admin? + Observation o = SessionControllerGetter.getObservationBean().getObservation(observationId); + VipsLogicUser user = (VipsLogicUser) httpServletRequest.getSession().getAttribute("user"); + if(user == null && userUUID != null) + { + user = SessionControllerGetter.getUserBean().findVipsLogicUser(UUID.fromString(userUUID)); + } + // Modification of location information: + // 1) If location is private, only the owner or super users/org admins may view them + // slett all geoInfo + if(user == null || (! user.isSuperUser() && ! user.isOrganizationAdmin())) + { + // Hide completely for all users except super and orgadmin + if(o.getLocationIsPrivate() && (user == null || ! o.getUserId().equals(user.getUserId()))) + { + o.setGeoinfos(null); + } + // This means the user wants to hide the exact location, + // so mask for all users except super and orgadmin + else if(o.getPolygonService() != null) + { + //System.out.println("Masking observation"); + List<Observation> intermediary = new ArrayList<>(); + intermediary.add(o); + intermediary = this.maskObservations(o.getPolygonService(), + SessionControllerGetter.getObservationBean().getObservationsWithLocations( + SessionControllerGetter.getObservationBean().getObservationsWithGeoInfo(intermediary) + ) + ); + o = intermediary.get(0); + } + } + + return Response.ok().entity(o).build(); } /** @@ -397,20 +456,116 @@ public class ObservationService { private List<Observation> getFilteredObservationsFromBackend(Integer organizationId, Integer pestId, Integer cropId, List<Integer> cropCategoryId, String fromStr, String toStr, VipsLogicUser user) { List<Observation> filteredObservations = this.getFilteredObservationsFromBackend(organizationId, pestId, cropId, cropCategoryId, fromStr, toStr); - // If user is not logged in, return only the publicly available observations - if(user == null) + //filteredObservations.forEach(o->System.out.println(o.getObservationId())); + // If superuser or orgadmin: Return everything, unchanged, uncensored + if(user != null && (user.isSuperUser() || user.isOrganizationAdmin())) { - return filteredObservations.stream().filter(obs->obs.getBroadcastMessage()).collect(Collectors.toList()); + return filteredObservations; } - // Else: If superuser: Return everything - if(user.isSuperUser() || user.isOrganizationAdmin()) + List<Observation> retVal = filteredObservations.stream().filter(obs->obs.getBroadcastMessage()).collect(Collectors.toList()); + //retVal.forEach(o->System.out.println(o.getObservationId())); + retVal = this.maskObservations(retVal); + //retVal.forEach(o->System.out.println(o.getObservationId())); + // If user is not logged in, return only the publicly available observations + if(user == null) { - return filteredObservations; + return retVal; } // Else: This is a registered user without special privileges. Show public observations + user's own - List<Observation> retVal = filteredObservations.stream().filter(obs->obs.getBroadcastMessage()).collect(Collectors.toList()); retVal.addAll(SessionControllerGetter.getObservationBean().getObservationsForUser(user)); return retVal; } + + /** + * Runs through the observations, check each to see if it should be masked + * (for privacy reasons) through a polygon service + * @param observations + * @return + */ + private List<Observation> maskObservations(List<Observation> observations) { + //System.out.println("maskObservations(List<Observation> observations)"); + + // Placing all observations with a polygon service in the correct bucket. + Map<PolygonService, List<Observation>> registeredPolygonServicesInObservationList = new HashMap<>(); + observations.stream().filter((obs) -> (!obs.getLocationIsPrivate() && obs.getPolygonService() != null)).forEachOrdered((obs) -> { + List<Observation> obsWithPolyServ = registeredPolygonServicesInObservationList.getOrDefault(obs.getPolygonService(), new ArrayList<>()); + obsWithPolyServ.add(obs); + registeredPolygonServicesInObservationList.put(obs.getPolygonService(), obsWithPolyServ); + }); + + // No buckets filled = No masking needed, return list unmodified + if(registeredPolygonServicesInObservationList.isEmpty()) + { + return observations; + } + else + { + // Loop through, mask + Map<Integer,Observation> maskedObservations = new HashMap<>(); + registeredPolygonServicesInObservationList.keySet().forEach((pService) -> { + this.maskObservations(pService, registeredPolygonServicesInObservationList.get(pService)) + .forEach(o->maskedObservations.put(o.getObservationId(), o)); + }); + + // Adding the rest of the observations (the ones that don't need masking) + observations.stream().filter(o->maskedObservations.get(o.getObservationId())==null).forEach(o->maskedObservations.put(o.getObservationId(), o)); + return new ArrayList(maskedObservations.values()); + } + } + + private List<Observation> maskObservations(PolygonService polygonService, List<Observation> observations) + { + //observations.forEach(o->System.out.println(o.getObservationId())); + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(polygonService.getGisSearchUrlTemplate()); + List<ReferencedPoint> points = observations.stream() + .filter(obs -> (obs.getGeoinfos()!=null && !obs.getGeoinfos().isEmpty()) || obs.getLocation() != null) + .map(obs->{ + ReferencedPoint rp = new ReferencedPoint(); + rp.setId(String.valueOf(obs.getObservationId())); + if(obs.getGeoinfos() != null) + { + rp.setLon(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().x); + rp.setLat(obs.getGeoinfos().get(0).getGisGeom().getCoordinate().y); + } + else + { + rp.setLon(obs.getLocation().getLongitude()); + rp.setLat(obs.getLocation().getLatitude()); + } + return rp; + }).collect(Collectors.toList()); + /*System.out.println("maskobservations - target.request() about to be called"); + ObjectMapper oMapper = new ObjectMapper(); + try { + System.out.println(oMapper.writeValueAsString(Entity.entity(points.toArray(new ReferencedPoint[points.size()]), MediaType.APPLICATION_JSON))); + } catch (JsonProcessingException ex) { + Logger.getLogger(ObservationService.class.getName()).log(Level.SEVERE, null, ex); + }*/ + PointMappingResponse response = target.request(MediaType.APPLICATION_JSON) + .post(Entity.entity(points.toArray(new ReferencedPoint[points.size()]), MediaType.APPLICATION_JSON), PointMappingResponse.class); + // We need to loop through the observations and find corresponding featurecollections and replace those + Map<Integer, Feature> indexedPolygons = new HashMap<>(); + for(Feature feature:response.getFeatureCollection().getFeatures()) + { + indexedPolygons.put((Integer)feature.getProperties().get("id"), feature); + } + GISEntityUtil gisEntityUtil = new GISEntityUtil(); + for(Map mapping:response.getMapping()) + { + Integer observationId = Integer.valueOf((String) mapping.get("id")); + Integer borderId = (Integer) mapping.get("borderid"); + observations.stream().filter((o) -> (o.getObservationId().equals(observationId))).forEachOrdered((o) -> { + Gis polygon = gisEntityUtil.getGisFromFeature(indexedPolygons.get(borderId)); + List<Gis> gis = new ArrayList<>(); + gis.add(polygon); + o.setGeoinfos(gis); + o.setLocation(null); + o.setLocationPointOfInterestId(null); + }); + } + + return observations; + } } diff --git a/src/main/java/no/nibio/vips/logic/util/GISEntityUtil.java b/src/main/java/no/nibio/vips/logic/util/GISEntityUtil.java index 6b9160c76ffb3e4230a69889ebe84473d3b7d142..a1e95eb46d43e7adab4e72bab32d549a0898858a 100755 --- a/src/main/java/no/nibio/vips/logic/util/GISEntityUtil.java +++ b/src/main/java/no/nibio/vips/logic/util/GISEntityUtil.java @@ -82,6 +82,14 @@ public class GISEntityUtil { return retVal; } + public Gis getGisFromFeature(Feature feature) + { + GeoJSONReader reader = new GeoJSONReader(); + Gis gis = new Gis(); + gis.setGisGeom(reader.read(feature.getGeometry())); + gis.getGisGeom().setSRID(GISUtil.DEFAULT_SRID); + return gis; + } /** * Converts a list of observations to a FeatureCollection diff --git a/src/main/resources/db/migration/V5__Add_PolygonService.sql b/src/main/resources/db/migration/V5__Add_PolygonService.sql new file mode 100644 index 0000000000000000000000000000000000000000..cba55e4dfdd66c50dd6f03f8225c415daa638082 --- /dev/null +++ b/src/main/resources/db/migration/V5__Add_PolygonService.sql @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +/** + * Author: Tor-Einar Skog <tor-einar.skog@nibio.no> + * Created: Aug 22, 2019 + */ + +-- ALTER TABLE public.observation +-- DROP COLUMN polygon_service_id; + +-- DROP TABLE public.organization_polygon_service; + +-- DROP TABLE public.polygon_service; + +CREATE TABLE public.polygon_service ( + polygon_service_id SERIAL PRIMARY KEY, + polygon_service_name VARCHAR(255), + description TEXT, + gis_search_url_template VARCHAR(1024), + EPSG INTEGER +); + +CREATE TABLE public.organization_polygon_service ( + organization_id INTEGER REFERENCES public.organization(organization_id), + polygon_service_id INTEGER REFERENCES public.polygon_service(polygon_service_id), + CONSTRAINT organization_polygon_service_pkey PRIMARY KEY (organization_id, polygon_service_id) +); + +ALTER TABLE public.observation + ADD COLUMN polygon_service_id INTEGER REFERENCES public.polygon_service(polygon_service_id); diff --git a/src/main/webapp/formdefinitions/observationForm.json b/src/main/webapp/formdefinitions/observationForm.json index 71b1eba94543beb8f7dbb8ea574a6c7842f203e8..652dd4e9b5e55d82104b62dcb438b15b484230c0 100755 --- a/src/main/webapp/formdefinitions/observationForm.json +++ b/src/main/webapp/formdefinitions/observationForm.json @@ -58,9 +58,10 @@ "required" : false }, { - "name" : "locationIsPrivate", + "name" : "locationVisibility", "dataType" : "STRING", - "required" : false + "fieldType" : "RADIO", + "required" : true }, { "name" : "organizationGroupId", diff --git a/src/main/webapp/js/validateForm.js b/src/main/webapp/js/validateForm.js index 66e1763c6c08e6354c351b41e3c901216bdb1016..3fa7365ff1fb573ac887fb1aad9974474990874c 100755 --- a/src/main/webapp/js/validateForm.js +++ b/src/main/webapp/js/validateForm.js @@ -39,6 +39,7 @@ var dataTypes = { var fieldTypes= { TYPE_SELECT_SINGLE: "SELECT_SINGLE", TYPE_SELECT_MULTIPLE: "SELECT_MULTIPLE", + TYPE_RADIO: "RADIO", TYPE_INPUT: "INPUT" }; @@ -64,7 +65,7 @@ var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/; /** * Validation function for all forms - * @copyright 2013 <a href="http://www.nibio.no/">NIBIO</a> + * @copyright 2013-2019 <a href="http://www.nibio.no/">NIBIO</a> * @author Tor-Einar Skog <tor-einar.skog@nibio.no> * * It depends on the following: @@ -101,7 +102,6 @@ function validateForm(theForm, formDefinitionKey) continue; } if(!validateFieldActual(theForm[fieldDefinition.name], theForm, formDefinitionKey)){ - //alert("Validation failed for " + fieldDefinition.name); isValid = false; } } @@ -241,9 +241,16 @@ function getValidationOutputEl(fieldEl, theForm) */ function validateFieldActual(fieldEl, theForm, formDefinitionKey) { - var webValue = fieldEl.value; - var fieldDefinition = getFieldDefinition(fieldEl.name, formDefinitions[formDefinitionKey !== null ? formDefinitionKey : theForm.id]); + // Determine the fieldName. + // If it's a RadioNodeList, get the name of the first radio element item + // Otherwise (AFAIK), it's the name of the field + var fieldName = fieldEl.name; + if(Object.prototype.toString.call(fieldEl).indexOf("RadioNodeList") >= 0) + { + fieldName = fieldEl.item(0).name; + } + var fieldDefinition = getFieldDefinition(fieldName, formDefinitions[formDefinitionKey !== null ? formDefinitionKey : theForm.id]); // Empty? if(webValue === null || webValue === "") { @@ -258,7 +265,6 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey) return true; } } - // Single select field - check for nullValue if(fieldDefinition.fieldType === fieldTypes.TYPE_SELECT_SINGLE) { @@ -302,6 +308,21 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey) return true; } } + + // Radio buttons + if(fieldDefinition.fieldType === fieldTypes.TYPE_RADIO) + { + if(fieldDefinition.required && fieldEl.value === "") + { + invalidizeField(fieldEl, theForm, getI18nMsg("fieldIsRequired",null)); + return false; + } + else + { + validizeField(fieldEl, theForm); + return true; + } + } // STRINGS // Strings are checked for length @@ -526,8 +547,8 @@ function evaluatePassword(passwordEl) */ function invalidizeField(inputEl, theForm, message) { - // Make sure we don't try to manipulate hidden fields - if(inputEl.type === "hidden") + // Make sure we don't try to manipulate hidden fields or radio node lists + if(inputEl.type === "hidden" || Object.prototype.toString.call(inputEl).indexOf("RadioNodeList") >= 0) { alert(message); return; @@ -544,8 +565,8 @@ function invalidizeField(inputEl, theForm, message) */ function validizeField(inputEl, theForm) { - // Make sure we don't try to manipulate hidden fields - if(inputEl.type === "hidden") + // Make sure we don't try to manipulate hidden fields or radio node lists + if(inputEl.type === "hidden" || Object.prototype.toString.call(inputEl).indexOf("RadioNodeList") >= 0) { return; } diff --git a/src/main/webapp/templates/observationForm.ftl b/src/main/webapp/templates/observationForm.ftl index 44d3661a834920900a014347931891ada0e8563a..07ec5674597e9b1ec2cc7311eca1cd8a324eca03 100755 --- a/src/main/webapp/templates/observationForm.ftl +++ b/src/main/webapp/templates/observationForm.ftl @@ -600,15 +600,39 @@ </select> <span class="help-block" id="${formId}_locationPointOfInterestId_validation"></span> </div> - <div class="form-group"> - <div class="checkbox"> - <#if editAccess!="W" && observation.locationIsPrivate?has_content && observation.locationIsPrivate == true><input type="hidden" name="locationIsPrivate" value="true"/></#if> - <label> - <input type="checkbox" name="locationIsPrivate"<#if !observation.locationIsPrivate?has_content || observation.locationIsPrivate == false><#else>checked="checked"</#if> <#if editAccess!="W">disabled="disabled"</#if>/> - </label> - ${i18nBundle.locationIsPrivate} - </div> - </div> + <#if editAccess!="W" && observation.locationIsPrivate?has_content && observation.locationIsPrivate == true> + <input type="hidden" name="locationVisibility" value="private"/> + <#else> + <div class="form-group"> + <div class="radio"> + <label> + <input type="radio" name="locationVisibility" value="public" + <#if locationVisibilityFormValue == "public">checked="checked"</#if> + /> + </label> + ${i18nBundle.locationIsPublic} + </div> + <div class="radio"> + + <label> + <input type="radio" name="locationVisibility" value="private" + <#if locationVisibilityFormValue == "private">checked="checked"</#if> + /> + </label> + ${i18nBundle.locationIsPrivate} + </div> + <#list polygonServices as polygonService> + <div class="radio"> + <label> + <input type="radio" name="locationVisibility" value="mask_${polygonService.polygonServiceId}" + <#if locationVisibilityFormValue == "mask_" + polygonService.polygonServiceId>checked="checked"</#if> + /> + ${i18nBundle.maskObservationWith} ${polygonService.polygonServiceName} + </label> + </div> + </#list> + </div> + </#if> <div class="form-group"> <label for="organizationGroupId">${i18nBundle.availableFor} ${i18nBundle.organizationGroupList?lower_case}</label> <select class="form-control chosen-select" name="organizationGroupId" multiple="multiple" data-placeholder="${i18nBundle.all}" <#if editAccess!="W">readonly="readonly"</#if>> diff --git a/src/test/java/no/nibio/vips/logic/entity/rest/PointMappingRequestTest.java b/src/test/java/no/nibio/vips/logic/entity/rest/PointMappingRequestTest.java new file mode 100644 index 0000000000000000000000000000000000000000..79d8899f586e8be8b69dd960e6b60ef319621f14 --- /dev/null +++ b/src/test/java/no/nibio/vips/logic/entity/rest/PointMappingRequestTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ +package no.nibio.vips.logic.entity.rest; + +import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; +import org.wololo.geojson.Polygon; + +/** + * + * @author treinar + */ +public class PointMappingRequestTest { + + public PointMappingRequestTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getPoints method, of class PointMappingRequest. + */ + //@Test + public void testGetPoints() { + System.out.println("getPoints"); + PointMappingRequest instance = new PointMappingRequest(); + List<ReferencedPoint> expResult = null; + List<ReferencedPoint> result = instance.getPoints(); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of setPoints method, of class PointMappingRequest. + */ + //@Test + public void testSetPoints() { + System.out.println("setPoints"); + List<ReferencedPoint> points = null; + PointMappingRequest instance = new PointMappingRequest(); + instance.setPoints(points); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + @Test + public void testClient() { + System.out.println("testClient"); + List<ReferencedPoint> points = new ArrayList<>(); + ReferencedPoint r1 = new ReferencedPoint(); + r1.setId("test1"); + r1.setLon(11.9028835076959); + r1.setLat(64.455583583917); + ReferencedPoint r2 = new ReferencedPoint(); + r2.setId("test2"); + r2.setLon(12.588734241028); + r2.setLat(64.5142545939044); + + points.add(r1); + points.add(r2); + + ReferencedPoint[] instance = points.toArray(new ReferencedPoint[points.size()]); + + Client client = ClientBuilder.newClient(); + WebTarget target = client.target("http://proxy1utv.int.nibio.no/municipality_cache_ws/pointmapping/1/4326"); + + //PointMappingResponse response = + PointMappingResponse response = target.request(MediaType.APPLICATION_JSON) + .post(Entity.entity(instance, MediaType.APPLICATION_JSON), PointMappingResponse.class);//.toString(); + + //System.out.println(((Polygon)response.getFeatureCollection().getFeatures()[0].getGeometry()).getCoordinates()[0].length); + + assertNotNull(response); + //System.out.println(Entity.entity(instance, MediaType.APPLICATION_JSON)); + + //System.out.println(target.request(MediaType.APPLICATION_JSON) + // .buildPost(Entity.entity(instance, MediaType.APPLICATION_JSON).)); + + + //System.out.println(response.getStatus()); + + + } +}