diff options
author | Vitaly Takmazov | 2017-10-04 15:31:44 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2017-10-09 14:34:51 +0300 |
commit | 51bfc341be1975b7a11e0b3a59cfbb4710e78446 (patch) | |
tree | 548bef919082406c510a7b653c2a2ac4e2aa4b2e | |
parent | 2996fb8279645b1329b38c5c7b6d81ba3c10612e (diff) |
juick-xmpp-wip: router component
22 files changed, 812 insertions, 376 deletions
diff --git a/juick-server-core/src/main/java/rocks/xmpp/core/session/debug/LogbackDebugger.java b/juick-server-core/src/main/java/rocks/xmpp/core/session/debug/LogbackDebugger.java index dd77661e..0b31ee87 100644 --- a/juick-server-core/src/main/java/rocks/xmpp/core/session/debug/LogbackDebugger.java +++ b/juick-server-core/src/main/java/rocks/xmpp/core/session/debug/LogbackDebugger.java @@ -28,10 +28,11 @@ import java.io.OutputStream; * Created by vitalyster on 17.11.2016. */ public class LogbackDebugger implements XmppDebugger { - private static final Logger logger = LoggerFactory.getLogger(LogbackDebugger.class); + private Logger logger; @Override public void initialize(XmppSession xmppSession) { + logger = LoggerFactory.getLogger(xmppSession.getClass()); } @Override diff --git a/juick-xmpp-bot/build.gradle b/juick-xmpp-wip/build.gradle index b42f6977..13c564b3 100644 --- a/juick-xmpp-bot/build.gradle +++ b/juick-xmpp-wip/build.gradle @@ -5,6 +5,12 @@ apply plugin: 'org.akhikhl.gretty' dependencies { compile project(':juick-server-jdbc') compile project(':juick-server-web') + compile project(':juick-xmpp') + + testCompile project(path: ':juick-core', configuration: 'testArtifacts') + testCompile project(path: ':juick-server-web', configuration: 'testArtifacts') + testCompile project(path: ':juick-server-jdbc', configuration: 'testArtifacts') + testCompile ('net.sourceforge.htmlunit:htmlunit:2.27') } compileJava.options.encoding = 'UTF-8' diff --git a/juick-xmpp-bot/src/main/java/com/juick/components/XMPPBot.java b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java index b551ff2b..b551ff2b 100644 --- a/juick-xmpp-bot/src/main/java/com/juick/components/XMPPBot.java +++ b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java diff --git a/juick-xmpp-wip/src/main/java/com/juick/components/XMPPRouter.java b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPRouter.java new file mode 100644 index 00000000..a39358c5 --- /dev/null +++ b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPRouter.java @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.components; + +import com.juick.components.s2s.BasicXmppSession; +import com.juick.xmpp.Message; +import com.juick.xmpp.StreamComponentServer; +import com.juick.xmpp.StreamListener; +import com.juick.xmpp.extensions.JuickMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.stanza.model.Stanza; +import rocks.xmpp.util.XmppUtils; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutorService; + +/** + * @author ugnich + */ +@Component +public class XMPPRouter implements Message.MessageListener, AutoCloseable, StreamListener { + private static final Logger logger = LoggerFactory.getLogger(XMPPRouter.class); + + @Inject + private ExecutorService service; + + private final List<StreamComponentServer> connections = Collections.synchronizedList(new ArrayList<>()); + + private ServerSocket listener; + + @Inject + private BasicXmppSession session; + + @Value("${router_port:5347}") + private int routerPort; + + @PostConstruct + public void init() { + + logger.info("component router initialized"); + service.submit(() -> { + try { + listener = new ServerSocket(routerPort); + logger.info("component router listening on {}", routerPort); + while (!listener.isClosed()) { + if (Thread.currentThread().isInterrupted()) break; + Socket socket = listener.accept(); + service.submit(() -> { + try { + StreamComponentServer client = new StreamComponentServer(socket.getInputStream(), socket.getOutputStream(), "secret"); + addConnectionIn(client); + client.addChildParser(new JuickMessage()); + client.addListener((Message.MessageListener) this); + client.addListener((StreamListener) this); + client.connect(); + } catch (IOException e) { + logger.error("component error", e); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } + }); + } + } catch (SocketException e) { + // shutdown + } catch (IOException e) { + logger.warn("io exception", e); + } + }); + } + + @Override + public void close() throws Exception { + if (!listener.isClosed()) { + listener.close(); + } + synchronized (getConnections()) { + for (Iterator<StreamComponentServer> i = getConnections().iterator(); i.hasNext(); ) { + StreamComponentServer c = i.next(); + c.logoff(); + i.remove(); + } + } + service.shutdown(); + logger.info("XMPP router destroyed"); + } + + private void addConnectionIn(StreamComponentServer c) { + synchronized (getConnections()) { + getConnections().add(c); + } + } + + private void sendOut(Stanza s) { + try { + StringWriter stanzaWriter = new StringWriter(); + XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( + session.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); + session.createMarshaller().marshal(s, xmppStreamWriter); + xmppStreamWriter.flush(); + xmppStreamWriter.close(); + String xml = stanzaWriter.toString(); + logger.info("XMPPRouter (out): {}", xml); + sendOut(s.getTo().getDomain(), xml); + } catch (XMLStreamException | JAXBException e1) { + logger.info("jaxb exception", e1); + } + } + + private void sendOut(String hostname, String xml) { + boolean haveAnyConn = false; + + StreamComponentServer connOut = null; + synchronized (getConnections()) { + for (StreamComponentServer c : getConnections()) { + if (c.to != null && c.to.getDomain().equals(hostname)) { + if (c.isLoggedIn()) { + connOut = c; + break; + } + } + } + } + if (connOut != null) { + connOut.send(xml); + return; + } + logger.error("component unavailable: {}", hostname); + + } + + private List<StreamComponentServer> getConnections() { + return connections; + } + + private Stanza parse(String xml) { + try { + Unmarshaller unmarshaller = session.createUnmarshaller(); + return (Stanza)unmarshaller.unmarshal(new StringReader(xml)); + } catch (JAXBException e) { + logger.error("JAXB exception", e); + } + return null; + } + @Override + public void onMessage(Message message) { + sendOut(parse(message.toString())); + } + + @Override + public void ready() { + + } + + @Override + public void fail(Exception e) { + + } + + @Override + public boolean filter(Jid jid, Jid jid1) { + return false; + } +} diff --git a/juick-xmpp-bot/src/main/java/com/juick/components/configuration/BotAppConfiguration.java b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java index 7009f2b7..7009f2b7 100644 --- a/juick-xmpp-bot/src/main/java/com/juick/components/configuration/BotAppConfiguration.java +++ b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java diff --git a/juick-xmpp-bot/src/main/java/com/juick/components/configuration/BotInitializer.java b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java index 8b5d9b94..8b5d9b94 100644 --- a/juick-xmpp-bot/src/main/java/com/juick/components/configuration/BotInitializer.java +++ b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java diff --git a/juick-xmpp-bot/src/main/java/com/juick/components/controllers/StatusController.java b/juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java index 350669b8..350669b8 100644 --- a/juick-xmpp-bot/src/main/java/com/juick/components/controllers/StatusController.java +++ b/juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java diff --git a/juick-xmpp-bot/src/main/resources/vCard.png b/juick-xmpp-wip/src/main/resources/vCard.png Binary files differindex 5ed5336a..5ed5336a 100644 --- a/juick-xmpp-bot/src/main/resources/vCard.png +++ b/juick-xmpp-wip/src/main/resources/vCard.png diff --git a/juick-xmpp-bot/src/main/webapp/WEB-INF/web.xml b/juick-xmpp-wip/src/main/webapp/WEB-INF/web.xml index a57cceb9..a57cceb9 100644 --- a/juick-xmpp-bot/src/main/webapp/WEB-INF/web.xml +++ b/juick-xmpp-wip/src/main/webapp/WEB-INF/web.xml diff --git a/juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java b/juick-xmpp-wip/src/test/java/com/juick/xmpp/XMPPTests.java index 0888b041..d154128c 100644 --- a/juick-xmpp/src/test/java/com/juick/xmpp/XMPPTests.java +++ b/juick-xmpp-wip/src/test/java/com/juick/xmpp/XMPPTests.java @@ -19,7 +19,9 @@ package com.juick.xmpp; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.WebClient; +import com.juick.components.XMPPConnection; import com.juick.components.configuration.XmppAppConfiguration; +import com.juick.components.XMPPRouter; import com.juick.configuration.MockDataConfiguration; import com.juick.server.configuration.BaseWebConfiguration; import com.juick.service.ShowQueriesService; @@ -35,10 +37,19 @@ 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 rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +import rocks.xmpp.core.session.ConnectionEvent; +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.Message; +import rocks.xmpp.extensions.component.accept.ExternalComponent; import javax.inject.Inject; - import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -73,4 +84,43 @@ public class XMPPTests { Page statusPage = webClient.getPage("http://localhost:8080/status"); assertThat(statusPage.getWebResponse().getStatusCode(), equalTo(200)); } + @Inject + XMPPRouter router; + @Inject + XMPPConnection component; + @Inject + ExecutorService service; + @Test + public void routerHandshake() throws XmppException, InterruptedException { + XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() + .extensions(Extension.of(com.juick.Message.class)) + .debugger(LogbackDebugger.class) + .build(); + ExternalComponent routerClient = ExternalComponent.create("test.localhost", + "secret", configuration, "localhost", 5347); + CountDownLatch lock = new CountDownLatch(1); + routerClient.addConnectionListener(connectionEvent -> { + if (connectionEvent.equals(ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) { + lock.countDown(); + } + }); + service.submit(() -> { + try { + Thread.sleep(3000); + routerClient.connect(); + } catch (XmppException | InterruptedException e) { + e.printStackTrace(); + } + lock.countDown(); + }); + lock.await(); + CountDownLatch lock2 = new CountDownLatch(2); + component.sendStanza(new Message(Jid.of("yoyo@test.localhost"), Message.Type.CHAT, "test" )); + component.sendStanza(new Message(Jid.of("yoyo@test.localhost"), Message.Type.CHAT, "test" )); + routerClient.addInboundMessageListener(messageEvent -> { + assertThat(messageEvent.getMessage().getBody(), equalTo("test")); + lock2.countDown(); + }); + lock2.await(); + } } diff --git a/juick-xmpp/build.gradle b/juick-xmpp/build.gradle index 0da3231a..a68ac356 100644 --- a/juick-xmpp/build.gradle +++ b/juick-xmpp/build.gradle @@ -9,10 +9,6 @@ dependencies { exclude group: 'xmlpull' } providedCompile 'xpp3:xpp3:1.1.4c' - testCompile project(path: ':juick-core', configuration: 'testArtifacts') - testCompile project(path: ':juick-server-web', configuration: 'testArtifacts') - testCompile project(path: ':juick-server-jdbc', configuration: 'testArtifacts') - testCompile ('net.sourceforge.htmlunit:htmlunit:2.27') } compileJava.options.encoding = 'UTF-8' diff --git a/juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java b/juick-xmpp/src/main/java/com/juick/components/CleaningUp.java index 45933141..067af6e8 100644 --- a/juick-xmpp/src/main/java/com/juick/components/s2s/CleaningUp.java +++ b/juick-xmpp/src/main/java/com/juick/components/CleaningUp.java @@ -15,20 +15,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package com.juick.components.s2s; +package com.juick.components; -import com.juick.components.XMPPServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; import javax.inject.Inject; -import java.util.Iterator; /** * * @author ugnich */ +@Component public class CleaningUp { private static final Logger logger = LoggerFactory.getLogger(CleaningUp.class); @@ -40,28 +40,22 @@ public class CleaningUp { public void cleanUp() { long now = System.currentTimeMillis(); - synchronized (xmpp.getOutConnections()) { - for (Iterator<ConnectionOut> 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<ConnectionIn> 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(); - } - } - } + 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/s2s/JuickBot.java b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java index 2a7e02e9..4f838344 100644 --- a/juick-xmpp/src/main/java/com/juick/components/s2s/JuickBot.java +++ b/juick-xmpp/src/main/java/com/juick/components/JuickBot.java @@ -15,24 +15,25 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package com.juick.components.s2s; +package com.juick.components; import com.juick.User; -import com.juick.components.XMPPServer; +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.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.*; 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; @@ -45,21 +46,45 @@ import java.util.stream.Collectors; * * @author ugnich */ +@Component public class JuickBot implements StanzaListener, AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(JuickBot.class); - XMPPServer xmpp; - PrettyTime pt; + @Inject + private XMPPServer xmpp; + @Inject + private XMPPConnection router; + @Value("${xmppbot_jid}") + private String xmppbotJidStr; + + private Jid jid; + + private PrettyTime pt; @Inject - public JuickBot(XMPPServer xmpp) { - this.xmpp = xmpp; + 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" @@ -96,7 +121,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { public boolean incomingPresence(Presence p) { final String username = p.getTo().getLocal(); - final boolean toJuick = username.equals(xmpp.getJid().getLocal()); + final boolean toJuick = username.equals(jid.getLocal()); if (p.getType() == null) { Presence reply = new Presence(); @@ -108,12 +133,12 @@ public class JuickBot implements StanzaListener, AutoCloseable { } else if (p.getType().equals(Presence.Type.PROBE)) { int uid_to = 0; if (!toJuick) { - uid_to = xmpp.userService.getUIDbyName(username); + uid_to = userService.getUIDbyName(username); } if (toJuick || uid_to > 0) { Presence reply = new Presence(); - reply.setFrom(p.getTo().withResource(xmpp.getJid().getResource())); + reply.setFrom(p.getTo().withResource(jid.getResource())); reply.setTo(p.getFrom()); reply.setPriority((byte)10); xmpp.sendOut(ClientPresence.from(reply)); @@ -133,9 +158,9 @@ public class JuickBot implements StanzaListener, AutoCloseable { if (toJuick) { canSubscribe = true; } else { - int uid_to = xmpp.userService.getUIDbyName(username); + int uid_to = userService.getUIDbyName(username); if (uid_to > 0) { - xmpp.pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); canSubscribe = true; } } @@ -147,7 +172,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { reply.setType(Presence.Type.SUBSCRIBED); xmpp.sendOut(ClientPresence.from(reply)); - reply.setFrom(reply.getFrom().withResource(xmpp.getJid().getResource())); + reply.setFrom(reply.getFrom().withResource(jid.getResource())); reply.setPriority((byte) 10); reply.setType(null); xmpp.sendOut(ClientPresence.from(reply)); @@ -165,9 +190,9 @@ public class JuickBot implements StanzaListener, AutoCloseable { } } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) { if (!toJuick) { - int uid_to = xmpp.userService.getUIDbyName(username); + int uid_to = userService.getUIDbyName(username); if (uid_to > 0) { - xmpp.pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); + pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); } } @@ -190,9 +215,9 @@ public class JuickBot implements StanzaListener, AutoCloseable { User user_from; String signuphash = StringUtils.EMPTY; - user_from = xmpp.userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); + user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); if (user_from == null) { - signuphash = xmpp.userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); + signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); } if (user_from == null) { @@ -200,7 +225,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { reply.setFrom(msg.getTo()); reply.setTo(msg.getFrom()); reply.setType(Message.Type.CHAT); - if (username.equals(xmpp.getJid().getLocal())) { + 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."); @@ -209,11 +234,11 @@ public class JuickBot implements StanzaListener, AutoCloseable { return true; } - if (username.equals(xmpp.getJid().getLocal())) { + if (username.equals(jid.getLocal())) { return incomingMessageJuick(user_from, msg); } - int uid_to = xmpp.userService.getUIDbyName(username); + int uid_to = userService.getUIDbyName(username); if (uid_to == 0) { Message reply = new Message(); @@ -227,36 +252,36 @@ public class JuickBot implements StanzaListener, AutoCloseable { } boolean success = false; - if (!xmpp.userService.isInBLAny(uid_to, user_from.getUid())) { - success = xmpp.pmQueriesService.createPM(user_from.getUid(), uid_to, msg.getBody()); + 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(xmpp.getJid().asBareJid()); + 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); - xmpp.getRouter().sendStanza(m); + router.sendStanza(m); m.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null)); - xmpp.getRouter().sendStanza(m); + router.sendStanza(m); List<String> jids; boolean inroster = false; - jids = xmpp.userService.getJIDsbyUID(uid_to); - for (String jid : jids) { + jids = userService.getJIDsbyUID(uid_to); + for (String userJid : jids) { Message mm = new Message(); - mm.setTo(Jid.of(jid)); + mm.setTo(Jid.of(userJid)); mm.setType(Message.Type.CHAT); - inroster = xmpp.pmQueriesService.havePMinRoster(user_from.getUid(), jid); + 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(xmpp.getJid()); + mm.setFrom(jid); mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + msg.getBody()); } xmpp.sendOut(ClientMessage.from(mm)); @@ -318,7 +343,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { private void commandPing(Message m) { Presence p = new Presence(m.getFrom()); - p.setFrom(xmpp.getJid()); + p.setFrom(jid); p.setPriority((byte) 10); xmpp.sendOut(ClientPresence.from(p)); @@ -330,7 +355,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { } private void commandLogin(Message m, User user_from) { - sendReply(m.getFrom(), "http://juick.com/login?hash=" + xmpp.userService.getHashByUID(user_from.getUid())); + 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) { @@ -341,15 +366,15 @@ public class JuickBot implements StanzaListener, AutoCloseable { boolean haveInRoster = false; if (user_to.indexOf('@') > 0) { - uid_to = xmpp.userService.getUIDbyJID(user_to); + uid_to = userService.getUIDbyJID(user_to); } else { - uid_to = xmpp.userService.getUIDbyName(user_to); + uid_to = 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); + 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; @@ -363,27 +388,27 @@ public class JuickBot implements StanzaListener, AutoCloseable { if (ret == 200) { Message msg = new Message(); - msg.setFrom(xmpp.getJid().asBareJid()); + 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); - xmpp.getRouter().sendStanza(msg); + router.sendStanza(msg); msg.setTo(Jid.of(Integer.toString(uid_to), "ws.juick.com", null)); - xmpp.getRouter().sendStanza(msg); + router.sendStanza(msg); - for (String jid : jids_to) { + for (String userJid : jids_to) { Message mm = new Message(); - mm.setTo(Jid.of(jid)); + mm.setTo(Jid.of(userJid)); mm.setType(Message.Type.CHAT); - haveInRoster = xmpp.pmQueriesService.havePMinRoster(user_from.getUid(), jid); + 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(xmpp.getJid()); + mm.setFrom(jid); mm.setBody("Private message from @" + user_from.getName() + ":\n" + body); } xmpp.sendOut(ClientMessage.from(mm)); @@ -404,8 +429,8 @@ public class JuickBot implements StanzaListener, AutoCloseable { } private void commandBLShow(Message m, User user_from) { - List<User> blusers = xmpp.userService.getUserBLUsers(user_from.getUid()); - List<String> bltags = xmpp.tagService.getUserBLTags(user_from.getUid()); + List<User> blusers = userService.getUserBLUsers(user_from.getUid()); + List<String> bltags = tagService.getUserBLTags(user_from.getUid()); String txt = StringUtils.EMPTY; if (bltags.size() > 0) { @@ -430,17 +455,17 @@ public class JuickBot implements StanzaListener, AutoCloseable { } boolean commandLast(Jid jidFrom) { - User user = xmpp.userService.getUserByJID(jidFrom.asBareJid().toEscapedString()); + User user = userService.getUserByJID(jidFrom.asBareJid().toEscapedString()); sendReply(jidFrom, "Last messages:\n" - + printMessages(xmpp.messagesService.getAll(user.getUid(), 0), true)); + + printMessages(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<String> recommendedUsers = xmpp.showQueriesService.getRecommendedUsers(currentUser); + User currentUser = userService.getUserByJID(jidFrom.asBareJid().toEscapedString()); + List<String> recommendedUsers = showQueriesService.getRecommendedUsers(currentUser); if (recommendedUsers.size() > 0) { for (String user : recommendedUsers) { msg.append("\n@").append(user); @@ -449,7 +474,7 @@ public class JuickBot implements StanzaListener, AutoCloseable { msg.append("\nNo recommendations now. Subscribe to more blogs. ;)"); } msg.append("\n\nTop 10 personal blogs:"); - List<String> topUsers = xmpp.showQueriesService.getTopUsers(); + List<String> topUsers = showQueriesService.getTopUsers(); if (topUsers.size() > 0) { for (String user : topUsers) { msg.append("\n@").append(user); @@ -463,13 +488,17 @@ public class JuickBot implements StanzaListener, AutoCloseable { void sendReply(Jid jidTo, String txt) { Message reply = new Message(); - reply.setFrom(xmpp.getJid()); + 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) { @@ -480,24 +509,27 @@ public class JuickBot implements StanzaListener, AutoCloseable { } else if (xmlValue instanceof Message) { Message msg = (Message) xmlValue; if (!incomingMessage(msg)) { - xmpp.getRouter().sendStanza(msg); + router.sendStanza(msg); } + } else if (xmlValue instanceof IQ) { + IQ iq = (IQ) xmlValue; + router.sendStanza(iq); } } String printMessages(List<Integer> mids, boolean crop) { - return xmpp.messagesService.getMessages(mids).stream() + 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(xmpp.getJid()); + presence.setFrom(jid); if (type != null) { presence.setType(type); } - xmpp.userService.getActiveJIDs().forEach(j -> { + userService.getActiveJIDs().forEach(j -> { try { presence.setTo(Jid.of(j)); xmpp.sendOut(ClientPresence.from(presence)); 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<String> 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<User> users = xmpp.subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid()); + List<User> 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<User> 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<String> brokenSSLhosts; - public List<String> bannedHosts; + @Value("${broken_ssl_hosts}") + public String[] brokenSSLhosts; + @Value("${banned_hosts}") + public String[] bannedHosts; - private final List<ConnectionIn> inConnections = Collections.synchronizedList(new ArrayList<>()); - private final List<ConnectionOut> outConnections = Collections.synchronizedList(new ArrayList<>()); - private final List<CacheEntry> outCache = Collections.synchronizedList(new ArrayList<>()); - private final List<StanzaListener> stanzaListeners = Collections.synchronizedList(new ArrayList<>()); + private final List<ConnectionIn> inConnections = new CopyOnWriteArrayList<>(); + private final List<ConnectionOut> outConnections = new CopyOnWriteArrayList<>(); + private final List<CacheEntry> outCache = new CopyOnWriteArrayList<>(); + private final List<StanzaListener> 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<ConnectionOut> i = getOutConnections().iterator(); i.hasNext(); ) { - ConnectionOut c = i.next(); - c.closeConnection(); - i.remove(); - } - } - - synchronized (getInConnections()) { - for (Iterator<ConnectionIn> 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<CacheEntry> 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<ConnectionOut> 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<ConnectionIn> 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<ConnectionIn> getInConnections() { - return inConnections; - } - - public List<ConnectionOut> getOutConnections() { - return outConnections; - } - - public List<CacheEntry> 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<ConnectionOut> 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<StanzaListener> 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<ConnectionIn> getInConnections() { + return inConnections; + } + + public List<ConnectionOut> 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/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<String> 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("<db:verify from='" + vto + "' to='" + vfrom + "' id='" + vid + "' type='valid'/>"); 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 += "<stream:features>"; - if (sc != null && !isSecured() && !xmpp.brokenSSLhosts.contains(from)) { + if (sc != null && !isSecured() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) { openStream += "<starttls xmlns=\"" + NS_TLS + "\"><optional/></starttls>"; } openStream += "</stream:features>"; 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("<starttls xmlns=\"" + NS_TLS + "\" />"); } else { @@ -191,10 +188,4 @@ public class ConnectionOut extends Connection implements Runnable { sendStanza("<db:verify from='" + xmpp.HOSTNAME + "' to='" + to + "' id='" + sid + "'>" + key + "</db:verify>"); } - - @Override - public void restartParser() throws XmlPullParserException, IOException { - super.restartParser(); - streamID = UUID.randomUUID().toString(); - } } 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 <http://www.gnu.org/licenses/>. + */ +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 += "<body>" + StringEscapeUtils.escapeXml10(getText()) + "</body>"; + } + if (!getTags().isEmpty()) { + for (Tag Tag : getTags()) { + ret += "<tag>" + StringEscapeUtils.escapeXml10(Tag.getName()) + "</tag>"; + } + } + 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 <http://www.gnu.org/licenses/>. + */ +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/settings.gradle b/settings.gradle index 3ba77cfb..09b3c7ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = "Juick" -include ':juick-core', ':juick-server-core', ':juick-server-jdbc', ':juick-server-web', ':juick-api', ':juick-www', ':juick-rss', ':juick-ws', ':juick-notifications', ':juick-crosspost', ':juick-xmpp', ':juick-xmpp-bot' +include ':juick-core', ':juick-server-core', ':juick-server-jdbc', ':juick-server-web', ':juick-api', ':juick-www', ':juick-rss', ':juick-ws', ':juick-notifications', ':juick-crosspost', ':juick-xmpp', ':juick-xmpp-wip' |