From 6933a80515e454b45e03c1d3609093ad1abcef92 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 6 Jul 2016 20:07:45 +0300 Subject: merge router and component --- src/main/java/com/juick/xmpp/s2s/ConnectionIn.java | 4 +- .../java/com/juick/xmpp/s2s/ConnectionRouter.java | 241 -------------------- src/main/java/com/juick/xmpp/s2s/JuickBot.java | 13 +- .../java/com/juick/xmpp/s2s/XMPPComponent.java | 244 +++++++++++++++++++-- 4 files changed, 230 insertions(+), 272 deletions(-) delete mode 100644 src/main/java/com/juick/xmpp/s2s/ConnectionRouter.java (limited to 'src/main/java/com/juick/xmpp/s2s') diff --git a/src/main/java/com/juick/xmpp/s2s/ConnectionIn.java b/src/main/java/com/juick/xmpp/s2s/ConnectionIn.java index bfc561b4..345caea9 100644 --- a/src/main/java/com/juick/xmpp/s2s/ConnectionIn.java +++ b/src/main/java/com/juick/xmpp/s2s/ConnectionIn.java @@ -117,7 +117,7 @@ public class ConnectionIn extends Connection implements Runnable { if (msg != null && (msg.type == null || !msg.type.equals(Message.Type.error))) { LOGGER.info("STREAM " + streamID + ": " + msg.toString()); if (!bot.incomingMessage(msg)) { - xmpp.connRouter.router.send(msg.toString()); + xmpp.router.send(msg.toString()); } } } else if (tag.equals("iq") && checkFromTo(parser)) { @@ -126,7 +126,7 @@ public class ConnectionIn extends Connection implements Runnable { String xml = XmlUtils.parseToString(parser, true); if (type == null || !type.equals(Iq.Type.error)) { LOGGER.info("STREAM " + streamID + ": " + xml); - xmpp.connRouter.router.send(xml); + xmpp.router.send(xml); } } else if (!isSecured() && tag.equals("starttls")) { LOGGER.info("STREAM " + streamID + " SECURING"); diff --git a/src/main/java/com/juick/xmpp/s2s/ConnectionRouter.java b/src/main/java/com/juick/xmpp/s2s/ConnectionRouter.java deleted file mode 100644 index 588cfe2c..00000000 --- a/src/main/java/com/juick/xmpp/s2s/ConnectionRouter.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.juick.xmpp.s2s; - -import com.juick.User; -import com.juick.server.MessagesQueries; -import com.juick.server.SubscriptionsQueries; -import com.juick.server.UserQueries; -import com.juick.xmpp.*; -import com.juick.xmpp.extensions.JuickMessage; -import com.juick.xmpp.extensions.Nickname; -import com.juick.xmpp.extensions.XOOB; - -import java.io.IOException; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author ugnich - */ -public class ConnectionRouter implements Stream.StreamListener, - Message.MessageListener, Iq.IqListener, Presence.PresenceListener { - private static final Logger logger = Logger.getLogger(ConnectionRouter.class.getName()); - - private String componentName; - private String password; - Stream router; - Socket socket; - XMPPComponent xmpp; - - ConnectionRouter(XMPPComponent s2s, String componentName, String password) { - this.xmpp = s2s; - this.componentName = componentName; - this.password = password; - reconnect(); - } - public void reconnect() { - try { - socket = new Socket("localhost", 5347); - router = new StreamComponent(new JID("s2s"), socket.getInputStream(), socket.getOutputStream(), password); - router.addChildParser(new JuickMessage()); - router.addListener((Stream.StreamListener) this); - router.addListener((Message.MessageListener) this); - router.addListener((Iq.IqListener) this); - router.startParsing(); - logger.info("STREAM ROUTER START"); - } catch (IOException e) { - logger.log(Level.SEVERE, "router failed", e); - } - } - public void closeConnection() throws IOException { - router.logoff(); - socket.close(); - } - - public void sendJuickMessage(JuickMessage jmsg) { - List jids = new ArrayList<>(); - - if (jmsg.FriendsOnly) { - jids = SubscriptionsQueries.getJIDSubscribedToUser(xmpp.sql, jmsg.getUser().getUID(), jmsg.FriendsOnly); - } else { - List users = SubscriptionsQueries.getSubscribedUsers(xmpp.sql, jmsg.getUser().getUID(), jmsg.getMID()); - for (User user : users) { - for (String jid : UserQueries.getJIDsbyUID(xmpp.sql, user.getUID())) { - jids.add(jid); - } - } - } - - String txt = "@" + jmsg.getUser().getUName() + ":" + jmsg.getTagsString() + "\n"; - String attachment = jmsg.getAttachmentURL(); - if (attachment != null) { - txt += attachment + "\n"; - } - txt += jmsg.getText() + "\n\n"; - txt += "#" + jmsg.getMID() + " http://juick.com/" + jmsg.getMID(); - - Nickname nick = new Nickname(); - nick.Nickname = "@" + jmsg.getUser().getUName(); - - com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); - msg.from = JuickBot.JuickJID; - msg.body = txt; - msg.type = Message.Type.chat; - msg.thread = "juick-" + jmsg.getMID(); - msg.addChild(jmsg); - msg.addChild(nick); - if (attachment != null) { - XOOB oob = new XOOB(); - oob.URL = attachment; - msg.addChild(oob); - } - - for (String jid : jids) { - msg.to = new JID(jid); - xmpp.sendOut(msg); - } - } - - public void sendJuickComment(JuickMessage jmsg) { - List users; - String replyQuote; - String replyTo; - - users = SubscriptionsQueries.getUsersSubscribedToComments(xmpp.sql, jmsg.getMID(), jmsg.getUser().getUID()); - com.juick.Message replyMessage = jmsg.ReplyTo > 0 ? MessagesQueries.getReply(xmpp.sql, jmsg.getMID(), jmsg.ReplyTo) - : MessagesQueries.getMessage(xmpp.sql, jmsg.getMID()); - replyTo = replyMessage.getUser().getUName(); - replyQuote = getReplyQuote(replyMessage); - - String txt = "Reply by @" + jmsg.getUser().getUName() + ":\n" + replyQuote + "\n@" + replyTo + " "; - String attachment = jmsg.getAttachmentURL(); - if (attachment != null) { - txt += attachment + "\n"; - } - txt += jmsg.getText() + "\n\n" + "#" + jmsg.getMID() + "/" + jmsg.getRID() + " http://juick.com/" + jmsg.getMID() + "#" + jmsg.getRID(); - - com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); - msg.from = JuickBot.JuickJID; - msg.body = txt; - msg.type = Message.Type.chat; - msg.addChild(jmsg); - for (User user : users) { - for (String jid : UserQueries.getJIDsbyUID(xmpp.sql, user.getUID())) { - msg.to = new JID(jid); - xmpp.sendOut(msg); - } - } - } - - private String getReplyQuote(com.juick.Message q) { - String quote = q.getText(); - if (quote.length() > 50) { - quote = ">" + quote.substring(0, 47).replace('\n', ' ') + "...\n"; - } else if (quote.length() > 0) { - quote = ">" + quote.replace('\n', ' ') + "\n"; - } - return quote; - } - - public void sendJuickRecommendation(JuickMessage recomm) { - List users; - JuickMessage jmsg; - jmsg = new JuickMessage(MessagesQueries.getMessage(xmpp.sql, recomm.getMID())); - users = SubscriptionsQueries.getUsersSubscribedToUserRecommendations(xmpp.sql, - recomm.getUser().getUID(), recomm.getMID(), jmsg.getUser().getUID()); - - String txt = "Recommended by @" + recomm.getUser().getUName() + ":\n"; - txt += "@" + jmsg.getUser().getUName() + ":" + jmsg.getTagsString() + "\n"; - String attachment = jmsg.getAttachmentURL(); - if (attachment != null) { - txt += attachment + "\n"; - } - txt += jmsg.getText() + "\n\n"; - txt += "#" + jmsg.getMID(); - if (jmsg.Replies > 0) { - if (jmsg.Replies % 10 == 1 && jmsg.Replies % 100 != 11) { - txt += " (" + jmsg.Replies + " reply)"; - } else { - txt += " (" + jmsg.Replies + " replies)"; - } - } - txt += " http://juick.com/" + jmsg.getMID(); - - Nickname nick = new Nickname(); - nick.Nickname = "@" + jmsg.getUser().getUName(); - - com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); - msg.from = JuickBot.JuickJID; - msg.body = txt; - msg.type = Message.Type.chat; - msg.thread = "juick-" + jmsg.getMID(); - msg.addChild(jmsg); - msg.addChild(nick); - if (attachment != null) { - XOOB oob = new XOOB(); - oob.URL = attachment; - msg.addChild(oob); - } - - for (User user : users) { - for (String jid : UserQueries.getJIDsbyUID(xmpp.sql, user.getUID())) { - msg.to = new JID(jid); - xmpp.sendOut(msg); - } - } - } - - @Override - public boolean onIq(Iq iq) { - JID jid = iq.to; - if (!jid.Host.equals(componentName)) { - logger.info("STREAM ROUTER (IQ): " + iq.toString()); - xmpp.sendOut(iq); - } - return false; - } - - @Override - public void onMessage(Message xmsg) { - logger.info("STREAM ROUTER (PROCESS): " + xmsg.toString()); - JuickMessage jmsg = (JuickMessage) xmsg.getChild(JuickMessage.XMLNS); - JID jid = xmsg.to; - if (jid.Host.equals(componentName)) { - if (jmsg != null) { - if (jid.Username != null && jid.Username.equals("recomm")) { - sendJuickRecommendation(jmsg); - } else { - if (jmsg.getRID() > 0) { - sendJuickComment(jmsg); - } else if (jmsg.getMID() > 0) { - sendJuickMessage(jmsg); - } - } - } - } else { - xmpp.sendOut(xmsg); - } - } - - @Override - public void onPresence(Presence presence) { - JID jid = presence.to; - if (!jid.Host.equals(componentName)) { - logger.info("STREAM ROUTER (PRESENCE): " + presence.toString()); - xmpp.sendOut(presence); - } - } - - @Override - public void onStreamReady() { - logger.info("STREAM ROUTER (READY)"); - } - - @Override - public void onStreamFail(Exception ex) { - logger.log(Level.SEVERE, "STREAM ROUTER (FAIL)", ex); - reconnect(); - } -} diff --git a/src/main/java/com/juick/xmpp/s2s/JuickBot.java b/src/main/java/com/juick/xmpp/s2s/JuickBot.java index 18e7580d..f0b71689 100644 --- a/src/main/java/com/juick/xmpp/s2s/JuickBot.java +++ b/src/main/java/com/juick/xmpp/s2s/JuickBot.java @@ -20,11 +20,12 @@ import java.util.regex.Pattern; */ public class JuickBot { XMPPComponent xmpp; - public JuickBot(XMPPComponent xmpp) { + public JuickBot(XMPPComponent xmpp, JID JuickJID) { this.xmpp = xmpp; + this.JuickJID = JuickJID; } - public static final JID JuickJID = new JID("juick", "juick.com", "Juick"); + public final JID JuickJID; private static final String HELPTEXT = "@username text - Send private message\n" + "*tagname Blah-blah-blah - Post a message with tag 'tagname'\n" @@ -187,10 +188,10 @@ public class JuickBot { jmsg.setUser(user_from); jmsg.setText(msg.body); m.childs.add(jmsg); - xmpp.connRouter.router.send(m.toString()); + xmpp.router.send(m.toString()); m.to.Host = "ws.juick.com"; - xmpp.connRouter.router.send(m.toString()); + xmpp.router.send(m.toString()); List jids; boolean inroster = false; @@ -315,10 +316,10 @@ public class JuickBot { jmsg.setUser(user_from); jmsg.setText(body); msg.childs.add(jmsg); - xmpp.connRouter.router.send(msg.toString()); + xmpp.router.send(msg.toString()); msg.to.Host = "ws.juick.com"; - xmpp.connRouter.router.send(msg.toString()); + xmpp.router.send(msg.toString()); for (String jid : jids_to) { Message mm = new Message(); diff --git a/src/main/java/com/juick/xmpp/s2s/XMPPComponent.java b/src/main/java/com/juick/xmpp/s2s/XMPPComponent.java index ecfe1671..9b8bb07e 100644 --- a/src/main/java/com/juick/xmpp/s2s/XMPPComponent.java +++ b/src/main/java/com/juick/xmpp/s2s/XMPPComponent.java @@ -1,8 +1,13 @@ package com.juick.xmpp.s2s; -import com.juick.xmpp.Stanza; -import com.juick.xmpp.StanzaChild; +import com.juick.User; +import com.juick.server.MessagesQueries; +import com.juick.server.SubscriptionsQueries; +import com.juick.server.UserQueries; +import com.juick.xmpp.*; import com.juick.xmpp.extensions.JuickMessage; +import com.juick.xmpp.extensions.Nickname; +import com.juick.xmpp.extensions.XOOB; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.xmlpull.v1.XmlPullParserException; @@ -17,9 +22,6 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -30,19 +32,20 @@ import java.util.logging.Logger; * * @author ugnich */ -public class XMPPComponent implements ServletContextListener { +public class XMPPComponent implements ServletContextListener, Stream.StreamListener, + Message.MessageListener, Iq.IqListener, Presence.PresenceListener { - private static final Logger LOGGER = Logger.getLogger(XMPPComponent.class.getName()); + private static final Logger logger = Logger.getLogger(XMPPComponent.class.getName()); public final ExecutorService executorService = Executors.newCachedThreadPool(); - ExecutorService xmppService = Executors.newSingleThreadExecutor(); + StreamComponent router; + JuickBot bot; - public String HOSTNAME = null; + public String HOSTNAME, componentName; public String STATSFILE = null; public String keystore; public String keystorePassword; public List brokenSSLhosts; - public ConnectionRouter connRouter; final List inConnections = Collections.synchronizedList(new ArrayList<>()); final List outConnections = Collections.synchronizedList(new ArrayList<>()); final List outCache = Collections.synchronizedList(new ArrayList<>()); @@ -135,7 +138,7 @@ public class XMPPComponent implements ServletContextListener { try { connOut.sendStanza(xml); } catch (IOException e) { - LOGGER.warning("STREAM TO " + connOut.to + " " + connOut.streamID + " ERROR: " + e.toString()); + logger.warning("STREAM TO " + connOut.to + " " + connOut.streamID + " ERROR: " + e.toString()); } return; } @@ -161,7 +164,7 @@ public class XMPPComponent implements ServletContextListener { connectionOut = new ConnectionOut(this, hostname); executorService.submit(connectionOut); } catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | XmlPullParserException | KeyStoreException | KeyManagementException | IOException e) { - LOGGER.log(Level.SEVERE, "s2s out error", e); + logger.log(Level.SEVERE, "s2s out error", e); } } } @@ -169,12 +172,13 @@ public class XMPPComponent implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { - LOGGER.info("component initialized"); + logger.info("component initialized"); Properties conf = new Properties(); try { conf.load(sce.getServletContext().getResourceAsStream("/WEB-INF/juick.conf")); HOSTNAME = conf.getProperty("hostname"); - String componentName = conf.getProperty("componentname"); + componentName = conf.getProperty("componentname"); + JID Jid = new JID(conf.getProperty("xmppbot_jid")); STATSFILE = conf.getProperty("statsfile"); keystore = conf.getProperty("keystore"); keystorePassword = conf.getProperty("keystore_password"); @@ -183,27 +187,34 @@ public class XMPPComponent implements ServletContextListener { dataSource.setDriverClassName(conf.getProperty("datasource_driver", "com.mysql.jdbc.Driver")); dataSource.setUrl(conf.getProperty("datasource_url")); sql = new JdbcTemplate(dataSource); + bot = new JuickBot(this, Jid); childParsers.put(JuickMessage.XMLNS, new JuickMessage()); - xmppService.submit(() -> { - connRouter = new ConnectionRouter(this, componentName, conf.getProperty("xmpp_password")); + Socket routerSocket = new Socket("localhost", 5347); + router = new StreamComponent(new JID("s2s"), routerSocket.getInputStream(), routerSocket.getOutputStream(), conf.getProperty("xmpp_password")); + router.addChildParser(new JuickMessage()); + router.addListener((Stream.StreamListener) this); + router.addListener((Message.MessageListener) this); + router.addListener((Iq.IqListener) this); + executorService.submit(() -> { + router.startParsing(); executorService.submit(new CleaningUp(this)); final ServerSocket listener = new ServerSocket(5269); - LOGGER.info("s2s listener ready"); + logger.info("s2s listener ready"); while (true) { try { Socket socket = listener.accept(); - ConnectionIn client = new ConnectionIn(this, new JuickBot(this), socket); + ConnectionIn client = new ConnectionIn(this, bot, socket); addConnectionIn(client); executorService.submit(client); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "s2s error", e); + logger.log(Level.SEVERE, "s2s error", e); } } }); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "XMPPComponent error", e); + logger.log(Level.SEVERE, "XMPPComponent error", e); } } @@ -228,11 +239,198 @@ public class XMPPComponent implements ServletContextListener { } try { - connRouter.closeConnection(); + closeRouterConnection(); } catch (IOException e) { - LOGGER.log(Level.WARNING, "router warning", e); + logger.log(Level.WARNING, "router warning", e); } executorService.shutdown(); - LOGGER.info("component destroyed"); + logger.info("component destroyed"); + } + public void closeRouterConnection() throws IOException { + router.logoff(); + } + + public void sendJuickMessage(JuickMessage jmsg) { + List jids = new ArrayList<>(); + + if (jmsg.FriendsOnly) { + jids = SubscriptionsQueries.getJIDSubscribedToUser(sql, jmsg.getUser().getUID(), jmsg.FriendsOnly); + } else { + List users = SubscriptionsQueries.getSubscribedUsers(sql, jmsg.getUser().getUID(), jmsg.getMID()); + for (User user : users) { + for (String jid : UserQueries.getJIDsbyUID(sql, user.getUID())) { + jids.add(jid); + } + } + } + + String txt = "@" + jmsg.getUser().getUName() + ":" + jmsg.getTagsString() + "\n"; + String attachment = jmsg.getAttachmentURL(); + if (attachment != null) { + txt += attachment + "\n"; + } + txt += jmsg.getText() + "\n\n"; + txt += "#" + jmsg.getMID() + " http://juick.com/" + jmsg.getMID(); + + Nickname nick = new Nickname(); + nick.Nickname = "@" + jmsg.getUser().getUName(); + + com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); + msg.from = bot.JuickJID; + msg.body = txt; + msg.type = Message.Type.chat; + msg.thread = "juick-" + jmsg.getMID(); + msg.addChild(jmsg); + msg.addChild(nick); + if (attachment != null) { + XOOB oob = new XOOB(); + oob.URL = attachment; + msg.addChild(oob); + } + + for (String jid : jids) { + msg.to = new JID(jid); + sendOut(msg); + } + } + + public void sendJuickComment(JuickMessage jmsg) { + List users; + String replyQuote; + String replyTo; + + users = SubscriptionsQueries.getUsersSubscribedToComments(sql, jmsg.getMID(), jmsg.getUser().getUID()); + com.juick.Message replyMessage = jmsg.ReplyTo > 0 ? MessagesQueries.getReply(sql, jmsg.getMID(), jmsg.ReplyTo) + : MessagesQueries.getMessage(sql, jmsg.getMID()); + replyTo = replyMessage.getUser().getUName(); + replyQuote = getReplyQuote(replyMessage); + + String txt = "Reply by @" + jmsg.getUser().getUName() + ":\n" + replyQuote + "\n@" + replyTo + " "; + String attachment = jmsg.getAttachmentURL(); + if (attachment != null) { + txt += attachment + "\n"; + } + txt += jmsg.getText() + "\n\n" + "#" + jmsg.getMID() + "/" + jmsg.getRID() + " http://juick.com/" + jmsg.getMID() + "#" + jmsg.getRID(); + + com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); + msg.from = bot.JuickJID; + msg.body = txt; + msg.type = Message.Type.chat; + msg.addChild(jmsg); + for (User user : users) { + for (String jid : UserQueries.getJIDsbyUID(sql, user.getUID())) { + msg.to = new JID(jid); + sendOut(msg); + } + } + } + + private String getReplyQuote(com.juick.Message q) { + String quote = q.getText(); + if (quote.length() > 50) { + quote = ">" + quote.substring(0, 47).replace('\n', ' ') + "...\n"; + } else if (quote.length() > 0) { + quote = ">" + quote.replace('\n', ' ') + "\n"; + } + return quote; + } + + public void sendJuickRecommendation(JuickMessage recomm) { + List users; + JuickMessage jmsg; + jmsg = new JuickMessage(MessagesQueries.getMessage(sql, recomm.getMID())); + users = SubscriptionsQueries.getUsersSubscribedToUserRecommendations(sql, + recomm.getUser().getUID(), recomm.getMID(), jmsg.getUser().getUID()); + + String txt = "Recommended by @" + recomm.getUser().getUName() + ":\n"; + txt += "@" + jmsg.getUser().getUName() + ":" + jmsg.getTagsString() + "\n"; + String attachment = jmsg.getAttachmentURL(); + if (attachment != null) { + txt += attachment + "\n"; + } + txt += jmsg.getText() + "\n\n"; + txt += "#" + jmsg.getMID(); + if (jmsg.Replies > 0) { + if (jmsg.Replies % 10 == 1 && jmsg.Replies % 100 != 11) { + txt += " (" + jmsg.Replies + " reply)"; + } else { + txt += " (" + jmsg.Replies + " replies)"; + } + } + txt += " http://juick.com/" + jmsg.getMID(); + + Nickname nick = new Nickname(); + nick.Nickname = "@" + jmsg.getUser().getUName(); + + com.juick.xmpp.Message msg = new com.juick.xmpp.Message(); + msg.from = bot.JuickJID; + msg.body = txt; + msg.type = Message.Type.chat; + msg.thread = "juick-" + jmsg.getMID(); + msg.addChild(jmsg); + msg.addChild(nick); + if (attachment != null) { + XOOB oob = new XOOB(); + oob.URL = attachment; + msg.addChild(oob); + } + + for (User user : users) { + for (String jid : UserQueries.getJIDsbyUID(sql, user.getUID())) { + msg.to = new JID(jid); + sendOut(msg); + } + } + } + + @Override + public boolean onIq(Iq iq) { + JID jid = iq.to; + if (!jid.Host.equals(componentName)) { + logger.info("STREAM ROUTER (IQ): " + iq.toString()); + sendOut(iq); + } + return false; + } + + @Override + public void onMessage(Message xmsg) { + logger.info("STREAM ROUTER (PROCESS): " + xmsg.toString()); + JuickMessage jmsg = (JuickMessage) xmsg.getChild(JuickMessage.XMLNS); + JID jid = xmsg.to; + if (jid.Host.equals(componentName)) { + if (jmsg != null) { + if (jid.Username != null && jid.Username.equals("recomm")) { + sendJuickRecommendation(jmsg); + } else { + if (jmsg.getRID() > 0) { + sendJuickComment(jmsg); + } else if (jmsg.getMID() > 0) { + sendJuickMessage(jmsg); + } + } + } + } else { + sendOut(xmsg); + } + } + + @Override + public void onPresence(Presence presence) { + JID jid = presence.to; + if (!jid.Host.equals(componentName)) { + logger.info("STREAM ROUTER (PRESENCE): " + presence.toString()); + sendOut(presence); + } + } + + @Override + public void onStreamReady() { + logger.info("STREAM ROUTER (READY)"); + } + + @Override + public void onStreamFail(Exception ex) { + logger.log(Level.SEVERE, "STREAM ROUTER (FAIL)", ex); } } -- cgit v1.2.3