From 51bfc341be1975b7a11e0b3a59cfbb4710e78446 Mon Sep 17 00:00:00 2001
From: Vitaly Takmazov
Date: Wed, 4 Oct 2017 15:31:44 +0300
Subject: juick-xmpp-wip: router component
---
.../main/java/com/juick/components/CleaningUp.java | 61 +++
.../main/java/com/juick/components/JuickBot.java | 546 +++++++++++++++++++++
.../java/com/juick/components/XMPPConnection.java | 124 +++--
.../main/java/com/juick/components/XMPPServer.java | 281 ++++-------
.../configuration/XmppAppConfiguration.java | 45 +-
.../java/com/juick/components/s2s/CleaningUp.java | 67 ---
.../java/com/juick/components/s2s/Connection.java | 3 +-
.../com/juick/components/s2s/ConnectionIn.java | 30 +-
.../com/juick/components/s2s/ConnectionOut.java | 15 +-
.../java/com/juick/components/s2s/JuickBot.java | 514 -------------------
.../com/juick/xmpp/extensions/JuickMessage.java | 164 +++++++
.../java/com/juick/xmpp/extensions/JuickUser.java | 65 +++
.../src/test/java/com/juick/xmpp/XMPPTests.java | 76 ---
13 files changed, 1050 insertions(+), 941 deletions(-)
create mode 100644 juick-xmpp/src/main/java/com/juick/components/CleaningUp.java
create mode 100644 juick-xmpp/src/main/java/com/juick/components/JuickBot.java
delete mode 100644 juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java
delete mode 100644 juick-xmpp/src/main/java/com/juick/components/s2s/JuickBot.java
create mode 100644 juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickMessage.java
create mode 100644 juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickUser.java
delete mode 100644 juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java
(limited to 'juick-xmpp/src')
diff --git a/juick-xmpp/src/main/java/com/juick/components/CleaningUp.java b/juick-xmpp/src/main/java/com/juick/components/CleaningUp.java
new file mode 100644
index 00000000..067af6e8
--- /dev/null
+++ b/juick-xmpp/src/main/java/com/juick/components/CleaningUp.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008-2017, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.juick.components;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+
+/**
+ *
+ * @author ugnich
+ */
+@Component
+public class CleaningUp {
+
+ private static final Logger logger = LoggerFactory.getLogger(CleaningUp.class);
+
+ @Inject
+ XMPPServer xmpp;
+
+ @Scheduled(fixedDelay = 10000)
+ public void cleanUp() {
+ long now = System.currentTimeMillis();
+
+ xmpp.getOutConnections().stream().filter(c -> {
+ int inactive = (int) ((double) (now - c.tsLocalData) / 1000.0);
+ return inactive > 900;
+ }).forEach(c -> {
+ logger.info("closing idle outgoing connection to {}", c.to);
+ c.closeConnection();
+ xmpp.getOutConnections().remove(c);
+ });
+
+ xmpp.getInConnections().stream().filter(c -> {
+ int inactive = (int) ((double) (now - c.tsRemoteData) / 1000.0);
+ return inactive > 900;
+ }).forEach(c -> {
+ logger.info("closing idle incoming connection from {}", c.from);
+ c.closeConnection();
+ xmpp.getInConnections().remove(c);
+ });
+ }
+}
diff --git a/juick-xmpp/src/main/java/com/juick/components/JuickBot.java b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java
new file mode 100644
index 00000000..4f838344
--- /dev/null
+++ b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2008-2017, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.juick.components;
+
+import com.juick.User;
+import com.juick.components.s2s.StanzaListener;
+import com.juick.formatters.PlainTextFormatter;
+import com.juick.service.*;
+import org.apache.commons.lang3.StringUtils;
+import org.ocpsoft.prettytime.PrettyTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import rocks.xmpp.addr.Jid;
+import rocks.xmpp.core.stanza.model.*;
+import rocks.xmpp.core.stanza.model.client.ClientMessage;
+import rocks.xmpp.core.stanza.model.client.ClientPresence;
+import rocks.xmpp.core.stanza.model.errors.Condition;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author ugnich
+ */
+@Component
+public class JuickBot implements StanzaListener, AutoCloseable {
+
+ private static final Logger logger = LoggerFactory.getLogger(JuickBot.class);
+
+ @Inject
+ private XMPPServer xmpp;
+ @Inject
+ private XMPPConnection router;
+ @Value("${xmppbot_jid}")
+ private String xmppbotJidStr;
+
+ private Jid jid;
+
+ private PrettyTime pt;
+
+ @Inject
+ public MessagesService messagesService;
+ @Inject
+ public UserService userService;
+ @Inject
+ public TagService tagService;
+ @Inject
+ public PMQueriesService pmQueriesService;
+ @Inject
+ public ShowQueriesService showQueriesService;
+
+ @PostConstruct
+ public void init() {
+ xmpp.addStanzaListener(this);
+ jid = Jid.of(xmppbotJidStr);
+ broadcastPresence(null);
+ pt = new PrettyTime(new Locale("ru"));
+ }
+
+ public Jid getJid() {
+ return jid;
+ }
+
+ private static final String HELPTEXT =
+ "@username text - Send private message\n"
+ + "*tagname Blah-blah-blah - Post a message with tag 'tagname'\n"
+ + "#1234 Blah-blah-blah - Answer to message #1234\n"
+ + "#1234/5 Blah - Answer to reply #1234/5\n"
+ + "! #1234 - Recommend post\n"
+ + "\n"
+ + "# - Show last messages from your feed (## - second page, ...)\n"
+ + "@ - Show recomendations and popular personal blogs\n"
+ + "* - Show your tags\n"
+ + "#1234 - Show message\n"
+ + "#1234+ - Show message with replies\n"
+ + "@username - Show user's info\n"
+ + "@username+ - Show user's info and last 10 messages\n"
+ + "@username *tag - User's messages with this tag\n"
+ + "*tag - Show last 10 messages with this tag\n"
+ + "? blah - Search posts for 'blah'\n"
+ + "? @username blah - Searching among user\'s posts for 'blah'\n"
+ + "D #123 - Delete message\n"
+ + "D #123/45 - Delete reply\n"
+ + "DL - Delete last message/reply\n"
+ + "S - Show your subscriptions\n"
+ + "S #123 - Subscribe to message replies\n"
+ + "S @username - Subscribe to user's blog\n"
+ + "U #123 - Unsubscribe from comments\n"
+ + "U @username - Unsubscribe from user's blog\n"
+ + "BL - Show your blacklist\n"
+ + "BL @username - Add/delete user to/from your blacklist\n"
+ + "BL *tag - Add/delete tag to/from your blacklist\n"
+ + "ON / OFF - Enable/disable subscriptions delivery\n"
+ + "PING - Pong\n"
+ + "\n"
+ + "Read more: http://juick.com/help/";
+
+ public boolean incomingPresence(Presence p) {
+ final String username = p.getTo().getLocal();
+ final boolean toJuick = username.equals(jid.getLocal());
+
+ if (p.getType() == null) {
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo().asBareJid());
+ reply.setTo(p.getFrom().asBareJid());
+ reply.setType(Presence.Type.UNSUBSCRIBE);
+ xmpp.sendOut(ClientPresence.from(reply));
+ return true;
+ } else if (p.getType().equals(Presence.Type.PROBE)) {
+ int uid_to = 0;
+ if (!toJuick) {
+ uid_to = userService.getUIDbyName(username);
+ }
+
+ if (toJuick || uid_to > 0) {
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo().withResource(jid.getResource()));
+ reply.setTo(p.getFrom());
+ reply.setPriority((byte)10);
+ xmpp.sendOut(ClientPresence.from(reply));
+ } else {
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo());
+ reply.setTo(p.getFrom());
+ reply.setType(Presence.Type.ERROR);
+ reply.setId(p.getId());
+ reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
+ xmpp.sendOut(ClientPresence.from(reply));
+ return true;
+ }
+ return true;
+ } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) {
+ boolean canSubscribe = false;
+ if (toJuick) {
+ canSubscribe = true;
+ } else {
+ int uid_to = userService.getUIDbyName(username);
+ if (uid_to > 0) {
+ pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString());
+ canSubscribe = true;
+ }
+ }
+
+ if (canSubscribe) {
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo());
+ reply.setTo(p.getFrom());
+ reply.setType(Presence.Type.SUBSCRIBED);
+ xmpp.sendOut(ClientPresence.from(reply));
+
+ reply.setFrom(reply.getFrom().withResource(jid.getResource()));
+ reply.setPriority((byte) 10);
+ reply.setType(null);
+ xmpp.sendOut(ClientPresence.from(reply));
+
+ return true;
+ } else {
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo());
+ reply.setTo(p.getFrom());
+ reply.setType(Presence.Type.ERROR);
+ reply.setId(p.getId());
+ reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
+ xmpp.sendOut(ClientPresence.from(reply));
+ return true;
+ }
+ } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) {
+ if (!toJuick) {
+ int uid_to = userService.getUIDbyName(username);
+ if (uid_to > 0) {
+ pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString());
+ }
+ }
+
+ Presence reply = new Presence();
+ reply.setFrom(p.getTo());
+ reply.setTo(p.getFrom());
+ reply.setType(Presence.Type.UNSUBSCRIBED);
+ xmpp.sendOut(ClientPresence.from(reply));
+ }
+
+ return false;
+ }
+
+ public boolean incomingMessage(Message msg) {
+ if (StringUtils.isBlank(msg.getBody()) || (msg.getType() != null && msg.getType().equals(Message.Type.ERROR))) {
+ return false;
+ }
+
+ String username = msg.getTo().getLocal();
+
+ User user_from;
+ String signuphash = StringUtils.EMPTY;
+ user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString());
+ if (user_from == null) {
+ signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString());
+ }
+
+ if (user_from == null) {
+ Message reply = new Message();
+ reply.setFrom(msg.getTo());
+ reply.setTo(msg.getFrom());
+ reply.setType(Message.Type.CHAT);
+ if (username.equals(jid.getLocal())) {
+ reply.setBody("Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account.");
+ } else {
+ reply.setBody("Внимание, системное сообщение!\nВаш JabberID не обнаружен в списке доверенных. Для того, чтобы отправить сообщение пользователю " + username + "@juick.com, пожалуйста зарегистрируйте свой JabberID в системе: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nWarning, system message!\nYour JabberID is not found in our server's white list. To send a message to " + username + "@juick.com, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account.");
+ }
+ xmpp.sendOut(ClientMessage.from(reply));
+ return true;
+ }
+
+ if (username.equals(jid.getLocal())) {
+ return incomingMessageJuick(user_from, msg);
+ }
+
+ int uid_to = userService.getUIDbyName(username);
+
+ if (uid_to == 0) {
+ Message reply = new Message();
+ reply.setFrom(msg.getTo());
+ reply.setTo(msg.getFrom());
+ reply.setType(Message.Type.ERROR);
+ reply.setId(msg.getId());
+ reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
+ xmpp.sendOut(ClientMessage.from(reply));
+ return true;
+ }
+
+ boolean success = false;
+ if (!userService.isInBLAny(uid_to, user_from.getUid())) {
+ success = pmQueriesService.createPM(user_from.getUid(), uid_to, msg.getBody());
+ }
+
+ if (success) {
+ Message m = new Message();
+ m.setFrom(jid.asBareJid());
+ m.setTo(Jid.of(Integer.toString(uid_to), "push.juick.com", null));
+ com.juick.Message jmsg = new com.juick.Message();
+ jmsg.setUser(user_from);
+ jmsg.setText(msg.getBody());
+ m.addExtension(jmsg);
+ router.sendStanza(m);
+
+ m.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null));
+ router.sendStanza(m);
+
+ List jids;
+ boolean inroster = false;
+ jids = userService.getJIDsbyUID(uid_to);
+ for (String userJid : jids) {
+ Message mm = new Message();
+ mm.setTo(Jid.of(userJid));
+ mm.setType(Message.Type.CHAT);
+ inroster = pmQueriesService.havePMinRoster(user_from.getUid(), userJid);
+ if (inroster) {
+ mm.setFrom(Jid.of(jmsg.getUser().getName(), "juick.com", "Juick"));
+ mm.setBody(msg.getBody());
+ } else {
+ mm.setFrom(jid);
+ mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + msg.getBody());
+ }
+ xmpp.sendOut(ClientMessage.from(mm));
+ }
+ } else {
+ Message reply = new Message();
+ reply.setFrom(msg.getTo());
+ reply.setTo(msg.getFrom());
+ reply.setType(Message.Type.ERROR);
+ reply.setId(msg.getId());
+ reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.NOT_ALLOWED));
+ xmpp.sendOut(ClientMessage.from(reply));
+ }
+
+ return false;
+ }
+ private static Pattern regexPM = Pattern.compile("^\\@(\\S+)\\s+([\\s\\S]+)$");
+
+ public boolean incomingMessageJuick(User user_from, Message msg) {
+ String command = msg.getBody().trim();
+ int commandlen = command.length();
+
+ // COMPATIBILITY
+ if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) {
+ command = command.substring(3).trim();
+ commandlen = command.length();
+ }
+
+ if (commandlen == 4) {
+ if (command.equalsIgnoreCase("PING")) {
+ commandPing(msg);
+ return true;
+ } else if (command.equalsIgnoreCase("HELP")) {
+ commandHelp(msg);
+ return true;
+ }
+ } else if (commandlen == 5 && command.equalsIgnoreCase("LOGIN")) {
+ commandLogin(msg, user_from);
+ return true;
+ } else if (command.charAt(0) == '@') {
+ Matcher matchPM = regexPM.matcher(command);
+ if (matchPM.find()) {
+ String user_to = matchPM.group(1);
+ String msgtxt = matchPM.group(2);
+ commandPM(msg, user_from, user_to, msgtxt);
+ return true;
+ }
+ } else if (commandlen == 2 && command.equalsIgnoreCase("BL")) {
+ commandBLShow(msg, user_from);
+ return true;
+ } else if (commandlen == 2 && command.equalsIgnoreCase("#+")) {
+ return commandLast(msg.getFrom());
+ } else if (command.equalsIgnoreCase("@")) {
+ return commandUsers(msg.getFrom());
+ }
+
+ return false;
+ }
+
+ private void commandPing(Message m) {
+ Presence p = new Presence(m.getFrom());
+ p.setFrom(jid);
+ p.setPriority((byte) 10);
+ xmpp.sendOut(ClientPresence.from(p));
+
+ sendReply(m.getFrom(), "PONG");
+ }
+
+ private void commandHelp(Message m) {
+ sendReply(m.getFrom(), HELPTEXT);
+ }
+
+ private void commandLogin(Message m, User user_from) {
+ sendReply(m.getFrom(), "http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid()));
+ }
+
+ private void commandPM(Message m, User user_from, String user_to, String body) {
+ int ret = 0;
+
+ int uid_to = 0;
+ List jids_to = null;
+ boolean haveInRoster = false;
+
+ if (user_to.indexOf('@') > 0) {
+ uid_to = userService.getUIDbyJID(user_to);
+ } else {
+ uid_to = userService.getUIDbyName(user_to);
+ }
+
+ if (uid_to > 0) {
+ if (!userService.isInBLAny(uid_to, user_from.getUid())) {
+ if (pmQueriesService.createPM(user_from.getUid(), uid_to, body)) {
+ jids_to = userService.getJIDsbyUID(uid_to);
+ ret = 200;
+ } else {
+ ret = 500;
+ }
+ } else {
+ ret = 403;
+ }
+ } else {
+ ret = 404;
+ }
+
+ if (ret == 200) {
+ Message msg = new Message();
+ msg.setFrom(jid.asBareJid());
+ msg.setTo(Jid.of(Integer.toString(uid_to), "push.juick.com", null));
+ com.juick.Message jmsg = new com.juick.Message();
+ jmsg.setUser(user_from);
+ jmsg.setText(body);
+ msg.addExtension(jmsg);
+ router.sendStanza(msg);
+
+ msg.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null));
+ router.sendStanza(msg);
+
+ for (String userJid : jids_to) {
+ Message mm = new Message();
+ mm.setTo(Jid.of(userJid));
+ mm.setType(Message.Type.CHAT);
+ haveInRoster = pmQueriesService.havePMinRoster(user_from.getUid(), userJid);
+ if (haveInRoster) {
+ mm.setFrom(Jid.of(user_from.getName(), "juick.com", "Juick"));
+ mm.setBody(body);
+ } else {
+ mm.setFrom(jid);
+ mm.setBody("Private message from @" + user_from.getName() + ":\n" + body);
+ }
+ xmpp.sendOut(ClientMessage.from(mm));
+ }
+ }
+
+ Message reply = new Message();
+ reply.setFrom(m.getTo());
+ reply.setTo(m.getFrom());
+ if (ret == 200) {
+ reply.setType(m.getType());
+ reply.setBody("Private message sent");
+ } else {
+ reply.setType(Message.Type.ERROR);
+ reply.setBody("Error " + ret);
+ }
+ xmpp.sendOut(ClientMessage.from(reply));
+ }
+
+ private void commandBLShow(Message m, User user_from) {
+ List blusers = userService.getUserBLUsers(user_from.getUid());
+ List bltags = tagService.getUserBLTags(user_from.getUid());
+
+ String txt = StringUtils.EMPTY;
+ if (bltags.size() > 0) {
+ for (String bltag : bltags) {
+ txt += "*" + bltag + "\n";
+ }
+
+ if (blusers.size() > 0) {
+ txt += "\n";
+ }
+ }
+ if (blusers.size() > 0) {
+ for (User bluser : blusers) {
+ txt += "@" + bluser.getName() + "\n";
+ }
+ }
+ if (txt.isEmpty()) {
+ txt = "You don't have any users or tags in your blacklist.";
+ }
+
+ sendReply(m.getFrom(), txt);
+ }
+
+ boolean commandLast(Jid jidFrom) {
+ User user = userService.getUserByJID(jidFrom.asBareJid().toEscapedString());
+ sendReply(jidFrom, "Last messages:\n"
+ + printMessages(messagesService.getAll(user.getUid(), 0), true));
+ return true;
+ }
+
+ boolean commandUsers(Jid jidFrom) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Recommended blogs");
+ User currentUser = userService.getUserByJID(jidFrom.asBareJid().toEscapedString());
+ List recommendedUsers = showQueriesService.getRecommendedUsers(currentUser);
+ if (recommendedUsers.size() > 0) {
+ for (String user : recommendedUsers) {
+ msg.append("\n@").append(user);
+ }
+ } else {
+ msg.append("\nNo recommendations now. Subscribe to more blogs. ;)");
+ }
+ msg.append("\n\nTop 10 personal blogs:");
+ List topUsers = showQueriesService.getTopUsers();
+ if (topUsers.size() > 0) {
+ for (String user : topUsers) {
+ msg.append("\n@").append(user);
+ }
+ } else {
+ msg.append("\nNo top users. Empty DB? ;)");
+ }
+ sendReply(jidFrom, msg.toString());
+ return true;
+ }
+
+ void sendReply(Jid jidTo, String txt) {
+ Message reply = new Message();
+ reply.setFrom(jid);
+ reply.setTo(jidTo);
+ reply.setType(Message.Type.CHAT);
+ reply.setBody(txt);
+ xmpp.sendOut(ClientMessage.from(reply));
+ }
+
+ void sendNotification(Stanza stanza) {
+ xmpp.sendOut(stanza);
+ }
+
+ @Override
+ public void stanzaReceived(Stanza xmlValue) {
+ if (xmlValue instanceof Presence) {
+ Presence p = (Presence) xmlValue;
+ if (p.getType() == null || !p.getType().equals(Presence.Type.ERROR)) {
+ incomingPresence(p);
+ }
+ } else if (xmlValue instanceof Message) {
+ Message msg = (Message) xmlValue;
+ if (!incomingMessage(msg)) {
+ router.sendStanza(msg);
+ }
+ } else if (xmlValue instanceof IQ) {
+ IQ iq = (IQ) xmlValue;
+ router.sendStanza(iq);
+ }
+ }
+
+ String printMessages(List mids, boolean crop) {
+ return messagesService.getMessages(mids).stream()
+ .sorted(Collections.reverseOrder())
+ .map(PlainTextFormatter::formatPostSummary).collect(Collectors.joining("\n\n"));
+ }
+
+ void broadcastPresence(Presence.Type type) {
+ Presence presence = new Presence();
+ presence.setFrom(jid);
+ if (type != null) {
+ presence.setType(type);
+ }
+ userService.getActiveJIDs().forEach(j -> {
+ try {
+ presence.setTo(Jid.of(j));
+ xmpp.sendOut(ClientPresence.from(presence));
+ } catch (IllegalArgumentException ex) {
+ logger.warn("Invalid jid: {}", j, ex);
+ }
+ });
+ }
+
+ @Override
+ public void close() throws Exception {
+ broadcastPresence(Presence.Type.UNAVAILABLE);
+ }
+}
diff --git a/juick-xmpp/src/main/java/com/juick/components/XMPPConnection.java b/juick-xmpp/src/main/java/com/juick/components/XMPPConnection.java
index a124c461..1bfaf429 100644
--- a/juick-xmpp/src/main/java/com/juick/components/XMPPConnection.java
+++ b/juick-xmpp/src/main/java/com/juick/components/XMPPConnection.java
@@ -18,14 +18,19 @@
package com.juick.components;
import com.juick.User;
+import com.juick.components.s2s.BasicXmppSession;
import com.juick.server.helpers.UserInfo;
+import com.juick.service.MessagesService;
+import com.juick.service.SubscriptionService;
+import com.juick.service.UserService;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.core.env.Environment;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.stereotype.Component;
import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.XmppException;
import rocks.xmpp.core.stanza.AbstractIQHandler;
@@ -41,7 +46,6 @@ import rocks.xmpp.extensions.filetransfer.FileTransferManager;
import rocks.xmpp.extensions.nick.model.Nickname;
import rocks.xmpp.extensions.oob.model.x.OobX;
import rocks.xmpp.extensions.ping.PingManager;
-import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager;
import rocks.xmpp.extensions.vcard.temp.model.VCard;
import rocks.xmpp.util.XmppUtils;
@@ -61,27 +65,44 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
/**
* @author ugnich
*/
+@Component
public class XMPPConnection implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(XMPPConnection.class);
private ExternalComponent router;
@Inject
- private XMPPServer xmpp;
+ private JuickBot bot;
+
+ @Value("${componentname:localhost}")
+ private String componentName;
+ @Value("${component_port:5347}")
+ private int componentPort;
+ @Value("${xmpp_password:secret}")
+ private String password;
+ @Value("${upload_tmp_dir:/tmp}")
+ private String tmpDir;
+
+ @Inject
+ public MessagesService messagesService;
+ @Inject
+ public UserService userService;
@Inject
- private Environment env;
+ public SubscriptionService subscriptionService;
+ @Inject
+ private BasicXmppSession session;
+ @Inject
+ private ExecutorService service;
@PostConstruct
public void init() {
- logger.info("stream router start");
- String componentName = env.getProperty("componentname");
- int componentPort = NumberUtils.toInt(env.getProperty("component_port"), 5347);
- String password = env.getProperty("xmpp_password");
- router = ExternalComponent.create(componentName, password, xmpp.getSession().getConfiguration(), "localhost", componentPort);
+ logger.info("stream router start connecting to {}", componentPort);
+ router = ExternalComponent.create(componentName, password, session.getConfiguration(), "localhost", componentPort);
PingManager pingManager = router.getManager(PingManager.class);
pingManager.setEnabled(true);
router.disableFeature(EntityCapabilities.NAMESPACE);
@@ -98,13 +119,13 @@ public class XMPPConnection implements AutoCloseable {
router.addIQHandler(VCard.class, new AbstractIQHandler(IQ.Type.GET) {
@Override
protected IQ processRequest(IQ iq) {
- if (iq.getTo().equals(xmpp.getJid()) || iq.getTo().asBareJid().equals(xmpp.getJid().asBareJid())
- || iq.getTo().asBareJid().toEscapedString().equals(xmpp.getJid().getDomain())) {
+ if (iq.getTo().equals(bot.getJid()) || iq.getTo().asBareJid().equals(bot.getJid().asBareJid())
+ || iq.getTo().asBareJid().toEscapedString().equals(bot.getJid().getDomain())) {
return iq.createResult(vCard);
}
- User user = xmpp.userService.getUserByName(iq.getTo().getLocal());
+ User user = userService.getUserByName(iq.getTo().getLocal());
if (user.getUid() > 0) {
- UserInfo info = xmpp.userService.getUserInfo(user);
+ UserInfo info = userService.getUserInfo(user);
VCard userVCard = new VCard();
userVCard.setFormattedName(info.getFullName());
userVCard.setNickname(user.getName());
@@ -124,7 +145,7 @@ public class XMPPConnection implements AutoCloseable {
router.addInboundMessageListener(e -> {
Message message = e.getMessage();
Jid jid = message.getTo();
- if (jid.getDomain().equals(Jid.of(componentName).getDomain())) {
+ if (jid.getDomain().equals(router.getDomain().toEscapedString())) {
com.juick.Message jmsg = message.getExtension(com.juick.Message.class);
if (jmsg != null) {
if (jid.getLocal().equals("recomm")) {
@@ -137,8 +158,8 @@ public class XMPPConnection implements AutoCloseable {
}
}
}
- } else if (jid.getDomain().endsWith(xmpp.HOSTNAME) && (jid.getDomain().equals(xmpp.HOSTNAME)
- || jid.getDomain().endsWith("." + xmpp.HOSTNAME))) {
+ } else if (jid.getDomain().endsWith(bot.getJid().getDomain()) && (jid.getDomain().equals(bot.getJid().getDomain())
+ || jid.getDomain().endsWith("." + bot.getJid().getDomain()))) {
if (logger.isInfoEnabled()) {
try {
logger.info("unhandled message: {}", stanzaToString(message));
@@ -147,17 +168,16 @@ public class XMPPConnection implements AutoCloseable {
}
}
} else {
- route(jid.getDomain(), ClientMessage.from(message));
+ route(ClientMessage.from(message));
}
});
router.addInboundIQListener(e -> {
IQ iq = e.getIQ();
Jid jid = iq.getTo();
- if (!jid.getDomain().equals(xmpp.HOSTNAME)) {
- route(jid.getDomain(), iq);
+ if (!jid.getDomain().equals(bot.getJid().getDomain())) {
+ route(iq);
}
});
- String tmpDir = env.getProperty("upload_tmp_dir", "/tmp");
FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class);
fileTransferManager.addFileTransferOfferListener(e -> {
try {
@@ -179,7 +199,7 @@ public class XMPPConnection implements AutoCloseable {
Message msg = new Message();
msg.setType(Message.Type.CHAT);
msg.setFrom(e.getInitiator());
- msg.setTo(xmpp.getJid());
+ msg.setTo(bot.getJid());
msg.setBody(e.getDescription());
try {
String attachmentUrl = String.format("juick://%s", targetFilename);
@@ -192,7 +212,7 @@ public class XMPPConnection implements AutoCloseable {
logger.info("transfer failed", ft.getException());
Message msg = new Message();
msg.setType(Message.Type.CHAT);
- msg.setFrom(xmpp.getJid());
+ msg.setFrom(bot.getJid());
msg.setTo(e.getInitiator());
msg.setBody("File transfer failed, please report to us");
router.sendMessage(msg);
@@ -210,11 +230,19 @@ public class XMPPConnection implements AutoCloseable {
logger.error("ft error", e1);
}
});
- try {
- router.connect();
- } catch (XmppException e) {
- logger.warn("xmpp exception", e);
- }
+ router.addConnectionListener(event -> {
+ if (event.getType().equals(rocks.xmpp.core.session.ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) {
+ logger.info("component connected");
+ }
+ });
+ service.submit(() -> {
+ try {
+ Thread.sleep(3000);
+ router.connect();
+ } catch (InterruptedException | XmppException e) {
+ logger.warn("xmpp exception", e);
+ }
+ });
}
String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException {
@@ -227,11 +255,11 @@ public class XMPPConnection implements AutoCloseable {
return stanzaWriter.toString();
}
- void route(String domain, Stanza stanza) {
+ void route(Stanza stanza) {
try {
String xml = stanzaToString(stanza);
logger.info("stream router (out): {}", xml);
- xmpp.sendOut(domain, xml);
+ bot.sendNotification(stanza);
} catch (XMLStreamException | JAXBException e) {
logger.error("JAXB exception", e);
}
@@ -248,16 +276,16 @@ public class XMPPConnection implements AutoCloseable {
List jids = new ArrayList<>();
if (jmsg.FriendsOnly) {
- jids = xmpp.subscriptionService.getJIDSubscribedToUser(jmsg.getUser().getUid(), jmsg.FriendsOnly);
+ jids = subscriptionService.getJIDSubscribedToUser(jmsg.getUser().getUid(), jmsg.FriendsOnly);
} else {
- List users = xmpp.subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid());
+ List users = subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid());
for (User user : users) {
- for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) {
+ for (String jid : userService.getJIDsbyUID(user.getUid())) {
jids.add(jid);
}
}
}
- com.juick.Message fullMsg = xmpp.messagesService.getMessage(jmsg.getMid());
+ com.juick.Message fullMsg = messagesService.getMessage(jmsg.getMid());
String txt = "@" + jmsg.getUser().getName() + ":" + fullMsg.getTagsString() + "\n";
String attachment = fullMsg.getAttachmentURL();
if (attachment != null) {
@@ -269,7 +297,7 @@ public class XMPPConnection implements AutoCloseable {
Nickname nick = new Nickname("@" + jmsg.getUser().getName());
Message msg = new Message();
- msg.setFrom(xmpp.getJid());
+ msg.setFrom(bot.getJid());
msg.setBody(txt);
msg.setType(Message.Type.CHAT);
msg.setThread("juick-" + jmsg.getMid());
@@ -285,7 +313,7 @@ public class XMPPConnection implements AutoCloseable {
}
for (String jid : jids) {
msg.setTo(Jid.of(jid));
- route(msg.getTo().getDomain(), ClientMessage.from(msg));
+ route(ClientMessage.from(msg));
}
}
@@ -294,11 +322,11 @@ public class XMPPConnection implements AutoCloseable {
String replyQuote;
String replyTo;
- users = xmpp.subscriptionService.getUsersSubscribedToComments(jmsg.getMid(), jmsg.getUser().getUid());
- com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? xmpp.messagesService.getReply(jmsg.getMid(), jmsg.getReplyto())
- : xmpp.messagesService.getMessage(jmsg.getMid());
+ users = subscriptionService.getUsersSubscribedToComments(jmsg.getMid(), jmsg.getUser().getUid());
+ com.juick.Message replyMessage = jmsg.getReplyto() > 0 ? messagesService.getReply(jmsg.getMid(), jmsg.getReplyto())
+ : messagesService.getMessage(jmsg.getMid());
replyTo = replyMessage.getUser().getName();
- com.juick.Message fullReply = xmpp.messagesService.getReply(jmsg.getMid(), jmsg.getRid());
+ com.juick.Message fullReply = messagesService.getReply(jmsg.getMid(), jmsg.getRid());
replyQuote = fullReply.getReplyQuote();
String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " ";
@@ -309,22 +337,22 @@ public class XMPPConnection implements AutoCloseable {
txt += jmsg.getText() + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/" + jmsg.getMid() + "#" + jmsg.getRid();
Message msg = new Message();
- msg.setFrom(xmpp.getJid());
+ msg.setFrom(bot.getJid());
msg.setBody(txt);
msg.setType(Message.Type.CHAT);
msg.addExtension(jmsg);
for (User user : users) {
- for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) {
+ for (String jid : userService.getJIDsbyUID(user.getUid())) {
msg.setTo(Jid.of(jid));
- route(msg.getTo().getDomain(), ClientMessage.from(msg));
+ route(ClientMessage.from(msg));
}
}
}
public void sendJuickRecommendation(com.juick.Message recomm) {
List users;
- com.juick.Message jmsg = xmpp.messagesService.getMessage(recomm.getMid());
- users = xmpp.subscriptionService.getUsersSubscribedToUserRecommendations(recomm.getUser().getUid(),
+ com.juick.Message jmsg = messagesService.getMessage(recomm.getMid());
+ users = subscriptionService.getUsersSubscribedToUserRecommendations(recomm.getUser().getUid(),
recomm.getMid(), jmsg.getUser().getUid());
String txt = "Recommended by @" + recomm.getUser().getName() + ":\n";
@@ -347,7 +375,7 @@ public class XMPPConnection implements AutoCloseable {
Nickname nick = new Nickname("@" + jmsg.getUser().getName());
Message msg = new Message();
- msg.setFrom(xmpp.getJid());
+ msg.setFrom(bot.getJid());
msg.setBody(txt);
msg.setType(Message.Type.CHAT);
msg.setThread("juick-" + jmsg.getMid());
@@ -363,9 +391,9 @@ public class XMPPConnection implements AutoCloseable {
}
for (User user : users) {
- for (String jid : xmpp.userService.getJIDsbyUID(user.getUid())) {
+ for (String jid : userService.getJIDsbyUID(user.getUid())) {
msg.setTo(Jid.of(jid));
- route(msg.getTo().getDomain(), ClientMessage.from(msg));
+ route(ClientMessage.from(msg));
}
}
}
diff --git a/juick-xmpp/src/main/java/com/juick/components/XMPPServer.java b/juick-xmpp/src/main/java/com/juick/components/XMPPServer.java
index e1ca72ad..1df7d575 100644
--- a/juick-xmpp/src/main/java/com/juick/components/XMPPServer.java
+++ b/juick-xmpp/src/main/java/com/juick/components/XMPPServer.java
@@ -18,20 +18,15 @@
package com.juick.components;
import com.juick.components.s2s.*;
-import com.juick.service.*;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.core.env.Environment;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
import org.xmlpull.v1.XmlPullParserException;
-import rocks.xmpp.addr.Jid;
-import rocks.xmpp.core.session.Extension;
-import rocks.xmpp.core.session.XmppSessionConfiguration;
-import rocks.xmpp.core.session.debug.LogbackDebugger;
import rocks.xmpp.core.stanza.model.Stanza;
import rocks.xmpp.util.XmppUtils;
+import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
@@ -40,180 +35,126 @@ import java.io.IOException;
import java.io.StringWriter;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
-import java.util.*;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
/**
* @author ugnich
*/
+@Component
public class XMPPServer implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(XMPPServer.class);
+ @Inject
public ExecutorService service;
-
+ @Value("${hostname}")
public String HOSTNAME;
+ @Value("${s2s_port:5269}")
+ private int s2sPort;
+ @Value("${keystore}")
public String keystore;
+ @Value("${keystore_password}")
public String keystorePassword;
- public List brokenSSLhosts;
- public List bannedHosts;
+ @Value("${broken_ssl_hosts}")
+ public String[] brokenSSLhosts;
+ @Value("${banned_hosts}")
+ public String[] bannedHosts;
- private final List inConnections = Collections.synchronizedList(new ArrayList<>());
- private final List outConnections = Collections.synchronizedList(new ArrayList<>());
- private final List outCache = Collections.synchronizedList(new ArrayList<>());
- private final List stanzaListeners = Collections.synchronizedList(new ArrayList<>());
+ private final List inConnections = new CopyOnWriteArrayList<>();
+ private final List outConnections = new CopyOnWriteArrayList<>();
+ private final List outCache = new CopyOnWriteArrayList<>();
+ private final List stanzaListeners = new CopyOnWriteArrayList<>();
- @Inject
- private XMPPConnection router;
- @Inject
- public MessagesService messagesService;
- @Inject
- public UserService userService;
- @Inject
- public TagService tagService;
- @Inject
- public PMQueriesService pmQueriesService;
- @Inject
- public SubscriptionService subscriptionService;
- @Inject
- public ShowQueriesService showQueriesService;
-
- private Jid jid;
-
private ServerSocket listener;
+ @Inject
private BasicXmppSession session;
- public XMPPServer(Environment env, ExecutorService service) {
- this.service = service;
- XmppSessionConfiguration configuration = XmppSessionConfiguration.builder()
- .extensions(Extension.of(com.juick.Message.class))
- .debugger(LogbackDebugger.class)
- .build();
+ @PostConstruct
+ public void init() {
logger.info("component initialized");
- try {
- HOSTNAME = env.getProperty("hostname");
- session = BasicXmppSession.create(HOSTNAME, configuration);
- int s2sPort = NumberUtils.toInt(env.getProperty("s2s_port"), 5269);
- keystore = env.getProperty("keystore");
- keystorePassword = env.getProperty("keystore_password");
- brokenSSLhosts = Arrays.asList(env.getProperty("broken_ssl_hosts", StringUtils.EMPTY).split(","));
- bannedHosts = Arrays.asList(env.getProperty("banned_hosts", StringUtils.EMPTY).split(","));
- jid = Jid.of(env.getProperty("xmppbot_jid"));
-
- service.submit(() -> {
- try {
- listener = new ServerSocket(s2sPort);
- logger.info("s2s listener ready");
- while (true) {
- if (Thread.currentThread().isInterrupted()) break;
- Socket socket = listener.accept();
- ConnectionIn client = new ConnectionIn(this, socket);
- addConnectionIn(client);
- service.submit(client);
- }
- } catch (IOException e) {
- logger.warn("io exception", e);
- Thread.currentThread().interrupt();
- } catch (Exception ex) {
- logger.warn("s2s error", ex);
+ service.submit(() -> {
+ try {
+ listener = new ServerSocket(s2sPort);
+ logger.info("s2s listener ready");
+ while (!listener.isClosed()) {
+ if (Thread.currentThread().isInterrupted()) break;
+ Socket socket = listener.accept();
+ ConnectionIn client = new ConnectionIn(this, socket);
+ addConnectionIn(client);
+ service.submit(client);
}
- logger.info("s2s interrupted");
- });
-
- } catch (Exception e) {
- logger.error("XMPPComponent error", e);
- }
+ } catch (SocketException e) {
+ // shutdown
+ } catch (IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException | XmlPullParserException | KeyManagementException e) {
+ logger.warn("xmpp exception", e);
+ }
+ });
}
@Override
public void close() throws Exception {
- synchronized (getOutConnections()) {
- for (Iterator i = getOutConnections().iterator(); i.hasNext(); ) {
- ConnectionOut c = i.next();
- c.closeConnection();
- i.remove();
- }
- }
-
- synchronized (getInConnections()) {
- for (Iterator i = getInConnections().iterator(); i.hasNext(); ) {
- ConnectionIn c = i.next();
- c.closeConnection();
- i.remove();
- }
+ if (!listener.isClosed()) {
+ listener.close();
}
+ outConnections.forEach(c -> {
+ c.closeConnection();
+ outConnections.remove(c);
+ });
+ inConnections.forEach(c -> {
+ c.closeConnection();
+ inConnections.remove(c);
+ });
if (!listener.isClosed()) {
listener.close();
}
+ service.shutdown();
logger.info("XMPP server destroyed");
}
public void addConnectionIn(ConnectionIn c) {
- synchronized (getInConnections()) {
- getInConnections().add(c);
- }
+ inConnections.add(c);
}
public void addConnectionOut(ConnectionOut c) {
- synchronized (getOutConnections()) {
- getOutConnections().add(c);
- }
+ outConnections.add(c);
}
public void removeConnectionIn(ConnectionIn c) {
- synchronized (getInConnections()) {
- getInConnections().remove(c);
- }
+ inConnections.remove(c);
}
public void removeConnectionOut(ConnectionOut c) {
- synchronized (getOutConnections()) {
- getOutConnections().remove(c);
- }
+ outConnections.remove(c);
}
public String getFromCache(String hostname) {
- CacheEntry ret = null;
- synchronized (getOutCache()) {
- for (Iterator i = getOutCache().iterator(); i.hasNext(); ) {
- CacheEntry c = i.next();
- if (c.hostname != null && c.hostname.equals(hostname)) {
- ret = c;
- i.remove();
- break;
- }
- }
- }
- return (ret != null) ? ret.xml : null;
+ final String[] cache = new String[1];
+ outCache.stream().filter(c -> c.hostname != null && c.hostname.equals(hostname)).findFirst().ifPresent(c -> {
+ cache[0] = c.xml;
+ outCache.remove(c);
+ });
+ return cache[0];
}
- public ConnectionOut getConnectionOut(String hostname, boolean needReady) {
- synchronized (getOutConnections()) {
- for (ConnectionOut c : getOutConnections()) {
- if (c.to != null && c.to.equals(hostname) && (!needReady || c.streamReady)) {
- return c;
- }
- }
- }
- return null;
+ public Optional getConnectionOut(String hostname, boolean needReady) {
+ return outConnections.stream().filter(c -> c.to != null &&
+ c.to.equals(hostname) && (!needReady || c.streamReady)).findFirst();
}
- public ConnectionIn getConnectionIn(String streamID) {
- synchronized (getInConnections()) {
- for (ConnectionIn c : getInConnections()) {
- if (c.streamID != null && c.streamID.equals(streamID)) {
- return c;
- }
- }
- }
- return null;
+ public Optional getConnectionIn(String streamID) {
+ return inConnections.stream().filter(c -> c.streamID != null && c.streamID.equals(streamID)).findFirst();
}
public void sendOut(Stanza s) {
@@ -236,16 +177,14 @@ public class XMPPServer implements AutoCloseable {
boolean haveAnyConn = false;
ConnectionOut connOut = null;
- synchronized (getOutConnections()) {
- for (ConnectionOut c : getOutConnections()) {
- if (c.to != null && c.to.equals(hostname)) {
- if (c.streamReady) {
- connOut = c;
- break;
- } else {
- haveAnyConn = true;
- break;
- }
+ for (ConnectionOut c : outConnections) {
+ if (c.to != null && c.to.equals(hostname)) {
+ if (c.streamReady) {
+ connOut = c;
+ break;
+ } else {
+ haveAnyConn = true;
+ break;
}
}
}
@@ -255,23 +194,22 @@ public class XMPPServer implements AutoCloseable {
}
boolean haveCache = false;
- synchronized (getOutCache()) {
- for (CacheEntry c : getOutCache()) {
- if (c.hostname != null && c.hostname.equals(hostname)) {
- c.xml += xml;
- c.tsUpdated = System.currentTimeMillis();
- haveCache = true;
- break;
- }
- }
- if (!haveCache) {
- getOutCache().add(new CacheEntry(hostname, xml));
+ for (CacheEntry c : outCache) {
+ if (c.hostname != null && c.hostname.equals(hostname)) {
+ c.xml += xml;
+ c.tsUpdated = System.currentTimeMillis();
+ haveCache = true;
+ break;
}
}
+ if (!haveCache) {
+ outCache.add(new CacheEntry(hostname, xml));
+ }
if (!haveAnyConn) {
try {
ConnectionOut connectionOut = new ConnectionOut(this, hostname);
+ addConnectionOut(connectionOut);
service.submit(connectionOut);
} catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | XmlPullParserException | KeyStoreException | KeyManagementException | IOException e) {
logger.error("s2s out error", e);
@@ -279,55 +217,34 @@ public class XMPPServer implements AutoCloseable {
}
}
- public XMPPConnection getRouter() {
- return router;
- }
-
- public List getInConnections() {
- return inConnections;
- }
-
- public List getOutConnections() {
- return outConnections;
- }
-
- public List getOutCache() {
- return outCache;
- }
-
public void startDialback(String from, String streamId, String dbKey) throws Exception {
- ConnectionOut c = getConnectionOut(from, false);
- if (c != null) {
- c.sendDialbackVerify(streamId, dbKey);
+ Optional c = getConnectionOut(from, false);
+ if (c.isPresent()) {
+ c.get().sendDialbackVerify(streamId, dbKey);
} else {
- c = new ConnectionOut(this, from, streamId, dbKey);
- service.submit(c);
+ ConnectionOut newConnection = new ConnectionOut(this, from, streamId, dbKey);
+ addConnectionOut(newConnection);
+ service.submit(newConnection);
}
}
- public List getStanzaListeners() {
- return stanzaListeners;
- }
-
public void addStanzaListener(StanzaListener listener) {
- synchronized (stanzaListeners) {
- stanzaListeners.add(listener);
- }
+ stanzaListeners.add(listener);
}
public void onStanzaReceived(Stanza xmlValue) {
stanzaListeners.forEach(l -> l.stanzaReceived(xmlValue));
}
- public Jid getJid() {
- return jid;
- }
-
public BasicXmppSession getSession() {
return session;
}
- public void setSession(BasicXmppSession session) {
- this.session = session;
+ public List getInConnections() {
+ return inConnections;
+ }
+
+ public List getOutConnections() {
+ return outConnections;
}
}
diff --git a/juick-xmpp/src/main/java/com/juick/components/configuration/XmppAppConfiguration.java b/juick-xmpp/src/main/java/com/juick/components/configuration/XmppAppConfiguration.java
index 92506cbd..02b1556d 100644
--- a/juick-xmpp/src/main/java/com/juick/components/configuration/XmppAppConfiguration.java
+++ b/juick-xmpp/src/main/java/com/juick/components/configuration/XmppAppConfiguration.java
@@ -21,50 +21,41 @@ package com.juick.components.configuration;
* Created by aalexeev on 11/12/16.
*/
-import com.juick.components.XMPPConnection;
-import com.juick.components.XMPPServer;
-import com.juick.components.s2s.CleaningUp;
-import com.juick.components.s2s.JuickBot;
+import com.juick.components.s2s.BasicXmppSession;
import com.juick.server.configuration.BaseWebConfiguration;
-import org.apache.commons.lang3.BooleanUtils;
-import org.springframework.context.annotation.*;
-import org.springframework.core.env.Environment;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import rocks.xmpp.core.session.Extension;
+import rocks.xmpp.core.session.XmppSessionConfiguration;
+import rocks.xmpp.core.session.debug.LogbackDebugger;
-import javax.inject.Inject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
-@ComponentScan(basePackages = {"com.juick.components.controllers"})
+@ComponentScan(basePackages = {"com.juick.components"})
@PropertySource("classpath:juick.conf")
@EnableScheduling
@EnableWebMvc
public class XmppAppConfiguration extends BaseWebConfiguration {
- @Inject
- private Environment env;
-
- @Bean
- public XMPPServer xmpp() {
- return new XMPPServer(env, service());
- }
+ @Value("${hostname}")
+ private String hostname;
@Bean
public ExecutorService service() {
return Executors.newCachedThreadPool();
}
@Bean
- public CleaningUp cleaningUp() {
- return new CleaningUp();
- }
- @Bean
- public JuickBot bot() {
- return new JuickBot(xmpp());
- }
- @Bean
- public XMPPConnection router() {
- boolean disabled = BooleanUtils.toBoolean(env.getProperty("xmpp_disabled", "false"));
- return disabled ? null : new XMPPConnection();
+ public BasicXmppSession session() {
+ XmppSessionConfiguration configuration = XmppSessionConfiguration.builder()
+ .extensions(Extension.of(com.juick.Message.class))
+ .debugger(LogbackDebugger.class)
+ .build();
+ return BasicXmppSession.create(hostname, configuration);
}
}
diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java b/juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java
deleted file mode 100644
index 45933141..00000000
--- a/juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.juick.components.s2s;
-
-import com.juick.components.XMPPServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.annotation.Scheduled;
-
-import javax.inject.Inject;
-import java.util.Iterator;
-
-/**
- *
- * @author ugnich
- */
-public class CleaningUp {
-
- private static final Logger logger = LoggerFactory.getLogger(CleaningUp.class);
-
- @Inject
- XMPPServer xmpp;
-
- @Scheduled(fixedDelay = 10000)
- public void cleanUp() {
- long now = System.currentTimeMillis();
-
- synchronized (xmpp.getOutConnections()) {
- for (Iterator i = xmpp.getOutConnections().iterator(); i.hasNext(); ) {
- ConnectionOut c = i.next();
- int inactive = (int) ((double) (now - c.tsLocalData) / 1000.0);
- if (inactive > 900) {
- logger.info("closing idle outgoing connection to {}", c.to);
- c.closeConnection();
- i.remove();
- }
- }
- }
-
- synchronized (xmpp.getInConnections()) {
- for (Iterator i = xmpp.getInConnections().iterator(); i.hasNext(); ) {
- ConnectionIn c = i.next();
- int inactive = (int) ((double) (now - c.tsRemoteData) / 1000.0);
- if (inactive > 900) {
- logger.info("closing idle incoming connection from {}", c.from);
- c.closeConnection();
- i.remove();
- }
- }
- }
- }
-}
diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java b/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java
index 693b278e..7fd036eb 100644
--- a/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java
+++ b/juick-xmpp/src/main/java/com/juick/components/s2s/Connection.java
@@ -33,6 +33,7 @@ import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
+import java.util.UUID;
/**
*
@@ -74,7 +75,6 @@ public class Connection {
public Connection(XMPPServer xmpp) throws XmlPullParserException, KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, KeyManagementException {
this.xmpp = xmpp;
tsCreated = System.currentTimeMillis();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream ksIs = new FileInputStream(xmpp.keystore)) {
ks.load(ksIs, xmpp.keystorePassword.toCharArray());
@@ -146,6 +146,7 @@ public class Connection {
}
public void restartParser() throws XmlPullParserException, IOException {
+ streamID = UUID.randomUUID().toString();
parser = factory.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(new InputStreamReader(socket.getInputStream()));
diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionIn.java b/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionIn.java
index eed72a18..e6f404ef 100644
--- a/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionIn.java
+++ b/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionIn.java
@@ -22,8 +22,8 @@ import com.juick.xmpp.extensions.StreamError;
import com.juick.xmpp.utils.XmlUtils;
import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import rocks.xmpp.addr.Jid;
-import rocks.xmpp.core.session.XmppSessionConfiguration;
import rocks.xmpp.core.stanza.model.Stanza;
import javax.net.ssl.SSLException;
@@ -35,7 +35,13 @@ import java.io.IOException;
import java.io.StringReader;
import java.net.Socket;
import java.net.SocketException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@@ -47,9 +53,8 @@ public class ConnectionIn extends Connection implements Runnable {
final public List from = new ArrayList<>();
public long tsRemoteData = 0;
public long packetsRemote = 0;
- XmppSessionConfiguration configuration;
- public ConnectionIn(XMPPServer xmpp, Socket socket) throws Exception {
+ public ConnectionIn(XMPPServer xmpp, Socket socket) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, XmlPullParserException, KeyManagementException, KeyStoreException, IOException {
super(xmpp);
this.socket = socket;
restartParser();
@@ -83,7 +88,7 @@ public class ConnectionIn extends Connection implements Runnable {
boolean xmppversionnew = parser.getAttributeValue(null, "version") != null;
String from = parser.getAttributeValue(null, "from");
- if (xmpp.bannedHosts.contains(from)) {
+ if (Arrays.asList(xmpp.bannedHosts).contains(from)) {
closeConnection();
return;
}
@@ -121,17 +126,14 @@ public class ConnectionIn extends Connection implements Runnable {
String vid = parser.getAttributeValue(null, "id");
String vkey = XmlUtils.getTagText(parser);
updateTsRemoteData();
- boolean valid = false;
+ final boolean[] valid = {false};
if (vfrom != null && vto != null && vid != null && vkey != null) {
- ConnectionOut c = xmpp.getConnectionOut(vfrom, false);
- if (c == null) {
- logger.warn("outgoing connection to {} not found", vfrom);
- } else {
+ xmpp.getConnectionOut(vfrom, false).ifPresent(c -> {
String dialbackKey = c.dbKey;
- valid = vkey.equals(dialbackKey);
- }
+ valid[0] = vkey.equals(dialbackKey);
+ });
}
- if (valid) {
+ if (valid[0]) {
sendStanza("");
logger.info("stream from {} {} dialback verify valid", vfrom, streamID);
} else {
@@ -154,7 +156,7 @@ public class ConnectionIn extends Connection implements Runnable {
String xml = XmlUtils.parseToString(parser, false);
if (type == null || !type.equals("error")) {
logger.info("stream {} iq: {}", streamID, xml);
- xmpp.getRouter().sendStanza(parse(xml));
+ xmpp.onStanzaReceived(parse(xml));
}
} else if (sc != null && !isSecured() && tag.equals("starttls")) {
logger.info("stream {} securing", streamID);
@@ -209,7 +211,7 @@ public class ConnectionIn extends Connection implements Runnable {
xmpp.HOSTNAME + "' id='" + streamID + "' version='1.0'>";
if (xmppversionnew) {
openStream += "";
- if (sc != null && !isSecured() && !xmpp.brokenSSLhosts.contains(from)) {
+ if (sc != null && !isSecured() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) {
openStream += "";
}
openStream += "";
diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionOut.java b/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionOut.java
index 903eec0c..589ed18a 100644
--- a/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionOut.java
+++ b/juick-xmpp/src/main/java/com/juick/components/s2s/ConnectionOut.java
@@ -38,6 +38,7 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
+import java.util.Arrays;
import java.util.UUID;
/**
@@ -97,7 +98,6 @@ public class ConnectionOut extends Connection implements Runnable {
}
logger.info("stream to {} {} open", to, streamID);
- xmpp.addConnectionOut(ConnectionOut.this);
boolean xmppversionnew = parser.getAttributeValue(null, "version") != null;
if (!xmppversionnew) {
processDialback();
@@ -131,15 +131,12 @@ public class ConnectionOut extends Connection implements Runnable {
String type = parser.getAttributeValue(null, "type");
String sid = parser.getAttributeValue(null, "id");
if (from != null && from.equals(to) && sid != null && !sid.isEmpty() && type != null) {
- ConnectionIn c = xmpp.getConnectionIn(sid);
- if (c != null) {
- c.sendDialbackResult(from, type);
- }
+ xmpp.getConnectionIn(sid).ifPresent(c -> c.sendDialbackResult(from, type));
}
XmlUtils.skip(parser);
} else if (tag.equals("features") && parser.getNamespace().equals(NS_STREAM)) {
StreamFeatures features = StreamFeatures.parse(parser);
- if (sc != null && !isSecured() && features.STARTTLS >= 0 && !xmpp.brokenSSLhosts.contains(to)) {
+ if (sc != null && !isSecured() && features.STARTTLS >= 0 && !Arrays.asList(xmpp.brokenSSLhosts).contains(to)) {
logger.info("stream to {} {} securing", to, streamID);
sendStanza("");
} else {
@@ -191,10 +188,4 @@ public class ConnectionOut extends Connection implements Runnable {
sendStanza("" +
key + "");
}
-
- @Override
- public void restartParser() throws XmlPullParserException, IOException {
- super.restartParser();
- streamID = UUID.randomUUID().toString();
- }
}
diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/JuickBot.java b/juick-xmpp/src/main/java/com/juick/components/s2s/JuickBot.java
deleted file mode 100644
index 2a7e02e9..00000000
--- a/juick-xmpp/src/main/java/com/juick/components/s2s/JuickBot.java
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.juick.components.s2s;
-
-import com.juick.User;
-import com.juick.components.XMPPServer;
-import com.juick.formatters.PlainTextFormatter;
-import org.apache.commons.lang3.StringUtils;
-import org.ocpsoft.prettytime.PrettyTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import rocks.xmpp.addr.Jid;
-import rocks.xmpp.core.stanza.model.Message;
-import rocks.xmpp.core.stanza.model.Presence;
-import rocks.xmpp.core.stanza.model.Stanza;
-import rocks.xmpp.core.stanza.model.StanzaError;
-import rocks.xmpp.core.stanza.model.client.ClientMessage;
-import rocks.xmpp.core.stanza.model.client.ClientPresence;
-import rocks.xmpp.core.stanza.model.errors.Condition;
-
-import javax.inject.Inject;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- *
- * @author ugnich
- */
-public class JuickBot implements StanzaListener, AutoCloseable {
-
- private static final Logger logger = LoggerFactory.getLogger(JuickBot.class);
-
- XMPPServer xmpp;
- PrettyTime pt;
-
- @Inject
- public JuickBot(XMPPServer xmpp) {
- this.xmpp = xmpp;
- xmpp.addStanzaListener(this);
- broadcastPresence(null);
- pt = new PrettyTime(new Locale("ru"));
- }
-
- private static final String HELPTEXT =
- "@username text - Send private message\n"
- + "*tagname Blah-blah-blah - Post a message with tag 'tagname'\n"
- + "#1234 Blah-blah-blah - Answer to message #1234\n"
- + "#1234/5 Blah - Answer to reply #1234/5\n"
- + "! #1234 - Recommend post\n"
- + "\n"
- + "# - Show last messages from your feed (## - second page, ...)\n"
- + "@ - Show recomendations and popular personal blogs\n"
- + "* - Show your tags\n"
- + "#1234 - Show message\n"
- + "#1234+ - Show message with replies\n"
- + "@username - Show user's info\n"
- + "@username+ - Show user's info and last 10 messages\n"
- + "@username *tag - User's messages with this tag\n"
- + "*tag - Show last 10 messages with this tag\n"
- + "? blah - Search posts for 'blah'\n"
- + "? @username blah - Searching among user\'s posts for 'blah'\n"
- + "D #123 - Delete message\n"
- + "D #123/45 - Delete reply\n"
- + "DL - Delete last message/reply\n"
- + "S - Show your subscriptions\n"
- + "S #123 - Subscribe to message replies\n"
- + "S @username - Subscribe to user's blog\n"
- + "U #123 - Unsubscribe from comments\n"
- + "U @username - Unsubscribe from user's blog\n"
- + "BL - Show your blacklist\n"
- + "BL @username - Add/delete user to/from your blacklist\n"
- + "BL *tag - Add/delete tag to/from your blacklist\n"
- + "ON / OFF - Enable/disable subscriptions delivery\n"
- + "PING - Pong\n"
- + "\n"
- + "Read more: http://juick.com/help/";
-
- public boolean incomingPresence(Presence p) {
- final String username = p.getTo().getLocal();
- final boolean toJuick = username.equals(xmpp.getJid().getLocal());
-
- if (p.getType() == null) {
- Presence reply = new Presence();
- reply.setFrom(p.getTo().asBareJid());
- reply.setTo(p.getFrom().asBareJid());
- reply.setType(Presence.Type.UNSUBSCRIBE);
- xmpp.sendOut(ClientPresence.from(reply));
- return true;
- } else if (p.getType().equals(Presence.Type.PROBE)) {
- int uid_to = 0;
- if (!toJuick) {
- uid_to = xmpp.userService.getUIDbyName(username);
- }
-
- if (toJuick || uid_to > 0) {
- Presence reply = new Presence();
- reply.setFrom(p.getTo().withResource(xmpp.getJid().getResource()));
- reply.setTo(p.getFrom());
- reply.setPriority((byte)10);
- xmpp.sendOut(ClientPresence.from(reply));
- } else {
- Presence reply = new Presence();
- reply.setFrom(p.getTo());
- reply.setTo(p.getFrom());
- reply.setType(Presence.Type.ERROR);
- reply.setId(p.getId());
- reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
- xmpp.sendOut(ClientPresence.from(reply));
- return true;
- }
- return true;
- } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) {
- boolean canSubscribe = false;
- if (toJuick) {
- canSubscribe = true;
- } else {
- int uid_to = xmpp.userService.getUIDbyName(username);
- if (uid_to > 0) {
- xmpp.pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString());
- canSubscribe = true;
- }
- }
-
- if (canSubscribe) {
- Presence reply = new Presence();
- reply.setFrom(p.getTo());
- reply.setTo(p.getFrom());
- reply.setType(Presence.Type.SUBSCRIBED);
- xmpp.sendOut(ClientPresence.from(reply));
-
- reply.setFrom(reply.getFrom().withResource(xmpp.getJid().getResource()));
- reply.setPriority((byte) 10);
- reply.setType(null);
- xmpp.sendOut(ClientPresence.from(reply));
-
- return true;
- } else {
- Presence reply = new Presence();
- reply.setFrom(p.getTo());
- reply.setTo(p.getFrom());
- reply.setType(Presence.Type.ERROR);
- reply.setId(p.getId());
- reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
- xmpp.sendOut(ClientPresence.from(reply));
- return true;
- }
- } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) {
- if (!toJuick) {
- int uid_to = xmpp.userService.getUIDbyName(username);
- if (uid_to > 0) {
- xmpp.pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString());
- }
- }
-
- Presence reply = new Presence();
- reply.setFrom(p.getTo());
- reply.setTo(p.getFrom());
- reply.setType(Presence.Type.UNSUBSCRIBED);
- xmpp.sendOut(ClientPresence.from(reply));
- }
-
- return false;
- }
-
- public boolean incomingMessage(Message msg) {
- if (StringUtils.isBlank(msg.getBody()) || (msg.getType() != null && msg.getType().equals(Message.Type.ERROR))) {
- return false;
- }
-
- String username = msg.getTo().getLocal();
-
- User user_from;
- String signuphash = StringUtils.EMPTY;
- user_from = xmpp.userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString());
- if (user_from == null) {
- signuphash = xmpp.userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString());
- }
-
- if (user_from == null) {
- Message reply = new Message();
- reply.setFrom(msg.getTo());
- reply.setTo(msg.getFrom());
- reply.setType(Message.Type.CHAT);
- if (username.equals(xmpp.getJid().getLocal())) {
- reply.setBody("Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account.");
- } else {
- reply.setBody("Внимание, системное сообщение!\nВаш JabberID не обнаружен в списке доверенных. Для того, чтобы отправить сообщение пользователю " + username + "@juick.com, пожалуйста зарегистрируйте свой JabberID в системе: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nWarning, system message!\nYour JabberID is not found in our server's white list. To send a message to " + username + "@juick.com, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account.");
- }
- xmpp.sendOut(ClientMessage.from(reply));
- return true;
- }
-
- if (username.equals(xmpp.getJid().getLocal())) {
- return incomingMessageJuick(user_from, msg);
- }
-
- int uid_to = xmpp.userService.getUIDbyName(username);
-
- if (uid_to == 0) {
- Message reply = new Message();
- reply.setFrom(msg.getTo());
- reply.setTo(msg.getFrom());
- reply.setType(Message.Type.ERROR);
- reply.setId(msg.getId());
- reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
- xmpp.sendOut(ClientMessage.from(reply));
- return true;
- }
-
- boolean success = false;
- if (!xmpp.userService.isInBLAny(uid_to, user_from.getUid())) {
- success = xmpp.pmQueriesService.createPM(user_from.getUid(), uid_to, msg.getBody());
- }
-
- if (success) {
- Message m = new Message();
- m.setFrom(xmpp.getJid().asBareJid());
- m.setTo(Jid.of(Integer.toString(uid_to), "push.juick.com", null));
- com.juick.Message jmsg = new com.juick.Message();
- jmsg.setUser(user_from);
- jmsg.setText(msg.getBody());
- m.addExtension(jmsg);
- xmpp.getRouter().sendStanza(m);
-
- m.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null));
- xmpp.getRouter().sendStanza(m);
-
- List jids;
- boolean inroster = false;
- jids = xmpp.userService.getJIDsbyUID(uid_to);
- for (String jid : jids) {
- Message mm = new Message();
- mm.setTo(Jid.of(jid));
- mm.setType(Message.Type.CHAT);
- inroster = xmpp.pmQueriesService.havePMinRoster(user_from.getUid(), jid);
- if (inroster) {
- mm.setFrom(Jid.of(jmsg.getUser().getName(), "juick.com", "Juick"));
- mm.setBody(msg.getBody());
- } else {
- mm.setFrom(xmpp.getJid());
- mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + msg.getBody());
- }
- xmpp.sendOut(ClientMessage.from(mm));
- }
- } else {
- Message reply = new Message();
- reply.setFrom(msg.getTo());
- reply.setTo(msg.getFrom());
- reply.setType(Message.Type.ERROR);
- reply.setId(msg.getId());
- reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.NOT_ALLOWED));
- xmpp.sendOut(ClientMessage.from(reply));
- }
-
- return false;
- }
- private static Pattern regexPM = Pattern.compile("^\\@(\\S+)\\s+([\\s\\S]+)$");
-
- public boolean incomingMessageJuick(User user_from, Message msg) {
- String command = msg.getBody().trim();
- int commandlen = command.length();
-
- // COMPATIBILITY
- if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) {
- command = command.substring(3).trim();
- commandlen = command.length();
- }
-
- if (commandlen == 4) {
- if (command.equalsIgnoreCase("PING")) {
- commandPing(msg);
- return true;
- } else if (command.equalsIgnoreCase("HELP")) {
- commandHelp(msg);
- return true;
- }
- } else if (commandlen == 5 && command.equalsIgnoreCase("LOGIN")) {
- commandLogin(msg, user_from);
- return true;
- } else if (command.charAt(0) == '@') {
- Matcher matchPM = regexPM.matcher(command);
- if (matchPM.find()) {
- String user_to = matchPM.group(1);
- String msgtxt = matchPM.group(2);
- commandPM(msg, user_from, user_to, msgtxt);
- return true;
- }
- } else if (commandlen == 2 && command.equalsIgnoreCase("BL")) {
- commandBLShow(msg, user_from);
- return true;
- } else if (commandlen == 2 && command.equalsIgnoreCase("#+")) {
- return commandLast(msg.getFrom());
- } else if (command.equalsIgnoreCase("@")) {
- return commandUsers(msg.getFrom());
- }
-
- return false;
- }
-
- private void commandPing(Message m) {
- Presence p = new Presence(m.getFrom());
- p.setFrom(xmpp.getJid());
- p.setPriority((byte) 10);
- xmpp.sendOut(ClientPresence.from(p));
-
- sendReply(m.getFrom(), "PONG");
- }
-
- private void commandHelp(Message m) {
- sendReply(m.getFrom(), HELPTEXT);
- }
-
- private void commandLogin(Message m, User user_from) {
- sendReply(m.getFrom(), "http://juick.com/login?hash=" + xmpp.userService.getHashByUID(user_from.getUid()));
- }
-
- private void commandPM(Message m, User user_from, String user_to, String body) {
- int ret = 0;
-
- int uid_to = 0;
- List jids_to = null;
- boolean haveInRoster = false;
-
- if (user_to.indexOf('@') > 0) {
- uid_to = xmpp.userService.getUIDbyJID(user_to);
- } else {
- uid_to = xmpp.userService.getUIDbyName(user_to);
- }
-
- if (uid_to > 0) {
- if (!xmpp.userService.isInBLAny(uid_to, user_from.getUid())) {
- if (xmpp.pmQueriesService.createPM(user_from.getUid(), uid_to, body)) {
- jids_to = xmpp.userService.getJIDsbyUID(uid_to);
- ret = 200;
- } else {
- ret = 500;
- }
- } else {
- ret = 403;
- }
- } else {
- ret = 404;
- }
-
- if (ret == 200) {
- Message msg = new Message();
- msg.setFrom(xmpp.getJid().asBareJid());
- msg.setTo(Jid.of(Integer.toString(uid_to), "push.juick.com", null));
- com.juick.Message jmsg = new com.juick.Message();
- jmsg.setUser(user_from);
- jmsg.setText(body);
- msg.addExtension(jmsg);
- xmpp.getRouter().sendStanza(msg);
-
- msg.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null));
- xmpp.getRouter().sendStanza(msg);
-
- for (String jid : jids_to) {
- Message mm = new Message();
- mm.setTo(Jid.of(jid));
- mm.setType(Message.Type.CHAT);
- haveInRoster = xmpp.pmQueriesService.havePMinRoster(user_from.getUid(), jid);
- if (haveInRoster) {
- mm.setFrom(Jid.of(user_from.getName(), "juick.com", "Juick"));
- mm.setBody(body);
- } else {
- mm.setFrom(xmpp.getJid());
- mm.setBody("Private message from @" + user_from.getName() + ":\n" + body);
- }
- xmpp.sendOut(ClientMessage.from(mm));
- }
- }
-
- Message reply = new Message();
- reply.setFrom(m.getTo());
- reply.setTo(m.getFrom());
- if (ret == 200) {
- reply.setType(m.getType());
- reply.setBody("Private message sent");
- } else {
- reply.setType(Message.Type.ERROR);
- reply.setBody("Error " + ret);
- }
- xmpp.sendOut(ClientMessage.from(reply));
- }
-
- private void commandBLShow(Message m, User user_from) {
- List blusers = xmpp.userService.getUserBLUsers(user_from.getUid());
- List bltags = xmpp.tagService.getUserBLTags(user_from.getUid());
-
- String txt = StringUtils.EMPTY;
- if (bltags.size() > 0) {
- for (String bltag : bltags) {
- txt += "*" + bltag + "\n";
- }
-
- if (blusers.size() > 0) {
- txt += "\n";
- }
- }
- if (blusers.size() > 0) {
- for (User bluser : blusers) {
- txt += "@" + bluser.getName() + "\n";
- }
- }
- if (txt.isEmpty()) {
- txt = "You don't have any users or tags in your blacklist.";
- }
-
- sendReply(m.getFrom(), txt);
- }
-
- boolean commandLast(Jid jidFrom) {
- User user = xmpp.userService.getUserByJID(jidFrom.asBareJid().toEscapedString());
- sendReply(jidFrom, "Last messages:\n"
- + printMessages(xmpp.messagesService.getAll(user.getUid(), 0), true));
- return true;
- }
-
- boolean commandUsers(Jid jidFrom) {
- StringBuilder msg = new StringBuilder();
- msg.append("Recommended blogs");
- User currentUser = xmpp.userService.getUserByJID(jidFrom.asBareJid().toEscapedString());
- List recommendedUsers = xmpp.showQueriesService.getRecommendedUsers(currentUser);
- if (recommendedUsers.size() > 0) {
- for (String user : recommendedUsers) {
- msg.append("\n@").append(user);
- }
- } else {
- msg.append("\nNo recommendations now. Subscribe to more blogs. ;)");
- }
- msg.append("\n\nTop 10 personal blogs:");
- List topUsers = xmpp.showQueriesService.getTopUsers();
- if (topUsers.size() > 0) {
- for (String user : topUsers) {
- msg.append("\n@").append(user);
- }
- } else {
- msg.append("\nNo top users. Empty DB? ;)");
- }
- sendReply(jidFrom, msg.toString());
- return true;
- }
-
- void sendReply(Jid jidTo, String txt) {
- Message reply = new Message();
- reply.setFrom(xmpp.getJid());
- reply.setTo(jidTo);
- reply.setType(Message.Type.CHAT);
- reply.setBody(txt);
- xmpp.sendOut(ClientMessage.from(reply));
- }
-
- @Override
- public void stanzaReceived(Stanza xmlValue) {
- if (xmlValue instanceof Presence) {
- Presence p = (Presence) xmlValue;
- if (p.getType() == null || !p.getType().equals(Presence.Type.ERROR)) {
- incomingPresence(p);
- }
- } else if (xmlValue instanceof Message) {
- Message msg = (Message) xmlValue;
- if (!incomingMessage(msg)) {
- xmpp.getRouter().sendStanza(msg);
- }
- }
- }
-
- String printMessages(List mids, boolean crop) {
- return xmpp.messagesService.getMessages(mids).stream()
- .sorted(Collections.reverseOrder())
- .map(PlainTextFormatter::formatPostSummary).collect(Collectors.joining("\n\n"));
- }
-
- void broadcastPresence(Presence.Type type) {
- Presence presence = new Presence();
- presence.setFrom(xmpp.getJid());
- if (type != null) {
- presence.setType(type);
- }
- xmpp.userService.getActiveJIDs().forEach(j -> {
- try {
- presence.setTo(Jid.of(j));
- xmpp.sendOut(ClientPresence.from(presence));
- } catch (IllegalArgumentException ex) {
- logger.warn("Invalid jid: {}", j, ex);
- }
- });
- }
-
- @Override
- public void close() throws Exception {
- broadcastPresence(Presence.Type.UNAVAILABLE);
- }
-}
diff --git a/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickMessage.java b/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickMessage.java
new file mode 100644
index 00000000..2e10606a
--- /dev/null
+++ b/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickMessage.java
@@ -0,0 +1,164 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.juick.xmpp.extensions;
+
+import com.juick.Tag;
+import com.juick.xmpp.StanzaChild;
+import com.juick.xmpp.utils.XmlUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class JuickMessage extends com.juick.Message implements StanzaChild {
+ public final static String XMLNS = "http://juick.com/message";
+ public final static String TagName = "juick";
+ private SimpleDateFormat df;
+ public JuickMessage() {
+ df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ @Override
+ public String getXMLNS() {
+ return XMLNS;
+ }
+ @Override
+ public JuickMessage parse(XmlPullParser parser) throws XmlPullParserException, IOException, ParseException {
+ JuickMessage jmsg = new JuickMessage();
+ final String sMID = parser.getAttributeValue(null, "mid");
+ if (sMID != null) {
+ jmsg.setMid(Integer.parseInt(sMID));
+ }
+ final String sRID = parser.getAttributeValue(null, "rid");
+ if (sRID != null) {
+ jmsg.setRid(Integer.parseInt(sRID));
+ }
+ final String sReplyTo = parser.getAttributeValue(null, "replyto");
+ if (sReplyTo != null) {
+ jmsg.setReplyto(Integer.parseInt(sReplyTo));
+ }
+ final String sPrivacy = parser.getAttributeValue(null, "privacy");
+ if (sPrivacy != null) {
+ jmsg.setPrivacy(Integer.parseInt(sPrivacy));
+ }
+ final String sFriendsOnly = parser.getAttributeValue(null, "friendsonly");
+ if (sFriendsOnly != null) {
+ jmsg.FriendsOnly = true;
+ }
+ final String sReadOnly = parser.getAttributeValue(null, "readonly");
+ if (sReadOnly != null) {
+ jmsg.ReadOnly = true;
+ }
+ jmsg.setTimestamp(df.parse(parser.getAttributeValue(null, "ts")).toInstant());
+ jmsg.setAttachmentType(parser.getAttributeValue(null, "attach"));
+ while (parser.next() == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ final String xmlns = parser.getNamespace();
+ if (tag.equals("body")) {
+ jmsg.setText(XmlUtils.getTagText(parser));
+ } else if (tag.equals(JuickUser.TagName) && xmlns != null && xmlns.equals(JuickUser.XMLNS)) {
+ jmsg.setUser(new JuickUser().parse(parser));
+ } else if (tag.equals("tag")) {
+ jmsg.getTags().add(new Tag(XmlUtils.getTagText(parser)));
+ } else {
+ XmlUtils.skip(parser);
+ }
+ }
+ return jmsg;
+ }
+ @Override
+ public String toString() {
+ String ret = "";
+ ret = "<" + TagName + " xmlns=\"" + XMLNS + "\"";
+ if (getMid() > 0) {
+ ret += " mid=\"" + getMid() + "\"";
+ }
+ if (getRid() > 0) {
+ ret += " rid=\"" + getRid() + "\"";
+ }
+ if (getReplyto() > 0) {
+ ret += " replyto=\"" + getReplyto() + "\"";
+ }
+ ret += " privacy=\"" + getPrivacy() + "\"";
+ if (FriendsOnly) {
+ ret += " friendsonly=\"1\"";
+ }
+ if (ReadOnly) {
+ ret += " readonly=\"1\"";
+ }
+ if (getTimestamp() != null) {
+ ret += " ts=\"" + df.format(Date.from(getTimestamp())) + "\"";
+ }
+ if (getAttachmentType() != null) {
+ ret += " attach=\"" + getAttachmentType() + "\"";
+ }
+ ret += ">";
+ if (getUser() != null) {
+ ret += JuickUser.toString(getUser());
+ }
+ if (getText() != null) {
+ ret += "" + StringEscapeUtils.escapeXml10(getText()) + "";
+ }
+ if (!getTags().isEmpty()) {
+ for (Tag Tag : getTags()) {
+ ret += "" + StringEscapeUtils.escapeXml10(Tag.getName()) + "";
+ }
+ }
+ ret += "" + TagName + ">";
+ return ret;
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof JuickMessage)) {
+ return false;
+ }
+ JuickMessage jmsg = (JuickMessage) obj;
+ return (this.getMid() == jmsg.getMid() && this.getRid() == jmsg.getRid());
+ }
+ @Override
+ public int compareTo(Object obj) throws ClassCastException {
+ if (!(obj instanceof JuickMessage)) {
+ throw new ClassCastException();
+ }
+ JuickMessage jmsg = (JuickMessage) obj;
+ if (this.getMid() != jmsg.getMid()) {
+ if (this.getMid() > jmsg.getMid()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ if (this.getRid() != jmsg.getRid()) {
+ if (this.getRid() < jmsg.getRid()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickUser.java b/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickUser.java
new file mode 100644
index 00000000..10d2b564
--- /dev/null
+++ b/juick-xmpp/src/main/java/com/juick/xmpp/extensions/JuickUser.java
@@ -0,0 +1,65 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.juick.xmpp.extensions;
+import com.juick.xmpp.StanzaChild;
+import com.juick.xmpp.utils.XmlUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class JuickUser extends com.juick.User implements StanzaChild {
+ public final static String XMLNS = "http://juick.com/user";
+ public final static String TagName = "user";
+ public JuickUser() {
+ }
+ @Override
+ public String getXMLNS() {
+ return XMLNS;
+ }
+ @Override
+ public JuickUser parse(final XmlPullParser parser) throws XmlPullParserException, IOException {
+ JuickUser juser = new JuickUser();
+ String strUID = parser.getAttributeValue(null, "uid");
+ if (strUID != null) {
+ juser.setUid(Integer.parseInt(strUID));
+ }
+ juser.setName(parser.getAttributeValue(null, "uname"));
+ XmlUtils.skip(parser);
+ return juser;
+ }
+ public static String toString(com.juick.User user) {
+ String str = "<" + TagName + " xmlns='" + XMLNS + "'";
+ if (user.getUid() > 0) {
+ str += " uid='" + user.getUid() + "'";
+ }
+ if (user.getName() != null && user.getName().length() > 0) {
+ str += " uname='" + StringEscapeUtils.escapeXml10(user.getName()) + "'";
+ }
+ str += "/>";
+ return str;
+ }
+ @Override
+ public String toString() {
+ return toString(this);
+ }
+}
\ No newline at end of file
diff --git a/juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java b/juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java
deleted file mode 100644
index 0888b041..00000000
--- a/juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.juick.xmpp;
-
-import com.gargoylesoftware.htmlunit.Page;
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.juick.components.configuration.XmppAppConfiguration;
-import com.juick.configuration.MockDataConfiguration;
-import com.juick.server.configuration.BaseWebConfiguration;
-import com.juick.service.ShowQueriesService;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.web.WebAppConfiguration;
-import org.springframework.test.web.servlet.htmlunit.MockMvcWebClientBuilder;
-import org.springframework.web.context.WebApplicationContext;
-
-import javax.inject.Inject;
-
-import java.io.IOException;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-
-@RunWith(SpringJUnit4ClassRunner.class)
-@WebAppConfiguration
-@ContextConfiguration
-public class XMPPTests {
- @Configuration
- @Import(value = {
- BaseWebConfiguration.class, XmppAppConfiguration.class, MockDataConfiguration.class
- })
- static class Config {
- @Bean
- public ShowQueriesService showQueriesService() {
- return Mockito.mock(ShowQueriesService.class);
- }
- }
-
- @Inject
- private WebApplicationContext wac;
-
- private WebClient webClient;
-
- @Before
- public void setup() {
- webClient = MockMvcWebClientBuilder.webAppContextSetup(this.wac).build();
- webClient.getOptions().setJavaScriptEnabled(false);
- }
- @Test
- public void statusPageIsUp() throws IOException {
- Page statusPage = webClient.getPage("http://localhost:8080/status");
- assertThat(statusPage.getWebResponse().getStatusCode(), equalTo(200));
- }
-}
--
cgit v1.2.3