From fddb796b97b8dfaa0fe1a96b4ad0d70920faf4b6 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Mon, 19 Sep 2022 14:01:40 +0200
Subject: [PATCH] doc: Endpoint documentation for observation endpoints

---
 .../logic/service/ObservationService.java     | 266 +++++++++++++-----
 1 file changed, 198 insertions(+), 68 deletions(-)

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 6e0d079a..e088442b 100755
--- a/src/main/java/no/nibio/vips/logic/service/ObservationService.java
+++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java
@@ -60,14 +60,7 @@ import no.nibio.vips.logic.controller.session.ObservationBean;
 import no.nibio.vips.logic.controller.session.OrganismBean;
 import no.nibio.vips.logic.controller.session.UserBean;
 
-import no.nibio.vips.logic.entity.Gis;
-import no.nibio.vips.logic.entity.Observation;
-import no.nibio.vips.logic.entity.ObservationIllustrationPK;
-import no.nibio.vips.logic.entity.ObservationStatusType;
-import no.nibio.vips.logic.entity.ObservationSyncInfo;
-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.*;
 import no.nibio.vips.logic.entity.rest.ObservationListItem;
 import no.nibio.vips.logic.entity.rest.PointMappingResponse;
 import no.nibio.vips.logic.entity.rest.ReferencedPoint;
@@ -79,6 +72,8 @@ import org.wololo.geojson.Feature;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wololo.geojson.FeatureCollection;
+import org.wololo.geojson.GeoJSON;
 
 /**
  * @copyright 2016-2022 <a href="http://www.nibio.no/">NIBIO</a>
@@ -102,7 +97,7 @@ public class ObservationService {
     MessagingBean messagingBean;
     
     /*
-     * NOTE TO SELF
+     * PostGIS tip:
      * How to query for observations within a bounding box
      * Select * from gis where ST_Intersects(
      *   ST_SetSRID(ST_MakeBox2D(ST_MakePoint(2.9004, 57.7511), ST_MakePoint(32.4316, 71.3851)),4326),
@@ -112,12 +107,12 @@ public class ObservationService {
     
     /**
      * 
-     * @param organizationId
-     * @param pestId
-     * @param cropId
-     * @param cropCategoryId
-     * @param fromStr
-     * @param toStr
+     * @param organizationId Database ID of the organization
+     * @param pestId Database ID of the pest
+     * @param cropId Database ID of the crop
+     * @param cropCategoryId Database IDs of the crop category/categories
+     * @param fromStr format "yyyy-MM-dd"
+     * @param toStr format "yyyy-MM-dd"
      * @return Observation objects with all data (full tree)
      */
     @GET
