From 86de6c607b83adfe3d79595d0cb2831db9afe489 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@bioforsk.no>
Date: Fri, 26 Feb 2016 14:35:41 +0100
Subject: [PATCH] Support for private forecasts

---
 .../ForecastConfigurationController.java      |  21 ++++
 .../controller/session/ForecastBean.java      |  69 +++++++++++-
 .../logic/entity/ForecastConfiguration.java   |  54 +++++++---
 .../vips/logic/service/LogicService.java      | 101 +++++++++++++++---
 .../vips/logic/i18n/vipslogictexts.properties |   4 +
 .../logic/i18n/vipslogictexts_bs.properties   |   4 +
 .../logic/i18n/vipslogictexts_hr.properties   |   4 +
 .../logic/i18n/vipslogictexts_nb.properties   |   4 +
 .../logic/i18n/vipslogictexts_sr.properties   |   4 +
 .../forecastConfigurationForm.json            |   5 +
 .../templates/forecastConfigurationForm.ftl   |  12 ++-
 .../templates/forecastConfigurationList.ftl   |  77 +++++++++++++
 12 files changed, 324 insertions(+), 35 deletions(-)

diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
index daaeae40..3275c260 100644
--- a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
+++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java
@@ -140,13 +140,29 @@ public class ForecastConfigurationController extends HttpServlet {
                     
                     request.setAttribute("organizations", organizations);
                     request.setAttribute("selectedOrganizationIds", selectedOrganizationIds);
+                    
+                    request.setAttribute("allUsers", userBean.getAllUsers());
+                    // If super user requests private forecasts for a user
+                    try
+                    {
+                        Integer otherUserId = Integer.valueOf(request.getParameter("otherUserId"));
+                        List<ForecastConfiguration> privateForecastConfigurationsForOtherUser = forecastBean.getPrivateForecastConfigurationsForUser(otherUserId);
+                        request.setAttribute("privateForecastConfigurationsForOtherUser", privateForecastConfigurationsForOtherUser);
+                        request.setAttribute("otherUserId", otherUserId);
+                    }
+                    catch(NumberFormatException nfe) {}
                 }
                 else
                 {
                     forecasts = forecastBean.getForecastConfigurations(user.getOrganizationId(), selectedModelIds, from, to);
                 }
                 Collections.sort(forecasts);
+                
                 request.setAttribute("forecastConfigurations", forecasts);
+                
+                List<ForecastConfiguration> privateForecasts;
+                privateForecasts = forecastBean.getPrivateForecastConfigurationsForUser(user.getUserId());
+                request.setAttribute("privateForecastConfigurations", privateForecasts);
                 request.setAttribute("modelInformation", modelInformationMap);
                 request.setAttribute("selectedModelIds", selectedModelIds);
                 request.setAttribute("from", from);
