Add Email Notifications when one of the collaborators is editing either a DMP or a Dataset (ref #244)

This commit is contained in:
George Kalampokis 2020-02-14 18:40:34 +02:00
parent f4cd087672
commit 25988ab272
20 changed files with 1144 additions and 1 deletions

View File

@ -0,0 +1,26 @@
package eu.eudat.data.dao.criteria;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.NotifyState;
public class NotificationCriteria {
private ActiveStatus isActive;
private NotifyState notifyState;
public ActiveStatus getIsActive() {
return isActive;
}
public void setIsActive(ActiveStatus isActive) {
this.isActive = isActive;
}
public NotifyState getNotifyState() {
return notifyState;
}
public void setNotifyState(NotifyState notifyState) {
this.notifyState = notifyState;
}
}

View File

@ -0,0 +1,13 @@
package eu.eudat.data.dao.entities;
import eu.eudat.data.dao.DatabaseAccessLayer;
import eu.eudat.data.dao.criteria.NotificationCriteria;
import eu.eudat.data.entities.Notification;
import eu.eudat.queryable.QueryableList;
import java.util.UUID;
public interface NotificationDao extends DatabaseAccessLayer<Notification, UUID> {
QueryableList<Notification> getWithCriteria(NotificationCriteria criteria);
}

View File

@ -0,0 +1,61 @@
package eu.eudat.data.dao.entities;
import eu.eudat.data.dao.DatabaseAccess;
import eu.eudat.data.dao.criteria.NotificationCriteria;
import eu.eudat.data.dao.databaselayer.service.DatabaseService;
import eu.eudat.data.entities.Lock;
import eu.eudat.data.entities.Notification;
import eu.eudat.queryable.QueryableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@Service("NotificationDao")
public class NotificationDaoImpl extends DatabaseAccess<Notification> implements NotificationDao {
@Autowired
public NotificationDaoImpl(DatabaseService<Notification> databaseService) {
super(databaseService);
}
@Override
public QueryableList<Notification> getWithCriteria(NotificationCriteria criteria) {
QueryableList<Notification> query = this.getDatabaseService().getQueryable(Notification.class);
if (criteria.getIsActive() != null)
query.where((builder, root) -> builder.equal(root.get("isActive"), criteria.getIsActive()));
if (criteria.getNotifyState() != null)
query.where(((builder, root) -> builder.equal(root.get("notifyState"), criteria.getNotifyState())));
return query;
}
@Override
public Notification createOrUpdate(Notification item) {
return this.getDatabaseService().createOrUpdate(item, Notification.class);
}
@Override
public CompletableFuture<Notification> createOrUpdateAsync(Notification item) {
return CompletableFuture.supplyAsync(() -> this.getDatabaseService().createOrUpdate(item, Notification.class));
}
@Override
public Notification find(UUID id) {
return this.getDatabaseService().getQueryable(Notification.class).where(((builder, root) -> builder.equal(root.get("id"), id))).getSingle();
}
@Override
public Notification find(UUID id, String hint) {
throw new UnsupportedOperationException();
}
@Override
public void delete(Notification item) {
this.getDatabaseService().delete(item);
}
@Override
public QueryableList<Notification> asQueryable() {
return this.getDatabaseService().getQueryable(Notification.class);
}
}

View File

@ -0,0 +1,175 @@
package eu.eudat.data.entities;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.ContactType;
import eu.eudat.data.enumeration.notification.NotificationType;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.queryable.queryableentity.DataEntity;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "\"Notification\"")
public class Notification implements DataEntity<Notification, UUID> {
@Id
@GeneratedValue
@GenericGenerator(name = "uuid2", strategy = "uuid2")
@Column(name = "id", updatable = false, nullable = false, columnDefinition = "BINARY(16)")
private UUID id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "\"UserId\"")
private UserInfo userId;
@Enumerated
@Column(name = "\"IsActive\"", nullable = false)
private ActiveStatus isActive;
@Enumerated
@Column(name = "\"Type\"", nullable = false)
private NotificationType type;
@Enumerated
@Column(name = "\"ContactTypeHint\"")
private ContactType contactTypeHint;
@Column(name = "\"ContactHint\"")
private String contactHint;
@Column(name = "\"Data\"")
private String data;
@Enumerated
@Column(name = "\"NotifyState\"")
private NotifyState notifyState;
@Column(name = "\"NotifiedAt\"")
private Date notifiedAt;
@Column(name = "\"RetryCount\"")
private Integer retryCount;
@Column(name = "\"CreatedAt\"")
private Date createdAt;
@Column(name = "\"UpdatedAt\"")
private Date updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UserInfo getUserId() {
return userId;
}
public void setUserId(UserInfo userId) {
this.userId = userId;
}
public ActiveStatus getIsActive() {
return isActive;
}
public void setIsActive(ActiveStatus isActive) {
this.isActive = isActive;
}
public NotificationType getType() {
return type;
}
public void setType(NotificationType type) {
this.type = type;
}
public ContactType getContactTypeHint() {
return contactTypeHint;
}
public void setContactTypeHint(ContactType contactTypeHint) {
this.contactTypeHint = contactTypeHint;
}
public String getContactHint() {
return contactHint;
}
public void setContactHint(String contactHint) {
this.contactHint = contactHint;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public NotifyState getNotifyState() {
return notifyState;
}
public void setNotifyState(NotifyState notifyState) {
this.notifyState = notifyState;
}
public Date getNotifiedAt() {
return notifiedAt;
}
public void setNotifiedAt(Date notifiedAt) {
this.notifiedAt = notifiedAt;
}
public Integer getRetryCount() {
return retryCount;
}
public void setRetryCount(Integer retryCount) {
this.retryCount = retryCount;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public void update(Notification entity) {
}
@Override
public UUID getKeys() {
return null;
}
@Override
public Notification buildFromTuple(List<Tuple> tuple, List<String> fields, String base) {
return null;
}
}

View File

@ -72,6 +72,9 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
@OneToMany(mappedBy = "lockedBy", fetch = FetchType.LAZY)
private Set<Lock> locks = new HashSet<>();
@OneToMany(mappedBy = "userId", fetch = FetchType.LAZY)
private Set<Notification> notifications = new HashSet<>();
public Set<DMP> getDmps() {
return dmps;
}
@ -176,6 +179,14 @@ public class UserInfo implements DataEntity<UserInfo, UUID> {
this.locks = locks;
}
public Set<Notification> getNotifications() {
return notifications;
}
public void setNotifications(Set<Notification> notifications) {
this.notifications = notifications;
}
@Override
public void update(UserInfo entity) {
this.name = entity.getName();

View File

@ -0,0 +1,27 @@
package eu.eudat.data.enumeration.notification;
public enum ActiveStatus {
ACTIVE(0),
INACTIVE(1);
private int status;
ActiveStatus(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
public ActiveStatus fromInteger(int status) {
switch (status) {
case 0:
return ACTIVE;
case 1:
return INACTIVE;
default:
throw new RuntimeException("Unsupported Active Status");
}
}
}

View File

@ -0,0 +1,24 @@
package eu.eudat.data.enumeration.notification;
public enum ContactType {
EMAIL(0);
private int type;
ContactType(int type) {
this.type = type;
}
public int getType() {
return type;
}
public ContactType fromInteger(int type) {
switch (type) {
case 0:
return EMAIL;
default:
throw new RuntimeException("Unsupported Contact Type");
}
}
}

View File

@ -0,0 +1,27 @@
package eu.eudat.data.enumeration.notification;
public enum NotificationType {
DMP_MODIFIED(0),
DATASET_MODIFIED(1);
private int type;
NotificationType(int type) {
this.type = type;
}
public int getType() {
return type;
}
public NotificationType fromInteger(int type) {
switch (type) {
case 0:
return DMP_MODIFIED;
case 1:
return DATASET_MODIFIED;
default:
throw new RuntimeException("Unsupported Notification Type");
}
}
}

View File

@ -0,0 +1,36 @@
package eu.eudat.data.enumeration.notification;
public enum NotifyState {
PENDING(0),
PROCESSING(1),
SENDING(2),
SUCCEEDED(3),
ERROR(4);
private int state;
NotifyState(int state) {
this.state = state;
}
public int getState() {
return state;
}
public NotifyState fromInteger(int state) {
switch (state) {
case 0:
return PENDING;
case 1:
return PROCESSING;
case 2:
return SENDING;
case 3:
return SUCCEEDED;
case 4:
return ERROR;
default:
throw new RuntimeException("Unsupported Notify State");
}
}
}

View File

@ -7,6 +7,7 @@ import eu.eudat.logic.services.operations.authentication.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@ -15,6 +16,7 @@ import java.util.List;
@EnableAsync
@Configuration
@EnableScheduling
public class WebMVCConfiguration extends WebMvcConfigurerAdapter {
private ApiContext apiContext;

View File

@ -9,6 +9,10 @@ import eu.eudat.data.dao.entities.*;
import eu.eudat.data.entities.Organisation;
import eu.eudat.data.entities.Researcher;
import eu.eudat.data.entities.*;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.ContactType;
import eu.eudat.data.enumeration.notification.NotificationType;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.data.query.items.item.dmp.DataManagementPlanCriteriaRequest;
import eu.eudat.data.query.items.table.datasetprofile.DatasetProfileTableRequestItem;
import eu.eudat.data.query.items.table.dmp.DataManagementPlanTableRequest;
@ -474,7 +478,7 @@ public class DataManagementPlanManager {
}
public DMP createOrUpdate(ApiContext apiContext, DataManagementPlanEditorModel dataManagementPlan, Principal principal) throws Exception {
boolean setNotification = false;
if (dataManagementPlan.getId() != null) {
DMP dmp1 = apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().find(dataManagementPlan.getId());
@ -491,6 +495,8 @@ public class DataManagementPlanManager {
}
if (dataManagementPlan.getStatus() == (int) DMP.DMPStatus.FINALISED.getValue() && dmp1.getStatus().equals(DMP.DMPStatus.FINALISED.getValue()))
throw new Exception("DMP is finalized, therefore cannot be edited.");
setNotification = true;
}
DMP newDmp = dataManagementPlan.toDataModel();
@ -568,9 +574,38 @@ public class DataManagementPlanManager {
if (dataManagementPlan.getAssociatedUsers().size() == 0)
assignUser(newDmp, user);
if (setNotification) {
this.sendNotification(newDmp, user);
}
return newDmp;
}
private void sendNotification(DMP dmp, UserInfo user) {
List<UserDMP> userDMPS = databaseRepository.getUserDmpDao().asQueryable().where(((builder, root) -> builder.equal(root.get("dmp").get("id"), dmp.getId()))).toList();
for (UserDMP userDMP : userDMPS) {
if (!userDMP.getUser().getId().equals(user.getId())) {
Notification notification = new Notification();
notification.setUserId(user);
notification.setType(NotificationType.DMP_MODIFIED);
notification.setNotifyState(NotifyState.PENDING);
notification.setIsActive(ActiveStatus.ACTIVE);
notification.setData("{" +
"\"userId\": \"" + userDMP.getUser().getId() + "\"" +
", \"id\": \"" + userDMP.getDmp().getId() + "\"" +
", \"name\": \"" + userDMP.getDmp().getLabel() + "\"" +
", \"path\": \"/plans/edit\"" +
"}");
notification.setCreatedAt(new Date());
notification.setUpdatedAt(notification.getCreatedAt());
notification.setContactTypeHint(ContactType.EMAIL);
notification.setContactHint(userDMP.getUser().getEmail());
databaseRepository.getNotificationDao().createOrUpdate(notification);
}
}
}
private void assignUser(DMP dmp, UserInfo userInfo) {
UserDMP userDMP = new UserDMP();

View File

@ -5,6 +5,10 @@ import eu.eudat.data.dao.entities.DataRepositoryDao;
import eu.eudat.data.dao.entities.DatasetDao;
import eu.eudat.data.dao.entities.RegistryDao;
import eu.eudat.data.entities.*;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.ContactType;
import eu.eudat.data.enumeration.notification.NotificationType;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.data.query.items.table.dataset.DatasetPublicTableRequest;
import eu.eudat.data.query.items.table.dataset.DatasetTableRequest;
import eu.eudat.data.query.items.table.datasetprofile.DatasetProfileTableRequestItem;
@ -400,6 +404,7 @@ public class DatasetManager {
}
public eu.eudat.data.entities.Dataset createOrUpdate(DatasetWizardModel datasetWizardModel, Principal principal) throws Exception {
Boolean sendNotification = false;
DMP dmp = apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().find(datasetWizardModel.getDmp().getId());
if (datasetWizardModel.getId() != null) {
Dataset tempDataset = apiContext.getOperationsContext().getDatabaseRepository().getDatasetDao().find(datasetWizardModel.getId());
@ -407,6 +412,7 @@ public class DatasetManager {
if (datasetWizardModel.getModified().getTime() != tempDataset.getModified().getTime()) {
throw new Exception("Dataset has been modified already by another user.");
}
sendNotification = true;
}
}
if (dmp.getStatus().equals(DMP.DMPStatus.FINALISED.getValue()) && datasetWizardModel.getId() != null)
@ -427,9 +433,37 @@ public class DatasetManager {
Dataset dataset1 = apiContext.getOperationsContext().getDatabaseRepository().getDatasetDao().createOrUpdate(dataset);
datasetWizardModel.setId(dataset1.getId());
updateTags(apiContext.getOperationsContext().getDatasetRepository(), datasetWizardModel);
if (sendNotification) {
this.sendNotification(dataset1, dataset1.getDmp(), userInfo);
}
return dataset1;
}
private void sendNotification(Dataset dataset, DMP dmp, UserInfo user) {
List<UserDMP> userDMPS = databaseRepository.getUserDmpDao().asQueryable().where(((builder, root) -> builder.equal(root.get("dmp").get("id"), dmp.getId()))).toList();
for (UserDMP userDMP : userDMPS) {
if (!userDMP.getUser().getId().equals(user.getId())) {
Notification notification = new Notification();
notification.setUserId(user);
notification.setType(NotificationType.DATASET_MODIFIED);
notification.setNotifyState(NotifyState.PENDING);
notification.setIsActive(ActiveStatus.ACTIVE);
notification.setData("{" +
"\"userId\": \"" + userDMP.getUser().getId() + "\"" +
", \"id\": \"" + dataset.getId() + "\"" +
", \"name\": \"" + dataset.getLabel() + "\"" +
", \"path\": \"/datasets/edit\"" +
"}");
notification.setCreatedAt(new Date());
notification.setUpdatedAt(notification.getCreatedAt());
notification.setContactTypeHint(ContactType.EMAIL);
notification.setContactHint(userDMP.getUser().getEmail());
databaseRepository.getNotificationDao().createOrUpdate(notification);
}
}
}
private void checkDatasetValidation(Dataset dataset) throws Exception {
List<String> datasetProfileValidators = new LinkedList<>();
DatasetProfile profile = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(dataset.getProfile().getId());

View File

@ -0,0 +1,118 @@
package eu.eudat.logic.managers;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.data.entities.Notification;
import eu.eudat.data.entities.UserInfo;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.NotificationType;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.utilities.MailService;
import eu.eudat.models.data.mail.SimpleMail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.mail.MessagingException;
import javax.transaction.Transactional;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@Component
public class NotificationManager {
private static final Logger logger = LoggerFactory.getLogger(NotificationManager.class);
private ApiContext apiContext;
private Environment environment;
private MailService mailService;
@Autowired
public NotificationManager(ApiContext apiContext, Environment environment, MailService mailService) {
this.apiContext = apiContext;
this.environment = environment;
this.mailService = mailService;
}
@Transactional
public void sendNotification(Notification notification) throws Exception {
if (notification.getNotifyState() == NotifyState.ERROR) {
if (notification.getRetryCount() == null) {
notification.setRetryCount(0);
}
notification.setRetryCount(notification.getRetryCount() + 1);
if (notification.getRetryCount() >= this.environment.getProperty("notification.maxRetries", Integer.class)) {
notification.setIsActive(ActiveStatus.INACTIVE);
notification.setUpdatedAt(new Date());
return;
}
}
notification.setNotifyState(NotifyState.PROCESSING);
notification.setNotifiedAt(new Date());
notification.setUpdatedAt(new Date());
try {
Map<String, String> data = new ObjectMapper().readValue(notification.getData(), HashMap.class);
UserInfo userInfo = this.apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(UUID.fromString(data.get("userId")));
String subjectTemplate = "";
String contentTemplate = "";
switch (notification.getType()) {
case DMP_MODIFIED:
case DATASET_MODIFIED:
subjectTemplate = this.environment.getProperty("mail.modified.notification.subject");
contentTemplate = mailService.getMailTemplateContent("classpath:modifiedNotification.html");
break;
}
switch (notification.getContactTypeHint()) {
case EMAIL:
this.sendEmailNotification(notification, userInfo, data, subjectTemplate, contentTemplate);
notification.setNotifyState(NotifyState.SUCCEEDED);
notification.setUpdatedAt(new Date());
break;
}
}catch (Exception e) {
notification.setNotifyState(NotifyState.ERROR);
notification.setUpdatedAt(new Date());
logger.error(e.getMessage(), e);
}
}
private void sendEmailNotification(Notification notification, UserInfo userInfo, Map<String, String> data, String subjectTemplate, String contentTemplate) throws IOException {
CompletableFuture.runAsync(() -> {
SimpleMail simpleMail = new SimpleMail();
simpleMail.setFrom(this.environment.getProperty("mail.from"));
simpleMail.setSubject(makeSubject(data, subjectTemplate));
simpleMail.setTo(notification.getContactHint());
simpleMail.setContent(makeContent(data, notification, userInfo, contentTemplate));
try {
mailService.sendSimpleMail(simpleMail);
} catch (MessagingException e) {
notification.setNotifyState(NotifyState.ERROR);
notification.setUpdatedAt(new Date());
logger.error(e.getMessage(), e);
}
});
}
private String makeSubject(Map<String, String> data, String subjectTemplate) {
return subjectTemplate.replace("{name}", data.get("name"));
}
private String makeContent(Map<String, String> data, Notification notification, UserInfo userInfo, String template) {
String content = template;
content = content.replace("{recipient}", userInfo.getName());
content = content.replace("{id}", data.get("id"));
content = content.replace("{name}", data.get("name"));
content = content.replace("{host}", this.environment.getProperty("dmp.domain"));
content = content.replace("{reasonName}", notification.getUserId().getName());
content = content.replace("{path}", data.get("path"));
return content;
}
}

View File

@ -54,5 +54,7 @@ public interface DatabaseRepository {
LockDao getLockDao();
NotificationDao getNotificationDao();
<T> void detachEntity(T entity);
}

View File

@ -36,6 +36,7 @@ public class DatabaseRepositoryImpl implements DatabaseRepository {
private ProjectDao projectDao;
private FunderDao funderDao;
private LockDao lockDao;
private NotificationDao notificationDao;
private EntityManager entityManager;
@ -284,6 +285,16 @@ public class DatabaseRepositoryImpl implements DatabaseRepository {
return lockDao;
}
@Override
public NotificationDao getNotificationDao() {
return notificationDao;
}
@Autowired
public void setNotificationDao(NotificationDao notificationDao) {
this.notificationDao = notificationDao;
}
public <T> void detachEntity(T entity) {
this.entityManager.detach(entity);
}

View File

@ -0,0 +1,48 @@
package eu.eudat.logic.utilities.schedule.notification;
import eu.eudat.data.entities.Notification;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.logic.managers.NotificationManager;
import eu.eudat.logic.services.ApiContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.List;
@Component
public class NotificationScheduleJob {
private static final Logger logger = LoggerFactory.getLogger(NotificationScheduleJob.class);
private ApiContext apiContext;
private NotificationManager notificationManager;
@Autowired
public NotificationScheduleJob(ApiContext apiContext, NotificationManager notificationManager) {
this.apiContext = apiContext;
this.notificationManager = notificationManager;
}
@Transactional
@Scheduled(fixedRateString = "${notification.rateInterval}")
public void sendNotifications() {
List<Notification> notifications = this.apiContext.getOperationsContext().getDatabaseRepository().getNotificationDao().asQueryable().where(((builder, root) ->
builder.and(
builder.or(
builder.equal(root.get("notifyState"), NotifyState.PENDING), builder.equal(root.get("notifyState"), NotifyState.ERROR))
, builder.equal(root.get("isActive"), ActiveStatus.ACTIVE)))).toList();
if (!notifications.isEmpty()) {
notifications.forEach(notification -> {
try {
this.notificationManager.sendNotification(notification);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
});
}
}
}

View File

@ -0,0 +1,163 @@
package eu.eudat.models.data.notiication;
import eu.eudat.data.enumeration.notification.ActiveStatus;
import eu.eudat.data.enumeration.notification.ContactType;
import eu.eudat.data.enumeration.notification.NotificationType;
import eu.eudat.data.enumeration.notification.NotifyState;
import eu.eudat.models.DataModel;
import eu.eudat.models.data.userinfo.UserInfo;
import java.util.Date;
import java.util.UUID;
public class Notification implements DataModel<eu.eudat.data.entities.Notification, Notification> {
private UUID id;
private UserInfo userId;
private ActiveStatus isActive;
private NotificationType type;
private ContactType contactTypeHint;
private String contactHint;
private String data;
private NotifyState notifyState;
private Date notifiedAt;
private Integer retryCount;
private Date createdAt;
private Date updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UserInfo getUserId() {
return userId;
}
public void setUserId(UserInfo userId) {
this.userId = userId;
}
public ActiveStatus getIsActive() {
return isActive;
}
public void setIsActive(ActiveStatus isActive) {
this.isActive = isActive;
}
public NotificationType getType() {
return type;
}
public void setType(NotificationType type) {
this.type = type;
}
public ContactType getContactTypeHint() {
return contactTypeHint;
}
public void setContactTypeHint(ContactType contactTypeHint) {
this.contactTypeHint = contactTypeHint;
}
public String getContactHint() {
return contactHint;
}
public void setContactHint(String contactHint) {
this.contactHint = contactHint;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public NotifyState getNotifyState() {
return notifyState;
}
public void setNotifyState(NotifyState notifyState) {
this.notifyState = notifyState;
}
public Date getNotifiedAt() {
return notifiedAt;
}
public void setNotifiedAt(Date notifiedAt) {
this.notifiedAt = notifiedAt;
}
public Integer getRetryCount() {
return retryCount;
}
public void setRetryCount(Integer retryCount) {
this.retryCount = retryCount;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public Notification fromDataModel(eu.eudat.data.entities.Notification entity) {
this.id = entity.getId();
this.contactHint = entity.getContactHint();
this.contactTypeHint = entity.getContactTypeHint();
this.createdAt = entity.getCreatedAt();
this.data = entity.getData();
this.isActive = entity.getIsActive();
this.notifiedAt = entity.getNotifiedAt();
this.notifyState = entity.getNotifyState();
this.retryCount = entity.getRetryCount();
this.type = entity.getType();
this.updatedAt = entity.getUpdatedAt();
this.userId = new UserInfo().fromDataModel(entity.getUserId());
return this;
}
@Override
public eu.eudat.data.entities.Notification toDataModel() throws Exception {
eu.eudat.data.entities.Notification entity = new eu.eudat.data.entities.Notification();
entity.setId(this.id);
entity.setContactHint(this.contactHint);
entity.setContactTypeHint(this.contactTypeHint);
entity.setCreatedAt(this.createdAt);
entity.setData(this.data);
entity.setIsActive(this.isActive);
entity.setNotifiedAt(this.notifiedAt);
entity.setNotifyState(this.notifyState);
entity.setRetryCount(this.retryCount);
entity.setType(this.type);
entity.setUpdatedAt(this.updatedAt);
entity.setUserId(this.userId.toDataModel());
return entity;
}
@Override
public String getHint() {
return null;
}
}

View File

@ -9,6 +9,7 @@ eu.eudat.logic.proxy.allowed.host=https://eestore.paas2.uninett.no
####################GENERIC MAIL CONFIGURATIONS#################
mail.subject=Invitation to DMP Plan {dmpname}
mail.from=TheApp@dev.cite.gr
mail.modified.notification.subject=[OpenDMP] The {name} has been modified
####################SPRING MAIL CONFIGURATIONS#################
spring.mail.default-encoding=UTF-8
@ -76,3 +77,8 @@ http-logger.delay = 10
#############GENERIC DATASOURCE CONFIGURATIONS#########
database.driver-class-name=org.postgresql.Driver
database.lock-fail-interval=120000
userguide.path=guide/
notification.rateInterval=30000
notification.maxRetries=10

View File

@ -0,0 +1,304 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just made changes to the {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{host}{path}/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,20 @@
CREATE TABLE public."Notification" (
id uuid NOT NULL,
"UserId" uuid,
"IsActive" integer NOT NULL,
"Type" integer NOT NULL,
"ContactTypeHint" integer,
"ContactHint" character varying,
"Data" character varying,
"NotifyState" integer NOT NULL,
"NotifiedAt" timestamp without time zone,
"RetryCount" integer,
"CreatedAt" timestamp without time zone,
"UpdatedAt" timestamp without time zone
);
ALTER TABLE ONLY public."Notification"
ADD CONSTRAINT "Notification_pkey" PRIMARY KEY (id);
ALTER TABLE ONLY public."Notification"
ADD CONSTRAINT "NotificationUserReference" FOREIGN KEY ("UserId") REFERENCES public."UserInfo"(id);