@@ -147,12 +142,12 @@ public class ObservationService {
     
     /**
      * 
-     * @param organizationId
-     * @param pestId
-     * @param cropId
-     * @param cropCategoryId
-     * @param from
-     * @param to
+     * @param organizationId Database ID of the organization
+     * @param pestId Database ID of the pest
+     * @param cropId Database ID of the crop
+     * @param cropCategoryId cropCategoryId Database IDs of the crop category/categories
+     * @param fromStr format "yyyy-MM-dd"
+     * @param toStr format "yyyy-MM-dd"
      * @return Observation objects for which the user is authorized to observe with properties relevant for lists
      */
     @GET
@@ -208,16 +203,16 @@ public class ObservationService {
         //o.setObservationDataSchema(observationBean.getObservationDataSchema(observer.getOrganizationId().getOrganizationId(), o.getOrganismId()));
         return Response.ok().entity(observations).build();
     }
-            
+
     /**
-     * 
-     * @param organizationId
-     * @param pestId
-     * @param cropId
-     * @param cropCategoryId
-     * @param fromStr
-     * @param toStr
-     * @return 
+     *
+     * @param organizationId Database ID of the organization
+     * @param pestId Database ID of the pest
+     * @param cropId Database ID of the crop
+     * @param cropCategoryId cropCategoryId Database IDs of the crop category/categories
+     * @param fromStr format "yyyy-MM-dd"
+     * @param toStr format "yyyy-MM-dd"
+     * @return Observation objects for which the user is authorized to observe with properties relevant for lists
      */
     private List<Observation> getFilteredObservationsFromBackend(
             Integer organizationId,
@@ -228,7 +223,6 @@ public class ObservationService {
             String toStr
     )
     {
-        //System.out.println("getFilteredObservationsFromBackend");
         SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat);
         //TODO Set correct timeZone!!!
         Date from = null;
@@ -250,11 +244,122 @@ public class ObservationService {
         );
         
     }
-    
+
+    /**
+     *
+     * @param organizationId
+     * @param pestId
+     * @param cropId
+     * @param cropCategoryId
+     * @param fromStr
+     * @param toStr
+     * @return
+     *
+     * @responseExample application/json
+     * {
+     *     "type": "FeatureCollection",
+     *     "features": [
+     *         {
+     *             "type": "Feature",
+     *             "id": 18423,
+     *             "geometry": {
+     *                 "type": "Point",
+     *                 "coordinates": [
+     *                     10.782463095356452,
+     *                     59.35794998304658,
+     *                     0.0
+     *                 ]
+     *             },
+     *             "properties": {
+     *                 "observationId": 18446,
+     *                 "observationHeading": "NLR Øst, Huggenes: Det er funnet potettørråte i Råde",
+     *                 "organism": {
+     *                     "organismId": 14,
+     *                     "latinName": "Phytophthora infestans",
+     *                     "tradeName": "",
+     *                     "logicallyDeleted": false,
+     *                     "isPest": true,
+     *                     "isCrop": false,
+     *                     "parentOrganismId": 124,
+     *                     "hierarchyCategoryId": 120,
+     *                     "organismLocaleSet": [
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 14,
+     *                                 "locale": "nb"
+     *                             },
+     *                             "localName": "Potettørråte"
+     *                         },
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 14,
+     *                                 "locale": "en"
+     *                             },
+     *                             "localName": "Late blight"
+     *                         }
+     *                     ],
+     *                     "organismExternalResourceSet": [],
+     *                     "childOrganisms": null,
+     *                     "extraProperties": {},
+     *                     "observationDataSchema": null
+     *                 },
+     *                 "cropOrganism": {
+     *                     "organismId": 5,
+     *                     "latinName": "Solanum tuberosum",
+     *                     "tradeName": "",
+     *                     "logicallyDeleted": false,
+     *                     "isPest": false,
+     *                     "isCrop": true,
+     *                     "parentOrganismId": 4,
+     *                     "hierarchyCategoryId": 120,
+     *                     "organismLocaleSet": [
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 5,
+     *                                 "locale": "bs"
+     *                             },
+     *                             "localName": "Potato"
+     *                         },
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 5,
+     *                                 "locale": "nb"
+     *                             },
+     *                             "localName": "Potet"
+     *                         },
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 5,
+     *                                 "locale": "en"
+     *                             },
+     *                             "localName": "Potato"
+     *                         },
+     *                         {
+     *                             "organismLocalePK": {
+     *                                 "organismId": 5,
+     *                                 "locale": "hr"
+     *                             },
+     *                             "localName": "Krompir"
+     *                         }
+     *                     ],
+     *                     "organismExternalResourceSet": [],
+     *                     "childOrganisms": null,
+     *                     "extraProperties": {},
+     *                     "observationDataSchema": null
+     *                 },
+     *                 "observationText": "Fredag 2.juli ble det gjort første funn av potettørråte i Råde. Det er noen flekker på bladene på øvre del av planten. Smitten ser ut til å ha kommet som sekundærsmitte med vinden.",
+     *                 "timeOfObservation": "2021-07-02T11:00:00+02:00"
+     *             }
+     *         }
+     *     ]
+     * }
+     *
+     */
     @GET
     @Path("filter/{organizationId}/geoJSON")
     @GZIP
     @Produces("application/json;charset=UTF-8")
+    @TypeHint(GeoJSON.class)
     public Response getFilteredObservationsAsGeoJSON(
             @PathParam("organizationId") Integer organizationId,
             @QueryParam("pestId") Integer pestId,
@@ -292,14 +397,13 @@ public class ObservationService {
     /**
      * Get a list of all observed pests for one organization
      * Practical for building effective select lists
-     * TODO: Should be cached??
-     * @param organizationId
-     * @return 
+     * @param organizationId Database ID of organization
+     * @return list of all observed pests for one organization
      */
     @GET
     @Path("pest/{organizationId}")
     @Produces("application/json;charset=UTF-8")
-    @TypeHint(Observation[].class)
+    @TypeHint(Organism[].class)
     public Response getObservedPests(@PathParam("organizationId") Integer organizationId)
     {
         return Response.ok().entity(observationBean.getObservedPests(organizationId)).build();
@@ -309,13 +413,13 @@ public class ObservationService {
      * Get a list of all crop cultures where observations have been made for one organization
      * Practical for building effective select lists
      * TODO: Should be cached??
-     * @param organizationId
+     * @param organizationId Database ID of organization
      * @return 
      */
     @GET
     @Path("crop/{organizationId}")
     @Produces("application/json;charset=UTF-8")
-    @TypeHint(Observation[].class)
+    @TypeHint(Organism[].class)
     public Response getObservedCrops(@PathParam("organizationId") Integer organizationId)
     {
         return Response.ok().entity(observationBean.getObservedCrops(organizationId)).build();
@@ -324,7 +428,7 @@ public class ObservationService {
     
     /**
      * Publicly available observations per organization
-     * @param organizationId
+     * @param organizationId Database ID of organization
      * @return APPROVED observations
      */
     @GET
@@ -339,8 +443,8 @@ public class ObservationService {
     /**
      * Get observations for a user
      * Requires a valid UUID to be provided in the Authorization header
-     * @param observationIds
-     * @return
+     * @param observationIds Comma separated list of Observation Ids
+     * @return Filtering by observation ids
      */
     @GET
     @Path("list/user")
@@ -383,6 +487,7 @@ public class ObservationService {
     
     /**
      * Get minimized (only synchronization info) observations for a user
+     * Used by the Observation app
      * Requires a valid UUID to be provided in the Authorization header
      * @return
      */
@@ -406,7 +511,7 @@ public class ObservationService {
    
     /**
      * Publicly available observations per organization
-     * @param organizationId
+     * @param organizationId Database id of the organization
      * @return APPROVED observations
      */
     @GET
@@ -449,7 +554,13 @@ public class ObservationService {
             return Response.ok().entity(observationBean.getBroadcastObservations(organizationId, season)).build();
         }
     }
-    
+
+    /**
+     * Get one observation
+     * @param observationId Database ID of the observation
+     * @param userUUID UUID that identifies the user (e.g. from VIPSWeb)
+     * @return
+     */
     @GET
     @Path("{observationId}")
     @Produces("application/json;charset=UTF-8")
@@ -503,9 +614,10 @@ public class ObservationService {
     }
     
     /**
-     * 
-     * @param organizationId
-     * @return 
+     * Polygon services are used to mask observations (privacy concerns)
+     * They may vary greatly between organizations (different countries)
+     * @param organizationId Database id of the organization
+     * @return A list of available polygon services for the requested organization
      */
     @GET
     @Path("polygonservices/{organizationId}")
@@ -521,7 +633,7 @@ public class ObservationService {
    
     /**
      * Deletes a gis entity and its corresponding observation
-     * @param gisId
+     * @param gisId Database id of the gis entity
      * @return 
      */
     @DELETE
@@ -549,10 +661,12 @@ public class ObservationService {
     }
     
     /**
-     * TODO Authentication
+     *
+     * Stores an observation from geoJson
      * @param geoJSON
-     * @return 
+     * @return the Url of the created entity (observation)
      */
+    // TODO Authentication
     @POST
     @Path("gisobservation")
     @Consumes("application/json;charset=UTF-8")
@@ -593,8 +707,8 @@ public class ObservationService {
 
     /**
      * Returns the time of the first observation in the system of the pest with given Id
-     * @param organismId
-     * @return 
+     * @param organismId Database id of the given organism
+     * @return the time of the first observation in the system of the pest with given Id
      */
     @GET
     @Path("first/{organismId}")
@@ -608,7 +722,8 @@ public class ObservationService {
     }
     
     /**
-     * When was the last time a change was made in cropCategories or organisms?
+     * When was the last time a change was made in cropCategories or organisms? Used for sync with the Field observation
+     * app.
      * @return last time a change was made in cropCategories or organisms
      * @responseExample application/json {"lastUpdated": "2021-02-08T00:00:00Z"}
      */
@@ -627,16 +742,24 @@ public class ObservationService {
 
     /**
      * 
-     * @param organizationId
-     * @param pestId
-     * @param cropId
-     * @param cropCategoryId
-     * @param fromStr
-     * @param toStr
-     * @param user
-     * @return 
+     * @param organizationId Database id of the organization
+     * @param pestId Database id of the pest
+     * @param cropId Database id of the crop
+     * @param cropCategoryId Database ids of the crop categories
+     * @param fromStr format "yyyy-MM-dd"
+     * @param toStr format "yyyy-MM-dd"
+     * @param user The user that requests this (used for authorization)
+     * @return A list of observations that meets the filter criteria
      */
-    private List<Observation> getFilteredObservationsFromBackend(Integer organizationId, Integer pestId, Integer cropId, List<Integer> cropCategoryId, String fromStr, String toStr, VipsLogicUser user) {
+    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);
         //filteredObservations.forEach(o->System.out.println(o.getObservationId()));
         // If superuser or orgadmin: Return everything, unchanged, uncensored
@@ -661,8 +784,8 @@ public class ObservationService {
     /**
      * Runs through the observations, check each to see if it should be masked
      * (for privacy reasons) through a polygon service
-     * @param observations
-     * @return 
+     * @param observations The list of observations to check
+     * @return The list of observations, with the locations masked with the selected polygon service
      */
     private List<Observation> maskObservations(List<Observation> observations) {
         //System.out.println("maskObservations(List<Observation> observations)");
@@ -691,10 +814,16 @@ public class ObservationService {
             
             // 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());
+            return new ArrayList<>(maskedObservations.values());
         }
     }
-    
+
+    /**
+     *
+     * @param polygonService The polygon service that should be used for masking
+     * @param observations The list of observations to mask
+     * @return The list of observations, with the locations masked with the selected polygon service
+     */
     private List<Observation> maskObservations(PolygonService polygonService, List<Observation> observations)
     {
         //observations.forEach(o->System.out.println(o.getObservationId()));
@@ -751,9 +880,10 @@ public class ObservationService {
     }
     
     /**
-     * 
-     * @param observationJson
-     * @return 
+     * This service is used by the VIPS Field observation app to sync data stored locally on the smartphone with the
+     * state(s) of the observation(s) in the VIPSLogic database
+     * @param observationJson Json representation of the observation(s)
+     * @return The observation(s) in their merged state, serialized to Json
      */
     @POST
     @Path("syncobservationfromapp")
-- 
GitLab