package com.juick.ws; import com.juick.User; import com.juick.json.MessageSerializer; import com.juick.server.MessagesQueries; import com.juick.server.SubscriptionsQueries; import com.juick.util.ThreadHelper; import com.juick.xmpp.JID; import com.juick.xmpp.Message; import com.juick.xmpp.Stream; import com.juick.xmpp.StreamComponent; import com.juick.xmpp.extensions.JuickMessage; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.util.Assert; import org.springframework.web.socket.TextMessage; import java.io.IOException; import java.net.Socket; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; /** * @author ugnich */ public class XMPPConnection implements InitializingBean, DisposableBean, Stream.StreamListener, Message.MessageListener { private static final Logger logger = LoggerFactory.getLogger(XMPPConnection.class); private final JdbcTemplate jdbc; private final ExecutorService service; private final WebsocketComponent wsHandler; private final String xmppPassword; private final MessageSerializer ms; private final int xmppPort; private final String wsJid; private Stream xmpp; public XMPPConnection( final Environment env, final ExecutorService service, final WebsocketComponent wsHandler, final JdbcTemplate jdbc) { Assert.notNull(env); Assert.notNull(service); Assert.notNull(wsHandler); Assert.notNull(jdbc); this.service = service; this.wsHandler = wsHandler; this.jdbc = jdbc; xmppPassword = env.getProperty("xmpp_password"); xmppPort = NumberUtils.toInt(env.getProperty("xmpp_port"), 5347); wsJid = env.getProperty("ws_jid", "ws.juick.local"); ms = new MessageSerializer(); } @Override public void afterPropertiesSet() throws Exception { try { Socket socket = new Socket("localhost", xmppPort); xmpp = new StreamComponent(new JID(wsJid), socket.getInputStream(), socket.getOutputStream(), xmppPassword); xmpp.addChildParser(new JuickMessage()); xmpp.addListener((Stream.StreamListener) this); xmpp.addListener((Message.MessageListener) this); service.submit(() -> xmpp.startParsing()); logger.info("XMPPConnection initialized"); } catch (IOException e) { logger.error("XMPPConnection initialization error", e); } } @Override public void destroy() throws Exception { ThreadHelper.shutdownAndAwaitTermination(service); logger.info("XMPPConnection destroyed"); } @Override public void onStreamReady() { logger.info("XMPP stream ready"); } @Override public void onStreamFail(final Exception ex) { logger.error("XMPP stream failed", ex); } @Override public void onMessage(final com.juick.xmpp.Message msg) { JuickMessage jmsg = (JuickMessage) msg.getChild(JuickMessage.XMLNS); if (jmsg != null) { logger.info("got jmsg: " + ms.serialize(jmsg).toString()); if (jmsg.getMid() == 0) { int uid_to = 0; try { uid_to = Integer.parseInt(msg.to.Username); } catch (Exception e) { } if (uid_to > 0) { onJuickPM(uid_to, jmsg); } } else if (jmsg.getRid() == 0) { onJuickMessagePost(jmsg); } else { // to get quote com.juick.Message reply = MessagesQueries.getReply(jdbc, jmsg.getMid(), jmsg.getRid()); onJuickMessageReply(reply); } } } MessageSerializer messageSerializer = new MessageSerializer(); private void onJuickPM(final int uid_to, final com.juick.Message jmsg) { String json = messageSerializer.serialize(jmsg).toString(); synchronized (wsHandler.getClients()) { wsHandler.getClients().stream().filter(c -> !c.legacy && c.visitor.getUid() == uid_to).forEach(c -> { try { logger.info("sending pm to " + c.visitor.getUid()); c.session.sendMessage(new TextMessage(json)); } catch (IOException e) { logger.warn("ws error", e); } }); } } private void onJuickMessagePost(final com.juick.Message jmsg) { String json = messageSerializer.serialize(jmsg).toString(); List uids = SubscriptionsQueries.getSubscribedUsers(jdbc, jmsg.getUser().getUid(), jmsg.getMid()) .stream().map(User::getUid).collect(Collectors.toList()); synchronized (wsHandler.getClients()) { wsHandler.getClients().stream().filter(c -> (!c.legacy && c.visitor.getUid() == 0) // anonymous users || (!c.legacy && uids.contains(c.visitor.getUid()))) // subscriptions .forEach(c -> { try { logger.info("sending message to " + c.visitor.getUid()); c.session.sendMessage(new TextMessage(json)); } catch (IOException e) { logger.warn("ws error", e); } }); wsHandler.getClients().stream().filter(c -> c.legacy && c.allMessages) // legacy all posts .forEach(c -> { try { logger.info("sending message to legacy client " + c.visitor.getUid()); c.session.sendMessage(new TextMessage(json)); } catch (IOException e) { logger.warn("ws error", e); } }); } } private void onJuickMessageReply(final com.juick.Message jmsg) { String json = messageSerializer.serialize(jmsg).toString(); List threadUsers = SubscriptionsQueries.getUsersSubscribedToComments(jdbc, jmsg.getMid(), jmsg.getUser().getUid()) .stream().map(User::getUid).collect(Collectors.toList()); synchronized (wsHandler.getClients()) { wsHandler.getClients().stream().filter(c -> (!c.legacy && c.visitor.getUid() == 0) // anonymous users || (!c.legacy && threadUsers.contains(c.visitor.getUid()))) // subscriptions .forEach(c -> { try { logger.info("sending reply to " + c.visitor.getUid()); c.session.sendMessage(new TextMessage(json)); } catch (IOException e) { logger.warn("ws error", e); } }); wsHandler.getClients().stream().filter(c -> (c.legacy && c.allReplies) || (c.legacy && c.MID == jmsg.getMid())) // legacy replies .forEach(c -> { try { logger.info("sending reply to legacy client " + c.visitor.getUid()); c.session.sendMessage(new TextMessage(json)); } catch (IOException e) { logger.warn("ws error", e); } }); } } }