diff options
3 files changed, 125 insertions, 155 deletions
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 69b64c0d..0402f36c 100644 --- a/juick-notifications/src/main/java/com/juick/components/Notifications.java +++ b/juick-notifications/src/main/java/com/juick/components/Notifications.java @@ -22,22 +22,22 @@ import com.google.android.gcm.server.*; import com.juick.ExternalToken; import com.juick.User; import com.juick.formatters.PlainTextFormatter; +import com.juick.server.component.MessageEvent; import com.turo.pushy.apns.ApnsClient; import com.turo.pushy.apns.PushNotificationResponse; import com.turo.pushy.apns.util.ApnsPayloadBuilder; import com.turo.pushy.apns.util.SimpleApnsPushNotification; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.text.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationListener; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.web.client.RestTemplate; -import rocks.xmpp.core.XmppException; -import rocks.xmpp.extensions.component.accept.ExternalComponent; +import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.io.IOException; @@ -49,14 +49,12 @@ import java.util.stream.Collectors; /** * @author Ugnich Anton */ -public class Notifications implements NotificationClientListener, AutoCloseable { +public class Notifications implements NotificationClientListener, AutoCloseable, ApplicationListener<MessageEvent> { private static Logger logger = LoggerFactory.getLogger(Notifications.class); @Inject private RestTemplate rest; @Inject - private ExternalComponent xmpp; - @Inject private Sender GCMSender; @Inject @@ -76,145 +74,138 @@ public class Notifications implements NotificationClientListener, AutoCloseable @PostConstruct public void init() throws IOException { mpnsClient.setListener(this); - xmpp.addInboundMessageListener(e -> { - rocks.xmpp.core.stanza.model.Message msg = e.getMessage(); - com.juick.Message jmsg = msg.getExtension(com.juick.Message.class); - boolean isPM = jmsg.getMid() == 0; - boolean isReply = jmsg.getRid() > 0; - int pmTo = NumberUtils.toInt(msg.getTo().getLocal(), 0); + } + @Override + public void onApplicationEvent(@Nonnull MessageEvent event) { + com.juick.Message jmsg = event.getMessage(); + boolean isPM = jmsg.getMid() == 0; + boolean isReply = jmsg.getRid() > 0; + User pmTo = jmsg.getTo(); - final List<User> users = new ArrayList<>(); - if (isPM) { - users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%d", - pmTo), + final List<User> users = new ArrayList<>(); + if (isPM) { + users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%d", + pmTo.getUid()), + HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() { + }).getBody()); + } else { + if (isReply) { + users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%d&mid=%d&rid=%d", + jmsg.getUser().getUid(), jmsg.getMid(), jmsg.getRid()), HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() { }).getBody()); } else { - if (isReply) { - users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%d&mid=%d&rid=%d", - jmsg.getUser().getUid(), jmsg.getMid(), jmsg.getRid()), - HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() { - }).getBody()); - } else { - users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%s&mid=%s", - jmsg.getUser().getUid(), jmsg.getMid()), - HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() { - }).getBody()); - } + users.addAll(rest.exchange(String.format("http://api.juick.com/notifications?uid=%s&mid=%s", + jmsg.getUser().getUid(), jmsg.getMid()), + HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() { + }).getBody()); } + } - // GCM - List<String> regids = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("gcm")) - .map(ExternalToken::getToken).collect(Collectors.toList()); - if (!regids.isEmpty()) { - try { - String json = jsonMapper.writeValueAsString(jmsg); - logger.info(json); - Message message = new Message.Builder().addData("message", json).build(); - MulticastResult result = GCMSender.send(message, regids, 3); - List<Result> results = result.getResults(); - for (int i = 0; i < results.size(); i++) { - Result currentResult = results.get(i); - logger.info("RES {}: {}", i, currentResult); - List<String> errorCodes = Arrays.asList(Constants.ERROR_MISMATCH_SENDER_ID, Constants.ERROR_NOT_REGISTERED); - if (errorCodes.contains(currentResult.getErrorCodeName())) { - // assuming results are in order of regids - // http://stackoverflow.com/a/11594531/1097384 - String currentId = regids.get(i); - logger.info("{} is scheduled to remove", currentId); - addInvalidGCMToken(currentId); - } + // GCM + List<String> regids = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("gcm")) + .map(ExternalToken::getToken).collect(Collectors.toList()); + if (!regids.isEmpty()) { + try { + String json = jsonMapper.writeValueAsString(jmsg); + logger.info(json); + Message message = new Message.Builder().addData("message", json).build(); + MulticastResult result = GCMSender.send(message, regids, 3); + List<Result> results = result.getResults(); + for (int i = 0; i < results.size(); i++) { + Result currentResult = results.get(i); + logger.info("RES {}: {}", i, currentResult); + List<String> errorCodes = Arrays.asList(Constants.ERROR_MISMATCH_SENDER_ID, Constants.ERROR_NOT_REGISTERED); + if (errorCodes.contains(currentResult.getErrorCodeName())) { + // assuming results are in order of regids + // http://stackoverflow.com/a/11594531/1097384 + String currentId = regids.get(i); + logger.info("{} is scheduled to remove", currentId); + addInvalidGCMToken(currentId); } - } catch (IOException ex) { - logger.error(ex.getMessage(), ex); - } catch (IllegalArgumentException err) { - logger.warn("Android: Invalid API Key", err); } - } else { - logger.info("GMS: no recipients"); + } catch (IOException ex) { + logger.error(ex.getMessage(), ex); + } catch (IllegalArgumentException err) { + logger.warn("Android: Invalid API Key", err); } + } else { + logger.info("GMS: no recipients"); + } - /*** WinPhone ***/ - List<String> urls = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("mpns")) - .map(ExternalToken::getToken).collect(Collectors.toList()); + /*** WinPhone ***/ + List<String> urls = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("mpns")) + .map(ExternalToken::getToken).collect(Collectors.toList()); - if (urls.isEmpty()) { - logger.info("WNS: no recipients"); - } else { - try { - String text1 = "@" + jmsg.getUser().getName(); - if (!jmsg.getTags().isEmpty()) { - text1 += ":" + StringEscapeUtils.escapeXml11(jmsg.getTagsString()); - } - String text2 = StringEscapeUtils.escapeXml11(StringUtils.defaultString(jmsg.getText())); - String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - + "<toast>" - + "<visual>" - + "<binding template=\"ToastImageAndText02\">" - + "<image id=\"1\" src=\"http://i.juick.com/as/" + jmsg.getUser().getUid() + ".png\" />" - + "<text id=\"1\">" + text1 + "</text>" - + "<text id=\"2\">" + text2 + "</text>" - + "</binding>" - + "</visual>" - + "<commands>" - + "<command arguments=\"/ThreadView.xaml?mid=" + jmsg.getMid() + "\" />" - + "</commands>" - + "</toast>"; - logger.trace(xml); - for (String url : urls) { - logger.info("WNS: {}", url); - mpnsClient.sendNotification(url, xml); - } - } catch (IOException | IllegalStateException ex) { - logger.error("WNS: ", ex); + if (urls.isEmpty()) { + logger.info("WNS: no recipients"); + } else { + try { + String text1 = "@" + jmsg.getUser().getName(); + if (!jmsg.getTags().isEmpty()) { + text1 += ":" + StringEscapeUtils.escapeXml11(jmsg.getTagsString()); } + String text2 = StringEscapeUtils.escapeXml11(StringUtils.defaultString(jmsg.getText())); + String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<toast>" + + "<visual>" + + "<binding template=\"ToastImageAndText02\">" + + "<image id=\"1\" src=\"http://i.juick.com/as/" + jmsg.getUser().getUid() + ".png\" />" + + "<text id=\"1\">" + text1 + "</text>" + + "<text id=\"2\">" + text2 + "</text>" + + "</binding>" + + "</visual>" + + "<commands>" + + "<command arguments=\"/ThreadView.xaml?mid=" + jmsg.getMid() + "\" />" + + "</commands>" + + "</toast>"; + logger.trace(xml); + for (String url : urls) { + logger.info("WNS: {}", url); + mpnsClient.sendNotification(url, xml); + } + } catch (IOException | IllegalStateException ex) { + logger.error("WNS: ", ex); } + } - /*** iOS ***/ - List<String> tokens = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("apns")) - .map(ExternalToken::getToken).collect(Collectors.toList()); - if (!tokens.isEmpty()) { - ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); - apnsPayloadBuilder.addCustomProperty("mid", jmsg.getMid()); - String post = PlainTextFormatter.formatPost(jmsg); - String[] parts = post.split("\n", 2); - String payload = apnsPayloadBuilder.setAlertTitle(parts[0]) - .setAlertBody(parts[1]).buildWithDefaultMaximumLength(); - for (String token : tokens) { - final Future<PushNotificationResponse<SimpleApnsPushNotification>> notification - = apns.sendNotification(new SimpleApnsPushNotification(token, topic, payload)); - try { - final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse - = notification.get(); - if (pushNotificationResponse.isAccepted()) { - logger.info("APNS accepted: {}", token); - } else { - String reason = pushNotificationResponse.getRejectionReason(); - logger.info("APNS rejected: {}", reason); - if (reason.equals("BadDeviceToken")) { - invalidAPNSTokens.add(token); - } + /*** iOS ***/ + List<String> tokens = users.stream().flatMap(u -> u.getTokens().stream()).filter(d -> d.getType().equals("apns")) + .map(ExternalToken::getToken).collect(Collectors.toList()); + if (!tokens.isEmpty()) { + ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); + apnsPayloadBuilder.addCustomProperty("mid", jmsg.getMid()); + String post = PlainTextFormatter.formatPost(jmsg); + String[] parts = post.split("\n", 2); + String payload = apnsPayloadBuilder.setAlertTitle(parts[0]) + .setAlertBody(parts[1]).buildWithDefaultMaximumLength(); + for (String token : tokens) { + final Future<PushNotificationResponse<SimpleApnsPushNotification>> notification + = apns.sendNotification(new SimpleApnsPushNotification(token, topic, payload)); + try { + final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse + = notification.get(); + if (pushNotificationResponse.isAccepted()) { + logger.info("APNS accepted: {}", token); + } else { + String reason = pushNotificationResponse.getRejectionReason(); + logger.info("APNS rejected: {}", reason); + if (reason.equals("BadDeviceToken")) { + invalidAPNSTokens.add(token); } - } catch (final ExecutionException | InterruptedException ex) { - logger.info("APNS exception", ex); } + } catch (final ExecutionException | InterruptedException ex) { + logger.info("APNS exception", ex); } - } else { - logger.info("APNS: no recipients"); } - }); - try { - xmpp.connect(); - } catch (XmppException e) { - logger.warn("xmpp extension", e); + } else { + logger.info("APNS: no recipients"); } } @Override public void close() throws Exception { apns.close(); - if (xmpp != null) - xmpp.close(); logger.info("ExternalComponent on notifications destroyed"); } 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 19d56d59..5cd2b3cd 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,7 +2,10 @@ package com.juick.components.configuration; import com.juick.components.CleanUp; import com.juick.components.Notifications; +import com.juick.server.component.JuickServerComponent; +import com.juick.server.component.JuickServerReconnectManager; import com.juick.server.configuration.BaseWebConfiguration; +import com.juick.server.configuration.JuickServerComponentConfiguration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.*; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -24,7 +27,7 @@ import java.util.List; @PropertySource("classpath:juick.conf") @ComponentScan(basePackages = "com.juick.components.service") @Import({ APNSConfiguration.class, MPNSConfiguration.class, - GCMConfiguration.class, XMPPConfiguration.class }) + GCMConfiguration.class, JuickServerComponentConfiguration.class}) public class NotificationsAppConfiguration extends BaseWebConfiguration { @Value("${api_user:juick}") private String apiUser; @@ -32,6 +35,15 @@ public class NotificationsAppConfiguration extends BaseWebConfiguration { private String apiSecret; @Bean + public JuickServerComponent juickServerComponent() { + return new JuickServerComponent(); + } + @Bean + public JuickServerReconnectManager juickServerReconnectManager() { + return new JuickServerReconnectManager(); + } + + @Bean public RestTemplate rest() { RestTemplate rest = new RestTemplate(); List<ClientHttpRequestInterceptor> interceptors = Collections.singletonList( diff --git a/juick-notifications/src/main/java/com/juick/components/configuration/XMPPConfiguration.java b/juick-notifications/src/main/java/com/juick/components/configuration/XMPPConfiguration.java deleted file mode 100644 index 0073b502..00000000 --- a/juick-notifications/src/main/java/com/juick/components/configuration/XMPPConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.juick.components.configuration; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import rocks.xmpp.core.session.Extension; -import rocks.xmpp.core.session.XmppSessionConfiguration; -import rocks.xmpp.core.session.debug.LogbackDebugger; -import rocks.xmpp.extensions.component.accept.ExternalComponent; - -/** - * Created by vital on 29.03.2017. - */ -@Configuration -public class XMPPConfiguration { - @Value("${push_jid}") - private String pushJid; - @Value("${xmpp_host:localhost}") - private String xmppHost; - @Value("${xmpp_port:5347}") - private int xmppPort; - @Value("${push_xmpp_password:secret}") - private String xmppPushPassword; - - @Bean - public ExternalComponent xmpp() { - XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() - .extensions(Extension.of(com.juick.Message.class)) - .debugger(LogbackDebugger.class) - .build(); - return ExternalComponent.create(pushJid, xmppPushPassword, configuration, xmppHost, xmppPort); - } -} |