diff options
Diffstat (limited to 'juick-server/src/main/java/com/juick/api/ApiServer.java')
-rw-r--r-- | juick-server/src/main/java/com/juick/api/ApiServer.java | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/juick-server/src/main/java/com/juick/api/ApiServer.java b/juick-server/src/main/java/com/juick/api/ApiServer.java new file mode 100644 index 00000000..51097440 --- /dev/null +++ b/juick-server/src/main/java/com/juick/api/ApiServer.java @@ -0,0 +1,251 @@ +/* + * 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.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.juick.User; +import com.juick.service.MessagesService; +import com.juick.service.SubscriptionService; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.socket.TextMessage; +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +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 rocks.xmpp.extensions.oob.model.x.OobX; +import rocks.xmpp.util.XmppUtils; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Ugnich Anton + */ +public class ApiServer implements AutoCloseable { + private static Logger logger = LoggerFactory.getLogger(ApiServer.class); + + private ExternalComponent xmpp; + + @Value("${xmpp_host:localhost}") + private String xmppHost; + @Value("${xmpp_password:secret}") + private String xmppPassword; + @Value("${ws_jid:ws.localhost}") + private String xmppJid; + @Value("${xmpp_port:5347}") + private int xmppPort; + @Value("${xmpp_disabled:false}") + private boolean isXmppDisabled; + @Inject + private ObjectMapper jsonMapper; + @Inject + private MessagesService messagesService; + @Inject + private SubscriptionService subscriptionService; + @Inject + private WebsocketManager wsHandler; + @Value("${service_user:juick}") + private String serviceUser; + + @PostConstruct + public void init() { + if (!isXmppDisabled) { + setupXmppComponent(xmppHost, xmppPort, xmppJid, xmppPassword); + } else { + logger.warn("XMPP is not enabled"); + } + } + + @Override + public void close() { + try { + if (xmpp != null) + xmpp.close(); + + logger.info("ExternalComponent on juick-api destroyed"); + } catch (Exception e) { + logger.warn("Exception occurs on juick-api destroy", e); + } + } + + public void setupXmppComponent(final String host, final int port, final String jid, final String password) { + XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() + .extensions(Extension.of(com.juick.Message.class)) + .build(); + xmpp = ExternalComponent.create(jid, password, configuration, host, port); + xmpp.addInboundMessageListener(e -> { + try { + Message msg = e.getMessage(); + com.juick.Message jmsg = msg.getExtension(com.juick.Message.class); + if (jmsg != null) { + if (logger.isInfoEnabled()) { // prevent writeValueAsString execution if log is disabled + try { + StringWriter stanzaWriter = new StringWriter(); + XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter( + xmpp.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter)); + xmpp.createMarshaller().marshal(msg, xmppStreamWriter); + xmppStreamWriter.flush(); + xmppStreamWriter.close(); + logger.info("got msg: {}", stanzaWriter.toString()); + } catch (XMLStreamException e1) { + logger.info("jaxb exception", e1); + } + + } + if (jmsg.getMid() == 0) { + int uid_to = NumberUtils.toInt(msg.getTo().getLocal(), 0); + if (uid_to > 0) { + onJuickPM(uid_to, jmsg); + } + } else if (jmsg.getRid() == 0) { + // to get full message with attachment, etc. + onJuickMessagePost(messagesService.getMessage(jmsg.getMid())); + } else { + // to get quote and attachment + com.juick.Message reply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); + onJuickMessageReply(reply); + } + } + } catch (JsonProcessingException ex) { + logger.error("mapper exception", ex); + } catch (JAXBException exc) { + logger.error("jaxb exception", exc); + } + }); + try { + xmpp.connect(); + } catch (XmppException e) { + logger.warn("xmpp extension", e); + } + } + + public void sendMessage(Message message) { + if (!isXmppDisabled) { + xmpp.sendMessage(message); + } + } + + public void processMessage(User visitor, String body, String attachmentName) { + Message xmsg = new Message(); + xmsg.setFrom(Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "perl")); + xmsg.setTo(Jid.of("juick@juick.com/Juick")); + xmsg.setBody(body); + try { + if (StringUtils.isNotEmpty(attachmentName)) { + String attachmentUrl = String.format("juick://%s", attachmentName); + xmsg.addExtension(new OobX(new URI(attachmentUrl), "!!!!Juick!!")); + } + sendMessage(xmsg); + } catch (URISyntaxException e1) { + logger.warn("attachment error", e1); + } + } + + private void onJuickPM(final int uid_to, final com.juick.Message jmsg) throws JsonProcessingException { + String json = jsonMapper.writeValueAsString(jmsg); + synchronized (wsHandler.getClients()) { + wsHandler.getClients().stream().filter(c -> + (!c.legacy && c.visitor.getUid() == uid_to) || c.visitor.getName().equals(serviceUser)) + .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) throws JsonProcessingException { + String json = jsonMapper.writeValueAsString(jmsg); + List<Integer> uids = subscriptionService.getSubscribedUsers(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.visitor.getName().equals(serviceUser) // services + || (!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) throws JsonProcessingException { + String json = jsonMapper.writeValueAsString(jmsg); + List<Integer> threadUsers = + subscriptionService.getUsersSubscribedToComments(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.visitor.getName().equals(serviceUser) // services + || (!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); + } + }); + } + } +} |