/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * 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. * * 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. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.net; //~--- non-JDK imports -------------------------------------------------------- import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.MalformedInputException; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import tigase.cert.CertCheckResult; import tigase.io.BufferUnderflowException; import tigase.io.IOInterface; import tigase.io.SocketIO; import tigase.io.TLSEventHandler; import tigase.io.TLSIO; import tigase.io.TLSUtil; import tigase.io.TLSWrapper; import tigase.io.ZLibIO; import tigase.stats.StatisticsList; import tigase.xmpp.JID; //~--- classes ---------------------------------------------------------------- /** * <code>IOService</code> offers thread safe <code>call()</code> method * execution, however you must be prepared that other methods can be called * simultaneously like <code>stop()</code>, <code>getProtocol()</code> or * <code>isConnected()</code>. <br/> * It is recommended that developers extend <code>AbsractServerService</code> * rather then implement <code>ServerService</code> interface directly. * <p> * If you directly implement <code>ServerService</code> interface you must take * care about <code>SocketChannel</code> I/O, queuing tasks, processing results * and thread safe execution of <code>call()</code> method. If you however * extend <code>IOService</code> class all this basic operation are implemented * and you have only to take care about parsing data received from network * socket. Parsing data is expected to be implemented in * <code>parseData(char[] data)</code> method. * </p> * * <p> * Created: Tue Sep 28 23:00:34 2004 * </p> * * @param <RefObject> * is a reference object stored by this service. This is e reference to * higher level data object keeping more information about the * connection. * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public abstract class IOService<RefObject> implements Callable<IOService<?>>, TLSEventHandler { /** * Variable <code>log</code> is a class logger. */ private static final Logger log = Logger.getLogger(IOService.class.getName()); /** * This is key used to store session ID in temporary session data storage. As * it is used by many components it is required that all components access * session ID with this constant. */ public static final String SESSION_ID_KEY = "sessionID"; /** Field description */ public static final String PORT_TYPE_PROP_KEY = "type"; /** Field description */ public static final String HOSTNAME_KEY = "hostname-key"; private static final long MAX_ALLOWED_EMPTY_CALLS = 1000; /** Field description */ public static final String CERT_CHECK_RESULT = "cert-check-result"; // ~--- fields --------------------------------------------------------------- private ConnectionType connectionType = null; private JID dataReceiver = null; /** Field description */ private long empty_read_call_count = 0; private String id = null; private JID connectionId = null; /** * This variable keeps the time of last transfer in any direction it is used * to help detect dead connections. */ private long lastTransferTime = 0; private String local_address = null; private long[] rdData = new long[60]; private RefObject refObject = null; private String remote_address = null; private IOServiceListener<IOService<RefObject>> serviceListener = null; private IOInterface socketIO = null; /** The saved partial bytes for multi-byte UTF-8 characters between reads */ private byte[] partialCharacterBytes = null; /** * <code>socketInput</code> buffer keeps data read from socket. */ private ByteBuffer socketInput = null; private int socketInputSize = 2048; private boolean stopping = false; private long[] wrData = new long[60]; private ConcurrentMap<String, Object> sessionData = new ConcurrentHashMap<String, Object>(4, 0.75f, 4); private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); private CharBuffer cb = CharBuffer.allocate(2048); private final ReentrantLock writeInProgress = new ReentrantLock(); private final ReentrantLock readInProgress = new ReentrantLock(); // ~--- methods -------------------------------------------------------------- /** * Method description * * * @throws IOException */ public abstract void processWaitingPackets() throws IOException; protected abstract void processSocketData() throws IOException; protected abstract int receivedPackets(); /** * Method <code>accept</code> is used to perform * * @param socketChannel * a <code>SocketChannel</code> value * * @throws IOException */ public void accept(final SocketChannel socketChannel) throws IOException { try { if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); } // end of if (socketChannel.isConnecyionPending()) socketIO = new SocketIO(socketChannel); } catch (IOException e) { String host = (String) sessionData.get("remote-hostname"); if (host == null) { host = (String) sessionData.get("remote-host"); } String sock_str = null; try { sock_str = socketChannel.socket().toString(); } catch (Exception ex) { sock_str = ex.toString(); } log.log( Level.INFO, "Problem connecting to remote host: {0}, address: {1}, socket: {2} - exception: {3}, session data: {4}", new Object[] { host, remote_address, sock_str, e, sessionData }); throw e; } socketInputSize = socketIO.getSocketChannel().socket().getReceiveBufferSize(); socketInput = ByteBuffer.allocate(socketInputSize); Socket sock = socketIO.getSocketChannel().socket(); local_address = sock.getLocalAddress().getHostAddress(); remote_address = sock.getInetAddress().getHostAddress(); id = local_address + "_" + sock.getLocalPort() + "_" + remote_address + "_" + sock.getPort(); setLastTransferTime(); } /** * Method <code>run</code> is used to perform * * * @return * @throws IOException */ @Override public IOService<?> call() throws IOException { writeData(null); boolean readLock = true; if (stopping) { stop(); } else { readLock = readInProgress.tryLock(); if (readLock) { try { processSocketData(); if ((receivedPackets() > 0) && (serviceListener != null)) { serviceListener.packetsReady(this); } // end of if (receivedPackets.size() > 0) } finally { readInProgress.unlock(); } } } return readLock ? this : null; } /** * Method description * * * @return */ public ConnectionType connectionType() { return this.connectionType; } /** * Method description * */ public void forceStop() { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Socket: {0}, Force stop called...", socketIO); } try { if ((socketIO != null) && socketIO.isConnected()) { synchronized (socketIO) { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Calling stop on: {0}", socketIO); } socketIO.stop(); } } } catch (Exception e) { // Well, do nothing, we are closing the connection anyway.... if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: " + socketIO + ", Exception while stopping service: " + connectionId, e); } } finally { if (serviceListener != null) { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Calling stop on the listener: {0}", serviceListener); } IOServiceListener<IOService<RefObject>> tmp = serviceListener; serviceListener = null; // The temp can still be null if the forceStop is called concurrently if (tmp != null) { tmp.serviceStopped(this); } } else { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Service listener is null: {0}", socketIO); } } } } // ~--- get methods ---------------------------------------------------------- /** * Method description * * * @return */ public JID getDataReceiver() { return this.dataReceiver; } /** * This method returns the time of last transfer in any direction through this * service. It is used to help detect dead connections. * * @return */ public long getLastTransferTime() { return lastTransferTime; } /** * Method description * * * @return */ public String getLocalAddress() { return local_address; } /** * Method description * * * @return */ public long[] getReadCounters() { return rdData; } /** * Method description * * * @return */ public RefObject getRefObject() { return refObject; } /** * Returns a remote IP address for the TCP/IP connection. * * * @return a remote IP address for the TCP/IP connection. */ public String getRemoteAddress() { return remote_address; } /** * Method description * * * @return */ public ConcurrentMap<String, Object> getSessionData() { return sessionData; } /** * Method <code>getSocketChannel</code> is used to perform * * @return a <code>SocketChannel</code> value */ public SocketChannel getSocketChannel() { return socketIO.getSocketChannel(); } /** * Method description * * * @param list * @param reset */ public void getStatistics(StatisticsList list, boolean reset) { if (socketIO != null) { socketIO.getStatistics(list, reset); } } /** * Method description * * * @return */ public String getUniqueId() { return id; } /** * Method description * * * @return */ public long[] getWriteCounters() { return wrData; } // ~--- methods -------------------------------------------------------------- /** * Method description * * * @param wrapper */ @Override public void handshakeCompleted(TLSWrapper wrapper) { CertCheckResult certCheckResult = wrapper.getCertificateStatus(false); sessionData.put(CERT_CHECK_RESULT, certCheckResult); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, TLS handshake completed: {1}", new Object[] { this, certCheckResult }); } serviceListener.tlsHandshakeCompleted(this); } // ~--- get methods ---------------------------------------------------------- /** * Describe <code>isConnected</code> method here. * * @return a <code>boolean</code> value */ public boolean isConnected() { boolean result = (socketIO == null) ? false : socketIO.isConnected(); if (log.isLoggable(Level.FINEST)) { // if (socketIO.getSocketChannel().socket().getLocalPort() == 5269) { // Throwable thr = new Throwable(); // // thr.fillInStackTrace(); // log.log(Level.FINEST, "Socket: " + socketIO + ", Connected: " + result, // thr); // } log.log(Level.FINEST, "Socket: {0}, Connected: {1}, id: {2}", new Object[] { socketIO, result, connectionId }); } return result; } // ~--- set methods ---------------------------------------------------------- /** * Method description * * * @param address */ public void setDataReceiver(JID address) { this.dataReceiver = address; } /** * Method description * * * @param sl */ // @SuppressWarnings("unchecked") public void setIOServiceListener(IOServiceListener<IOService<RefObject>> sl) { this.serviceListener = sl; } /** * Method description * * * @param refObject */ public void setRefObject(RefObject refObject) { this.refObject = refObject; } /** * Method description * * * @param props */ public void setSessionData(Map<String, Object> props) { // Sometimes, some values are null which is allowed in the original Map // however, ConcurrentHashMap does not allow nulls as value so we have // to copy Maps carefully. sessionData = new ConcurrentHashMap<String, Object>(props.size()); for (Map.Entry<String, Object> entry : props.entrySet()) { if (entry.getValue() != null) { sessionData.put(entry.getKey(), entry.getValue()); } } connectionType = ConnectionType.valueOf(sessionData.get(PORT_TYPE_PROP_KEY).toString()); } // ~--- methods -------------------------------------------------------------- /** * Method description * * * @param clientMode * * @throws IOException */ public void startSSL(boolean clientMode) throws IOException { if (socketIO instanceof TLSIO) { throw new IllegalStateException("SSL mode is already activated."); } TLSWrapper wrapper = new TLSWrapper(TLSUtil.getSSLContext("SSL", (String) sessionData.get(HOSTNAME_KEY)), this, clientMode); socketIO = new TLSIO(socketIO, wrapper); setLastTransferTime(); encoder.reset(); decoder.reset(); } /** * Method description * * * @param clientMode * * @throws IOException */ public void startTLS(boolean clientMode) throws IOException { if (socketIO.checkCapabilities(TLSIO.TLS_CAPS)) { throw new IllegalStateException("TLS mode is already activated " + connectionId); } // This should not take more then 100ms int counter = 0; while (isConnected() && waitingToSend() && (++counter < 10)) { writeData(null); try { Thread.sleep(10); } catch (InterruptedException ex) { } } if (counter >= 10) { stop(); } else { String tls_hostname = (String) sessionData.get(HOSTNAME_KEY); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, Starting TLS for domain: {1}", new Object[] { this, tls_hostname }); } TLSWrapper wrapper = new TLSWrapper(TLSUtil.getSSLContext("TLS", tls_hostname), this, clientMode); socketIO = new TLSIO(socketIO, wrapper); setLastTransferTime(); encoder.reset(); decoder.reset(); } } /** * Method description * * * @param level */ public void startZLib(int level) { if (socketIO.checkCapabilities(ZLibIO.ZLIB_CAPS)) { throw new IllegalStateException("ZLIB mode is already activated."); } socketIO = new ZLibIO(socketIO, level); } /** * Describe <code>stop</code> method here. * */ public void stop() { if ((socketIO != null) && socketIO.waitingToSend()) { stopping = true; } else { forceStop(); } } /** * Method description * * * @return */ @Override public String toString() { return getConnectionId() + ", type: " + connectionType + ", Socket: " + socketIO; } /** * Method description * * * @return */ public boolean waitingToRead() { return true; } /** * Method description * * * @return */ public boolean waitingToSend() { return socketIO.waitingToSend(); } /** * Method description * * * @return */ public int waitingToSendSize() { return socketIO.waitingToSendSize(); } /** * Method description * * @return */ protected boolean isInputBufferEmpty() { return socketInput != null && socketInput.remaining() == socketInput.capacity(); } /** * Describe <code>debug</code> method here. * * @param msg * a <code>char[]</code> value * @return a <code>boolean</code> value */ protected boolean debug(final char[] msg) { if (msg != null) { System.out.print(new String(msg)); // log.finest("\n" + new String(msg) + "\n"); } // end of if (msg != null) return true; } /** * Describe <code>debug</code> method here. * * @param msg * a <code>String</code> value * @return a <code>boolean</code> value */ protected boolean debug(final String msg, final String prefix) { if (log.isLoggable(Level.FINEST)) { if ((msg != null) && (msg.trim().length() > 0)) { String log_msg = "\n" + ((connectionType() != null) ? connectionType().toString() : "null-type") + " " + prefix + "\n" + msg + "\n"; // System.out.print(log_msg); log.finest(log_msg); } } return true; } protected void readCompleted() { decoder.reset(); } /** * Describe <code>readData</code> method here. * * @return a <code>char[]</code> value * @exception IOException * if an error occurs */ protected char[] readData() throws IOException { setLastTransferTime(); if (log.isLoggable(Level.FINEST) && (empty_read_call_count > 10)) { Throwable thr = new Throwable(); thr.fillInStackTrace(); log.log(Level.FINEST, "Socket: " + socketIO, thr); } // Generally it should not happen as it is called only in // call() which has concurrent call protection. // synchronized (socketIO) { try { // resizeInputBuffer(); // Maybe we can shrink the input buffer?? if ((socketInput.capacity() > socketInputSize) && (socketInput.remaining() == socketInput.capacity())) { // Yes, looks like we can if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Socket: {0}, Resizing socketInput down to {1} bytes.", new Object[] { socketIO, socketInputSize }); } socketInput = ByteBuffer.allocate(socketInputSize); cb = CharBuffer.allocate(socketInputSize * 4); } // if (log.isLoggable(Level.FINEST)) { // log.finer("Before read from socket."); // log.finer("socketInput.capacity()=" + socketInput.capacity()); // log.finer("socketInput.remaining()=" + socketInput.remaining()); // log.finer("socketInput.limit()=" + socketInput.limit()); // log.finer("socketInput.position()=" + socketInput.position()); // } ByteBuffer tmpBuffer = socketIO.read(socketInput); if (socketIO.bytesRead() > 0) { empty_read_call_count = 0; char[] result = null; // There might be some characters read from the network // but the buffer may still be null or empty because there might // be not enough data to decode TLS or compressed buffer. if (tmpBuffer != null) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, Reading network binary data: {1}", new Object[] { socketIO, socketIO.bytesRead() }); } // Restore the partial bytes for multibyte UTF8 characters if (partialCharacterBytes != null) { if (log.isLoggable(Level.FINEST)) { log.finest("Reloading partial bytes " + partialCharacterBytes.length); } ByteBuffer oldTmpBuffer = tmpBuffer; tmpBuffer = ByteBuffer.allocate(partialCharacterBytes.length + oldTmpBuffer.remaining() + 2); tmpBuffer.put(partialCharacterBytes); tmpBuffer.put(oldTmpBuffer); tmpBuffer.flip(); oldTmpBuffer.clear(); partialCharacterBytes = null; } // if (log.isLoggable(Level.FINEST)) { // log.finer("Before decoding data"); // log.finer("socketInput.capacity()=" + socketInput.capacity()); // log.finer("socketInput.remaining()=" + socketInput.remaining()); // log.finer("socketInput.limit()=" + socketInput.limit()); // log.finer("socketInput.position()=" + socketInput.position()); // log.finer("tmpBuffer.capacity()=" + tmpBuffer.capacity()); // log.finer("tmpBuffer.remaining()=" + tmpBuffer.remaining()); // log.finer("tmpBuffer.limit()=" + tmpBuffer.limit()); // log.finer("tmpBuffer.position()=" + tmpBuffer.position()); // log.finer("cb.capacity()=" + cb.capacity()); // log.finer("cb.remaining()=" + cb.remaining()); // log.finer("cb.limit()=" + cb.limit()); // log.finer("cb.position()=" + cb.position()); // } // tmpBuffer.flip(); if (cb.capacity() < tmpBuffer.remaining() * 4) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, resizing character buffer to: {1}", new Object[] { socketIO, tmpBuffer.remaining() }); } cb = CharBuffer.allocate(tmpBuffer.remaining() * 4); } CoderResult cr = decoder.decode(tmpBuffer, cb, false); if (cr.isMalformed()) { throw new MalformedInputException(tmpBuffer.remaining()); } if (cb.remaining() > 0) { cb.flip(); result = new char[cb.remaining()]; cb.get(result); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, Decoded character data: {1}", new Object[] { socketIO, new String(result) }); } // if (log.isLoggable(Level.FINEST)) { // log.finer("Just after decoding."); // log.finer("tmpBuffer.capacity()=" + tmpBuffer.capacity()); // log.finer("tmpBuffer.remaining()=" + tmpBuffer.remaining()); // log.finer("tmpBuffer.limit()=" + tmpBuffer.limit()); // log.finer("tmpBuffer.position()=" + tmpBuffer.position()); // log.finer("cb.capacity()=" + cb.capacity()); // log.finer("cb.remaining()=" + cb.remaining()); // log.finer("cb.limit()=" + cb.limit()); // log.finer("cb.position()=" + cb.position()); // } } if (cr.isUnderflow() && (tmpBuffer.remaining() > 0)) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, UTF-8 decoder data underflow: {1}", new Object[] { socketIO, tmpBuffer.remaining() }); } // Save the partial bytes of a multibyte character such that they // can be restored on the next read. partialCharacterBytes = new byte[tmpBuffer.remaining()]; tmpBuffer.get(partialCharacterBytes); } tmpBuffer.clear(); cb.clear(); // if (log.isLoggable(Level.FINEST)) { // log.finer("Before return from method."); // log.finer("tmpBuffer.capacity()=" + tmpBuffer.capacity()); // log.finer("tmpBuffer.remaining()=" + tmpBuffer.remaining()); // log.finer("tmpBuffer.limit()=" + tmpBuffer.limit()); // log.finer("tmpBuffer.position()=" + tmpBuffer.position()); // log.finer("cb.capacity()=" + cb.capacity()); // log.finer("cb.remaining()=" + cb.remaining()); // log.finer("cb.limit()=" + cb.limit()); // log.finer("cb.position()=" + cb.position()); // } return result; } } else { // Detecting infinite read 0 bytes // sometimes it happens that the connection has been lost // and the select thinks there are some bytes waiting for reading // and 0 bytes are read if ((++empty_read_call_count) > MAX_ALLOWED_EMPTY_CALLS && (!writeInProgress.isLocked())) { log.log(Level.WARNING, "Socket: {0}, Max allowed empty calls excceeded, closing connection.", socketIO); forceStop(); } } } catch (BufferUnderflowException ex) { // Obtain more inbound network data for src, // then retry the operation. resizeInputBuffer(); return null; // } catch (MalformedInputException ex) { // // This happens after TLS initialization sometimes, maybe reset helps // decoder.reset(); } catch (Exception eof) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: " + socketIO + ", Exception reading data", eof); } // eof.printStackTrace(); forceStop(); } // end of try-catch // } return null; } protected ByteBuffer readBytes() throws IOException { setLastTransferTime(); if (log.isLoggable(Level.FINEST) && (empty_read_call_count > 10)) { Throwable thr = new Throwable(); thr.fillInStackTrace(); log.log(Level.FINEST, "Socket: " + socketIO, thr); } try { ByteBuffer tmpBuffer = socketIO.read(socketInput); if (socketIO.bytesRead() > 0) { empty_read_call_count = 0; return tmpBuffer; } else { if ((++empty_read_call_count) > MAX_ALLOWED_EMPTY_CALLS && (!writeInProgress.isLocked())) { log.log(Level.WARNING, "Socket: {0}, Max allowed empty calls excceeded, closing connection.", socketIO); forceStop(); } } } catch (Exception eof) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: " + socketIO + ", Exception reading data", eof); } // eof.printStackTrace(); forceStop(); } return null; } /** * Describe <code>writeData</code> method here. * * @param data * a <code>String</code> value */ protected void writeData(final String data) { // Try to lock the data writing method boolean locked = writeInProgress.tryLock(); // If cannot lock and nothing to send, just leave if (!locked && (data == null)) { return; } // Otherwise wait..... if (!locked) { writeInProgress.lock(); } // Avoid concurrent calls here (one from call() and another from // application) try { if ((data != null) && (data.length() > 0)) { if (log.isLoggable(Level.FINEST)) { if (data.length() < 256) { log.log(Level.FINEST, "Socket: {0}, Writing data ({1}): {2}", new Object[] { socketIO, data.length(), data }); } else { log.log(Level.FINEST, "Socket: {0}, Writing data: {1}", new Object[] { socketIO, data.length() }); } } ByteBuffer dataBuffer = null; // int out_buff_size = data.length(); // int idx_start = 0; // int idx_offset = Math.min(idx_start + out_buff_size, data.length()); // // while (idx_start < data.length()) { // String data_str = data.substring(idx_start, idx_offset); // if (log.isLoggable(Level.FINEST)) { // log.finest("Writing data_str (" + data_str.length() + "), idx_start=" // + idx_start + ", idx_offset=" + idx_offset + ": " + data_str); // } encoder.reset(); // dataBuffer = encoder.encode(CharBuffer.wrap(data, idx_start, // idx_offset)); dataBuffer = encoder.encode(CharBuffer.wrap(data)); encoder.flush(dataBuffer); socketIO.write(dataBuffer); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, wrote: {1}", new Object[] { socketIO, data.length() }); } // idx_start = idx_offset; // idx_offset = Math.min(idx_start + out_buff_size, data.length()); // } setLastTransferTime(); // addWritten(data.length()); empty_read_call_count = 0; } else { if (socketIO.waitingToSend()) { socketIO.write(null); setLastTransferTime(); empty_read_call_count = 0; } } } catch (Exception e) { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Data writing exception " + connectionId, e); } forceStop(); } finally { writeInProgress.unlock(); } } protected void writeBytes(ByteBuffer data) { // Try to lock the data writing method boolean locked = writeInProgress.tryLock(); // If cannot lock and nothing to send, just leave if (!locked && (data == null)) { return; } // Otherwise wait..... if (!locked) { writeInProgress.lock(); } // Avoid concurrent calls here (one from call() and another from // application) try { if (data != null && data.hasRemaining()) { int length = data.remaining(); socketIO.write(data); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Socket: {0}, wrote: {1}", new Object[] { socketIO, length }); } // idx_start = idx_offset; // idx_offset = Math.min(idx_start + out_buff_size, data.length()); // } setLastTransferTime(); // addWritten(data.length()); empty_read_call_count = 0; } else { if (socketIO.waitingToSend()) { socketIO.write(null); setLastTransferTime(); empty_read_call_count = 0; } } } catch (Exception e) { if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "Data writing exception " + connectionId, e); } forceStop(); } finally { writeInProgress.unlock(); } } private void resizeInputBuffer() throws IOException { int netSize = socketIO.getInputPacketSize(); // Resize buffer if needed. // if (netSize > socketInput.remaining()) { if (netSize > socketInput.capacity() - socketInput.remaining()) { // int newSize = netSize + socketInput.capacity(); int newSize = socketInput.capacity() + socketInputSize; if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Socket: {0}, Resizing socketInput to {1} bytes.", new Object[] { socketIO, newSize }); } ByteBuffer b = ByteBuffer.allocate(newSize); b.put(socketInput); socketInput = b; } else { // if (log.isLoggable(Level.FINEST)) { // log.finer(" Before flip()"); // log.finer("input.capacity()=" + socketInput.capacity()); // log.finer("input.remaining()=" + socketInput.remaining()); // log.finer("input.limit()=" + socketInput.limit()); // log.finer("input.position()=" + socketInput.position()); // } // socketInput.flip(); // if (log.isLoggable(Level.FINEST)) { // log.finer(" Before compact()"); // log.finer("input.capacity()=" + socketInput.capacity()); // log.finer("input.remaining()=" + socketInput.remaining()); // log.finer("input.limit()=" + socketInput.limit()); // log.finer("input.position()=" + socketInput.position()); // } if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Socket: {0}, Compacting socketInput.", socketIO); } socketInput.compact(); // if (log.isLoggable(Level.FINEST)) { // log.finer(" After compact()"); // log.finer("input.capacity()=" + socketInput.capacity()); // log.finer("input.remaining()=" + socketInput.remaining()); // log.finer("input.limit()=" + socketInput.limit()); // log.finer("input.position()=" + socketInput.position()); // } } } // ~--- set methods ---------------------------------------------------------- private void setLastTransferTime() { lastTransferTime = System.currentTimeMillis(); } /** * @return the connectionId */ public JID getConnectionId() { return connectionId; } /** * @param connectionId * the connectionId to set */ public void setConnectionId(JID connectionId) { this.connectionId = connectionId; socketIO.setLogId(connectionId.toString()); } } // IOService // ~ Formatted in Sun Code Convention // ~ Formatted by Jindent --- http://www.jindent.com