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 += ""; + 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