@@ -185,6 +201,11 @@ public class ForecastConfigurationController extends HttpServlet {
                     {
                         response.sendError(403,"Access not authorized"); // HTTP Forbidden
                     }
+                    // Only superusers can view and edit private forecasts from other users
+                    else if(forecastConfiguration.getIsPrivate() && ! user.isSuperUser() && forecastConfiguration.getVipsLogicUserId() != null && !forecastConfiguration.getVipsLogicUserId().getUserId().equals(user.getUserId()))
+                    {
+                        response.sendError(403,"Access not authorized"); // HTTP Forbidden
+                    }
                     else
                     {
                         // TODO: More intelligent selection of locations, weather stations and users
diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
index d4f7a713..da528cf1 100644
--- a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java
@@ -41,6 +41,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.ejb.Stateless;
@@ -104,7 +105,26 @@ public class ForecastBean {
         Query q = em.createNamedQuery("ForecastResult.findByForecastConfigurationId");
         q.setParameter("forecastConfigurationId", forecastConfigurationId);
         return q.getResultList();
-        
+    }
+    
+    public boolean isUserAuthorizedForForecastConfiguration(Long forecastConfigurationId, String userUUID)
+    {
+        // Authentication
+        ForecastConfiguration fc = em.find(ForecastConfiguration.class, forecastConfigurationId);
+        if(fc.getIsPrivate())
+        {
+            if(userUUID == null)
+            {
+                return false;
+            }
+            UUID uUUID = UUID.fromString(userUUID);
+            VipsLogicUser user = SessionControllerGetter.getUserBean().findVipsLogicUser(uUUID);
+            if(user == null || user.getUserId().equals( fc.getVipsLogicUserId()))
+            {
+                return false;
+            }
+        }
+        return true;
     }
     
     public List<ForecastResult> getForecastResults(Long forecastConfigurationId, Integer latestDays)
@@ -180,7 +200,7 @@ public class ForecastBean {
     
     
     /**
-     * Get all forecast configurations for one user. 
+     * Get all PUBLIC forecast configurations for one user. 
      * TODO: Should be season based, or possibly based on start/stop date
      * @param userId
      * @return 
@@ -193,6 +213,23 @@ public class ForecastBean {
         return q.getResultList();
     }
     
+    /**
+     * Get all PRIVATE forecast configurations for one user. 
+     * TODO: Should be season based, or possibly based on start/stop date
+     * @param userId
+     * @return 
+     */
+    public List<ForecastConfiguration> getPrivateForecastConfigurationsForUser(Integer userId)
+    {
+        VipsLogicUser user = em.find(VipsLogicUser.class, userId);
+        Query q = em.createNamedQuery("ForecastConfiguration.findPrivateByVipsLogicUserId");
+        q.setParameter("vipsLogicUserId", user);
+        return q.getResultList();
+    }
+    
+    
+    
+    
     public List<ForecastConfiguration> getForecastConfigurationsForUserAndDate(Integer userId, Date from, Date to)
     {
         VipsLogicUser user = em.find(VipsLogicUser.class, userId);
@@ -404,6 +441,7 @@ public class ForecastBean {
         forecastConfiguration.setModelId(formFields.get("modelId").getWebValue());
         forecastConfiguration.setCropOrganismId(em.find(Organism.class, formFields.get("cropOrganismId").getValueAsInteger()));
         forecastConfiguration.setPestOrganismId(em.find(Organism.class, formFields.get("pestOrganismId").getValueAsInteger()));
+        forecastConfiguration.setIsPrivate(formFields.get("isPrivate").getWebValue() != null);
         PointOfInterest locationPoi = em.find(PointOfInterest.class, formFields.get("locationPointOfInterestId").getValueAsInteger());
         forecastConfiguration.setLocationPointOfInterestId(locationPoi);
         PointOfInterest weatherStationPoi = em.find(PointOfInterestWeatherStation.class, formFields.get("weatherStationPointOfInterestId").getValueAsInteger());
@@ -878,7 +916,8 @@ public class ForecastBean {
                         "WHERE forecast_configuration_id IN( \n" +
                         "	SELECT forecast_configuration_id \n" +
                         "	FROM forecast_configuration \n" +
-                        "	WHERE location_point_of_interest_id=:locationPointOfInterestId \n" +
+                        "	WHERE is_private IS FALSE \n" +
+                        "       AND location_point_of_interest_id=:locationPointOfInterestId \n" +
                         (cropOrganismIds != null && ! cropOrganismIds.isEmpty() ? "     AND crop_organism_id IN (" + StringUtils.join(cropOrganismIds, ",") + ") " : "") +
                         ")\n" +
                         "AND to_char(result_valid_time, '" + dateFormat + "') = :dateStr";
@@ -912,6 +951,10 @@ public class ForecastBean {
         List<ForecastResult> results = new ArrayList<>();
         for(ForecastConfiguration forecastConfiguration:forecastConfigurations)
         {
+            if(forecastConfiguration.getIsPrivate())
+            {
+                continue;
+            }
             mappedForecastConfigurations.put(forecastConfiguration.getForecastConfigurationId(), forecastConfiguration);
             Query q = em.createNativeQuery(
                     "SELECT * FROM forecast_result WHERE forecast_configuration_id = :forecastConfigurationId "
@@ -968,5 +1011,25 @@ public class ForecastBean {
         
         return forecastConfiguration;
     }
+
+    public List<ForecastConfiguration> getPrivateForecastConfigurationSummaries(VipsLogicUser user) {
+         List<ForecastConfiguration> forecastConfigurations = this.getPrivateForecastConfigurationsForUser(user.getUserId());
+        // TODO: Filter forecastconfigurations based on criteria (activity, crops, geography etc)
+        List<ForecastConfiguration> filteredConfigs = new ArrayList<>();
+        Query q = em.createNamedQuery("ForecastSummary.findByForecastConfigurationId");
+        for(ForecastConfiguration config: forecastConfigurations)
+        {
+            config.setForecastSummaries(
+                    
+                    q.setParameter("forecastConfigurationId", config.getForecastConfigurationId())
+                    .getResultList()
+            );
+            if(config.getForecastSummaries() != null && !config.getForecastSummaries().isEmpty())
+            {
+                filteredConfigs.add(config);
+            }
+        }
+        return filteredConfigs;
+    }
     
 }
diff --git a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
index 679b66da..6fa08609 100644
--- a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
+++ b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java
@@ -47,31 +47,35 @@ import javax.persistence.Transient;
 import no.nibio.vips.util.WeatherUtil;
 
 /**
- * @copyright 2014-2015 <a href="http://www.nibio.no/">NIBIO</a>
+ * @copyright 2014-2016 <a href="http://www.nibio.no/">NIBIO</a>
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 @Entity
 @Table(name = "forecast_configuration")
 @XmlRootElement
 @NamedQueries({
-    @NamedQuery(name = "ForecastConfiguration.findAll", query = "SELECT f FROM ForecastConfiguration f"),
+    @NamedQuery(name = "ForecastConfiguration.findAll", query = "SELECT f FROM ForecastConfiguration f WHERE f.isPrivate = FALSE"),
     @NamedQuery(name = "ForecastConfiguration.findByForecastConfigurationId", query = "SELECT f FROM ForecastConfiguration f WHERE f.forecastConfigurationId = :forecastConfigurationId"),
     @NamedQuery(name = "ForecastConfiguration.findByForecastConfigurationIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.forecastConfigurationId IN(:forecastConfigurationIds)"),
-    @NamedQuery(name = "ForecastConfiguration.findByModelId", query = "SELECT f FROM ForecastConfiguration f WHERE f.modelId = :modelId"),
-    @NamedQuery(name = "ForecastConfiguration.findByModelIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.modelId IN (:modelIds)"),
-    @NamedQuery(name = "ForecastConfiguration.findByDateStart", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateStart = :dateStart"),
-    @NamedQuery(name = "ForecastConfiguration.findByDateEnd", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateEnd = :dateEnd"),
-    @NamedQuery(name = "ForecastConfiguration.findActiveAtDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateStart <= :currentDate AND f.dateEnd >= :currentDate"),
-    @NamedQuery(name = "ForecastConfiguration.findByLocationPointOfInterestId", query = "SELECT f FROM ForecastConfiguration f WHERE f.locationPointOfInterestId = :locationPointOfInterestId"),
-    @NamedQuery(name = "ForecastConfiguration.findByWeatherStationPointOfInterestId", query = "SELECT f FROM ForecastConfiguration f WHERE f.weatherStationPointOfInterestId = :weatherStationPointOfInterestId"),
-    @NamedQuery(name = "ForecastConfiguration.findByWeatherStationPointOfInterestIdAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.weatherStationPointOfInterestId = :weatherStationPointOfInterestId AND f.dateStart <= :to AND f.dateEnd >= :from"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.dateStart <= :to AND f.dateEnd >= :from" ),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndCropOrganismId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds)"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndCropOrganismIdsAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds) AND f.dateStart <= :to AND f.dateEnd >= :from"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds)"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdsAndModelIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds) AND f.modelId IN (:modelIds)"),
-    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdsAndModelIdsAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds) AND f.modelId IN (:modelIds) AND f.dateStart <= :to AND f.dateEnd >= :from")})
+    @NamedQuery(name = "ForecastConfiguration.findByModelId", query = "SELECT f FROM ForecastConfiguration f WHERE f.modelId = :modelId AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByModelIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.modelId IN (:modelIds) AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByDateStart", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateStart = :dateStart AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByDateEnd", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateEnd = :dateEnd AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findActiveAtDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateStart <= :currentDate AND f.dateEnd >= :currentDate AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByLocationPointOfInterestId", query = "SELECT f FROM ForecastConfiguration f WHERE f.locationPointOfInterestId = :locationPointOfInterestId AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByWeatherStationPointOfInterestId", query = "SELECT f FROM ForecastConfiguration f WHERE f.weatherStationPointOfInterestId = :weatherStationPointOfInterestId AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByWeatherStationPointOfInterestIdAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.weatherStationPointOfInterestId = :weatherStationPointOfInterestId AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = FALSE" ),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndCropOrganismId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds) AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdAndCropOrganismIdsAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds) AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findPrivateByVipsLogicUserId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.isPrivate = TRUE"),
+    @NamedQuery(name = "ForecastConfiguration.findPrivateByVipsLogicUserIdAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = TRUE" ),
+    @NamedQuery(name = "ForecastConfiguration.findPrivateByVipsLogicUserIdAndCropOrganismId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds) AND f.isPrivate = TRUE"),
+    @NamedQuery(name = "ForecastConfiguration.findPrivateByVipsLogicUserIdAndCropOrganismIdsAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId AND f.cropOrganismId.organismId IN (:cropOrganismIds) AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = TRUE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds) AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdsAndModelIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds) AND f.modelId IN (:modelIds) AND f.isPrivate = FALSE"),
+    @NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIdsAndModelIdsAndDate", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds) AND f.modelId IN (:modelIds) AND f.dateStart <= :to AND f.dateEnd >= :from AND f.isPrivate = FALSE")})
 public class ForecastConfiguration implements Serializable, Comparable {
     @OneToMany(cascade = CascadeType.ALL, mappedBy = "forecastConfiguration", fetch = FetchType.EAGER)
     private Set<ForecastModelConfiguration> forecastModelConfigurationSet;
@@ -109,6 +113,8 @@ public class ForecastConfiguration implements Serializable, Comparable {
     @JoinColumn(name = "pest_organism_id", referencedColumnName = "organism_id")
     @ManyToOne
     private Organism pestOrganismId;
+    @Column(name = "is_private")
+    private Boolean isPrivate;
     
     @Transient
     private WeatherUtil weatherUtil;
@@ -370,4 +376,18 @@ public class ForecastConfiguration implements Serializable, Comparable {
         format.setTimeZone(timeZone1);
         return format.format(this.getDateEnd());
     }*/
+
+    /**
+     * @return the isPrivate
+     */
+    public Boolean getIsPrivate() {
+        return isPrivate;
+    }
+
+    /**
+     * @param isPrivate the isPrivate to set
+     */
+    public void setIsPrivate(Boolean isPrivate) {
+        this.isPrivate = isPrivate;
+    }
 }
diff --git a/src/main/java/no/nibio/vips/logic/service/LogicService.java b/src/main/java/no/nibio/vips/logic/service/LogicService.java
index 07a4764d..53a70b1a 100644
--- a/src/main/java/no/nibio/vips/logic/service/LogicService.java
+++ b/src/main/java/no/nibio/vips/logic/service/LogicService.java
@@ -87,14 +87,24 @@ public class LogicService {
     @GET
     @Path("forecastresults/{forecastConfigurationId}")
     @Produces("application/json;charset=UTF-8")
-    public Response getForecastResults(@PathParam("forecastConfigurationId") Long forecastConfigurationId)
+    public Response getForecastResults(
+            @PathParam("forecastConfigurationId") Long forecastConfigurationId,
+            @QueryParam("userUUID") String userUUID
+    )
     {
-        List<ForecastResult> results = SessionControllerGetter.getForecastBean().getForecastResults(forecastConfigurationId);
-        if(results == null)
+        if(SessionControllerGetter.getForecastBean().isUserAuthorizedForForecastConfiguration(forecastConfigurationId, userUUID))
+        {
+            List<ForecastResult> results = SessionControllerGetter.getForecastBean().getForecastResults(forecastConfigurationId);
+            if(results == null)
+            {
+                results = new ArrayList<>();
+            }
+            return Response.ok().entity(results).build();
+        }
+        else
         {
-            results = new ArrayList<>();
+            return Response.status(Response.Status.UNAUTHORIZED).build();
         }
-        return Response.ok().entity(results).build();
     }
     
     @GET
@@ -102,15 +112,23 @@ public class LogicService {
     @Produces("application/json;charset=UTF-8")
     public Response getForecastResults(
             @PathParam("forecastConfigurationId") Long forecastConfigurationId,
-            @PathParam("latestDays") Integer latestDays
+            @PathParam("latestDays") Integer latestDays,
+            @QueryParam("userUUID") String userUUID
     )
     {
-        List<ForecastResult> results = SessionControllerGetter.getForecastBean().getForecastResults(forecastConfigurationId, latestDays);
-        if(results == null)
+        if(SessionControllerGetter.getForecastBean().isUserAuthorizedForForecastConfiguration(forecastConfigurationId, userUUID))
         {
-            results = new ArrayList<>();
+            List<ForecastResult> results = SessionControllerGetter.getForecastBean().getForecastResults(forecastConfigurationId, latestDays);
+            if(results == null)
+            {
+                results = new ArrayList<>();
+            }
+            return Response.ok().entity(results).build();
+        }
+        else
+        {
+            return Response.status(Response.Status.UNAUTHORIZED).build();
         }
-        return Response.ok().entity(results).build();
     }
     
     @GET
@@ -125,6 +143,27 @@ public class LogicService {
         return Response.ok().entity(summaries).build();
     }
     
+    @GET
+    @Path("forecastconfigurationsummaries/private/{userUUID}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getForecastSummaries(
+            @PathParam("userUUID") String userUUID
+    )
+    {
+        UUID uUUID = UUID.fromString(userUUID);
+        VipsLogicUser user = SessionControllerGetter.getUserBean().findVipsLogicUser(uUUID);
+        if(user != null)
+        {
+            List<ForecastConfiguration> summaries = SessionControllerGetter.getForecastBean().getPrivateForecastConfigurationSummaries(user);
+            return Response.ok().entity(summaries).build();
+        }
+        else
+        {
+            return Response.status(Response.Status.UNAUTHORIZED).build();
+        }
+        
+    }
+    
     /**
      * Returns the requested forecast configuration
      * @param forecastConfigurationId
@@ -133,15 +172,51 @@ public class LogicService {
     @GET
     @Path("forecastconfigurations/{forecastConfigurationId}")
     @Produces("application/json;charset=UTF-8")
-    public Response getForecastConfiguration(@PathParam("forecastConfigurationId") Long forecastConfigurationId)
+    public Response getForecastConfiguration(@PathParam("forecastConfigurationId") Long forecastConfigurationId,@QueryParam("userUUID") String userUUID)
+    {
+        if(SessionControllerGetter.getForecastBean().isUserAuthorizedForForecastConfiguration(forecastConfigurationId, userUUID))
+        {
+            ForecastConfiguration forecastConfiguration = SessionControllerGetter.getForecastBean().getForecastConfiguration(forecastConfigurationId);
+            return Response.ok().entity(forecastConfiguration).build();
+        }
+        else
+        {
+            return Response.status(Response.Status.UNAUTHORIZED).build();
+        }
+    }
+    
+    @GET
+    @Path("forecastconfigurations/private/{userUUID}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getPrivateForecastConfigurations(@PathParam("userUUID") String userUUID)
     {
-        ForecastConfiguration forecastConfiguration = SessionControllerGetter.getForecastBean().getForecastConfiguration(forecastConfigurationId);
-        return Response.ok().entity(forecastConfiguration).build();
+        try
+        {
+            UUID uUUID = UUID.fromString(userUUID);
+            VipsLogicUser user = SessionControllerGetter.getUserBean().findVipsLogicUser(uUUID);
+            if(user != null)
+            {
+                List<ForecastConfiguration> retVal = SessionControllerGetter.getForecastBean().getPrivateForecastConfigurationsForUser(user.getUserId());
+                return Response.ok().entity(retVal).build();
+            }
+            else
+            {
+                return Response.status(Response.Status.UNAUTHORIZED).build();
+            }
+        }
+        catch(NullPointerException npe)
+        {
+            return Response.noContent().build();
+        }
+        
     }
     
     /**
      * Returns a list of forecasts for given organization
      * @param organizationId
+     * @param cropOrganismIds
+     * @param from
+     * @param to
      * @return 
      */
     @GET
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
index fac5f04c..829aee3a 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
@@ -338,3 +338,7 @@ includeAllChildCrops=Include all child crops
 pestOrganismIds=Pests
 cropPestUpdated=Crop pest was updated
 surveillanceMessageInformation=If you want to create a message about an observation, please use the observation registration form
+isPrivate=Is private
+privateForecasts=Private forecasts
+privateForecastsForOtherUser=Private forecasts for other user
+noPrivateForecastsFoundForUser=No private forecasts found for user
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
index 5a5afd5e..b707f0fb 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
@@ -338,3 +338,7 @@ includeAllChildCrops=Include all child crops
 pestOrganismIds=Pests
 cropPestUpdated=Crop pest was updated
 surveillanceMessageInformation=If you want to create a message about an observation, please use the observation registration form
+isPrivate=Is private
+privateForecasts=Private forecasts
+privateForecastsForOtherUser=Private forecasts for other user
+noPrivateForecastsFoundForUser=No private forecasts found for user
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
index 2f79968f..5beb86b7 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
@@ -337,3 +337,7 @@ includeAllChildCrops=Include all child crops
 pestOrganismIds=Pests
 cropPestUpdated=Crop pest was updated
 surveillanceMessageInformation=If you want to create a message about an observation, please use the observation registration form
+isPrivate=Is private
+privateForecasts=Private forecasts
+privateForecastsForOtherUser=Private forecasts for other user
+noPrivateForecastsFoundForUser=No private forecasts found for user
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
index 035fda63..a58b7a01 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
@@ -338,3 +338,7 @@ includeAllChildCrops=Inkluder alle underarter, sorter og varianter
 pestOrganismIds=Skadegj\u00f8rere
 cropPestUpdated=Skadegj\u00f8rere for kultur ble lagret
 surveillanceMessageInformation=Hvis du \u00f8nsker \u00e5 lage en melding om en observasjon/et f\u00f8rstefunn, vennligst bruk <a href="/observation?action=newObservationForm">observasjonsregistreringsskjemaet</a>
+isPrivate=Er privat
+privateForecasts=Private varsler
+privateForecastsForOtherUser=Private varsler for annen bruker
+noPrivateForecastsFoundForUser=Fant ingen private varsler for brukeren
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
index fb2ab4d0..0c076376 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
@@ -338,3 +338,7 @@ includeAllChildCrops=Include all child crops
 pestOrganismIds=Pests
 cropPestUpdated=Crop pest was updated
 surveillanceMessageInformation=If you want to create a message about an observation, please use the observation registration form
+isPrivate=Is private
+privateForecasts=Private forecasts
+privateForecastsForOtherUser=Private forecasts for other user
+noPrivateForecastsFoundForUser=No private forecasts found for user
diff --git a/src/main/webapp/formdefinitions/forecastConfigurationForm.json b/src/main/webapp/formdefinitions/forecastConfigurationForm.json
index b5b1d23c..0273d045 100644
--- a/src/main/webapp/formdefinitions/forecastConfigurationForm.json
+++ b/src/main/webapp/formdefinitions/forecastConfigurationForm.json
@@ -51,6 +51,11 @@
             "nullValue": "-1",
             "required" : true
         },
+        {
+            "name" : "isPrivate",
+            "dataType" : "STRING",
+            "required" : false
+        },
         {
             "name" : "locationPointOfInterestId",
             "dataType" : "INTEGER",
diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl
index 376f35a7..6f3bdf60 100644
--- a/src/main/webapp/templates/forecastConfigurationForm.ftl
+++ b/src/main/webapp/templates/forecastConfigurationForm.ftl
@@ -1,5 +1,5 @@
 <#-- 
-    Copyright (c) 2014 NIBIO <http://www.nibio.no/>. 
+    Copyright (c) 2016 NIBIO <http://www.nibio.no/>. 
   
   This file is part of VIPSLogic.
   VIPSLogic is free software: you can redistribute it and/or modify
@@ -16,7 +16,7 @@
   along with VIPSLogic.  If not, see <http://www.nibio.no/licenses/>.
 --><#include "master.ftl">
 <#macro page_head>
-        <title></title>
+        <title>${i18nBundle.viewForecastConfiguration}</title>
 </#macro>
 <#macro custom_js>
 	<script src="/js/resourcebundle.js"></script>
@@ -85,6 +85,14 @@
 	    </select>
 	    <span class="help-block" id="${formId}_cropOrganismId_validation"></span>
 	  </div>
+	  <div class="form-group">
+		  <div class="checkbox">
+			<label>
+				<input type="checkbox" name="isPrivate"<#if forecastConfiguration.isPrivate?has_content && forecastConfiguration.isPrivate == true> checked="checked"</#if>/>
+			</label>
+			${i18nBundle.isPrivate}
+		</div>
+	  </div>
 	  <div class="form-group">
 	    <label for="pestOrganismId">${i18nBundle.pestOrganismId}</label>
 	    <select class="form-control" id="pestOrganismId" name="pestOrganismId" onblur="validateField(this);" onchange="renderModelSpecificFields('${formId}');">
diff --git a/src/main/webapp/templates/forecastConfigurationList.ftl b/src/main/webapp/templates/forecastConfigurationList.ftl
index 4ba2845c..5349d69f 100644
--- a/src/main/webapp/templates/forecastConfigurationList.ftl
+++ b/src/main/webapp/templates/forecastConfigurationList.ftl
@@ -95,6 +95,83 @@
 		</tbody>
         </table>
         </div>
+        <h2>${i18nBundle.privateForecasts}</h2>
+        <div class="table-responsive">
+	<table class="table table-striped">
+		<thead>
+			<th>${i18nBundle.modelId}</th>
+			<th>${i18nBundle.poi}</th>
+			<th>${i18nBundle.weatherStationPointOfInterestId}</th>
+			<th>${i18nBundle.dateStart}</th>
+			<th>${i18nBundle.dateEnd}</th>
+		</thead>
+		<tbody>
+		<#list privateForecastConfigurations as forecastConfiguration>
+		    <tr style="cursor: pointer;" onclick="window.location.href='/forecastConfiguration?action=viewForecastConfiguration&forecastConfigurationId=${forecastConfiguration.forecastConfigurationId}';">
+		    	<td>
+				<#if i18nBundle.containsKey(forecastConfiguration.modelId)>
+					${i18nBundle[forecastConfiguration.modelId]}
+				<#else>
+					${modelInformation[forecastConfiguration.modelId].defaultName}
+				</#if>
+		    	</td>
+		    	<td>${forecastConfiguration.locationPointOfInterestId.name}</td>
+		    	<td>${forecastConfiguration.weatherStationPointOfInterestId.name}</td>
+		    	<td>${forecastConfiguration.dateStart}</td>
+		    	<td>${forecastConfiguration.dateEnd}</td>
+		    </tr>
+		</#list>
+		</tbody>
+        </table>
+        </div>
+        <#if user.isSuperUser() >
+        <h2>${i18nBundle.privateForecastsForOtherUser}</h2>
+        <div class="form-group">
+	    <label for="otherUserId">${i18nBundle.user}</label>
+	    <select class="form-control" name="otherUserId" onchange="window.location.href='/forecastConfiguration?otherUserId=' + this.options[this.options.selectedIndex].value;">
+		<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.user?lower_case}</option>
+		<#list allUsers?sort_by("lastName") as otherUser>
+			<#if otherUser.userId != user.userId>
+				<option value="${otherUser.userId}"
+					<#if (otherUserId?has_content && otherUserId == otherUser.userId)>selected="selected"</#if>
+				>${otherUser.lastName}, ${otherUser.firstName}</option>
+			</#if>
+		</#list>
+	    </select>
+	 </div>
+		<#if privateForecastConfigurationsForOtherUser?has_content>
+		<div class="table-responsive">
+		<table class="table table-striped">
+			<thead>
+				<th>${i18nBundle.modelId}</th>
+				<th>${i18nBundle.poi}</th>
+				<th>${i18nBundle.weatherStationPointOfInterestId}</th>
+				<th>${i18nBundle.dateStart}</th>
+				<th>${i18nBundle.dateEnd}</th>
+			</thead>
+			<tbody>
+			<#list privateForecastConfigurations as forecastConfiguration>
+			    <tr style="cursor: pointer;" onclick="window.location.href='/forecastConfiguration?action=viewForecastConfiguration&forecastConfigurationId=${forecastConfiguration.forecastConfigurationId}';">
+				<td>
+					<#if i18nBundle.containsKey(forecastConfiguration.modelId)>
+						${i18nBundle[forecastConfiguration.modelId]}
+					<#else>
+						${modelInformation[forecastConfiguration.modelId].defaultName}
+					</#if>
+				</td>
+				<td>${forecastConfiguration.locationPointOfInterestId.name}</td>
+				<td>${forecastConfiguration.weatherStationPointOfInterestId.name}</td>
+				<td>${forecastConfiguration.dateStart}</td>
+				<td>${forecastConfiguration.dateEnd}</td>
+			    </tr>
+			</#list>
+			</tbody>
+		</table>
+		</div>
+		<#elseif otherUserId?has_content>
+		<div class="alert alert-warning">${i18nBundle.noPrivateForecastsFoundForUser}</div>
+		</#if>
+        </#if>
 </div>
 </#macro>
 <@page_html/>
-- 
GitLab