From 9395a62a4779fdb17dbed113a97a99cb587c7aaf Mon Sep 17 00:00:00 2001 From: Vitaly Litvak Date: Sat, 25 Apr 2015 16:36:00 +0300 Subject: [PATCH] For #148 - apply templates when sending notification messages --- .../server/model/NotificationServiceImpl.java | 182 ++++++++---------- .../traccar/web/shared/model/DeviceEvent.java | 7 + .../shared/model/NotificationSettings.java | 12 ++ .../model/NotificationTemplateTest.java | 44 +++++ 4 files changed, 138 insertions(+), 107 deletions(-) create mode 100644 src/test/java/org/traccar/web/server/model/NotificationTemplateTest.java diff --git a/src/main/java/org/traccar/web/server/model/NotificationServiceImpl.java b/src/main/java/org/traccar/web/server/model/NotificationServiceImpl.java index c3961644..fdc960c5 100644 --- a/src/main/java/org/traccar/web/server/model/NotificationServiceImpl.java +++ b/src/main/java/org/traccar/web/server/model/NotificationServiceImpl.java @@ -65,75 +65,13 @@ public class NotificationServiceImpl extends RemoteServiceServlet implements Not @Inject private DataService dataService; - static class DeviceEvents implements Comparator { - Set offlineEvents; - Set geoFenceEvents; - - void addEvent(DeviceEvent deviceEvent) { - switch (deviceEvent.getType()) { - case OFFLINE: - if (offlineEvents == null) { - offlineEvents = new HashSet(); - } - offlineEvents.add(deviceEvent); - break; - case GEO_FENCE_ENTER: - case GEO_FENCE_EXIT: - if (geoFenceEvents == null) { - geoFenceEvents = new HashSet(); - } - geoFenceEvents.add(deviceEvent); - break; - } - } - - @Override - public int compare(DeviceEvent o1, DeviceEvent o2) { - int r = o1.getDevice().getName().compareTo(o2.getDevice().getName()); - if (r == 0) { - if (o1.getType() == DeviceEventType.GEO_FENCE_ENTER || o1.getType() == DeviceEventType.GEO_FENCE_EXIT) { - return o1.getPosition().getTime().compareTo(o2.getPosition().getTime()); - } - return o1.getTime().compareTo(o2.getTime()); - } - return r; - } - - List offlineEvents() { - return sorted(offlineEvents); - } - - List geoFenceEvents() { - return sorted(geoFenceEvents); - } - - List sorted(Set unsorted) { - if (unsorted == null) { - return Collections.emptyList(); - } - List result = new ArrayList(unsorted); - Collections.sort(result, this); - return result; - } - - void markAsSent() { - markAsSent(offlineEvents); - markAsSent(geoFenceEvents); - } - - void markAsSent(Set events) { - if (events != null) { - for (DeviceEvent event : events) { - event.setNotificationSent(true); - } - } - } - } - public static class NotificationSender extends ScheduledTask { @Inject Provider entityManager; + @Inject + Provider applicationSettings; + @Transactional @Override public void doWork() throws Exception { @@ -146,7 +84,7 @@ public void doWork() throws Exception { return; } - Map events = new HashMap(); + Map> events = new HashMap>(); List admins = null; Map> managers = new HashMap>(); @@ -189,7 +127,7 @@ public void doWork() throws Exception { } } - for (Map.Entry entry : events.entrySet()) { + for (Map.Entry> entry : events.entrySet()) { User user = entry.getKey(); if (user.getEmail() == null || user.getEmail().trim().isEmpty()) { logger.warning("User '" + user.getLogin() + "' has empty email field"); @@ -202,23 +140,28 @@ public void doWork() throws Exception { continue; } - DeviceEvents deviceEvents = entry.getValue(); + for (DeviceEvent deviceEvent : entry.getValue()) { + NotificationTemplate template = settings.findTemplate(deviceEvent.getType()); + if (template == null) { + template = new NotificationTemplate(); + template.setBody(defaultBody(deviceEvent.getType(), applicationSettings.get().getLanguage())); + } - StringBuilder message = new StringBuilder(); - if (appendOfflineEventsText(message, deviceEvents.offlineEvents())) { - message.append("\n\n"); - } - appendGeoFenceText(message, deviceEvents.geoFenceEvents()); + Engine engine = getTemplateEngine(); + Map model = getTemplateModel(deviceEvent); + String subject = engine.transform(template.getSubject(), model); + String body = engine.transform(template.getBody(), model); - boolean sentEmail = sendEmail(settings, user, "[traccar-web] Notification", message.toString()); - boolean sentPushbullet = sendPushbullet(settings, user, "[traccar-web] Notification", message.toString()); - if (sentPushbullet || sentEmail) { - deviceEvents.markAsSent(); + boolean sentEmail = sendEmail(settings, user, subject, body, template.getContentType()); + boolean sentPushbullet = sendPushbullet(settings, user, subject, body); + if (sentPushbullet || sentEmail) { + deviceEvent.setNotificationSent(true); + } } } } - private void addEvent(Map events, User user, DeviceEvent event) { + private void addEvent(Map> events, User user, DeviceEvent event) { // check whether user wants to receive such notification events if (!user.getNotificationEvents().contains(event.getType())) { return; @@ -230,12 +173,12 @@ private void addEvent(Map events, User user, DeviceEvent eve } } - DeviceEvents userEvents = events.get(user); + Set userEvents = events.get(user); if (userEvents == null) { - userEvents = new DeviceEvents(); + userEvents = new HashSet(); events.put(user, userEvents); } - userEvents.addEvent(event); + userEvents.add(event); } private NotificationSettings findNotificationSettings(User user) { @@ -283,16 +226,7 @@ private boolean appendOfflineEventsText(StringBuilder msg, List eve return !events.isEmpty(); } - private boolean appendGeoFenceText(StringBuilder msg, List events) { - for (DeviceEvent event : events) { - msg.append("Device '").append(event.getDevice().getName()).append("' ") - .append(event.getType() == DeviceEventType.GEO_FENCE_ENTER ? "entered" : "exited") - .append(" geo-fence '").append(event.getGeoFence().getName()).append("' at ").append(event.getPosition().getTime()).append('\n'); - } - return !events.isEmpty(); - } - - private boolean sendEmail(NotificationSettings settings, User user, String subject, String body) { + private boolean sendEmail(NotificationSettings settings, User user, String subject, String body, String contentType) { // perform some validation of e-mail settings if (settings.getServer() == null || settings.getServer().trim().isEmpty() || settings.getFromAddress() == null || settings.getFromAddress().trim().isEmpty()) { @@ -302,14 +236,14 @@ private boolean sendEmail(NotificationSettings settings, User user, String subje logger.info("Sending Email notification to '" + user.getEmail() + "'..."); Session session = getSession(settings); - Message msg = new MimeMessage(session); + MimeMessage msg = new MimeMessage(session); Transport transport = null; try { msg.setFrom(new InternetAddress(settings.getFromAddress())); msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(user.getLogin() + " <" + user.getEmail() + ">", false)); msg.setSubject(subject); - msg.setText(body); + msg.setContent(body, contentType); msg.setHeader("X-Mailer", "traccar-web.sendmail"); msg.setSentDate(new Date()); @@ -498,15 +432,36 @@ public String checkTemplate(NotificationTemplate template) { testGeoFence = geoFences.get(0); } - Map model = new HashMap(); - model.put(MessagePlaceholder.deviceName.name(), testDevice.getName()); - model.put(MessagePlaceholder.geoFenceName.name(), testGeoFence.getName()); - model.put(MessagePlaceholder.eventTime.name(), new Date()); + Position testPosition = new Position(); Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR, c.get(Calendar.HOUR) - 1); c.set(Calendar.MINUTE, c.get(Calendar.MINUTE) - 15); - model.put(MessagePlaceholder.positionTime.name(), c.getTime()); + testPosition.setTime(c.getTime()); + + Engine engine = getTemplateEngine(); + Map model = getTemplateModel(new DeviceEvent(new Date(), testDevice, testPosition, testGeoFence)); + + String transformedSubject = engine.transform(template.getSubject(), model); + String transformedBody = engine.transform(template.getBody(), model); + + return "
" + + "" + + "" + + "" + + "
" + transformedSubject + "
" + transformedBody + "
" + + "
"; + } + + private static Map getTemplateModel(DeviceEvent event) { + Map model = new HashMap(); + model.put(MessagePlaceholder.deviceName.name(), event.getDevice() == null ? "N/A" : event.getDevice().getName()); + model.put(MessagePlaceholder.geoFenceName.name(), event.getGeoFence() == null ? "N/A" : event.getGeoFence().getName()); + model.put(MessagePlaceholder.eventTime.name(), event.getTime()); + model.put(MessagePlaceholder.positionTime.name(), event.getPosition() == null ? null : event.getPosition().getTime()); + return model; + } + private static Engine getTemplateEngine() { Engine engine = new Engine(); engine.registerNamedRenderer(new NamedRenderer() { @Override @@ -530,15 +485,7 @@ public Class[] getSupportedClasses() { return new Class[] { Date.class }; } }); - String transformedSubject = engine.transform(template.getSubject(), model); - String transformedBody = engine.transform(template.getBody(), model); - - return "
" + - "" + - "" + - "" + - "
" + transformedSubject + "
" + transformedBody + "
" + - "
"; + return engine; } @Transactional @@ -590,4 +537,25 @@ public void saveSettings(NotificationSettings settings) { } } } + + static String defaultBody(DeviceEventType type, String locale) throws IOException { + Properties defaultMessages = new Properties(); + defaultMessages.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("org/traccar/web/client/i18n/Messages.properties")); + + Properties localeMessages = new Properties(); + InputStream messagesIS = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/traccar/web/client/i18n/Messages_" + locale + ".properties"); + if (messagesIS == null) { + localeMessages = defaultMessages; + } else { + localeMessages.load(new InputStreamReader(messagesIS, "UTF-8")); + } + + String key = "defaultNotificationTemplate[" + type.name() + "]"; + String body = localeMessages.getProperty(key, defaultMessages.getProperty(key)); + return body.replace("''", "'") + .replace("{1}", "${deviceName}") + .replace("{2}", "${geoFenceName}") + .replace("{3}", "${eventTime}") + .replace("{4}", "${positionTime}"); + } } diff --git a/src/main/java/org/traccar/web/shared/model/DeviceEvent.java b/src/main/java/org/traccar/web/shared/model/DeviceEvent.java index 23e4bb11..7367e908 100644 --- a/src/main/java/org/traccar/web/shared/model/DeviceEvent.java +++ b/src/main/java/org/traccar/web/shared/model/DeviceEvent.java @@ -51,6 +51,13 @@ public class DeviceEvent { public DeviceEvent() { } + public DeviceEvent(Date time, Device device, Position position, GeoFence geoFence) { + this.time = time; + this.device = device; + this.position = position; + this.geoFence = geoFence; + } + public long getId() { return id; } diff --git a/src/main/java/org/traccar/web/shared/model/NotificationSettings.java b/src/main/java/org/traccar/web/shared/model/NotificationSettings.java index 58ca11fd..63e97463 100644 --- a/src/main/java/org/traccar/web/shared/model/NotificationSettings.java +++ b/src/main/java/org/traccar/web/shared/model/NotificationSettings.java @@ -202,4 +202,16 @@ public void copyFrom(NotificationSettings s) { setSecureConnectionType(s.getSecureConnectionType()); setPushbulletAccessToken(s.getPushbulletAccessToken()); } + + public NotificationTemplate findTemplate(DeviceEventType type) { + if (getTransferTemplates() == null) { + setTransferTemplates(new HashMap()); + } + if (getTransferTemplates().size() < getTemplates().size()) { + for (NotificationTemplate template : getTemplates()) { + getTransferTemplates().put(template.getType(), template); + } + } + return getTransferTemplates().get(type); + } } diff --git a/src/test/java/org/traccar/web/server/model/NotificationTemplateTest.java b/src/test/java/org/traccar/web/server/model/NotificationTemplateTest.java new file mode 100644 index 00000000..60390d05 --- /dev/null +++ b/src/test/java/org/traccar/web/server/model/NotificationTemplateTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Vitaly Litvak (vitavaque@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web.server.model; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.traccar.web.shared.model.DeviceEventType; + +import java.io.IOException; + +public class NotificationTemplateTest { + @Test + public void testDefaultBodyEng() throws IOException { + assertEquals("Device '${deviceName}' went offline at ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, null)); + assertEquals("Device '${deviceName}' went offline at ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, "en")); + assertEquals("Device '${deviceName}' went offline at ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, "default")); + assertEquals("Device '${deviceName}' went offline at ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, "not_exist")); + assertEquals("Device '${deviceName}' went offline at ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, "")); + + assertEquals("Device '${deviceName}' entered geo-fence '${geoFenceName}' at ${positionTime}", NotificationServiceImpl.defaultBody(DeviceEventType.GEO_FENCE_ENTER, null)); + assertEquals("Device '${deviceName}' exited geo-fence '${geoFenceName}' at ${positionTime}", NotificationServiceImpl.defaultBody(DeviceEventType.GEO_FENCE_EXIT, null)); + } + + @Test + public void testDefaultBodyRus() throws IOException { + assertEquals("Потеряна связь с устройством '${deviceName}' в ${eventTime}", NotificationServiceImpl.defaultBody(DeviceEventType.OFFLINE, "ru")); + assertEquals("Устройство '${deviceName}' вошло в геозону '${geoFenceName}' в ${positionTime}", NotificationServiceImpl.defaultBody(DeviceEventType.GEO_FENCE_ENTER, "ru")); + assertEquals("Устройство '${deviceName}' покинуло геозону '${geoFenceName}' в ${positionTime}", NotificationServiceImpl.defaultBody(DeviceEventType.GEO_FENCE_EXIT, "ru")); + } +}