From 6ffb6124343056dcd4cbb69c5ec2aed745b7f133 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@bioforsk.no>
Date: Fri, 22 Jan 2016 13:36:37 +0100
Subject: [PATCH] First complete version of notification subscription system

---
 nb-configuration.xml                          |   9 +-
 .../NotificationSubscriptionController.java   |  30 +-
 .../nibio/vips/logic/entity/Organization.java |  16 ++
 .../vips/logic/messaging/ForecastEvent.java   | 125 +++++++++
 ...ForecastEventNotificationSubscription.java | 131 +++++++++
 .../messaging/ForecastNotificationLog.java    | 127 +++++++++
 .../messaging/ForecastNotificationLogPK.java  | 118 ++++++++
 .../vips/logic/messaging/MessagingBean.java   | 262 +++++++++++++++---
 .../scheduling/VipsLogicTaskFactory.java      |   7 +-
 .../SendForecastEventNotificationsTask.java   |  67 +++++
 .../vips/logic/i18n/vipslogictexts.properties |   6 +
 .../logic/i18n/vipslogictexts_bs.properties   |   6 +
 .../logic/i18n/vipslogictexts_hr.properties   |   6 +
 .../logic/i18n/vipslogictexts_nb.properties   |   6 +
 .../logic/i18n/vipslogictexts_sr.properties   |   6 +
 src/main/webapp/WEB-INF/glassfish-web.xml     |  10 +
 .../notificationSubscriptionForm.ftl          |  46 ++-
 .../messaging/UniversalMessagingTest.java     |   2 +-
 18 files changed, 916 insertions(+), 64 deletions(-)
 create mode 100644 src/main/java/no/nibio/vips/logic/messaging/ForecastEvent.java
 create mode 100644 src/main/java/no/nibio/vips/logic/messaging/ForecastEventNotificationSubscription.java
 create mode 100644 src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLog.java
 create mode 100644 src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLogPK.java
 create mode 100644 src/main/java/no/nibio/vips/logic/scheduling/tasks/SendForecastEventNotificationsTask.java
 create mode 100644 src/main/webapp/WEB-INF/glassfish-web.xml

diff --git a/nb-configuration.xml b/nb-configuration.xml
index 05a987a7..d096f3f9 100644
--- a/nb-configuration.xml
+++ b/nb-configuration.xml
@@ -16,7 +16,14 @@ Any value defined here will override the pom.xml file value but is only applicab
         <netbeans.hint.j2eeVersion>1.5</netbeans.hint.j2eeVersion>
         <org-netbeans-modules-maven-jaxws.rest_2e_config_2e_type>ide</org-netbeans-modules-maven-jaxws.rest_2e_config_2e_type>
         <org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder>js</org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder>
-        <org-netbeans-modules-maven-j2ee.netbeans_2e_hint_2e_deploy_2e_server>JBoss4</org-netbeans-modules-maven-j2ee.netbeans_2e_hint_2e_deploy_2e_server>
         <netbeans.hint.license>nibio_open_source_license.ftl</netbeans.hint.license>
+        <org-netbeans-modules-css-prep.sass_2e_compiler_2e_options/>
+        <org-netbeans-modules-css-prep.less_2e_mappings>/less:/css</org-netbeans-modules-css-prep.less_2e_mappings>
+        <org-netbeans-modules-css-prep.less_2e_enabled>false</org-netbeans-modules-css-prep.less_2e_enabled>
+        <org-netbeans-modules-css-prep.sass_2e_mappings>/scss:/css</org-netbeans-modules-css-prep.sass_2e_mappings>
+        <org-netbeans-modules-css-prep.sass_2e_enabled>false</org-netbeans-modules-css-prep.sass_2e_enabled>
+        <org-netbeans-modules-css-prep.less_2e_compiler_2e_options/>
+        <org-netbeans-modules-maven-j2ee.netbeans_2e_hint_2e_deploy_2e_server>WildFly</org-netbeans-modules-maven-j2ee.netbeans_2e_hint_2e_deploy_2e_server>
+        <org-netbeans-modules-projectapi.jsf_2e_language>Facelets</org-netbeans-modules-projectapi.jsf_2e_language>
     </properties>
 </project-shared-configuration>
diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/NotificationSubscriptionController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/NotificationSubscriptionController.java
index 66a41c55..fcd93445 100644
--- a/src/main/java/no/nibio/vips/logic/controller/servlet/NotificationSubscriptionController.java
+++ b/src/main/java/no/nibio/vips/logic/controller/servlet/NotificationSubscriptionController.java
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
 import no.nibio.vips.logic.controller.session.UserBean;
 import no.nibio.vips.logic.entity.VipsLogicRole;
 import no.nibio.vips.logic.entity.VipsLogicUser;
+import no.nibio.vips.logic.messaging.ForecastEventNotificationSubscription;
 import no.nibio.vips.logic.messaging.MessageNotificationSubscription;
 import no.nibio.vips.logic.messaging.UniversalMessageFormat;
 import no.nibio.vips.logic.util.SessionControllerGetter;
