private void startService(Map<String, Object> port_props) { if (port_props == null) { throw new NullPointerException("port_props cannot be null."); } ConnectionListenerImpl cli = new ConnectionListenerImpl(port_props); if (cli.getConnectionType() == ConnectionType.accept) { pending_open.add(cli); } connectThread.addConnectionOpenListener(cli); }
/** * Describe class ConnectionManager here. * * <p>Created: Sun Jan 22 22:52:58 2006 * * @param <IO> * @author <a href="mailto:[email protected]">Artur Hefczyc</a> * @version $Rev$ */ public abstract class ConnectionManager<IO extends XMPPIOService<?>> extends AbstractMessageReceiver implements XMPPIOServiceListener<IO> { /** Field description */ public static final String HT_TRAFFIC_THROTTLING_PROP_KEY = "--cm-ht-traffic-throttling"; /** Field description */ public static final String HT_TRAFFIC_THROTTLING_PROP_VAL = "xmpp:25k:0:disc,bin:200m:0:disc"; /** Field description. */ public static final String NET_BUFFER_HT_PROP_KEY = "--net-buff-high-throughput"; /** Field description. */ public static final String NET_BUFFER_ST_PROP_KEY = "--net-buff-standard"; /** Field description. */ public static final String PORT_LOCAL_HOST_PROP_KEY = "local-host"; /** Field description */ public static final String ST_TRAFFIC_THROTTLING_PROP_KEY = "--cm-traffic-throttling"; /** Field description */ public static final String ST_TRAFFIC_THROTTLING_PROP_VAL = "xmpp:2500:0:disc,bin:20m:0:disc"; /** Field description */ public static final String TRAFFIC_THROTTLING_PROP_KEY = "traffic-throttling"; /** Field description */ protected static final long LAST_MINUTE_BIN_LIMIT_PROP_VAL = 20000000L; /** Field description */ protected static final long LAST_MINUTE_PACKETS_LIMIT_PROP_VAL = 2500L; /** Field description */ protected static final String MAX_INACTIVITY_TIME = "max-inactivity-time"; /** Field description */ protected static final String MAX_RECONNECTS_PROP_KEY = "max-reconnects"; /** Field description */ protected static final int NET_BUFFER_HT_PROP_VAL = 64 * 1024; /** Field description */ protected static final String NET_BUFFER_PROP_KEY = "net-buffer"; /** Field description */ protected static final int NET_BUFFER_ST_PROP_VAL = 2 * 1024; /** Field description */ protected static final int NET_BUFFER_LIMIT_HT_PROP_VAL = 20 * 1024 * 1024; /** Field description */ protected static final String NET_BUFFER_LIMIT_PROP_KEY = "net-buffer-limit"; /** Field description */ protected static final int NET_BUFFER_LIMIT_ST_PROP_VAL = 2 * 1024 * 1024; /** * Key name of the system property for configuration protection from system overload and DOS * attack. */ public static final String ELEMENTS_NUMBER_LIMIT_PROP_KEY = "elements-number-limit"; /** * Default value for the system property for configuration protection from system overload and DOS * attack. */ public static int ELEMENTS_NUMBER_LIMIT_PROP_VAL = 1000; /** Field description */ protected static final String PORT_CLASS_PROP_KEY = "class"; /** Field description */ protected static final String PORT_IFC_PROP_KEY = "ifc"; /** Field description */ protected static final String PORT_KEY = "port-no"; /** Field description */ protected static final String PORT_REMOTE_HOST_PROP_KEY = "remote-host"; /** Field description */ protected static final String PORT_REMOTE_HOST_PROP_VAL = "localhost"; /** Field description */ protected static final String PORT_SOCKET_PROP_KEY = "socket"; /** Field description */ protected static final String PORT_TYPE_PROP_KEY = "type"; /** Field description */ protected static final String PROP_KEY = "connections/"; /** Field description */ protected static final long TOTAL_BIN_LIMIT_PROP_VAL = 0L; /** Field description */ protected static final long TOTAL_PACKETS_LIMIT_PROP_VAL = 0L; /** Field description */ protected static final String WHITE_CHAR_ACK_PROP_KEY = "white-char-ack"; /** Field description */ protected static final String XMPP_ACK_PROP_KEY = "xmpp-ack"; private static final Logger log = Logger.getLogger(ConnectionManager.class.getName()); private static ConnectionOpenThread connectThread = ConnectionOpenThread.getInstance(); /** Field description */ protected static final boolean XMPP_ACK_PROP_VAL = false; /** Field description */ protected static final boolean WHITE_CHAR_ACK_PROP_VAL = false; /** Field description */ protected static final String PORTS_PROP_KEY = PROP_KEY + "ports"; /** Field description */ protected static final boolean TLS_USE_PROP_VAL = true; // J- /** Field description */ protected static final String TLS_PROP_KEY = PROP_KEY + "tls/"; /** Field description */ protected static final String TLS_USE_PROP_KEY = TLS_PROP_KEY + "use"; /** Field description */ protected static final boolean TLS_REQUIRED_PROP_VAL = false; /** Field description */ protected static final String TLS_REQUIRED_PROP_KEY = TLS_PROP_KEY + "required"; protected static final String WATCHDOG_DELAY = "watchdog_delay"; protected static final String WATCHDOG_TIMEOUT = "watchdog_timeout"; protected static final String WATCHDOG_PING_TYPE_KEY = "watchdog_ping_type"; protected static final Element pingElement = new Element( "iq", new Element[] { new Element("ping", new String[] {"xmlns"}, new String[] {"urn:xmpp:ping"}) }, new String[] {"type", "id"}, new String[] {"get", "tigase-ping"}); // ~--- fields --------------------------------------------------------------- /** Field description. */ public String[] PORT_IFC_PROP_VAL = {"*"}; private long bytesReceived = 0; private long bytesSent = 0; private int services_size = 0; private long socketOverflow = 0; private Thread watchdog = null; private long watchdogRuns = 0; private long watchdogStopped = 0; private long watchdogTests = 0; protected long watchdogDelay = 10 * MINUTE; // 600 000 protected long watchdogTimeout = 29 * MINUTE; // 1 740 000 private boolean white_char_ack = WHITE_CHAR_ACK_PROP_VAL; private boolean xmpp_ack = XMPP_ACK_PROP_VAL; private LinkedList<Map<String, Object>> waitingTasks = new LinkedList<Map<String, Object>>(); private long total_packets_limit = TOTAL_PACKETS_LIMIT_PROP_VAL; private long total_bin_limit = TOTAL_BIN_LIMIT_PROP_VAL; /** * Protection from the system overload and DOS attack. We want to limit number of elements created * within a single XMPP stanza. */ protected int elements_number_limit = 0; private ConcurrentHashMap<String, IO> services = new ConcurrentHashMap<String, IO>(); private Set<ConnectionListenerImpl> pending_open = Collections.synchronizedSet(new HashSet<ConnectionListenerImpl>());; private long maxInactivityTime = getMaxInactiveTime(); private long last_minute_packets_limit = LAST_MINUTE_PACKETS_LIMIT_PROP_VAL; private long last_minute_bin_limit = LAST_MINUTE_BIN_LIMIT_PROP_VAL; private int net_buffer_limit = 0; private IOServiceStatisticsGetter ioStatsGetter = new IOServiceStatisticsGetter(); private boolean initializationCompleted = false; /** Field description */ protected int net_buffer = NET_BUFFER_ST_PROP_VAL; /** Field description */ protected long connectionDelay = 2 * SECOND; private LIMIT_ACTION xmppLimitAction = LIMIT_ACTION.DISCONNECT; protected WATCHDOG_PING_TYPE watchdogPingType = WATCHDOG_PING_TYPE.WHITESPACE; // ~--- constant enums ------------------------------------------------------- /** Describe class <code>LIMIT_ACTION</code> here. */ public static enum LIMIT_ACTION { DISCONNECT, DROP_PACKETS; } /** * Holds possible types of ping to be used in watchdog service for detection of broken connections */ public static enum WATCHDOG_PING_TYPE { WHITESPACE, XMPP; } // ~--- methods -------------------------------------------------------------- /** * Method description * * @param serv * @return a value of <code>boolean</code> */ public boolean checkTrafficLimits(IO serv) { boolean xmppLimitHit = false; if (last_minute_packets_limit > 0) { xmppLimitHit = (serv.getPacketsReceived(false) >= last_minute_packets_limit) // || (serv.getPacketsSent(false) >= last_minute_packets_limit) ; } if (!xmppLimitHit && (total_packets_limit > 0)) { xmppLimitHit = (serv.getTotalPacketsReceived() >= total_packets_limit) // || (serv.getTotalPacketsSent() >= total_packets_limit) ; } if (xmppLimitHit) { Level level = Level.FINER; if (isHighThroughput()) { level = Level.WARNING; } switch (xmppLimitAction) { case DROP_PACKETS: if (log.isLoggable(level)) { log.log( level, "[[{0}]] XMPP Limits exceeded on connection {1}" + " dropping pakcets: {2}", new Object[] {getName(), serv, serv.getReceivedPackets()}); } while (serv.getReceivedPackets().poll() != null) ; break; default: if (log.isLoggable(level)) { log.log( level, "[[{0}]] XMPP Limits exceeded on connection {1}" + " stopping, packets dropped: {2}", new Object[] {getName(), serv, serv.getReceivedPackets()}); } serv.forceStop(); break; } return false; } boolean binLimitHit = false; long bytesSent = serv.getBytesSent(false); long bytesReceived = serv.getBytesReceived(false); if (last_minute_bin_limit > 0) { binLimitHit = (bytesSent >= last_minute_bin_limit) || (bytesReceived >= last_minute_bin_limit); } long totalSent = serv.getTotalBytesSent(); long totalReceived = serv.getTotalBytesReceived(); if (!binLimitHit && (total_bin_limit > 0)) { binLimitHit = (totalReceived >= total_bin_limit) || (totalSent >= total_bin_limit); } if (binLimitHit) { if (log.isLoggable(Level.FINE)) { log.log( Level.FINE, "[[{0}]] Binary Limits exceeded ({1}:{2}:{3}:{4}) on" + " connection {5} stopping, packets dropped: {6}", new Object[] { getName(), bytesSent, bytesReceived, totalSent, totalReceived, serv, serv.getReceivedPackets() }); } serv.forceStop(); return false; } return true; } @Override public synchronized void everyMinute() { super.everyMinute(); // This variable used to provide statistics gets off on a busy // services as it is handled in methods called concurrently by // many threads. While accuracy of this variable is not critical // for the server functions, statistics should be as accurate as // possible to provide valuable metrics data. // So in the watchdog thread we re-synchronize this number int tmp = services.size(); services_size = tmp; doForAllServices(ioStatsGetter); } @Override public int hashCodeForPacket(Packet packet) { if (packet.getStanzaTo() != null) { return packet.getStanzaTo().hashCode(); } if (packet.getTo() != null) { return packet.getTo().hashCode(); } return super.hashCodeForPacket(packet); } @Override public void initBindings(Bindings binds) { super.initBindings(binds); binds.put(CommandIfc.SERVICES_MAP, services); } @Override public void initializationCompleted() { if (isInitializationComplete()) { // Do we really need to do this again? return; } super.initializationCompleted(); initializationCompleted = true; for (Map<String, Object> params : waitingTasks) { reconnectService(params, connectionDelay); } waitingTasks.clear(); if (null != watchdog) { watchdog.start(); } } @Override public void packetsReady(IO serv) throws IOException { // Under a high load data, especially lots of packets on a single // connection it may happen that one threads started processing // socketData and then another thread reads more packets which // may take over earlier data depending on a thread scheduler used. // synchronized (serv) { if (checkTrafficLimits(serv)) { writePacketsToSocket(serv, processSocketData(serv)); } } @Override public int processingInThreads() { return Runtime.getRuntime().availableProcessors() * 4; } @Override public int processingOutThreads() { return Runtime.getRuntime().availableProcessors() * 4; } @Override public void processPacket(Packet packet) { writePacketToSocket(packet); } /** * Method description * * @param serv * @return a value of {@code Queue<Packet>} */ public abstract Queue<Packet> processSocketData(IO serv); /** * Processes undelivered packets * * @param packet * @param stamp - timestamp when packet was received to be written to XMPPIOService * @param errorMessage */ public boolean processUndeliveredPacket(Packet packet, Long stamp, String errorMessage) { return false; } /** * Method description * * @param port_props */ public abstract void reconnectionFailed(Map<String, Object> port_props); @Override public void release() { // delayedTasks.cancel(); releaseListeners(); super.release(); } /** * Method description * * @param service */ @TODO( note = "Do something if service with the same unique ID is already started, " + "possibly kill the old one...") public void serviceStarted(final IO service) { // synchronized(services) { String id = getUniqueId(service); if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "[[{0}]] Connection started: {1}", new Object[] {getName(), service}); } IO serv = services.get(id); if (serv != null) { if (serv == service) { log.log( Level.WARNING, "{0}: That would explain a lot, adding the same service twice, ID: {1}", new Object[] {getName(), serv}); } else { // Is it at all possible to happen??? // let's log it for now.... log.log( Level.FINE, "{0}: Attempt to add different service with the same ID: {1}", new Object[] {getName(), service}); // And stop the old service.... serv.stop(); } } services.put(id, service); ++services_size; // } } @Override public boolean serviceStopped(IO service) { // Hopefuly there is no exception at this point, but just in case... // This is a very fresh code after all try { ioStatsGetter.check(service); } catch (Exception e) { log.log(Level.INFO, "Nothing serious to worry about but please notify the developer.", e); } // synchronized(service) { String id = getUniqueId(service); if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "[[{0}]] Connection stopped: {1}", new Object[] {getName(), service}); } // id might be null if service is stopped in accept method due to // an exception during establishing TCP/IP connection // IO serv = (id != null ? services.get(id) : null); if (id != null) { boolean result = services.remove(id, service); if (result) { --services_size; } else if (log.isLoggable(Level.FINER)) { // Is it at all possible to happen??? // let's log it for now.... log.log( Level.FINER, "[[{0}]] Attempt to stop incorrect service: {1}", new Object[] {getName(), service}); } return result; } return false; } @Override public void stop() { this.releaseListeners(); // when stopping connection manager we need to stop all active connections as well for (IO service : services.values()) { service.forceStop(); } super.stop(); } /** * Method description * * @param port_props */ public void updateConnectionDetails(Map<String, Object> port_props) {} /** * Method description * * @param serv * @param packets */ public void writePacketsToSocket(IO serv, Queue<Packet> packets) { if (serv != null) { // synchronized (serv) { if ((packets != null) && (packets.size() > 0)) { Packet p = null; while ((p = packets.poll()) != null) { if (log.isLoggable(Level.FINER) && !log.isLoggable(Level.FINEST)) { log.log( Level.FINER, "{0}, Processing packet: {1}, type: {2}", new Object[] {serv, p.getElemName(), p.getType()}); } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, Writing packet: {1}", new Object[] {serv, p}); } serv.addPacketToSend(p); } // end of for () try { serv.processWaitingPackets(); SocketThread.addSocketService(serv); } catch (Exception e) { log.log(Level.WARNING, serv + "Exception during writing packets: ", e); try { serv.stop(); } catch (Exception e1) { log.log(Level.WARNING, serv + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } // end of try-catch } // } } else { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Can't find service for packets: [{0}] ", packets); } } // end of if (ios != null) else } /** * Method description * * @param ios * @param p * @return a value of <code>boolean</code> */ public boolean writePacketToSocket(IO ios, Packet p) { if (ios != null) { if (log.isLoggable(Level.FINER) && !log.isLoggable(Level.FINEST)) { log.log( Level.FINER, "{0}, Processing packet: {1}, type: {2}", new Object[] {ios, p.getElemName(), p.getType()}); } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, Writing packet: {1}", new Object[] {ios, p}); } // synchronized (ios) { ios.addPacketToSend(p); if (ios.writeInProgress.tryLock()) { try { ios.processWaitingPackets(); SocketThread.addSocketService(ios); return true; } catch (Exception e) { log.log(Level.WARNING, ios + "Exception during writing packets: ", e); try { ios.stop(); } catch (Exception e1) { log.log(Level.WARNING, ios + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } finally { ios.writeInProgress.unlock(); } } } else { if (log.isLoggable(Level.FINE)) { log.log( Level.FINE, "Can''t find service for packet: <{0}> {1}, service id: {2}", new Object[] {p.getElemName(), p.getTo(), getServiceId(p)}); } } // end of if (ios != null) else return false; } // ~--- get methods ---------------------------------------------------------- @Override public Map<String, Object> getDefaults(Map<String, Object> params) { log.log(Level.CONFIG, "{0} defaults: {1}", new Object[] {getName(), params.toString()}); Map<String, Object> props = super.getDefaults(params); props.put(TLS_USE_PROP_KEY, TLS_USE_PROP_VAL); checkHighThroughputProperty( NET_BUFFER_HT_PROP_KEY, NET_BUFFER_HT_PROP_VAL, NET_BUFFER_ST_PROP_KEY, NET_BUFFER_ST_PROP_VAL, NET_BUFFER_PROP_KEY, Integer.class, params, props); checkHighThroughputProperty( HT_TRAFFIC_THROTTLING_PROP_KEY, getDefTrafficThrottling(), ST_TRAFFIC_THROTTLING_PROP_KEY, getDefTrafficThrottling(), TRAFFIC_THROTTLING_PROP_KEY, String.class, params, props); props.put( NET_BUFFER_LIMIT_PROP_KEY, isHighThroughput() ? NET_BUFFER_LIMIT_HT_PROP_VAL : NET_BUFFER_LIMIT_ST_PROP_VAL); if (params.get("--" + ELEMENTS_NUMBER_LIMIT_PROP_KEY) != null) { elements_number_limit = Integer.valueOf((String) params.get("--" + ELEMENTS_NUMBER_LIMIT_PROP_KEY)); } else { elements_number_limit = ELEMENTS_NUMBER_LIMIT_PROP_VAL; } props.put(ELEMENTS_NUMBER_LIMIT_PROP_KEY, elements_number_limit); if (params.get("--" + WATCHDOG_DELAY) != null) { watchdogDelay = Integer.valueOf((String) params.get("--" + WATCHDOG_DELAY)); } props.put(WATCHDOG_DELAY, watchdogDelay); if (params.get("--" + WATCHDOG_TIMEOUT) != null) { watchdogTimeout = Integer.valueOf((String) params.get("--" + WATCHDOG_TIMEOUT)); } props.put(WATCHDOG_TIMEOUT, watchdogTimeout); WATCHDOG_PING_TYPE pingtype = WATCHDOG_PING_TYPE.WHITESPACE; if (params.get("--" + WATCHDOG_PING_TYPE_KEY) != null) { String type = (String) params.get("--" + WATCHDOG_PING_TYPE_KEY); pingtype = WATCHDOG_PING_TYPE.valueOf(type.toUpperCase()); } props.put(WATCHDOG_PING_TYPE_KEY, pingtype); int[] ports = null; String ports_str = (String) params.get("--" + getName() + "-ports"); if (ports_str != null) { String[] ports_stra = ports_str.split(","); ports = new int[ports_stra.length]; int k = 0; for (String p : ports_stra) { try { ports[k++] = Integer.parseInt(p); } catch (NumberFormatException e) { log.warning("Incorrect ports default settings: " + p); } } } int ports_size = 0; if (ports != null) { log.config("Port settings preset: " + Arrays.toString(ports)); for (int port : ports) { putDefPortParams(props, port, SocketType.plain); } // end of for (int i = 0; i < idx; i++) props.put(PORTS_PROP_KEY, ports); } else { int[] plains = getDefPlainPorts(); if (plains != null) { ports_size += plains.length; } // end of if (plains != null) int[] ssls = getDefSSLPorts(); if (ssls != null) { ports_size += ssls.length; } // end of if (ssls != null) if (ports_size > 0) { ports = new int[ports_size]; } // end of if (ports_size > 0) if (ports != null) { int idx = 0; if (plains != null) { idx = plains.length; for (int i = 0; i < idx; i++) { ports[i] = plains[i]; putDefPortParams(props, ports[i], SocketType.plain); } // end of for (int i = 0; i < idx; i++) } // end of if (plains != null) if (ssls != null) { for (int i = idx; i < idx + ssls.length; i++) { ports[i] = ssls[i - idx]; putDefPortParams(props, ports[i], SocketType.ssl); } // end of for (int i = 0; i < idx + ssls.length; i++) } // end of if (ssls != null) props.put(PORTS_PROP_KEY, ports); } // end of if (ports != null) } String acks = (String) params.get(XMPP_STANZA_ACK); if (acks != null) { String[] acks_arr = acks.split(","); for (String ack_type : acks_arr) { if (STANZA_WHITE_CHAR_ACK.equals(ack_type)) { white_char_ack = true; } if (STANZA_XMPP_ACK.equals(ack_type)) { xmpp_ack = true; } } } props.put(WHITE_CHAR_ACK_PROP_KEY, white_char_ack); props.put(XMPP_ACK_PROP_KEY, xmpp_ack); props.put(MAX_INACTIVITY_TIME, getMaxInactiveTime() / SECOND); return props; } @Override public void getStatistics(StatisticsList list) { super.getStatistics(list); list.add(getName(), "Open connections", services_size, Level.INFO); if (list.checkLevel(Level.FINEST) || (services.size() < 1000)) { int waitingToSendSize = 0; for (IO serv : services.values()) { waitingToSendSize += serv.waitingToSendSize(); } list.add(getName(), "Waiting to send", waitingToSendSize, Level.FINE); } list.add(getName(), "Bytes sent", bytesSent, Level.FINE); list.add(getName(), "Bytes received", bytesReceived, Level.FINE); list.add(getName(), "Socket overflow", socketOverflow, Level.FINE); list.add(getName(), "Watchdog runs", watchdogRuns, Level.FINER); list.add(getName(), "Watchdog tests", watchdogTests, Level.FINE); list.add(getName(), "Watchdog stopped", watchdogStopped, Level.FINE); } /** * Method description * * @param serviceId * @return a value of <code>IO</code> */ public IO getXMPPIOService(String serviceId) { return services.get(serviceId); } // ~--- set methods ---------------------------------------------------------- @Override public void setName(String name) { super.setName(name); setupWatchdogThread(); } protected void setupWatchdogThread() { watchdog = new Thread(new Watchdog(), "Watchdog - " + getName()); watchdog.setDaemon(true); } @Override public void setProperties(Map<String, Object> props) throws ConfigurationException { super.setProperties(props); if (props.get(MAX_INACTIVITY_TIME) != null) { maxInactivityTime = (Long) props.get(MAX_INACTIVITY_TIME) * SECOND; } if (props.get(WHITE_CHAR_ACK_PROP_KEY) != null) { white_char_ack = (Boolean) props.get(WHITE_CHAR_ACK_PROP_KEY); } if (props.get(XMPP_ACK_PROP_KEY) != null) { xmpp_ack = (Boolean) props.get(XMPP_ACK_PROP_KEY); } if (props.get(NET_BUFFER_PROP_KEY) != null) { net_buffer = (Integer) props.get(NET_BUFFER_PROP_KEY); } if (props.get(NET_BUFFER_LIMIT_PROP_KEY) != null) { net_buffer_limit = (Integer) props.get(NET_BUFFER_LIMIT_PROP_KEY); } if (props.get(TRAFFIC_THROTTLING_PROP_KEY) != null) { String[] tmp = ((String) props.get(TRAFFIC_THROTTLING_PROP_KEY)).split(","); for (String tmp_s : tmp) { String[] tmp_thr = tmp_s.split(":"); if (tmp_thr[0].equalsIgnoreCase("xmpp")) { last_minute_packets_limit = DataTypes.parseNum(tmp_thr[1], Long.class, LAST_MINUTE_PACKETS_LIMIT_PROP_VAL); log.warning(getName() + " last_minute_packets_limit = " + last_minute_packets_limit); total_packets_limit = DataTypes.parseNum(tmp_thr[2], Long.class, TOTAL_PACKETS_LIMIT_PROP_VAL); log.warning(getName() + " total_packets_limit = " + total_packets_limit); if (tmp_thr[3].equalsIgnoreCase("disc")) { xmppLimitAction = LIMIT_ACTION.DISCONNECT; } if (tmp_thr[3].equalsIgnoreCase("drop")) { xmppLimitAction = LIMIT_ACTION.DROP_PACKETS; } } if (tmp_thr[0].equalsIgnoreCase("bin")) { last_minute_bin_limit = DataTypes.parseNum(tmp_thr[1], Long.class, LAST_MINUTE_BIN_LIMIT_PROP_VAL); log.warning(getName() + " last_minute_bin_limit = " + last_minute_bin_limit); total_bin_limit = DataTypes.parseNum(tmp_thr[2], Long.class, TOTAL_BIN_LIMIT_PROP_VAL); log.warning(getName() + " total_bin_limit = " + total_bin_limit); } } } if (props.get(ELEMENTS_NUMBER_LIMIT_PROP_KEY) != null) { elements_number_limit = (int) props.get(ELEMENTS_NUMBER_LIMIT_PROP_KEY); } if (props.get(WATCHDOG_DELAY) != null) { watchdogDelay = (long) props.get(WATCHDOG_DELAY); } if (props.get(WATCHDOG_TIMEOUT) != null) { watchdogTimeout = (long) props.get(WATCHDOG_TIMEOUT); } if (props.get(WATCHDOG_PING_TYPE_KEY) != null) { String value = String.valueOf(props.get(WATCHDOG_PING_TYPE_KEY)); watchdogPingType = WATCHDOG_PING_TYPE.valueOf(value.toUpperCase()); } if (props.size() == 1) { // If props.size() == 1, it means this is a single property update and // ConnectionManager does not support it yet. return; } if (isInitializationComplete()) { // Do we really need to do this again? // Looks like reconfiguration for the port is not working correctly anyway // so for now we do not want to do it. return; } releaseListeners(); int[] ports = (int[]) props.get(PORTS_PROP_KEY); if (ports != null) { for (int i = 0; i < ports.length; i++) { Map<String, Object> port_props = new LinkedHashMap<String, Object>(20); for (Map.Entry<String, Object> entry : props.entrySet()) { if (entry.getKey().startsWith(PROP_KEY + ports[i])) { int idx = entry.getKey().lastIndexOf('/'); String key = entry.getKey().substring(idx + 1); log.log( Level.CONFIG, "Adding port property key: {0}={1}", new Object[] {key, entry.getValue()}); port_props.put(key, entry.getValue()); } // end of if (entry.getKey().startsWith()) } // end of for () port_props.put(PORT_KEY, ports[i]); if (port_props.containsKey(PORT_TYPE_PROP_KEY) && !(port_props.get(PORT_TYPE_PROP_KEY) instanceof ConnectionType)) { Object val = port_props.get(PORT_TYPE_PROP_KEY); port_props.put(PORT_TYPE_PROP_KEY, ConnectionType.valueOf(val.toString())); } if (port_props.containsKey(PORT_SOCKET_PROP_KEY) && !(port_props.get(PORT_SOCKET_PROP_KEY) instanceof SocketType)) { Object val = port_props.get(PORT_SOCKET_PROP_KEY); port_props.put(PORT_SOCKET_PROP_KEY, SocketType.valueOf(val.toString())); } addWaitingTask(port_props); // reconnectService(port_props, startDelay); } // end of for (int i = 0; i < ports.length; i++) } // end of if (ports != null) } // ~--- methods -------------------------------------------------------------- /** * Method description * * @param conn */ protected void addWaitingTask(Map<String, Object> conn) { if (initializationCompleted) { reconnectService(conn, connectionDelay); } else { waitingTasks.add(conn); } } /** * Method description * * @param ht_def_key * @param ht_dev_val * @param st_def_key * @param st_def_val * @param prop_key * @param prop_val_class * @param params * @param props * @param <T> */ protected <T> void checkHighThroughputProperty( String ht_def_key, T ht_dev_val, String st_def_key, T st_def_val, String prop_key, Class<T> prop_val_class, Map<String, Object> params, Map<String, Object> props) { T tmp = st_def_val; String str_tmp = null; if (isHighThroughput()) { tmp = ht_dev_val; str_tmp = (String) params.get(ht_def_key); } else { tmp = st_def_val; str_tmp = (String) params.get(st_def_key); } if (prop_val_class.isAssignableFrom(Integer.class)) { tmp = prop_val_class.cast(DataTypes.parseNum(str_tmp, Integer.class, (Integer) tmp)); } if (prop_val_class.isAssignableFrom(Long.class)) { tmp = prop_val_class.cast(DataTypes.parseNum(str_tmp, Long.class, (Long) tmp)); } if (prop_val_class.isAssignableFrom(String.class)) { tmp = prop_val_class.cast(str_tmp); } props.put(prop_key, tmp); } /** * Returns number of active network connections (IOServices). * * @return number of active network connections (IOServices). */ protected int countIOServices() { return services.size(); } /** * Perform a given action defined by ServiceChecker for all active IOService objects (active * network connections). * * @param checker is a <code>ServiceChecker</code> instance defining an action to perform for all * IOService objects. */ protected void doForAllServices(ServiceChecker<IO> checker) { for (IO service : services.values()) { checker.check(service); } } /** * @param p * @return a value of <code>boolean</code> */ protected boolean writePacketToSocket(Packet p) { IO ios = getXMPPIOService(p); if (ios != null) { return writePacketToSocket(ios, p); } else { return false; } } /** * Method description * * @param p * @param serviceId * @return a value of <code>boolean</code> */ protected boolean writePacketToSocket(Packet p, String serviceId) { IO ios = getXMPPIOService(serviceId); if (ios != null) { return writePacketToSocket(ios, p); } else { return false; } } /** * Method description * * @param ios * @param data */ protected void writeRawData(IO ios, String data) { try { ios.writeRawData(data); SocketThread.addSocketService(ios); } catch (Exception e) { log.log(Level.WARNING, ios + "Exception during writing data: " + data, e); try { ios.stop(); } catch (Exception e1) { log.log(Level.WARNING, ios + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } } // ~--- get methods ---------------------------------------------------------- /** * Method description * * @return a value of <code>int[]</code> */ protected int[] getDefPlainPorts() { return null; } /** * Method description * * @return a value of <code>int[]</code> */ protected int[] getDefSSLPorts() { return null; } /** * Method description * * @return a value of <code>String</code> */ protected String getDefTrafficThrottling() { String result = ST_TRAFFIC_THROTTLING_PROP_VAL; if (isHighThroughput()) { result = HT_TRAFFIC_THROTTLING_PROP_VAL; } return result; } /** * Method description * * @return a value of <code>long</code> */ protected abstract long getMaxInactiveTime(); /** * Method description * * @param port * @return a value of {@code Map<String,Object>} */ protected Map<String, Object> getParamsForPort(int port) { return null; } /** * Method description * * @param packet * @return a value of <code>String</code> */ protected String getServiceId(Packet packet) { return getServiceId(packet.getTo()); } /** * Method description * * @param jid * @return a value of <code>String</code> */ protected String getServiceId(JID jid) { return jid.getResource(); } /** * Method description * * @param serv * @return a value of <code>String</code> */ protected String getUniqueId(IO serv) { return serv.getUniqueId(); } /** * Method description * * @param p * @return a value of <code>IO</code> */ protected IO getXMPPIOService(Packet p) { String id = getServiceId(p); if (id != null) { return services.get(id); } return null; } /** * Method description * * @return a value of <code>IO</code> */ protected abstract IO getXMPPIOServiceInstance(); /** * Method description * * @return a value of <code>boolean</code> */ protected boolean isHighThroughput() { return false; } // ~--- methods -------------------------------------------------------------- private void putDefPortParams(Map<String, Object> props, int port, SocketType sock) { log.log(Level.CONFIG, "Generating defaults for port: {0,number,#}", port); props.put(PROP_KEY + port + "/" + PORT_TYPE_PROP_KEY, ConnectionType.accept); props.put(PROP_KEY + port + "/" + PORT_SOCKET_PROP_KEY, sock); props.put(PROP_KEY + port + "/" + PORT_IFC_PROP_KEY, PORT_IFC_PROP_VAL); props.put(PROP_KEY + port + "/" + PORT_REMOTE_HOST_PROP_KEY, PORT_REMOTE_HOST_PROP_VAL); props.put(PROP_KEY + port + "/" + TLS_REQUIRED_PROP_KEY, TLS_REQUIRED_PROP_VAL); Map<String, Object> extra = getParamsForPort(port); if (extra != null) { for (Map.Entry<String, Object> entry : extra.entrySet()) { props.put(PROP_KEY + port + "/" + entry.getKey(), entry.getValue()); } // end of for () } // end of if (extra != null) } private void reconnectService(final Map<String, Object> port_props, long delay) { if (log.isLoggable(Level.FINER)) { String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname"); log.log( Level.FINER, "Reconnecting service for: {0}, scheduling next try in {1}secs, cid: {2}, props: {3}", new Object[] {getName(), delay / 1000, cid, port_props}); } addTimerTask( new tigase.util.TimerTask() { @Override public void run() { String host = (String) port_props.get(PORT_REMOTE_HOST_PROP_KEY); if (host == null) { host = (String) port_props.get("remote-hostname"); } int port = (Integer) port_props.get(PORT_KEY); if (log.isLoggable(Level.FINE)) { log.log( Level.FINE, "Reconnecting service for component: {0}, to remote host: {1} on port: {2,number,#}", new Object[] {getName(), host, port}); } startService(port_props); } }, delay); } private void releaseListeners() { for (ConnectionListenerImpl cli : pending_open) { connectThread.removeConnectionOpenListener(cli); } pending_open.clear(); } private void startService(Map<String, Object> port_props) { if (port_props == null) { throw new NullPointerException("port_props cannot be null."); } ConnectionListenerImpl cli = new ConnectionListenerImpl(port_props); if (cli.getConnectionType() == ConnectionType.accept) { pending_open.add(cli); } connectThread.addConnectionOpenListener(cli); } // ~--- inner classes -------------------------------------------------------- private class ConnectionListenerImpl implements ConnectionOpenListener { private Map<String, Object> port_props = null; // ~--- constructors ------------------------------------------------------- private ConnectionListenerImpl(Map<String, Object> port_props) { this.port_props = port_props; } // ~--- methods ------------------------------------------------------------ @Override public void accept(SocketChannel sc) { String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname"); if (log.isLoggable(Level.FINEST)) { log.log( Level.FINEST, "Accept called for service: {0}, port_props: {1}", new Object[] {cid, port_props}); } IO serv = getXMPPIOServiceInstance(); serv.setBufferLimit(net_buffer_limit); ((XMPPDomBuilderHandler) serv.getSessionData().get(DOM_HANDLER)) .setElementsLimit(elements_number_limit); serv.setIOServiceListener(ConnectionManager.this); serv.setSessionData(port_props); try { serv.accept(sc); if (getSocketType() == SocketType.ssl) { serv.startSSL(false, false, false); } // end of if (socket == SocketType.ssl) serviceStarted(serv); SocketThread.addSocketService(serv); } catch (Exception e) { if (getConnectionType() == ConnectionType.connect) { // Accept side for component service is not ready yet? // Let's wait for a few secs and try again. if (log.isLoggable(Level.FINEST)) { log.log( Level.FINEST, "Problem reconnecting the service: {0}, port_props: {1}, exception: {2}", new Object[] {serv, port_props, e}); } updateConnectionDetails(port_props); boolean reconnect = false; Integer reconnects = (Integer) port_props.get(MAX_RECONNECTS_PROP_KEY); if (reconnects != null) { int recon = reconnects.intValue(); if (recon != 0) { port_props.put(MAX_RECONNECTS_PROP_KEY, (--recon)); reconnect = true; } // end of if (recon != 0) } if (reconnect) { reconnectService(port_props, connectionDelay); } else { reconnectionFailed(port_props); } } else { // Ignore } // } catch (Exception e) { // if (log.isLoggable(Level.FINEST)) { // log.log(Level.FINEST, "Can not accept connection cid: " + cid, e); // } // log.log(Level.WARNING, "Can not accept connection.", e); // serv.stop(); } // end of try-catch } @Override public String toString() { return port_props.toString(); } // ~--- get methods -------------------------------------------------------- @Override public ConnectionType getConnectionType() { String type = null; if (port_props.get(PORT_TYPE_PROP_KEY) == null) { log.warning( getName() + ": connection type is null: " + port_props.get(PORT_KEY).toString()); } else { type = port_props.get(PORT_TYPE_PROP_KEY).toString(); } return ConnectionType.valueOf(type); } @Override public String[] getIfcs() { return (String[]) port_props.get(PORT_IFC_PROP_KEY); } @Override public int getPort() { return (Integer) port_props.get(PORT_KEY); } @Override public int getReceiveBufferSize() { return net_buffer; } @Override public InetSocketAddress getRemoteAddress() { return (InetSocketAddress) port_props.get("remote-address"); } @Override public String getRemoteHostname() { if (port_props.containsKey(PORT_REMOTE_HOST_PROP_KEY)) { return (String) port_props.get(PORT_REMOTE_HOST_PROP_KEY); } return (String) port_props.get("remote-hostname"); } @Override public SocketType getSocketType() { return SocketType.valueOf(port_props.get(PORT_SOCKET_PROP_KEY).toString()); } @Override public String getSRVType() { String type = (String) this.port_props.get("srv-type"); if ((type == null) || type.isEmpty()) { return null; } return type; } @Override public int getTrafficClass() { if (isHighThroughput()) { return IPTOS_THROUGHPUT; } else { return DEF_TRAFFIC_CLASS; } } } private class IOServiceStatisticsGetter implements ServiceChecker<IO> { private StatisticsList list = new StatisticsList(Level.ALL); // ~--- methods ------------------------------------------------------------ @Override public synchronized void check(IO service) { bytesReceived += service.getBytesReceived(true); bytesSent += service.getBytesSent(true); socketOverflow += service.getBuffOverflow(true); service.getPacketsReceived(true); service.getPacketsSent(true); // service.getStatistics(list, true); // bytesReceived += list.getValue("socketio", "Bytes received", -1l); // bytesSent += list.getValue("socketio", "Bytes sent", -1l); // socketOverflow += list.getValue("socketio", "Buffers overflow", -1l); } } /** * Class looks in all established connections and checks whether any of them is dead by performing * either whitspace or XMPP ping. If client fails to respond within defined time then the service * is stopped. */ private class Watchdog implements Runnable { Packet pingPacket; @Override public void run() { while (true) { try { // Sleep... Thread.sleep(watchdogDelay); ++watchdogRuns; /** * Walk through all connections and check whether they are really alive. Depending on the * configuration send either whitespace or XMPP ping if the service is inactive for the * configured period of time */ doForAllServices( new ServiceChecker<IO>() { @Override public void check(final XMPPIOService service) { try { if (null != service) { long curr_time = System.currentTimeMillis(); long lastTransfer; switch (watchdogPingType) { case XMPP: lastTransfer = service.getLastXmppPacketReceiveTime(); break; case WHITESPACE: default: lastTransfer = service.getLastTransferTime(); break; } if (curr_time - lastTransfer >= maxInactivityTime) { // Stop the service if max keep-alive time is exceeded // for non-active connections. if (log.isLoggable(Level.INFO)) { log.log( Level.INFO, "{0}: Max inactive time exceeded, stopping: {1}", new Object[] {getName(), service}); } ++watchdogStopped; service.stop(); } else { if (curr_time - lastTransfer >= (watchdogTimeout)) { /** * At least once every configured timings check if the connection is still * alive with the use of configured ping type. */ switch (watchdogPingType) { case XMPP: pingPacket = Iq.packetInstance( pingElement.clone(), JID.jidInstanceNS( (String) service .getSessionData() .get(XMPPIOService.HOSTNAME_KEY)), JID.jidInstanceNS(service.getUserJid())); if (!writePacketToSocket((IO) service, pingPacket)) { // writing failed, stopp service ++watchdogStopped; service.stop(); } break; case WHITESPACE: service.writeRawData(" "); break; } ++watchdogTests; } } } } catch (IOException e) { // Close the service try { if (service != null) { log.info(getName() + "Found dead connection, stopping: " + service); ++watchdogStopped; service.forceStop(); } } catch (Exception ignore) { // Do nothing here as we expect Exception to be thrown here... } } } }); } catch (InterruptedException e) { /* Do nothing here */ } } } } } // ConnectionManager
private void releaseListeners() { for (ConnectionListenerImpl cli : pending_open) { connectThread.removeConnectionOpenListener(cli); } pending_open.clear(); }