Skip to content
Snippets Groups Projects
Commit febbcb3e authored by Tor-Einar Skog's avatar Tor-Einar Skog
Browse files

First usable version of universal messaging system

parent d2452752
No related branches found
No related tags found
No related merge requests found
Showing
with 1175 additions and 74 deletions
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action>
<actionName>build</actionName>
<packagings>
<packaging>*</packaging>
</packagings>
<goals>
<goal>install</goal>
</goals>
<properties>
<skipTests>true</skipTests>
</properties>
</action>
<action>
<actionName>rebuild</actionName>
<packagings>
<packaging>*</packaging>
</packagings>
<goals>
<goal>clean</goal>
<goal>install</goal>
</goals>
<properties>
<skipTests>true</skipTests>
</properties>
</action>
</actions>
/*
* 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.controller.servlet;
import freemarker.core.ParseException;
import java.io.IOException;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
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.MessageNotificationSubscription;
import no.nibio.vips.logic.messaging.UniversalMessageFormat;
import no.nibio.vips.logic.util.SessionControllerGetter;
import no.nibio.vips.util.ServletUtil;
import no.nibio.web.forms.FormUtil;
/**
* @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
public class NotificationSubscriptionController extends HttpServlet {
@PersistenceContext(unitName="VIPSLogic-PU")
EntityManager em;
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
VipsLogicUser user = (VipsLogicUser) request.getSession().getAttribute("user");
UserBean userBean = SessionControllerGetter.getUserBean();
try
{
// Default: View list of user subscriptions
// TODO: Only superusers and organization admins should see other users' subscriptions
if(action == null)
{
Integer userId = Integer.valueOf(request.getParameter("userId"));
VipsLogicUser viewUser;
if(userBean.authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER))
{
viewUser = em.find(VipsLogicUser.class, userId);
}
else
{
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]);
request.setAttribute("viewUser", viewUser);
request.setAttribute("allCrops", em.createNamedQuery("Organism.findAllCrops").getResultList());
request.setAttribute("messageTagSet", em.createNamedQuery("MessageTag.findAll").getResultList());
request.setAttribute("universalMessageFormats", SessionControllerGetter.getMessagingBean().getAllUniversalMessageFormats());
request.setAttribute("messageKey", request.getParameter("messageKey"));
request.getRequestDispatcher("/notificationSubscriptionForm.ftl").forward(request, response);
}
else if(action.equals("notificationSubscriptionFormSubmit"))
{
Integer userId = Integer.valueOf(request.getParameter("userId"));
VipsLogicUser viewUser;
if(userBean.authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER))
{
viewUser = em.find(VipsLogicUser.class, userId);
}
else
{
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);
// Redirect to form with confirmation message
response.sendRedirect(new StringBuilder("http://")
.append(ServletUtil.getServerName(request))
.append("/user/notificationsubscription?userId=").append(viewUser.getUserId())
.append("&messageKey=").append("notificationSubscriptionsUpdated").toString()
);
}
}
catch(ParseException e)
{
e.printStackTrace();
}
}
// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Handles the HTTP <code>POST</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Returns a short description of the servlet.
*
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}
......@@ -22,6 +22,7 @@ package no.nibio.vips.logic.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Basic;
......@@ -128,6 +129,16 @@ public class Message implements Serializable {
public Set<MessageTag> getMessageTagSet() {
return messageTagSet;
}
@Transient
public List<Integer> getMessageTagIds(){
List<Integer> retVal = new ArrayList<>();
for(MessageTag tag: this.getMessageTagSet())
{
retVal.add(tag.getMessageTagId());
}
return retVal;
}
public void setMessageTagSet(Set<MessageTag> messageTagSet) {
this.messageTagSet = messageTagSet;
......@@ -150,6 +161,15 @@ public class Message implements Serializable {
public void setMessageLocaleSet(Set<MessageLocale> messageLocaleSet) {
this.messageLocaleSet = messageLocaleSet;
}
public void addMessageLocale(MessageLocale messageLocale)
{
if(this.messageLocaleSet == null)
{
this.messageLocaleSet = new HashSet<>();
}
this.messageLocaleSet.add(messageLocale);
}
@Override
public int hashCode() {
......
/*
* 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;
/**
* @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
public class MessageLocalVersion {
private String locale, msgSubject, msgLeadParagraph, msgBody, msgDownloadUrl;
public MessageLocalVersion(){}
public MessageLocalVersion(
String locale,
String msgSubject,
String msgLeadParagraph,
String msgBody,
String msgDownloadUrl
)
{
this.locale = locale;
this.msgSubject = msgSubject;
this.msgLeadParagraph = msgLeadParagraph;
this.msgBody = msgBody;
this.msgDownloadUrl = msgDownloadUrl;
}
/**
* @return the locale
*/
public String getLocale() {
return locale;
}
/**
* @param locale the locale to set
*/
public void setLocale(String locale) {
this.locale = locale;
}
/**
* @return the msgSubject
*/
public String getMsgSubject() {
return msgSubject;
}
/**
* @param msgSubject the msgSubject to set
*/
public void setMsgSubject(String msgSubject) {
this.msgSubject = msgSubject;
}
/**
* @return the msgLeadParagraph
*/
public String getMsgLeadParagraph() {
return msgLeadParagraph;
}
/**
* @param msgLeadParagraph the msgLeadParagraph to set
*/
public void setMsgLeadParagraph(String msgLeadParagraph) {
this.msgLeadParagraph = msgLeadParagraph;
}
/**
* @return the msgBody
*/
public String getMsgBody() {
return msgBody;
}
/**
* @param msgBody the msgBody to set
*/
public void setMsgBody(String msgBody) {
this.msgBody = msgBody;
}
/**
* @return the msgDownloadUrl
*/
public String getMsgDownloadUrl() {
return msgDownloadUrl;
}
/**
* @param msgDownloadUrl the msgDownloadUrl to set
*/
public void setMsgDownloadUrl(String msgDownloadUrl) {
this.msgDownloadUrl = msgDownloadUrl;
}
}
/*
* 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.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 = "message_notification_subscription", schema = "messaging")
@TypeDefs( {@TypeDef( name= "IntegerArray", typeClass = IntegerArrayUserType.class)})
public class MessageNotificationSubscription implements Serializable {
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 = "message_tag_ids")
private Integer[] messageTagIds;
@Type(type = "IntegerArray")
@Column(name = "crop_ids")
private Integer[] cropIds;
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
@Override
public int hashCode() {
int hash = 0;
hash += (userId != null ? userId.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 MessageNotificationSubscription)) {
return false;
}
MessageNotificationSubscription other = (MessageNotificationSubscription) object;
if ((this.userId == null && other.userId != null) || (this.userId != null && !this.userId.equals(other.userId))) {
return false;
}
return true;
}
@Override
public String toString() {
return "no.nibio.vips.logic.messaging.MessageNotificationSubscription[ 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 messageTagIds
*/
public Integer[] getMessageTagIds() {
return messageTagIds;
}
/**
* @param messageTagIds the messageTagIds to set
*/
public void setMessageTagIds(Integer[] messageTagIds) {
this.messageTagIds = messageTagIds;
}
/**
* For your convenience
* @param messageTagIds
*/
public void setMessageTagIds(List<Integer> messageTagIds)
{
this.messageTagIds = messageTagIds.toArray(new Integer[messageTagIds.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()]);
}
}
......@@ -19,27 +19,42 @@
package no.nibio.vips.logic.messaging;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @copyright 2015 <a href="http://www.nibio.no/">NIBIO</a>
* @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
public class MessageRecipient {
@Entity
public class MessageRecipient implements Serializable {
@Column(name = "preferred_locale")
private String preferredLocale;
private String type;
@Column(name = "msg_delivery_address")
private String msgDeliveryAddress;
private String name;
@Id
@Column(name = "recipient_id")
private String recipientId;
public MessageRecipient(){}
public MessageRecipient(
String recipientId,
String name,
String type,
String msgDeliveryAddress
String msgDeliveryAddress,
String preferredLocale
)
{
this.recipientId = recipientId;
this.name = name;
this.type=type;
this.msgDeliveryAddress = msgDeliveryAddress;
this.preferredLocale = preferredLocale;
}
/**
......@@ -97,4 +112,18 @@ public class MessageRecipient {
public void setRecipientId(String recipientId) {
this.recipientId = recipientId;
}
/**
* @return the preferredLocale
*/
public String getPreferredLocale() {
return preferredLocale;
}
/**
* @param preferredLocale the preferredLocale to set
*/
public void setPreferredLocale(String preferredLocale) {
this.preferredLocale = preferredLocale;
}
}
......@@ -19,16 +19,26 @@
package no.nibio.vips.logic.messaging;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
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.logic.entity.Message;
import no.nibio.vips.logic.entity.MessageLocale;
import no.nibio.vips.logic.entity.VipsLogicUser;
import no.nibio.vips.logic.util.RESTAuthenticator;
import no.nibio.vips.logic.util.SessionControllerGetter;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
/**
* @copyright 2015 <a href="http://www.nibio.no/">NIBIO</a>
......@@ -45,7 +55,7 @@ public class MessagingBean {
Message m = SessionControllerGetter.getMessageBean().getMessage(5);
MessageLocale ml = m.getLocalMessage("nn");
UniversalMessage um = new UniversalMessage(
ml.getHeading(), ml.getLeadParagraph(), ml.getBody(), "http://localhost:8000/messages/" + m.getMessageId()
"nn",ml.getHeading(), ml.getLeadParagraph(), ml.getBody(), "http://localhost:8000/messages/" + m.getMessageId()
);
um.setExpiresAt(new Date());
List<MessageRecipient> distributionList = new ArrayList<>();
......@@ -55,7 +65,8 @@ public class MessagingBean {
String.valueOf(user.getUserId()),
user.getFirstName() + " " + user.getLastName(),
"Mail",
user.getEmail()
user.getEmail(),
user.getPreferredLocale()
);
distributionList.add(r);
}
......@@ -65,22 +76,119 @@ public class MessagingBean {
List<UniversalMessage> ums = em.createNamedQuery("UniversalMessage.findAll").getResultList();
for(UniversalMessage uMe : ums)
{
System.out.println(uMe.getMsgSubject());
for(MessageLocalVersion lv:uMe.getMessageLocalVersionObjects())
{
System.out.println(lv.getMsgSubject());
}
}
}
public void sendUniversalMessage(Message message)
{
String msgDownloadUrlTpl = "http://www.vips-landbruk.no/";
// Create a universal message from the message
// TODO: When UniversalMessage has changed, pick
MessageLocale ml = message.getLocalMessageWithFallback("nb", "nb");
UniversalMessage uMessage = new UniversalMessage();
uMessage.setExpiresAt(message.getDateValidTo());
for(MessageLocale ml : message.getMessageLocaleSet())
{
uMessage.addMessageLocalVersion(ml.getMessageLocalePK().getLocale(), ml.getHeading(), ml.getLeadParagraph(), ml.getBody(), msgDownloadUrlTpl);
}
// Find the suscribers, create distribution list
uMessage.setDistributionList(this.getMessageNotificationSubscribers(message));
// Send it
this.sendUniversalMessage(uMessage);
// Log it
}
public void sendUniversalMessage(UniversalMessage uMessage)
{
if(uMessage.getDistributionListObjects().isEmpty())
{
return;
}
// Store it
em.persist(uMessage);
// Send it
ResteasyClient client = new ResteasyClientBuilder().build();
//client.register(new RESTAuthenticator("user", "userPass"));
client.register(new RESTAuthenticator("VIPSLogic", "plmoknijbuhv000"));
//ResteasyWebTarget target = client.target("http://kart13utv.ad.skogoglandskap.no:8080");
ResteasyWebTarget target = client.target("http://localhost:8080");
//ResteasyWebTarget target = client.target("http://logic.testvips2.nibio.no");
UniversalMessagingServiceClient umClient = target.proxy(UniversalMessagingServiceClient.class);
try
{
//System.out.println(new ObjectMapper().writeValueAsString(um));
Response r = umClient.sendMessage(new ObjectMapper().writeValueAsString(uMessage));
// Log it
// TODO: Handle result
String result = r.readEntity(String.class);
}
catch(JsonProcessingException ex)
{
// Handle error
}
}
private List<MessageRecipient> getMessageNotificationSubscribers(Message message) {
Query q = em.createNativeQuery(
"SELECT \n" +
" u.preferred_locale,\n" +
" umf.format_name AS type,\n" +
" CASE mns.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.message_notification_subscription mns, messaging.universal_message_format umf\n" +
"WHERE mns.user_id=u.user_id\n" +
"AND mns.universal_message_format_id = umf.universal_message_format_id\n" +
"AND u.user_id IN (\n" +
" SELECT user_id FROM messaging.message_notification_subscription\n" +
" WHERE message_tag_ids && ARRAY" + message.getMessageTagIds().toString() + " \n" + // && is the array_overlaps operator
");\n",
MessageRecipient.class);
//q.setParameter("messageTagIds", message.getMessageTagIds());
return q.getResultList();
}
public MessageNotificationSubscription getMessageNotificationSubscription(Integer userId)
{
Query q = em.createNativeQuery(
"SELECT * FROM messaging.message_notification_subscription m "
+ "WHERE m.user_id=:userId",
MessageNotificationSubscription.class
).setParameter("userId", userId);
try
{
return (MessageNotificationSubscription) q.getSingleResult();
}
catch(NoResultException ex)
{
return null;
}
}
public void storeMessageNotificationSubscription(MessageNotificationSubscription subscription)
{
em.merge(subscription);
}
public List<UniversalMessageFormat> getAllUniversalMessageFormats()
{
return em.createNamedQuery("UniversalMessageFormat.findAll").getResultList();
}
}
......@@ -20,18 +20,19 @@
package no.nibio.vips.logic.messaging;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
......@@ -41,16 +42,15 @@ import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Size;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import no.nibio.vips.logic.util.PostgresJSONStringConverter;
import no.nibio.vips.logic.util.StringJsonUserType;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
/**
* @copyright 2015 <a href="http://www.nibio.no/">NIBIO</a>
* @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
@Entity
......@@ -60,10 +60,6 @@ import org.hibernate.annotations.TypeDefs;
@NamedQueries({
@NamedQuery(name = "UniversalMessage.findAll", query = "SELECT u FROM UniversalMessage u"),
@NamedQuery(name = "UniversalMessage.findByUniversalMessageId", query = "SELECT u FROM UniversalMessage u WHERE u.universalMessageId = :universalMessageId"),
@NamedQuery(name = "UniversalMessage.findByMsgSubject", query = "SELECT u FROM UniversalMessage u WHERE u.msgSubject = :msgSubject"),
@NamedQuery(name = "UniversalMessage.findByMsgLeadParagraph", query = "SELECT u FROM UniversalMessage u WHERE u.msgLeadParagraph = :msgLeadParagraph"),
@NamedQuery(name = "UniversalMessage.findByMsgBody", query = "SELECT u FROM UniversalMessage u WHERE u.msgBody = :msgBody"),
@NamedQuery(name = "UniversalMessage.findByMsgDownloadUrl", query = "SELECT u FROM UniversalMessage u WHERE u.msgDownloadUrl = :msgDownloadUrl"),
@NamedQuery(name = "UniversalMessage.findByExpiresAt", query = "SELECT u FROM UniversalMessage u WHERE u.expiresAt = :expiresAt")})
public class UniversalMessage implements Serializable {
private static final long serialVersionUID = 1L;
......@@ -72,18 +68,6 @@ public class UniversalMessage implements Serializable {
@Basic(optional = false)
@Column(name = "universal_message_id")
private Integer universalMessageId;
@Size(max = 2047)
@Column(name = "msg_subject")
private String msgSubject;
@Size(max = 2147483647)
@Column(name = "msg_lead_paragraph")
private String msgLeadParagraph;
@Size(max = 2147483647)
@Column(name = "msg_body")
private String msgBody;
@Size(max = 1023)
@Column(name = "msg_download_url")
private String msgDownloadUrl;
@Column(name = "expires_at")
@Temporal(TemporalType.TIMESTAMP)
private Date expiresAt;
......@@ -94,21 +78,41 @@ public class UniversalMessage implements Serializable {
@Column(name = "distribution_list")
private String distributionList;
@Type(type = "StringJsonObject")
@Column(name = "message_local_versions")
private String messageLocalVersions;
@Transient
private ObjectMapper objectMapper;
public UniversalMessage() {
}
public UniversalMessage(
String msgSubject,
String msgLeadParagraph,
String msgBody,
String msgDownloadUrl
String locale,
String msgSubject,
String msgLeadParagraph,
String msgBody,
String msgDownloadUrl
)
{
this.addMessageLocalVersion(locale, msgSubject, msgLeadParagraph, msgBody, msgDownloadUrl);
}
public final void addMessageLocalVersion(
String locale,
String msgSubject,
String msgLeadParagraph,
String msgBody,
String msgDownloadUrl
)
{
this.msgSubject = msgSubject;
this.msgLeadParagraph = msgLeadParagraph;
this.msgBody = msgBody;
this.msgDownloadUrl = msgDownloadUrl;
MessageLocalVersion newLocalVersion = new MessageLocalVersion(locale, msgSubject, msgLeadParagraph, msgBody, msgDownloadUrl);
List<MessageLocalVersion> existingLocalVersions = this.getMessageLocalVersionObjects();
existingLocalVersions.add(newLocalVersion);
this.setMessageLocalVersions(existingLocalVersions);
}
public UniversalMessage(Integer universalMessageId) {
......@@ -124,38 +128,6 @@ public class UniversalMessage implements Serializable {
this.universalMessageId = universalMessageId;
}
public String getMsgSubject() {
return msgSubject;
}
public void setMsgSubject(String msgSubject) {
this.msgSubject = msgSubject;
}
public String getMsgLeadParagraph() {
return msgLeadParagraph;
}
public void setMsgLeadParagraph(String msgLeadParagraph) {
this.msgLeadParagraph = msgLeadParagraph;
}
public String getMsgBody() {
return msgBody;
}
public void setMsgBody(String msgBody) {
this.msgBody = msgBody;
}
public String getMsgDownloadUrl() {
return msgDownloadUrl;
}
public void setMsgDownloadUrl(String msgDownloadUrl) {
this.msgDownloadUrl = msgDownloadUrl;
}
@JsonIgnore
public Date getExpiresAt() {
return expiresAt;
......@@ -176,11 +148,24 @@ public class UniversalMessage implements Serializable {
public void setDistributionList(List<MessageRecipient> distributionList) {
try {
this.distributionList = new ObjectMapper().writeValueAsString(distributionList);
this.distributionList = this.getObjectMapper().writeValueAsString(distributionList);
} catch (JsonProcessingException ex) {
Logger.getLogger(UniversalMessage.class.getName()).log(Level.SEVERE, null, ex);
}
}
@JsonIgnore
@Transient
public List<MessageRecipient> getDistributionListObjects()
{
List<MessageRecipient> retVal = null;
try {
retVal = this.getObjectMapper().readValue(this.getDistributionList(), new TypeReference<List<MessageRecipient>>(){});
} catch (IOException | NullPointerException ex) {
Logger.getLogger(UniversalMessage.class.getName()).log(Level.SEVERE, null, ex);
}
return retVal != null ? retVal : new ArrayList<MessageRecipient>();
}
@Override
public int hashCode() {
......@@ -207,4 +192,51 @@ public class UniversalMessage implements Serializable {
return "no.nibio.vips.logic.messaging.UniversalMessage[ universalMessageId=" + universalMessageId + " ]";
}
/**
* @return the messageLocalVersions
*/
@JsonRawValue
public String getMessageLocalVersions() {
return messageLocalVersions != null ? this.messageLocalVersions : "[]";
}
@JsonIgnore
@Transient
public List<MessageLocalVersion> getMessageLocalVersionObjects()
{
List<MessageLocalVersion> retVal = null;
try {
retVal = this.getObjectMapper().readValue(this.getMessageLocalVersions(), new TypeReference<List<MessageLocalVersion>>(){});
} catch (IOException | NullPointerException ex) {
Logger.getLogger(UniversalMessage.class.getName()).log(Level.SEVERE, null, ex);
}
return retVal != null ? retVal : new ArrayList<MessageLocalVersion>();
}
/**
* @param messageLocalVersions the messageLocalVersions to set
*/
public void setMessageLocalVersions(String messageLocalVersions) {
this.messageLocalVersions = messageLocalVersions;
}
public void setMessageLocalVersions(List<MessageLocalVersion> messageLocalVersions)
{
try {
this.messageLocalVersions = this.getObjectMapper().writeValueAsString(messageLocalVersions);
} catch (JsonProcessingException ex) {
Logger.getLogger(UniversalMessage.class.getName()).log(Level.SEVERE, null, ex);
}
}
private ObjectMapper getObjectMapper()
{
if(this.objectMapper == null)
{
this.objectMapper = new ObjectMapper();
}
return this.objectMapper;
}
}
/*
* 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 javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
@Entity
@Table(name = "universal_message_format", schema = "messaging")
@NamedQueries({
@NamedQuery(name = "UniversalMessageFormat.findAll", query = "SELECT u FROM UniversalMessageFormat u")
})
public class UniversalMessageFormat implements Serializable{
public static final Integer FORMAT_EMAIL = 1;
public static final Integer FORMAT_SMS = 2;
@Id
@Column(name = "universal_message_format_id")
private Integer universalMessageFormatId;
@Column(name = "format_name")
private String formatName;
/**
* @return the universalMessageFormatId
*/
public Integer getUniversalMessageFormatId() {
return universalMessageFormatId;
}
/**
* @param universalMessageFormatId the universalMessageFormatId to set
*/
public void setUniversalMessageFormatId(Integer universalMessageFormatId) {
this.universalMessageFormatId = universalMessageFormatId;
}
/**
* @return the formatName
*/
public String getFormatName() {
return formatName;
}
/**
* @param formatName the formatName to set
*/
public void setFormatName(String formatName) {
this.formatName = formatName;
}
}
/*
* Copyright (c) 2015 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.util;
import java.io.Serializable;
import java.sql.Array;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
/**
* Adapted from this: http://stackoverflow.com/questions/21940642/hibernate-postgres-array-type
* @copyright 2015 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
public class IntegerArrayUserType implements UserType {
/**
* Return the SQL type codes for the columns mapped by this type. The
* codes are defined on <tt>java.sql.Types</tt>.
*
* @return int[] the typecodes
* @see java.sql.Types
*/
@Override
public int[] sqlTypes() {
return new int[] { Types.ARRAY };
}
/**
* The class returned by <tt>nullSafeGet()</tt>.
*
* @return Class
*/
@Override
public Class returnedClass() {
return Integer[].class;
}
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*
* @param x
* @param y
* @return boolean
*/
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if( x== null){
return y== null;
}
return x.equals( y);
}
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors
* should handle possibility of null values.
*
* @param rs a JDBC result set
* @param names the column names
* @param session
* @param owner the containing entity @return Object
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(
ResultSet rs,
String[] names,
SessionImplementor session,
Object owner) throws HibernateException, SQLException {
if (rs.wasNull()) {
return null;
}
Integer[] array = (Integer[]) rs.getArray(names[0]).getArray();
return array;
}
/**
* Write an instance of the mapped class to a prepared statement. Implementors
* should handle possibility of null values. A multi-column type should be written
* to parameters starting from <tt>index</tt>.
*
* @param st a JDBC prepared statement
* @param value the object to write
* @param index statement parameter index
* @param session
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}
Integer[] castObject = (Integer[]) value;
Array array = session.connection().createArrayOf("integer", castObject); // The postgres array data type
st.setArray(index, array);
}
/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
/**
* Are objects of this type mutable?
*
* @return boolean
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws org.hibernate.HibernateException
*
*/
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Integer[])this.deepCopy( value);
}
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws org.hibernate.HibernateException
*
*/
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy( cached);
}
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param original the value from the detached entity being merged
* @param target the value in the managed entity
* @return the value to be merged
*/
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
......@@ -21,12 +21,12 @@
-->
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="VIPSLogic-PU" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/jboss/datasources/vipslogic</jta-data-source>
<class>no.nibio.vips.logic.messaging.MessageNotificationSubscription</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.spatial.dialect.postgis.PostgisDialect"/>
<property name="hibernate.dialect" value="org.hibernate.spatial.dialect.postgis.PostgisDialect"/>
</properties>
</persistence-unit>
</persistence>
......@@ -294,3 +294,13 @@ point=Point
polygon=Polygon
clearAll=Clear all
clearOne=Clear one
phone=Telephone
notificationSubscriptions=Notification subscriptions
userAccountInformation=User account information
notificationSubscriptionsUpdated=Notification subscriptions was successfully updated
messageNotifications=Message notifications
forecastNotifications=Forecast notifications
notificationSubscriptionDescription=You can subscribe to different kinds of notifications below. Choose the format for notification, for instance email or SMS
messageFormat=Message format
Mail=Email
Sms=SMS
......@@ -294,3 +294,13 @@ point=point
polygon=Polygon
clearAll=Clear all
clearOne=Clear one
phone=Telephone
notificationSubscriptions=Notification subscriptions
userAccountInformation=User account information
notificationSubscriptionsUpdated=Notification subscriptions was successfully updated
messageNotifications=Message notifications
forecastNotifications=Forecast notifications
notificationSubscriptionDescription=You can subscribe to different kinds of notifications below. Choose the format for notification, for instance email or SMS
messageFormat=Message format
Mail=Email
Sms=SMS
......@@ -293,3 +293,13 @@ point=point
polygon=Polygon
clearAll=Clear all
clearOne=Clear one
phone=Telephone
notificationSubscriptions=Notification subscriptions
userAccountInformation=User account information
notificationSubscriptionsUpdated=Notification subscriptions was successfully updated
messageNotifications=Message notifications
forecastNotifications=Forecast notifications
notificationSubscriptionDescription=You can subscribe to different kinds of notifications below. Choose the format for notification, for instance email or SMS
messageFormat=Message format
Mail=Email
Sms=SMS
......@@ -294,3 +294,13 @@ point=Punkt
polygon=Omr\u00e5de
clearAll=Slett alt
clearOne=Slett ett element
phone=Telefon
notificationSubscriptions=Meldingsabonnement
userAccountInformation=Brukerkontoinformasjon
notificationSubscriptionsUpdated=Meldingsabonnementene ble oppdatert
messageNotifications=Meldingsmeldinger
forecastNotifications=Varslingsmeldinger
notificationSubscriptionDescription=Du kan abonnere p\u00e5 ulike typer meldinger nedenfor. Velg meldingsformat, for eksempel e-post eller SMS.
messageFormat=Meldingsformat
Mail=E-post
Sms=SMS
......@@ -294,3 +294,13 @@ point=point
polygon=Polygon
clearAll=Clear all
clearOne=Clear one
phone=Telephone
notificationSubscriptions=Notification subscriptions
userAccountInformation=User account information
notificationSubscriptionsUpdated=Notification subscriptions was successfully updated
messageNotifications=Message notifications
forecastNotifications=Forecast notifications
notificationSubscriptionDescription=You can subscribe to different kinds of notifications below. Choose the format for notification, for instance email or SMS
messageFormat=Message format
Mail=Email
Sms=SMS
......@@ -63,6 +63,10 @@
<servlet-name>ObservationServlet</servlet-name>
<servlet-class>no.nibio.vips.logic.controller.servlet.ObservationController</servlet-class>
</servlet>
<servlet>
<servlet-name>NotificationSubscriptionController</servlet-name>
<servlet-class>no.nibio.vips.logic.controller.servlet.NotificationSubscriptionController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PointOfInterestController</servlet-name>
<url-pattern>/poi/*</url-pattern>
......@@ -111,6 +115,10 @@
<servlet-name>ObservationServlet</servlet-name>
<url-pattern>/observation</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>NotificationSubscriptionController</servlet-name>
<url-pattern>/user/notificationsubscription</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
......
......@@ -23,6 +23,11 @@
"dataType" : "EMAIL",
"required" : true
},
{
"name" : "phone",
"dataType" : "STRING",
"required" : false
},
{
"name" : "firstName",
"dataType" : "STRING",
......
<#--
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/>.
--><#include "master.ftl">
<#macro page_head>
<title>${i18nBundle.user}</title>
</#macro>
<#macro custom_js>
<script src="/js/resourcebundle.js"></script>
<script src="/js/validateForm.js"></script>
<script type="text/javascript" src="/js/3rdparty/chosen.jquery.min.js"></script>
<script type="text/javascript">
// Activating chosen plugin
$(".chosen-select").chosen();
// Load main form definition (for validation)
//loadFormDefinition("userForm");
</script>
</#macro>
<#macro custom_css>
<link href="/css/3rdparty/chosen.min.css" rel="stylesheet" />
</#macro>
<#macro page_contents>
<p><a href="/user" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
<h1><#if viewUser.userId?has_content>${viewUser.firstName!""} ${viewUser.lastName}<#else>${i18nBundle.newUser}</#if></h1>
<a href="/user?action=viewUser&userId=${viewUser.userId}" class="btn btn-default" role="button">${i18nBundle.userAccountInformation}</a>
<button type="button" class="btn btn-primary">${i18nBundle.notificationSubscriptions}</button>
<div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
<#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if>
</div>
<#if messageKey?has_content>
<div class="alert alert-success">${i18nBundle(messageKey)}</div>
</#if>
<p>${i18nBundle.notificationSubscriptionDescription}</p>
<#assign formId = "notificationSubscriptionForm">
<form id="${formId}" role="form" action="/user/notificationsubscription" method="POST" onsubmit="return validateForm(this);">
<input type="hidden" name="action" value="notificationSubscriptionFormSubmit"/>
<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);">
<#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="cropOrganismIds">${i18nBundle.cropOrganismIds}</label>
<select class="form-control chosen-select" name="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>
>${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">
<#list messageTagSet as messageTag>
<option value="${messageTag.messageTagId}"
<#if userMessageTagIds?seq_contains(messageTag.messageTagId)> selected="selected"</#if>
>${(messageTag.getLocalName(currentLocale))!messageTag.defaultTagName}</option>
</#list>
</select>
</div>
<h2>${i18nBundle.forecastNotifications}</h2>
<p>Under construction</p>
<button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
</form>
</#macro>
<@page_html/>
......@@ -29,6 +29,10 @@
<#macro page_contents>
<p><a href="/user" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
<h1><#if viewUser.userId?has_content>${viewUser.firstName!""} ${viewUser.lastName}<#else>${i18nBundle.newUser}</#if></h1>
<#if viewUser.userId?has_content>
<button type="button" class="btn btn-primary">${i18nBundle.userAccountInformation}</button>
<a href="/user/notificationsubscription?userId=${viewUser.userId}" class="btn btn-default" role="button">${i18nBundle.notificationSubscriptions}</a>
</#if>
<div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
<#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if>
</div>
......@@ -44,6 +48,11 @@
<input type="email" class="form-control" name="email" placeholder="${i18nBundle.email}" value="${viewUser.email!""}" onblur="validateField(this);" />
<span class="help-block" id="${formId}_email_validation"></span>
</div>
<div class="form-group">
<label for="phone">${i18nBundle.phone}</label>
<input type="tel" class="form-control" name="phone" placeholder="${i18nBundle.phone}" value="${viewUser.phone!""}" onblur="validateField(this);" />
<span class="help-block" id="${formId}_phone_validation"></span>
</div>
<div class="form-group">
<label for="firstName">${i18nBundle.firstName}</label>
<input type="text" class="form-control" name="firstName" placeholder="${i18nBundle.firstName}" value="${viewUser.firstName!""}" onblur="validateField(this);"/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment