package com.juick.xmpp.s2s; import com.juick.xmpp.Stanza; import com.juick.xmpp.StanzaChild; import com.juick.xmpp.extensions.JuickMessage; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.xmlpull.v1.XmlPullParserException; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyManagementException; 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; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author ugnich */ public class XMPPComponent implements ServletContextListener { private static final Logger LOGGER = Logger.getLogger(XMPPComponent.class.getName()); public final ExecutorService executorService = Executors.newCachedThreadPool(); ExecutorService xmppService = Executors.newSingleThreadExecutor(); public String HOSTNAME = null; 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<>()); JdbcTemplate sql; final public HashMap childParsers = new HashMap<>(); public void addConnectionIn(ConnectionIn c) { synchronized (inConnections) { inConnections.add(c); } } public void addConnectionOut(ConnectionOut c) { synchronized (outConnections) { outConnections.add(c); } } public void removeConnectionIn(ConnectionIn c) { synchronized (inConnections) { inConnections.remove(c); } } public void removeConnectionOut(ConnectionOut c) { synchronized (outConnections) { outConnections.remove(c); } } public String getFromCache(String hostname) { CacheEntry ret = null; synchronized (outCache) { for (Iterator i = outCache.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; } public ConnectionOut getConnectionOut(String hostname, boolean needReady) { synchronized (outConnections) { for (ConnectionOut c : outConnections) { if (c.to != null && c.to.equals(hostname) && (!needReady || c.streamReady)) { return c; } } } return null; } public ConnectionIn getConnectionIn(String streamID) { synchronized (inConnections) { for (ConnectionIn c : inConnections) { if (c.streamID != null && c.streamID.equals(streamID)) { return c; } } } return null; } public void sendOut(Stanza s) { sendOut(s.to.Host, s.toString()); } public void sendOut(String hostname, String xml) { boolean haveAnyConn = false; ConnectionOut connOut = null; synchronized (outConnections) { for (ConnectionOut c : outConnections) { if (c.to != null && c.to.equals(hostname)) { if (c.streamReady) { connOut = c; break; } else { haveAnyConn = true; break; } } } } if (connOut != null) { try { connOut.sendStanza(xml); } catch (IOException e) { LOGGER.warning("STREAM TO " + connOut.to + " " + connOut.streamID + " ERROR: " + e.toString()); } return; } boolean haveCache = false; synchronized (outCache) { 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) { ConnectionOut connectionOut = null; try { 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); } } } @Override public void contextInitialized(ServletContextEvent sce) { LOGGER.info("component initialized"); xmppService.submit(() -> { Properties conf = new Properties(); try { conf.load(sce.getServletContext().getResourceAsStream("/WEB-INF/juick.conf")); HOSTNAME = conf.getProperty("hostname"); String componentName = conf.getProperty("componentname"); STATSFILE = conf.getProperty("statsfile"); keystore = conf.getProperty("keystore"); keystorePassword = conf.getProperty("keystore_password"); brokenSSLhosts = Arrays.asList(conf.getProperty("broken_ssl_hosts", "").split(",")); DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(conf.getProperty("datasource_driver", "com.mysql.jdbc.Driver")); dataSource.setUrl(conf.getProperty("datasource_url")); sql = new JdbcTemplate(dataSource); childParsers.put(JuickMessage.XMLNS, new JuickMessage()); executorService.submit(() -> connRouter = new ConnectionRouter(this, componentName, conf.getProperty("xmpp_password"))); executorService.submit(new CleaningUp(this)); final ServerSocket listener = new ServerSocket(5269); LOGGER.info("s2s listener ready"); while (true) { try { Socket socket = listener.accept(); ConnectionIn client = new ConnectionIn(this, new JuickBot(this), socket); addConnectionIn(client); executorService.submit(client); } catch (Exception e) { LOGGER.log(Level.SEVERE, "s2s error", e); } } } catch (Exception e) { LOGGER.log(Level.SEVERE, "XMPPComponent error", e); } }); } @Override public void contextDestroyed(ServletContextEvent sce) { synchronized (outConnections) { for (Iterator i = outConnections.iterator(); i.hasNext();) { ConnectionOut c = i.next(); c.closeConnection(); i.remove(); } } synchronized (inConnections) { for (Iterator i = inConnections.iterator(); i.hasNext();) { ConnectionIn c = i.next(); c.closeConnection(); i.remove(); } } try { connRouter.closeConnection(); } catch (IOException e) { LOGGER.log(Level.WARNING, "router warning", e); } // Now deregister JDBC drivers in this context's ClassLoader: // Get the webapp's ClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); // Loop through all drivers Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); if (driver.getClass().getClassLoader() == cl) { // This driver was registered by the webapp's ClassLoader, so deregister it: try { LOGGER.info(String.format("Deregistering JDBC driver %s", driver.toString())); DriverManager.deregisterDriver(driver); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, String.format("Error deregistering JDBC driver %s", driver), ex); } } else { // driver was not registered by the webapp's ClassLoader and may be in use elsewhere LOGGER.log(Level.SEVERE, String.format("Not deregistering JDBC driver %s as it does not belong to this webapp's ClassLoader", driver)); } } executorService.shutdown(); LOGGER.info("component destroyed"); } }