@@ -79,13 +80,14 @@ public class NotificationSubscriptionController extends HttpServlet {
                 {
                     viewUser = user;
                 }
-                MessageNotificationSubscription subscription = SessionControllerGetter.getMessagingBean().getMessageNotificationSubscription(viewUser.getUserId());
-                request.setAttribute("messageNotificationSubscription",subscription);
-                request.setAttribute("userCropIds", subscription != null ? subscription.getCropIds() : new Integer[0]);
-                request.setAttribute("userMessageTagIds", subscription != null ? subscription.getMessageTagIds() : new Integer[0]);
+                MessageNotificationSubscription messageNotificationSubscription = SessionControllerGetter.getMessagingBean().getMessageNotificationSubscription(viewUser.getUserId());
+                request.setAttribute("messageNotificationSubscription",messageNotificationSubscription);
+                ForecastEventNotificationSubscription forecastEventNotificationSubscription = SessionControllerGetter.getMessagingBean().getForecastEventNotificationSubscription(viewUser.getUserId());
+                request.setAttribute("forecastEventNotificationSubscription",forecastEventNotificationSubscription);
                 request.setAttribute("viewUser", viewUser);
                 request.setAttribute("allCrops", em.createNamedQuery("Organism.findAllCrops").getResultList());
                 request.setAttribute("messageTagSet", em.createNamedQuery("MessageTag.findAll").getResultList());
+                request.setAttribute("weatherStationIds", SessionControllerGetter.getPointOfInterestBean().getWeatherstationsForUser(viewUser));
                 request.setAttribute("universalMessageFormats", SessionControllerGetter.getMessagingBean().getAllUniversalMessageFormats());
                 request.setAttribute("messageKey", request.getParameter("messageKey"));
                 request.getRequestDispatcher("/notificationSubscriptionForm.ftl").forward(request, response);
@@ -103,12 +105,20 @@ public class NotificationSubscriptionController extends HttpServlet {
                 {
                     viewUser = user;
                 }
-                MessageNotificationSubscription subscription = new MessageNotificationSubscription();
-                subscription.setUniversalMessageFormatId(Integer.valueOf(request.getParameter("universalMessageFormatId")));
-                subscription.setCropIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("cropOrganismIds")));
-                subscription.setMessageTagIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("messageTagIds")));
-                subscription.setUserId(viewUser.getUserId());
-                SessionControllerGetter.getMessagingBean().storeMessageNotificationSubscription(subscription);
+                MessageNotificationSubscription mSubscription = new MessageNotificationSubscription();
+                mSubscription.setUniversalMessageFormatId(Integer.valueOf(request.getParameter("messageN_universalMessageFormatId")));
+                mSubscription.setCropIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("messageN_cropOrganismIds")));
+                mSubscription.setMessageTagIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("messageN_messageTagIds")));
+                mSubscription.setUserId(viewUser.getUserId());
+                SessionControllerGetter.getMessagingBean().storeMessageNotificationSubscription(mSubscription);
+                
+                ForecastEventNotificationSubscription fSubscription = new ForecastEventNotificationSubscription();
+                fSubscription.setUniversalMessageFormatId(Integer.valueOf(request.getParameter("forecastN_universalMessageFormatId")));
+                fSubscription.setCropIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("forecastN_cropOrganismIds")));
+                fSubscription.setWeatherStationIds(FormUtil.getIdsFromMultipleSelect(request.getParameterValues("forecastN_weatherStationIds")));
+                fSubscription.setUserId(viewUser.getUserId());
+                SessionControllerGetter.getMessagingBean().storeForecastEventNotificationSubscription(fSubscription);
+                
                 // Redirect to form with confirmation message
                 response.sendRedirect(new StringBuilder("http://")
                             .append(ServletUtil.getServerName(request))
diff --git a/src/main/java/no/nibio/vips/logic/entity/Organization.java b/src/main/java/no/nibio/vips/logic/entity/Organization.java
index 8957d074..1f2db83f 100644
--- a/src/main/java/no/nibio/vips/logic/entity/Organization.java
+++ b/src/main/java/no/nibio/vips/logic/entity/Organization.java
@@ -102,6 +102,8 @@ public class Organization implements Serializable {
     private String defaultLocale;
     @OneToMany(mappedBy = "organizationId")
     private Set<VipsLogicUser> vipsLogicUserSet;
+    @Column(name = "vipsweb_url")
+    private String vipswebUrl;
 
     public Organization() {
     }
@@ -295,4 +297,18 @@ public class Organization implements Serializable {
         this.defaultVipsCoreUserId = defaultVipsCoreUserId;
     }
 
+    /**
+     * @return the vipswebUrl
+     */
+    public String getVipswebUrl() {
+        return vipswebUrl;
+    }
+
+    /**
+     * @param vipswebUrl the vipswebUrl to set
+     */
+    public void setVipswebUrl(String vipswebUrl) {
+        this.vipswebUrl = vipswebUrl;
+    }
+
 }
diff --git a/src/main/java/no/nibio/vips/logic/messaging/ForecastEvent.java b/src/main/java/no/nibio/vips/logic/messaging/ForecastEvent.java
new file mode 100644
index 00000000..8ad75d14
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/messaging/ForecastEvent.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * 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.messaging;
+
+import java.io.Serializable;
+import java.util.Set;
+import javax.persistence.Basic;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "forecast_event", schema = "messaging")
+@XmlRootElement
+@NamedQueries({
+    @NamedQuery(name = "ForecastEvent.findAll", query = "SELECT f FROM ForecastEvent f"),
+    @NamedQuery(name = "ForecastEvent.findByForecastEventId", query = "SELECT f FROM ForecastEvent f WHERE f.forecastEventId = :forecastEventId"),
+    @NamedQuery(name = "ForecastEvent.findByEventName", query = "SELECT f FROM ForecastEvent f WHERE f.eventName = :eventName"),
+    @NamedQuery(name = "ForecastEvent.findByEventDescription", query = "SELECT f FROM ForecastEvent f WHERE f.eventDescription = :eventDescription")})
+public class ForecastEvent implements Serializable {
+    
+    public final static Integer TO_RED = 1;
+    public final static Integer GREEN_TO_YELLOW = 2;
+
+    private static final long serialVersionUID = 1L;
+    @Id
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "forecast_event_id")
+    private Integer forecastEventId;
+    @Size(max = 255)
+    @Column(name = "event_name")
+    private String eventName;
+    @Size(max = 2147483647)
+    @Column(name = "event_description")
+    private String eventDescription;
+   
+
+    public ForecastEvent() {
+    }
+
+    public ForecastEvent(Integer forecastEventId) {
+        this.forecastEventId = forecastEventId;
+    }
+
+    public Integer getForecastEventId() {
+        return forecastEventId;
+    }
+
+    public void setForecastEventId(Integer forecastEventId) {
+        this.forecastEventId = forecastEventId;
+    }
+
+    public String getEventName() {
+        return eventName;
+    }
+
+    public void setEventName(String eventName) {
+        this.eventName = eventName;
+    }
+
+    public String getEventDescription() {
+        return eventDescription;
+    }
+
+    public void setEventDescription(String eventDescription) {
+        this.eventDescription = eventDescription;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (forecastEventId != null ? forecastEventId.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 ForecastEvent)) {
+            return false;
+        }
+        ForecastEvent other = (ForecastEvent) object;
+        if ((this.forecastEventId == null && other.forecastEventId != null) || (this.forecastEventId != null && !this.forecastEventId.equals(other.forecastEventId))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.messaging.ForecastEvent[ forecastEventId=" + forecastEventId + " ]";
+    }
+
+}
diff --git a/src/main/java/no/nibio/vips/logic/messaging/ForecastEventNotificationSubscription.java b/src/main/java/no/nibio/vips/logic/messaging/ForecastEventNotificationSubscription.java
new file mode 100644
index 00000000..9e729435
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/messaging/ForecastEventNotificationSubscription.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ * 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.messaging;
+
+import java.util.List;
+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.Table;
+import no.nibio.vips.logic.util.IntegerArrayUserType;
+import org.hibernate.annotations.Type;
+import org.hibernate.annotations.TypeDef;
+import org.hibernate.annotations.TypeDefs;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "forecast_event_notification_subscription", schema = "messaging")
+@TypeDefs( {@TypeDef( name= "IntegerArray", typeClass = IntegerArrayUserType.class)})
+public class ForecastEventNotificationSubscription {
+
+    private static final long serialVersionUID = 1L;
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Basic(optional = false)
+    @Column(name = "user_id")
+    private Integer userId;
+    
+    @Column(name = "universal_message_format_id")
+    private Integer universalMessageFormatId;
+    
+    @Type(type = "IntegerArray")
+    @Column(name = "weather_station_ids")
+    private Integer[] weatherStationIds;
+    
+    @Type(type = "IntegerArray")
+    @Column(name = "crop_ids")
+    private Integer[] cropIds;
+
+    /**
+     * @return the userId
+     */
+    public Integer getUserId() {
+        return userId;
+    }
+
+    /**
+     * @param userId the userId to set
+     */
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * @return the universalMessageFormatId
+     */
+    public Integer getUniversalMessageFormatId() {
+        return universalMessageFormatId;
+    }
+
+    /**
+     * @param universalMessageFormatId the universalMessageFormatId to set
+     */
+    public void setUniversalMessageFormatId(Integer universalMessageFormatId) {
+        this.universalMessageFormatId = universalMessageFormatId;
+    }
+
+    /**
+     * @return the weatherStationIds
+     */
+    public Integer[] getWeatherStationIds() {
+        return weatherStationIds;
+    }
+
+    /**
+     * @param weatherStationIds the weatherStationIds to set
+     */
+    public void setWeatherStationIds(Integer[] weatherStationIds) {
+        this.weatherStationIds = weatherStationIds;
+    }
+    
+    /**
+     * Convenience method
+     * @param weatherStationIds 
+     */
+    public void setWeatherStationIds(List<Integer> weatherStationIds)
+    {
+        this.weatherStationIds = weatherStationIds.toArray(new Integer[weatherStationIds.size()]);
+    }
+
+    /**
+     * @return the cropIds
+     */
+    public Integer[] getCropIds() {
+        return cropIds;
+    }
+
+    /**
+     * @param cropIds the cropIds to set
+     */
+    public void setCropIds(Integer[] cropIds) {
+        this.cropIds = cropIds;
+    }
+    
+    public void setCropIds(List<Integer> cropIds)
+    {
+        this.cropIds = cropIds.toArray(new Integer[cropIds.size()]);
+    }
+}
diff --git a/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLog.java b/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLog.java
new file mode 100644
index 00000000..a1f97004
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLog.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ * 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.messaging;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "forecast_notification_log", schema = "messaging")
+@XmlRootElement
+@NamedQueries({
+    @NamedQuery(name = "ForecastNotificationLog.findAll", query = "SELECT f FROM ForecastNotificationLog f"),
+    @NamedQuery(name = "ForecastNotificationLog.findByForecastConfigurationId", query = "SELECT f FROM ForecastNotificationLog f WHERE f.forecastNotificationLogPK.forecastConfigurationId = :forecastConfigurationId"),
+    @NamedQuery(name = "ForecastNotificationLog.findByForecastEventId", query = "SELECT f FROM ForecastNotificationLog f WHERE f.forecastNotificationLogPK.forecastEventId = :forecastEventId"),
+    @NamedQuery(name = "ForecastNotificationLog.findByEventDate", query = "SELECT f FROM ForecastNotificationLog f WHERE f.forecastNotificationLogPK.eventDate = :eventDate"),
+    @NamedQuery(name = "ForecastNotificationLog.findByPK", query = "SELECT f FROM ForecastNotificationLog f WHERE f.forecastNotificationLogPK.eventDate = :eventDate AND f.forecastNotificationLogPK.forecastEventId = :forecastEventId AND f.forecastNotificationLogPK.forecastConfigurationId = :forecastConfigurationId"),
+    @NamedQuery(name = "ForecastNotificationLog.findByCreatedTime", query = "SELECT f FROM ForecastNotificationLog f WHERE f.createdTime = :createdTime")})
+public class ForecastNotificationLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    @EmbeddedId
+    protected ForecastNotificationLogPK forecastNotificationLogPK;
+    @Column(name = "created_time")
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date createdTime;
+    @Column(name = "universal_message_id")
+    private Integer universalMessageId;
+    
+
+    public ForecastNotificationLog() {
+    }
+
+    public ForecastNotificationLog(ForecastNotificationLogPK forecastNotificationLogPK) {
+        this.forecastNotificationLogPK = forecastNotificationLogPK;
+    }
+
+    public ForecastNotificationLog(Long forecastConfigurationId, int forecastEvent, Date eventDate) {
+        this.forecastNotificationLogPK = new ForecastNotificationLogPK(forecastConfigurationId, forecastEvent, eventDate);
+    }
+
+    public ForecastNotificationLogPK getForecastNotificationLogPK() {
+        return forecastNotificationLogPK;
+    }
+
+    public void setForecastNotificationLogPK(ForecastNotificationLogPK forecastNotificationLogPK) {
+        this.forecastNotificationLogPK = forecastNotificationLogPK;
+    }
+
+    public Date getCreatedTime() {
+        return createdTime;
+    }
+
+    public void setCreatedTime(Date createdTime) {
+        this.createdTime = createdTime;
+    }
+
+    
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (forecastNotificationLogPK != null ? forecastNotificationLogPK.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 ForecastNotificationLog)) {
+            return false;
+        }
+        ForecastNotificationLog other = (ForecastNotificationLog) object;
+        if ((this.forecastNotificationLogPK == null && other.forecastNotificationLogPK != null) || (this.forecastNotificationLogPK != null && !this.forecastNotificationLogPK.equals(other.forecastNotificationLogPK))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.messaging.ForecastNotificationLog[ forecastNotificationLogPK=" + forecastNotificationLogPK + " ]";
+    }
+
+    /**
+     * @return the universalMessageId
+     */
+    public Integer getUniversalMessageId() {
+        return universalMessageId;
+    }
+
+    /**
+     * @param universalMessageId the universalMessageId to set
+     */
+    public void setUniversalMessageId(Integer universalMessageId) {
+        this.universalMessageId = universalMessageId;
+    }
+
+}
diff --git a/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLogPK.java b/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLogPK.java
new file mode 100644
index 00000000..eda72b87
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/messaging/ForecastNotificationLogPK.java
@@ -0,0 +1,118 @@
+/*
+ * 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
+ * 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.messaging;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Embeddable
+public class ForecastNotificationLogPK implements Serializable {
+
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "forecast_configuration_id")
+    private Long forecastConfigurationId;
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "forecast_event_id")
+    private int forecastEventId;
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "event_date")
+    @Temporal(TemporalType.DATE)
+    private Date eventDate;
+
+    public ForecastNotificationLogPK() {
+    }
+
+    public ForecastNotificationLogPK(Long forecastConfigurationId, int forecastEventId, Date eventDate) {
+        this.forecastConfigurationId = forecastConfigurationId;
+        this.forecastEventId = forecastEventId;
+        this.eventDate = eventDate;
+    }
+
+    public Long getForecastConfigurationId() {
+        return forecastConfigurationId;
+    }
+
+    public void setForecastConfigurationId(Long forecastConfigurationId) {
+        this.forecastConfigurationId = forecastConfigurationId;
+    }
+
+    public int getForecastEventId() {
+        return forecastEventId;
+    }
+
+    public void setForecastEventId(int forecastEventId) {
+        this.forecastEventId = forecastEventId;
+    }
+
+    public Date getEventDate() {
+        return eventDate;
+    }
+
+    public void setEventDate(Date eventDate) {
+        this.eventDate = eventDate;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (long) forecastConfigurationId;
+        hash += (int) forecastEventId;
+        hash += (eventDate != null ? eventDate.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 ForecastNotificationLogPK)) {
+            return false;
+        }
+        ForecastNotificationLogPK other = (ForecastNotificationLogPK) object;
+        if (this.forecastConfigurationId != other.forecastConfigurationId) {
+            return false;
+        }
+        if (this.forecastEventId != other.forecastEventId) {
+            return false;
+        }
+        if ((this.eventDate == null && other.eventDate != null) || (this.eventDate != null && !this.eventDate.equals(other.eventDate))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.messaging.ForecastNotificationLogPK[ forecastConfigurationId=" + forecastConfigurationId + ", forecastEvent=" + forecastEventId + ", eventDate=" + eventDate + " ]";
+    }
+
+}
diff --git a/src/main/java/no/nibio/vips/logic/messaging/MessagingBean.java b/src/main/java/no/nibio/vips/logic/messaging/MessagingBean.java
index 618644e2..71155571 100644
--- a/src/main/java/no/nibio/vips/logic/messaging/MessagingBean.java
+++ b/src/main/java/no/nibio/vips/logic/messaging/MessagingBean.java
@@ -21,21 +21,30 @@ package no.nibio.vips.logic.messaging;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ibm.icu.text.MessageFormat;
+import com.ibm.icu.util.ULocale;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.ResourceBundle;
 import javax.ejb.Stateless;
 import javax.persistence.EntityManager;
 import javax.persistence.NoResultException;
 import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
-import javax.persistence.TemporalType;
 import javax.ws.rs.core.Response;
+import no.nibio.vips.entity.Result;
+import no.nibio.vips.logic.entity.ForecastConfiguration;
+import no.nibio.vips.logic.entity.ForecastResult;
 import no.nibio.vips.logic.entity.Message;
 import no.nibio.vips.logic.entity.MessageLocale;
 import no.nibio.vips.logic.entity.VipsLogicUser;
+import no.nibio.vips.logic.util.Globals;
 import no.nibio.vips.logic.util.RESTAuthenticator;
 import no.nibio.vips.logic.util.SessionControllerGetter;
+import no.nibio.vips.logic.util.SystemTime;
 import org.jboss.resteasy.client.jaxrs.ResteasyClient;
 import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
 import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
@@ -49,41 +58,6 @@ public class MessagingBean {
     @PersistenceContext(unitName="VIPSLogic-PU")
     EntityManager em;
     
-    public void testStoreUniversalMessage()
-    {
-        List<VipsLogicUser> allUsers=SessionControllerGetter.getUserBean().getAllUsers();
-        Message m = SessionControllerGetter.getMessageBean().getMessage(5);
-        MessageLocale ml = m.getLocalMessage("nn");
-        UniversalMessage um = new UniversalMessage(
-                "nn",ml.getHeading(), ml.getLeadParagraph(), ml.getBody(), "http://localhost:8000/messages/" + m.getMessageId()
-        );
-        um.setExpiresAt(new Date());
-        List<MessageRecipient> distributionList = new ArrayList<>();
-        for(VipsLogicUser user : allUsers)
-        {
-            MessageRecipient r = new MessageRecipient(
-                    String.valueOf(user.getUserId()),
-                    user.getFirstName() + " " + user.getLastName(), 
-                    "Mail", 
-                    user.getEmail(),
-                    user.getPreferredLocale()
-            );
-            distributionList.add(r);
-        }
-        um.setDistributionList(distributionList);
-        //em.persist(um);
-        
-        List<UniversalMessage> ums = em.createNamedQuery("UniversalMessage.findAll").getResultList();
-        for(UniversalMessage uMe : ums)
-        {
-            for(MessageLocalVersion lv:uMe.getMessageLocalVersionObjects())
-            {
-                System.out.println(lv.getMsgSubject());
-            }
-            
-        }
-    }
-    
     public void sendUniversalMessage(Message message)
     {
         String msgDownloadUrlTpl = "http://www.vips-landbruk.no/";
@@ -105,11 +79,11 @@ public class MessagingBean {
         
     }
     
-    public void sendUniversalMessage(UniversalMessage uMessage)
+    public UniversalMessage sendUniversalMessage(UniversalMessage uMessage)
     {
         if(uMessage.getDistributionListObjects().isEmpty())
         {
-            return;
+            return uMessage;
         }
         
         // Store it
@@ -126,21 +100,25 @@ public class MessagingBean {
 
         try
         {
-        //System.out.println(new ObjectMapper().writeValueAsString(um));
-        Response r = umClient.sendMessage(new ObjectMapper().writeValueAsString(uMessage));
-        
-        // TODO: Handle result
+            //System.out.println(new ObjectMapper().writeValueAsString(um));
+            Response r = umClient.sendMessage(new ObjectMapper().writeValueAsString(uMessage));
 
-        String result = r.readEntity(String.class);
+            // TODO: Handle result
+
+            String result = r.readEntity(String.class);
+            System.out.println(result);
         }
         catch(JsonProcessingException ex)
         {
             // Handle error
         }
+        
+        return uMessage;
     }
 
     private List<MessageRecipient> getMessageNotificationSubscribers(Message message) {
         
+        // TODO??? also filter by crops??
         Query q = em.createNativeQuery(
                 "SELECT \n" +
                 "	u.preferred_locale,\n" +
@@ -191,4 +169,202 @@ public class MessagingBean {
     {
         return em.createNamedQuery("UniversalMessageFormat.findAll").getResultList();
     }
+    
+    // For getting all available notification translations
+    public final static String[] AVAILABLE_FORECAST_NOTIFICATION_LOCALES = {"nb","en"};
+    
+    public void sendForecastEventNotifications()
+    {
+        // Find forecast configurations with a change from green to yellow or [all colors] to red
+        // in the future, and when that is
+        // Meaning: Create a list of forecast_notification_logs items
+        // First: Find all forecast configurations with results for the next 10 days
+        Date systemTime = SystemTime.getSystemTime();
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(systemTime);
+        cal.add(Calendar.DATE, 10);
+        Date in10Days = cal.getTime();
+        List<ForecastConfiguration> forecastConfigurations = em.createNativeQuery(
+                "SELECT * FROM public.forecast_configuration f "
+                + "WHERE f.forecast_configuration_id IN ("
+                + "     SELECT forecast_configuration_id FROM forecast_result WHERE result_valid_time between :dateStart AND :dateEnd"
+                + ")",
+                ForecastConfiguration.class
+            )
+            .setParameter("dateStart", systemTime)
+            .setParameter("dateEnd", in10Days)
+            .getResultList();
+        
+        List<ForecastNotificationLog> newNotifications = new ArrayList<>();
+        ForecastEvent toRed = em.find(ForecastEvent.class, ForecastEvent.TO_RED);
+        ForecastEvent greenToYellow = em.find(ForecastEvent.class, ForecastEvent.GREEN_TO_YELLOW);
+        ForecastEvent currentEvent = null;
+        for(ForecastConfiguration conf:forecastConfigurations)
+        {
+            List<ForecastResult> results = em.createNamedQuery("ForecastResult.findByForecastConfigurationId", ForecastResult.class)
+                    .setParameter("forecastConfigurationId", conf.getForecastConfigurationId())
+                    .getResultList();
+            
+            Integer previousWarningStatus = Result.WARNING_STATUS_NO_WARNING;
+            
+            // There may be several new events for the next few days. 
+            // If you find a NEW TO_RED event first, skip the rest
+            // If you find a NEW GREEN_TO_YELLOW event first, add it and search for a new TO_RED event
+            for(ForecastResult result:results)
+            {
+                if(result.getResultValidTime().before(systemTime) || result.getResultValidTime().after(in10Days))
+                {
+                    continue;
+                }
+                // TO_RED event detected
+                if( 
+                        result.getWarningStatus().equals(Result.WARNING_STATUS_HIGH_RISK)
+                        && !previousWarningStatus.equals(Result.WARNING_STATUS_HIGH_RISK) 
+                )
+                {
+                    currentEvent = toRed;
+                }
+                // GREEN_TO_YELLOW event detehcted
+                else if(
+                        result.getWarningStatus().equals(Result.WARNING_STATUS_MINOR_RISK)
+                        && ! previousWarningStatus.equals(Result.WARNING_STATUS_MINOR_RISK)
+                        && ! previousWarningStatus.equals(Result.WARNING_STATUS_HIGH_RISK)
+                        )
+                {
+                    currentEvent = greenToYellow;
+                }
+                else
+                {
+                    currentEvent = null;
+                }
+                
+                if(currentEvent != null)
+                {
+                    ForecastNotificationLogPK pk = new ForecastNotificationLogPK(
+                            conf.getForecastConfigurationId(), 
+                            currentEvent.getForecastEventId(), 
+                            result.getResultValidTime()
+                    );
+                    
+                    // Could not find same event. We persist it so we won't
+                    // have any more of the same events the same day
+                    if(em.find(ForecastNotificationLog.class, pk) == null)
+                    {
+                        ForecastNotificationLog newNotification = new ForecastNotificationLog(pk);
+                        newNotification.setCreatedTime(SystemTime.getSystemTime());
+                        em.persist(newNotification);
+                        newNotifications.add(newNotification);
+                    }
+                    if(currentEvent.getForecastEventId().equals(ForecastEvent.TO_RED))
+                    {
+                        break;
+                    }
+                }
+                previousWarningStatus = result.getWarningStatus();
+            }
+        }
+        
+        SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat);
+        
+        // For the new ones: Create and send Universal messages
+        for(ForecastNotificationLog newNotification:newNotifications)
+        {
+            ForecastConfiguration fConf = em.find(ForecastConfiguration.class, newNotification.getForecastNotificationLogPK().getForecastConfigurationId());
+            UniversalMessage uMessage = new UniversalMessage();
+            // Get all translations for this, create allMessageLocalVersions
+            for(String locale:MessagingBean.AVAILABLE_FORECAST_NOTIFICATION_LOCALES)
+            {
+                ResourceBundle localBundle = ResourceBundle.getBundle("no.nibio.vips.logic.i18n.vipslogictexts",new ULocale(locale).toLocale());
+                 String headingTemplate = localBundle.getString("forecastNotificationMessageHeadingTpl_" + 
+                        newNotification.getForecastNotificationLogPK().getForecastEventId()
+                );
+                String bodyTemplate = localBundle.getString("forecastNotificationMessageBodyTpl_" + 
+                        newNotification.getForecastNotificationLogPK().getForecastEventId()
+                );
+                
+                // Template is as follows (in English):
+                // Forecast warning status has turned to high risk of infection 
+                //for {0} in {1} at location {2} at date {3}. Model: {4}. To 
+                //read details, please visit: {5}
+                String detailsUrl = fConf.getVipsLogicUserId().getOrganizationId().getVipswebUrl() + "forecasts/" + fConf.getForecastConfigurationId();
+                Object[] templateParts = {
+                    fConf.getPestOrganismId().getLocalName(locale),
+                    fConf.getCropOrganismId().getLocalName(locale),
+                    fConf.getLocationPointOfInterestId().getName(),
+                    format.format(newNotification.getForecastNotificationLogPK().getEventDate()),
+                    detailsUrl
+                };
+                
+                uMessage.addMessageLocalVersion(locale, headingTemplate, "",MessageFormat.format(bodyTemplate, templateParts),detailsUrl);
+                
+            }
+            uMessage.setDistributionList(this.getForecastEventNotificationSubscribers(fConf));
+            cal.setTime(systemTime);
+            cal.add(Calendar.DATE, 1);
+            uMessage.setExpiresAt(cal.getTime());
+            
+            this.sendUniversalMessage(uMessage);
+            // If sending was successful, we also store the universalMessage id in the events
+            if(uMessage.getUniversalMessageId() != null)
+            {
+                newNotification.setUniversalMessageId(uMessage.getUniversalMessageId());
+            }
+            // Otherwise, we remove the events!
+            else
+            {
+                em.remove(newNotification);
+            }
+        }
+    }
+    
+    public List<MessageRecipient> getForecastEventNotificationSubscribers(ForecastConfiguration config)
+    {
+        Query q = em.createNativeQuery(
+                "SELECT \n" +
+                "	u.preferred_locale,\n" +
+                "	umf.format_name AS type,\n" +
+                "	CASE fns.universal_message_format_id " + 
+                "           WHEN " + UniversalMessageFormat.FORMAT_EMAIL + " THEN u.email " +
+                "           WHEN " + UniversalMessageFormat.FORMAT_SMS + " THEN u.phone " +
+                "           ELSE '' " +
+                "       END AS msg_delivery_address, \n" + // Needs update as more options are added
+                "	u.first_name || ' ' || u.last_name AS name,\n" +
+                "	u.user_id AS recipient_id\n" +
+                "FROM public.vips_logic_user u, messaging.forecast_event_notification_subscription fns, messaging.universal_message_format umf \n" +
+                "WHERE fns.user_id=u.user_id\n" +
+                "AND fns.universal_message_format_id = umf.universal_message_format_id\n" +
+                "AND u.user_id IN (\n" +
+                "	SELECT user_id FROM messaging.forecast_event_notification_subscription\n" +
+                "	WHERE :weatherStationId = ANY(weather_station_ids) \n" +
+                "       AND :cropOrganismId = ANY(crop_ids)" + 
+                ");\n",
+                MessageRecipient.class);
+        
+        q.setParameter("weatherStationId", config.getWeatherStationPointOfInterestId().getPointOfInterestId());
+        q.setParameter("cropOrganismId", config.getCropOrganismId().getOrganismId());
+        
+        return q.getResultList();
+    }
+
+    public ForecastEventNotificationSubscription getForecastEventNotificationSubscription(Integer userId) {
+        Query q = em.createNativeQuery(
+                "SELECT * FROM messaging.forecast_event_notification_subscription m "
+                + "WHERE m.user_id=:userId",
+                ForecastEventNotificationSubscription.class
+        ).setParameter("userId", userId);
+        try
+        {
+            return (ForecastEventNotificationSubscription) q.getSingleResult();
+        }
+        catch(NoResultException ex)
+        {
+            return null;
+        }
+    }
+    
+    public void storeForecastEventNotificationSubscription(ForecastEventNotificationSubscription subscription)
+    {
+        em.merge(subscription);
+    }
+    
 }
diff --git a/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java b/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java
index 2726028e..580320af 100644
--- a/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java
+++ b/src/main/java/no/nibio/vips/logic/scheduling/VipsLogicTaskFactory.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import no.nibio.vips.logic.scheduling.tasks.DeleteAllExpiredUserUuidsTask;
 import no.nibio.vips.logic.scheduling.tasks.RunAllForecastConfigurationsTask;
+import no.nibio.vips.logic.scheduling.tasks.SendForecastEventNotificationsTask;
 import no.nibio.vips.logic.scheduling.tasks.UpdateForecastResultCacheTableTask;
 import no.nibio.vips.logic.scheduling.tasks.UpdateForecastSummaryTableTask;
 import no.nibio.vips.logic.scheduling.tasks.UpdateModelInformationTask;
@@ -41,8 +42,9 @@ public class VipsLogicTaskFactory {
     public static final int UPDATE_FORECAST_RESULT_CACHE_TABLE_TASK = 3;
     public static final int UPDATE_FORECAST_SUMMARY_TABLE_TASK = 4;
     public static final int DELETE_ALL_EXPIRED_UUIDS_TASK = 5;
+    public static final int SEND_FORECAST_EVENT_NOTIFICATIONS_TASK = 6;
     
-    private final static int[] ALL_TASK_IDS = {1,2,3,4,5};
+    private final static int[] ALL_TASK_IDS = {1,2,3,4,5,6};
     
     private static List<VipsLogicTask> allTasksList;
     private static Map<String,VipsLogicTask> allTasksMap;
@@ -72,6 +74,9 @@ public class VipsLogicTaskFactory {
             case DELETE_ALL_EXPIRED_UUIDS_TASK:
                 retVal =  new DeleteAllExpiredUserUuidsTask();
                 break;
+            case SEND_FORECAST_EVENT_NOTIFICATIONS_TASK:
+                retVal =  new SendForecastEventNotificationsTask();
+                break;
             default:
                 return null;
         }
diff --git a/src/main/java/no/nibio/vips/logic/scheduling/tasks/SendForecastEventNotificationsTask.java b/src/main/java/no/nibio/vips/logic/scheduling/tasks/SendForecastEventNotificationsTask.java
new file mode 100644
index 00000000..fdb10062
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/scheduling/tasks/SendForecastEventNotificationsTask.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ * 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.scheduling.tasks;
+
+import it.sauronsoftware.cron4j.TaskExecutionContext;
+import no.nibio.vips.logic.scheduling.VipsLogicTask;
+import no.nibio.vips.logic.scheduling.VipsLogicTaskFactory;
+import no.nibio.vips.logic.util.SessionControllerGetter;
+import no.nibio.web.forms.FormField;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+public class SendForecastEventNotificationsTask extends VipsLogicTask {
+
+    @Override
+    public String getConfigFormDefinition(String language) {
+        String retVal = "{"
+                        + "   \"fields\":["
+                        + "       {"
+                        + "           \"name\":\"factoryId\","
+                        + "           \"dataType\":\"" + FormField.DATA_TYPE_INTEGER + "\","
+                        + "           \"fieldType\":\"" + FormField.FIELD_TYPE_HIDDEN + "\","
+                        + "           \"webValue\":[\"" + VipsLogicTaskFactory.SEND_FORECAST_EVENT_NOTIFICATIONS_TASK + "\"]"
+                        + "       }"
+                        + "    ]}";
+         return retVal;
+    }
+
+    @Override
+    public void execute(TaskExecutionContext tec) throws RuntimeException {
+        tec.setCompleteness(0d);
+        SessionControllerGetter.getMessagingBean().sendForecastEventNotifications();
+        tec.setCompleteness(1d);
+    }
+    
+    @Override
+    public boolean supportsStatusTracking()
+    {
+        return true;
+    }
+    
+    @Override
+    public boolean supportsCompletenessTracking()
+    {
+        return true;
+    }
+
+}
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 1a8499fd..1c5c2ffa 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
@@ -304,3 +304,9 @@ notificationSubscriptionDescription=You can subscribe to different kinds of noti
 messageFormat=Message format
 Mail=Email
 Sms=SMS
+forecastNotificationMessageBodyTpl_1=Forecast warning status has turned to high risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageBodyTpl_2=Forecast warning status has turned to moderate risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageHeadingTpl_1=Notification of high risk of infection
+forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
+task_SendForecastEventNotificationsTask_name=Send forecast event notifications
+task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
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 e5d8895c..40de134a 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
@@ -304,3 +304,9 @@ notificationSubscriptionDescription=You can subscribe to different kinds of noti
 messageFormat=Message format
 Mail=Email
 Sms=SMS
+forecastNotificationMessageBodyTpl_1=Forecast warning status has turned to high risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageBodyTpl_2=Forecast warning status has turned to moderate risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageHeadingTpl_1=Notification of high risk of infection
+forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
+task_SendForecastEventNotificationsTask_name=Send forecast event notifications
+task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
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 7d7af415..31c5b62f 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
@@ -303,3 +303,9 @@ notificationSubscriptionDescription=You can subscribe to different kinds of noti
 messageFormat=Message format
 Mail=Email
 Sms=SMS
+forecastNotificationMessageBodyTpl_1=Forecast warning status has turned to high risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageBodyTpl_2=Forecast warning status has turned to high risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageHeadingTpl_1=Notification of high risk of infection
+forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
+task_SendForecastEventNotificationsTask_name=Send forecast event notifications
+task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
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 f948f783..9ad1df42 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
@@ -304,3 +304,9 @@ notificationSubscriptionDescription=Du kan abonnere p\u00e5 ulike typer meldinge
 messageFormat=Meldingsformat
 Mail=E-post
 Sms=SMS
+forecastNotificationMessageBodyTpl_1=Varselstatus er endret til h\u00f8y infeksjonsrisiko for {0} i {1} ved {2}, tidspunkt {3}. Modell: {4}. For \u00e5 lese detaljer, vennligst bes\u00f8k:{5}
+forecastNotificationMessageBodyTpl_2=Varselstatus er endret til moderat infeksjonsrisiko for {0} i {1} ved {2}, tidspunkt {3}. Modell: {4}. For \u00e5 lese detaljer, vennligst bes\u00f8k:{5}
+forecastNotificationMessageHeadingTpl_1=Melding om h\u00f8y infeksjonsrisiko
+forecastNotificationMessageHeadingTpl_2=Melding om moderat infeksjonsrisiko
+task_SendForecastEventNotificationsTask_name=Send meldinger om endringer i varsel
+task_SendForecastEventNotificationsTask_description=Ser om varsler har endret set til GUL eller R\u00d8D status. I tilfelle s\u00f8kes abonnenter opp og meldinger blir distribuert.
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 a16fa416..b9ed7c4b 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
@@ -304,3 +304,9 @@ notificationSubscriptionDescription=You can subscribe to different kinds of noti
 messageFormat=Message format
 Mail=Email
 Sms=SMS
+forecastNotificationMessageBodyTpl_1=Forecast warning status has turned to high risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageBodyTpl_2=Forecast warning status has turned to moderate risk of infection for {0} in {1} at location {2} at date {3}. Model: {4}. To read details, please visit: {5}
+forecastNotificationMessageHeadingTpl_1=Notification of high risk of infection
+forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
+task_SendForecastEventNotificationsTask_name=Send forecast event notifications
+task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
diff --git a/src/main/webapp/WEB-INF/glassfish-web.xml b/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 00000000..13e0059f
--- /dev/null
+++ b/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
+<glassfish-web-app error-url="">
+  <class-loader delegate="true"/>
+  <jsp-config>
+    <property name="keepgenerated" value="true">
+      <description>Keep a copy of the generated servlet class' java code.</description>
+    </property>
+  </jsp-config>
+</glassfish-web-app>
diff --git a/src/main/webapp/templates/notificationSubscriptionForm.ftl b/src/main/webapp/templates/notificationSubscriptionForm.ftl
index 09c35eee..14f32cd0 100644
--- a/src/main/webapp/templates/notificationSubscriptionForm.ftl
+++ b/src/main/webapp/templates/notificationSubscriptionForm.ftl
@@ -50,8 +50,8 @@
         <input type="hidden" name="userId" value="${viewUser.userId}"/>
           <h2>${i18nBundle.messageNotifications}</h2>
           <div class="form-group">
-		<label for="universalMessageFormatId">${i18nBundle.messageFormat}</label>
-		<select name="universalMessageFormatId" onblur="validateField(this);">
+		<label for="messageN_universalMessageFormatId">${i18nBundle.messageFormat}</label>
+		<select name="messageN_universalMessageFormatId" onblur="validateField(this);">
 			<#list universalMessageFormats as format>
 			<option value="${format.universalMessageFormatId}"
 			<#if messageNotificationSubscription.universalMessageFormatId == format.universalMessageFormatId> selected="selected"</#if>
@@ -60,26 +60,56 @@
 		</select>
 	</div>
           <div class="form-group">
-		<label for="cropOrganismIds">${i18nBundle.cropOrganismIds}</label>
-		<select class="form-control chosen-select" name="cropOrganismIds" onblur="validateField(this);" multiple="multiple">
+		<label for="messageN_cropOrganismIds">${i18nBundle.cropOrganismIds}</label>
+		<select class="form-control chosen-select" name="messageN_cropOrganismIds" onblur="validateField(this);" multiple="multiple">
 			<#list allCrops?sort_by("latinName") as organism>
 			<option value="${organism.organismId}"
-			<#if userCropIds?seq_contains(organism.organismId)> selected="selected"</#if>
+			<#if messageNotificationSubscription.cropIds?seq_contains(organism.organismId)> selected="selected"</#if>
 			>${organism.latinName!""} / ${organism.tradeName!""} / ${organism.getLocalName(currentLocale.language)!""}</option>
 		</#list>
 		</select>
 	</div>
 	<div class="form-group">
-	    <label for="messageTagIds">${i18nBundle.messageTags}</label>
-	    <select class="form-control chosen-select" name="messageTagIds" onblur="validateField(this);" multiple="multiple">
+	    <label for="messageN_messageTagIds">${i18nBundle.messageTags}</label>
+	    <select class="form-control chosen-select" name="messageN_messageTagIds" onblur="validateField(this);" multiple="multiple">
 		<#list messageTagSet as messageTag>
 			<option value="${messageTag.messageTagId}"
-				<#if userMessageTagIds?seq_contains(messageTag.messageTagId)> selected="selected"</#if>
+				<#if messageNotificationSubscription.messageTagIds?seq_contains(messageTag.messageTagId)> selected="selected"</#if>
 			>${(messageTag.getLocalName(currentLocale))!messageTag.defaultTagName}</option>
 		</#list>
 	     </select>
 	</div>
           <h2>${i18nBundle.forecastNotifications}</h2>
+          <div class="form-group">
+		<label for="forecastN_universalMessageFormatId">${i18nBundle.messageFormat}</label>
+		<select name="forecastN_universalMessageFormatId" onblur="validateField(this);">
+			<#list universalMessageFormats as format>
+			<option value="${format.universalMessageFormatId}"
+			<#if messageNotificationSubscription.universalMessageFormatId == format.universalMessageFormatId> selected="selected"</#if>
+			>${i18nBundle(format.formatName)}</option>
+		</#list>
+		</select>
+	</div>
+          <div class="form-group">
+		<label for="forecastN_cropOrganismIds">${i18nBundle.cropOrganismIds}</label>
+		<select class="form-control chosen-select" name="forecastN_cropOrganismIds" multiple="multiple">
+			<#list allCrops?sort_by("latinName") as organism>
+			<option value="${organism.organismId}"
+			<#if forecastEventNotificationSubscription.cropIds?seq_contains(organism.organismId)> selected="selected"</#if>
+			>${organism.latinName!""} / ${organism.tradeName!""} / ${organism.getLocalName(currentLocale.language)!""}</option>
+		</#list>
+		</select>
+	</div>
+	<div class="form-group">
+	    <label for="forecastN_weatherStationIds">${i18nBundle.weatherStations}</label>
+	    <select class="form-control chosen-select" name="forecastN_weatherStationIds" multiple="multiple">
+	    	<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.weatherStations?lower_case}</option>
+	    	<#list weatherStationIds?sort_by("name") as poi>
+	    	<option value="${poi.pointOfInterestId}"<#if forecastEventNotificationSubscription.weatherStationIds?seq_contains(poi.pointOfInterestId)> selected="selected"</#if>>${poi.name}</option>
+	    	</#list>
+	    </select>
+	    <span class="help-block" id="${formId}_weatherStationPointOfInterestId_validation"></span>
+	  </div>
           <p>Under construction</p>
           <button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
 	</form>
diff --git a/src/test/java/no/nibio/vips/logic/messaging/UniversalMessagingTest.java b/src/test/java/no/nibio/vips/logic/messaging/UniversalMessagingTest.java
index 25805a70..2f8d3d4d 100644
--- a/src/test/java/no/nibio/vips/logic/messaging/UniversalMessagingTest.java
+++ b/src/test/java/no/nibio/vips/logic/messaging/UniversalMessagingTest.java
@@ -146,7 +146,7 @@ public class UniversalMessagingTest {
             
             
             result = r.readEntity(String.class);
-            //System.out.println(result);
+            System.out.println(result);
             assertEquals(200,r.getStatus());
             //Response r = umClient.sendMessage(um);
         }
-- 
GitLab