aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2016-11-22 17:49:03 +0300
committerGravatar Vitaly Takmazov2016-11-22 17:49:03 +0300
commitaa3c1a06ed46f49b617e2956b6cf9a1b0d367fb0 (patch)
tree722163db0e9b060a5c4f7902fd422c0c2b13032e
parent8b267a3b630f5843623801f42eb90d0ad6a9d849 (diff)
add APNS feedback component
-rw-r--r--juick-api/src/main/java/com/juick/api/Main.java25
-rw-r--r--juick-api/src/main/java/com/juick/api/Notifications.java17
-rw-r--r--juick-notifications/build.gradle2
-rw-r--r--juick-notifications/src/main/java/com/juick/components/CleanUp.java52
-rw-r--r--juick-notifications/src/main/java/com/juick/components/Notifications.java9
-rw-r--r--juick-notifications/src/main/java/com/juick/components/configuration/NotificationsAppConfiguration.java7
-rw-r--r--juick-notifications/src/test/java/com/juick/components/test/NotificationTests.java69
-rw-r--r--juick-server/src/main/java/com/juick/configuration/DataConfiguration.java1
-rw-r--r--juick-server/src/main/java/com/juick/server/PushQueries.java4
-rw-r--r--juick-server/src/main/java/com/juick/server/helpers/TokensList.java30
-rw-r--r--juick-server/src/main/java/com/juick/service/PushQueriesService.java2
-rw-r--r--juick-server/src/main/java/com/juick/service/PushQueriesServiceImpl.java5
12 files changed, 218 insertions, 5 deletions
diff --git a/juick-api/src/main/java/com/juick/api/Main.java b/juick-api/src/main/java/com/juick/api/Main.java
index 2bb42e3a..dffb9d66 100644
--- a/juick-api/src/main/java/com/juick/api/Main.java
+++ b/juick-api/src/main/java/com/juick/api/Main.java
@@ -477,6 +477,31 @@ public class Main extends HttpServlet {
Main.replyJSON(request, response, serializer.serialize(jmsg).toString());
}
+ @Override
+ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ if (req.getCharacterEncoding() == null) {
+ req.setCharacterEncoding("UTF-8");
+ }
+
+ int vuid = Utils.getHttpAuthUID(jdbc, req);
+ if (vuid == 0) {
+ vuid = Utils.getVisitorQueryStringUID(jdbc, req);
+ }
+ if (vuid == 0) {
+ resp.sendError(401);
+ return;
+ }
+ String uri = req.getRequestURI();
+ switch (uri) {
+ case "/notifications":
+ notifications.doDelete(req, resp, vuid);
+ break;
+ default:
+ resp.sendError(400);
+ break;
+ }
+ }
+
public static void replyJSON(HttpServletRequest request, HttpServletResponse response, String json) throws IOException {
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
diff --git a/juick-api/src/main/java/com/juick/api/Notifications.java b/juick-api/src/main/java/com/juick/api/Notifications.java
index a0c9cae9..707df6e4 100644
--- a/juick-api/src/main/java/com/juick/api/Notifications.java
+++ b/juick-api/src/main/java/com/juick/api/Notifications.java
@@ -1,5 +1,6 @@
package com.juick.api;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.Message;
import com.juick.User;
@@ -7,8 +8,10 @@ import com.juick.server.MessagesQueries;
import com.juick.server.PushQueries;
import com.juick.server.SubscriptionsQueries;
import com.juick.server.UserQueries;
+import com.juick.server.helpers.TokensList;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.jdbc.core.JdbcTemplate;
+import spark.utils.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -89,4 +92,18 @@ public class Notifications {
}
}
}
+
+ public void doDelete(HttpServletRequest request, HttpServletResponse response, int vuid) throws IOException {
+ User visitor = UserQueries.getUserByUID(jdbc, vuid).orElse(new User());
+ if ((visitor.getUid() == 0) || !(visitor.getName().equals("juick"))) {
+ response.sendError(403);
+ return;
+ }
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
+ TokensList list = mapper.readValue(IOUtils.toString(request.getInputStream()), TokensList.class);
+ list.getTokens().forEach(t -> PushQueries.deleteAPNSToken(jdbc, t));
+ }
}
diff --git a/juick-notifications/build.gradle b/juick-notifications/build.gradle
index 861c7234..3dc52132 100644
--- a/juick-notifications/build.gradle
+++ b/juick-notifications/build.gradle
@@ -12,6 +12,8 @@ dependencies {
compile "org.springframework:spring-webmvc:${springFrameworkVersion}"
compile 'com.mitchellbosecke:pebble-spring4:2.2.3'
providedRuntime 'mysql:mysql-connector-java:5.1.40'
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.+"
}
compileJava.options.encoding = 'UTF-8'
diff --git a/juick-notifications/src/main/java/com/juick/components/CleanUp.java b/juick-notifications/src/main/java/com/juick/components/CleanUp.java
new file mode 100644
index 00000000..06d96471
--- /dev/null
+++ b/juick-notifications/src/main/java/com/juick/components/CleanUp.java
@@ -0,0 +1,52 @@
+package com.juick.components;
+
+import com.juick.server.helpers.TokensList;
+import com.notnoop.apns.ApnsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Created by vitalyster on 22.11.2016.
+ */
+@Component
+public class CleanUp {
+
+ private static Logger logger = LoggerFactory.getLogger(CleanUp.class);
+
+ @Inject
+ ApnsService apns;
+ @Inject
+ RestTemplate rest;
+
+ @Scheduled(fixedRate = 600000)
+ public void cleanupTokens() {
+ logger.info("initializing apns tokens cleanup");
+ Collection<String> devices = apns.getInactiveDevices().keySet();
+ int count = devices.size();
+ if (count > 0) {
+ logger.info(String.format("%d tokens to delete", count));
+ TokensList list = new TokensList();
+ list.setType("apns");
+ list.setTokens(new ArrayList<>(devices));
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+ rest.exchange("http://api.juick.com/notifications",
+ HttpMethod.DELETE, new HttpEntity<>(list, headers), new ParameterizedTypeReference<Void>() {
+ });
+ } else {
+ logger.info("No APNS tokens to delete");
+ }
+ }
+}
diff --git a/juick-notifications/src/main/java/com/juick/components/Notifications.java b/juick-notifications/src/main/java/com/juick/components/Notifications.java
index ebc69bd1..cb120ce9 100644
--- a/juick-notifications/src/main/java/com/juick/components/Notifications.java
+++ b/juick-notifications/src/main/java/com/juick/components/Notifications.java
@@ -48,6 +48,7 @@ import rocks.xmpp.core.session.XmppSessionConfiguration;
import rocks.xmpp.extensions.component.accept.ExternalComponent;
import javax.annotation.PostConstruct;
+import javax.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -70,10 +71,12 @@ public class Notifications implements AutoCloseable {
private final int xmppPort;
private final String xmppPushPassword;
+ @Inject
+ private ApnsService apns;
+
public Notifications(final Environment env, final RestTemplate rest) {
this.rest = rest;
-
wns_application_sip = env.getProperty("wns_application_sip", "");
wns_client_secret = env.getProperty("wns_client_secret", "");
GCMSender = new Sender(env.getProperty("gcm_key", ""), Endpoint.GCM);
@@ -191,12 +194,10 @@ public class Notifications implements AutoCloseable {
}).getBody());
}
if (!tokens.isEmpty()) {
- ApnsService service = APNS.newService().withCert("/etc/juick/ios.p12", "juick")
- .withSandboxDestination().build();
for (String token : tokens) {
String payload = APNS.newPayload().alertTitle("@" + jmsg.getUser().getName()).alertBody(jmsg.getText()).build();
logger.info("APNS: " + token);
- service.push(token, payload);
+ apns.push(token, payload);
}
} else {
logger.info("APNS: no recipients");
diff --git a/juick-notifications/src/main/java/com/juick/components/configuration/NotificationsAppConfiguration.java b/juick-notifications/src/main/java/com/juick/components/configuration/NotificationsAppConfiguration.java
index c7747aa1..1bb4694a 100644
--- a/juick-notifications/src/main/java/com/juick/components/configuration/NotificationsAppConfiguration.java
+++ b/juick-notifications/src/main/java/com/juick/components/configuration/NotificationsAppConfiguration.java
@@ -2,6 +2,8 @@ package com.juick.components.configuration;
import com.juick.components.Notifications;
import com.juick.configuration.DataConfiguration;
+import com.notnoop.apns.APNS;
+import com.notnoop.apns.ApnsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -41,4 +43,9 @@ public class NotificationsAppConfiguration {
public Notifications push() {
return new Notifications(env, rest());
}
+ @Bean
+ public ApnsService apns() {
+ return APNS.newService().withCert(env.getProperty("ios_pkcs12_file"), env.getProperty("ios_pkcs12_password"))
+ .withSandboxDestination().build();
+ }
}
diff --git a/juick-notifications/src/test/java/com/juick/components/test/NotificationTests.java b/juick-notifications/src/test/java/com/juick/components/test/NotificationTests.java
new file mode 100644
index 00000000..c4a4ed24
--- /dev/null
+++ b/juick-notifications/src/test/java/com/juick/components/test/NotificationTests.java
@@ -0,0 +1,69 @@
+package com.juick.components.test;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.Appender;
+import com.juick.components.CleanUp;
+import com.notnoop.apns.ApnsService;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Created by vitalyster on 22.11.2016.
+ */
+public class NotificationTests {
+ @InjectMocks
+ CleanUp cleanUp;
+
+ @Mock
+ ApnsService service;
+ @Mock
+ RestTemplate rest;
+
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void CleanUpTest() {
+ Map<String, Date> inactiveDevices = new HashMap<>();
+ inactiveDevices.put("yoyoyo", new Date());
+ when(service.getInactiveDevices()).thenReturn(inactiveDevices);
+ ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ final Appender mockAppender = mock(Appender.class);
+ when(mockAppender.getName()).thenReturn("MOCK");
+ root.addAppender(mockAppender);
+ cleanUp.cleanupTokens();
+ verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
+ @Override
+ public boolean matches(final Object argument) {
+ return ((LoggingEvent)argument).getFormattedMessage().contains("1 tokens to delete");
+ }
+ }));
+ when(service.getInactiveDevices()).thenReturn(new HashMap<>());
+ cleanUp.cleanupTokens();
+ verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
+ @Override
+ public boolean matches(final Object argument) {
+ return ((LoggingEvent)argument).getFormattedMessage().contains("No APNS tokens to delete");
+ }
+ }));
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/configuration/DataConfiguration.java b/juick-server/src/main/java/com/juick/configuration/DataConfiguration.java
index 9f733184..89fda764 100644
--- a/juick-server/src/main/java/com/juick/configuration/DataConfiguration.java
+++ b/juick-server/src/main/java/com/juick/configuration/DataConfiguration.java
@@ -1,7 +1,6 @@
package com.juick.configuration;
import org.apache.commons.dbcp2.BasicDataSource;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
diff --git a/juick-server/src/main/java/com/juick/server/PushQueries.java b/juick-server/src/main/java/com/juick/server/PushQueries.java
index 904b3c0d..5ee3cef3 100644
--- a/juick-server/src/main/java/com/juick/server/PushQueries.java
+++ b/juick-server/src/main/java/com/juick/server/PushQueries.java
@@ -41,4 +41,8 @@ public class PushQueries {
return sql.queryForList("SELECT token FROM ios INNER JOIN users " +
"ON (users.id=ios.user_id) WHERE users.id IN (" + StringUtils.collectionToCommaDelimitedString(uids) + ")", String.class);
}
+
+ public static boolean deleteAPNSToken(JdbcTemplate sql, String token) {
+ return sql.update("DELETE FROM ios WHERE token=?", token) > 0;
+ }
}
diff --git a/juick-server/src/main/java/com/juick/server/helpers/TokensList.java b/juick-server/src/main/java/com/juick/server/helpers/TokensList.java
new file mode 100644
index 00000000..cb331e04
--- /dev/null
+++ b/juick-server/src/main/java/com/juick/server/helpers/TokensList.java
@@ -0,0 +1,30 @@
+package com.juick.server.helpers;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * Created by vitalyster on 22.11.2016.
+ */
+public class TokensList {
+ private String type;
+ private List<String> tokens;
+
+ @JsonProperty("type")
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public List<String> getTokens() {
+ return tokens;
+ }
+
+ public void setTokens(List<String> tokens) {
+ this.tokens = tokens;
+ }
+}
diff --git a/juick-server/src/main/java/com/juick/service/PushQueriesService.java b/juick-server/src/main/java/com/juick/service/PushQueriesService.java
index 8e7ce543..fe956c3a 100644
--- a/juick-server/src/main/java/com/juick/service/PushQueriesService.java
+++ b/juick-server/src/main/java/com/juick/service/PushQueriesService.java
@@ -17,4 +17,6 @@ public interface PushQueriesService {
List<String> getAPNSToken(int uid);
List<String> getAPNSTokens(List<Integer> uids);
+
+ boolean deleteAPNSToken(String token);
}
diff --git a/juick-server/src/main/java/com/juick/service/PushQueriesServiceImpl.java b/juick-server/src/main/java/com/juick/service/PushQueriesServiceImpl.java
index 91861bd9..9bbadef6 100644
--- a/juick-server/src/main/java/com/juick/service/PushQueriesServiceImpl.java
+++ b/juick-server/src/main/java/com/juick/service/PushQueriesServiceImpl.java
@@ -61,6 +61,11 @@ public class PushQueriesServiceImpl extends BaseJdbcService implements PushQueri
}
@Override
+ public boolean deleteAPNSToken(String token) {
+ return getJdbcTemplate().update("DELETE FROM ios WHERE token=?", token) > 0;
+ }
+
+ @Override
public List<String> getAPNSTokens(final List<Integer> uids) {
return getJdbcTemplate().queryForList(
"SELECT token FROM ios INNER JOIN users " +