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/XMPPBot.java | 161 +++++++++++++++++ .../main/java/com/juick/components/XMPPRouter.java | 198 +++++++++++++++++++++ .../configuration/BotAppConfiguration.java | 48 +++++ .../components/configuration/BotInitializer.java | 57 ++++++ .../components/controllers/StatusController.java | 43 +++++ 5 files changed, 507 insertions(+) create mode 100644 juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java create mode 100644 juick-xmpp-wip/src/main/java/com/juick/components/XMPPRouter.java create mode 100644 juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java create mode 100644 juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java create mode 100644 juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java (limited to 'juick-xmpp-wip/src/main/java/com/juick/components') diff --git a/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java new file mode 100644 index 00000000..b551ff2b --- /dev/null +++ b/juick-xmpp-wip/src/main/java/com/juick/components/XMPPBot.java @@ -0,0 +1,161 @@ +/* + * 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.server.helpers.UserInfo; +import com.juick.server.protocol.JuickProtocol; +import com.juick.server.protocol.ProtocolListener; +import com.juick.service.PMQueriesService; +import com.juick.service.UserService; +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 rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +import rocks.xmpp.core.stanza.model.Message; +import rocks.xmpp.extensions.component.accept.ExternalComponent; +import rocks.xmpp.extensions.vcard.temp.VCardManager; +import rocks.xmpp.extensions.vcard.temp.model.VCard; +import rocks.xmpp.extensions.version.SoftwareVersionManager; +import rocks.xmpp.extensions.version.model.SoftwareVersion; + +import javax.inject.Inject; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.List; + +/** + * Created by vt on 12/11/2016. + */ +public class XMPPBot implements AutoCloseable, ProtocolListener { + private static final Logger logger = LoggerFactory.getLogger(XMPPBot.class); + @Inject + UserService userService; + @Inject + PMQueriesService pmQueriesService; + @Inject + JuickProtocol juickProtocol; + + Jid juickJid; + + private ExternalComponent component; + + public XMPPBot(Environment env) { + component = ExternalComponent.create(env.getProperty("component_name", "juick.com"), + env.getProperty("component_password", "secret"), env.getProperty("component_host", "localhost"), + NumberUtils.toInt(env.getProperty("component_port", "5347"), 5347)); + juickJid = Jid.of(env.getProperty("xmppbot_jid", "juick@juick.com/Juick")); + juickProtocol.setListener(this); + try { + SoftwareVersionManager softwareVersionManager = component.getManager(SoftwareVersionManager.class); + softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "git", System.getProperty("os.name", "generic"))); + VCardManager vCardManager = component.getManager(VCardManager.class); + vCardManager.setEnabled(true); + VCard ownVCard = new VCard(); + ownVCard.setNickname("Juick"); + ownVCard.setUrl(new URL("http://juick.com/")); + ownVCard.setPhoto(new VCard.Image("image/png", IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream("vCard.png")))); + vCardManager.setVCard(ownVCard); + component.addInboundMessageListener(e -> { + Message message = e.getMessage(); + if (message.getType().equals(Message.Type.ERROR) || message.getType().equals(Message.Type.GROUPCHAT)) { + return; + } + String text = message.getBody().trim(); + String command = text.toUpperCase(); + User user = userService.getUserByJID(message.getFrom().asBareJid().toString()); + if (command.equals("VCARD")) { + try { + VCard vCard = vCardManager.getVCard(message.getFrom().asBareJid()).getResult(); + UserInfo info = new UserInfo(); + info.setFullName(vCard.getFormattedName()); + info.setCountry(vCard.getAddresses().get(0).getCountry()); + info.setUrl(vCard.getUrl().toString()); + userService.updateUserInfo(user, info); + component.sendMessage(new Message(message.getFrom(), Message.Type.CHAT, "vCard updated")); + } catch (XmppException vce) { + logger.warn("vcard exception", vce); + } + } else { + try { + String reply = juickProtocol.getReply(user, text); + Message replyMessage = new Message(message.getFrom(), Message.Type.CHAT); + replyMessage.setBody(reply); + replyMessage.setFrom(juickJid); + component.send(replyMessage); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException ex) { + logger.warn("unhandled error", ex); + } + } + }); + component.connect(); + } catch (XmppException | IOException e) { + logger.error("bot connection error", e); + } + } + + @Override + public void close() throws Exception { + if (component != null) + component.close(); + + logger.info("ExternalComponent on xmpp-bot destroyed"); + } + + @Override + public void privateMessage(User from, User to, String body) { + List toJids = userService.getJIDsbyUID(to.getUid()); + toJids.forEach(jid -> { + Message mm = new Message(); + mm.setTo(Jid.of(jid)); + mm.setType(Message.Type.CHAT); + boolean haveInRoster = pmQueriesService.havePMinRoster(from.getUid(), jid); + if (haveInRoster) { + mm.setFrom(Jid.of(from.getName(), juickJid.getDomain(), "Juick")); + mm.setBody(body); + } else { + mm.setFrom(Jid.of("juick", juickJid.getDomain(), "Juick")); + mm.setBody("Private message from @" + from.getName() + ":\n" + body); + } + component.send(mm); + }); + } + + @Override + public void userSubscribed(User from, User to) { + String notification = String.format("%s subscribed to your blog", from.getName()); + List toJids = userService.getJIDsbyUID(to.getUid()); + toJids.forEach(jid -> { + Message mm = new Message(); + mm.setTo(Jid.of(jid)); + mm.setType(Message.Type.CHAT); + mm.setFrom(juickJid); + mm.setBody(notification); + component.send(mm); + }); + } + + @Override + public void messagePosted(com.juick.Message msg) { + + } +} 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 . + */ + +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 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 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 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-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java new file mode 100644 index 00000000..7009f2b7 --- /dev/null +++ b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotAppConfiguration.java @@ -0,0 +1,48 @@ +/* + * 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.configuration; + +import com.juick.components.XMPPBot; +import com.juick.server.configuration.BaseWebConfiguration; +import com.juick.server.protocol.JuickProtocol; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +import javax.inject.Inject; + +/** + * Created by aalexeev on 11/12/16. + */ +@Configuration +@ComponentScan(basePackages = {"com.juick.components.controllers"}) +@PropertySource("classpath:juick.conf") +@Import(BaseWebConfiguration.class) +public class BotAppConfiguration extends WebMvcConfigurationSupport { + @Inject + private Environment env; + + @Bean + public XMPPBot xmpp() { + return new XMPPBot(env); + } + @Bean + public JuickProtocol juickProtocol() { + return new JuickProtocol("http://juick.com"); + } +} diff --git a/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java new file mode 100644 index 00000000..8b5d9b94 --- /dev/null +++ b/juick-xmpp-wip/src/main/java/com/juick/components/configuration/BotInitializer.java @@ -0,0 +1,57 @@ +/* + * 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.configuration; + +import com.juick.configuration.DataConfiguration; +import org.apache.commons.codec.CharEncoding; +import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +import javax.servlet.Filter; + +/** + * Created by vt on 09/02/16. + */ +public class BotInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { + + @Override + protected Class[] getRootConfigClasses() { + return new Class[]{ DataConfiguration.class }; + } + + @Override + protected Class[] getServletConfigClasses() { + return new Class[]{ BotAppConfiguration.class }; + } + + @Override + protected String[] getServletMappings() { + return new String[]{"/"}; + } + + @Override + protected Filter[] getServletFilters() { + CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(CharEncoding.UTF_8); + return new Filter[]{characterEncodingFilter}; + } + + @Override + protected String getServletName() { + return "Bot dispatcher servlet"; + } +} diff --git a/juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java b/juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java new file mode 100644 index 00000000..350669b8 --- /dev/null +++ b/juick-xmpp-wip/src/main/java/com/juick/components/controllers/StatusController.java @@ -0,0 +1,43 @@ +/* + * 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.controllers; + +import com.juick.components.XMPPBot; +import com.juick.Status; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.inject.Inject; + +/** + * Created by vitalyster on 24.10.2016. + */ +@Controller +@ResponseBody +public class StatusController { + @Inject + private XMPPBot xmpp; + + @RequestMapping(method = RequestMethod.GET, value = "/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status status() { + return xmpp != null ? Status.OK : Status.ERROR; + } +} -- cgit v1.2.3