All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xsocket.connection.ConnectionUtils Maven / Gradle / Ivy

There is a newer version: 2.8.15
Show newest version
/*
 * Copyright (c) xlightweb.org, 2006 - 2010. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
 * The latest copy of this software may be found on http://www.xsocket.org/
 */
package org.xsocket.connection;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.SocketTimeoutException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.ILifeCycle;
import org.xsocket.Resource;




/**
 * utility class 
 * 
 * @author [email protected]
 */
@SuppressWarnings("unchecked")
public final class ConnectionUtils {
	
	private static final Logger LOG = Logger.getLogger(ConnectionUtils.class.getName());
	
	public static final String DEFAULT_DOMAIN = "org.xsocket.connection";
	public static final String SERVER_TRHREAD_PREFIX = "xServer";
	
	private static final IoProvider IO_PROVIDER = new IoProvider();
	
	private static final Map handlerInfoCache = newMapCache(25);
	private static final Map completionHandlerInfoCache = newMapCache(25);

	private static String implementationVersion;
	private static String implementationDate;
	

	private ConnectionUtils() { }

	

	static IoProvider getIoProvider() {
		return IO_PROVIDER;
	}
	
	
	/**
	 * validate, based on a leading int length field. The length field will be removed
	 * 
	 * @param connection     the connection
	 * @return the length 
	 * @throws IOException if an exception occurs
	 * @throws BufferUnderflowException if not enough data is available
	 */
	public static int validateSufficientDatasizeByIntLengthField(INonBlockingConnection connection) throws IOException, BufferUnderflowException {

		connection.resetToReadMark();
		connection.markReadPosition();
		
		// check if enough data is available
		int length = connection.readInt();
		if (connection.available() < length) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("[" + connection.getId() + "]insufficient data. require " + length + " got "  + connection.available());
			}
			throw new BufferUnderflowException();
	
		} else { 
			// ...yes, remove mark
			connection.removeReadMark();
			return length;
		}
	}  


	
	/**
	 * validate, based on a leading int length field, that enough data (getNumberOfAvailableBytes() >= length) is available. If not,
	 * an BufferUnderflowException will been thrown. Example:
	 * 
	 * //client
	 * connection.setAutoflush(false);  // avoid immediate write
	 * ...
	 * connection.markWritePosition();  // mark current position
	 * connection.write((int) 0);       // write "emtpy" length field
	 *  
	 * // write and count written size
	 * int written = connection.write(CMD_PUT);
	 * written += ...
	 *  
	 * connection.resetToWriteMark();  // return to length field position
	 * connection.write(written);      // and update it
	 * connection.flush(); // flush (marker will be removed implicit)
	 * ...
	 * 
	 * 
	 * // server
	 * class MyHandler implements IDataHandler {
	 *    ...
	 *    public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException {
	 *       int length = ConnectionUtils.validateSufficientDatasizeByIntLengthField(connection);
	 *       
	 *       // enough data (BufferUnderflowException hasn`t been thrown)
	 *       byte cmd = connection.readByte();
	 *       ...
	 *    }
	 *  }      
	 * 
* * @param connection the connection * @param removeLengthField true, if length field should be removed * @return the length * @throws IOException if an exception occurs * @throws BufferUnderflowException if not enough data is available */ public static int validateSufficientDatasizeByIntLengthField(INonBlockingConnection connection, boolean removeLengthField) throws IOException, BufferUnderflowException { connection.resetToReadMark(); connection.markReadPosition(); // check if enough data is available int length = connection.readInt(); if (connection.available() < length) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("[" + connection.getId() + "]insufficient data. require " + length + " got " + connection.available()); } throw new BufferUnderflowException(); } else { // ...yes, remove mark if (!removeLengthField) { connection.resetToReadMark(); } connection.removeReadMark(); return length; } } /** * starts the given server within a dedicated thread. This method blocks * until the server is open. If the server hasn't been started within * 60 sec a timeout exception will been thrown. * * @param server the server to start * @throws SocketTimeoutException is the timeout has been reached */ public static void start(IServer server) throws SocketTimeoutException { start(server, 60); } /** * starts the given server within a dedicated thread. This method blocks * until the server is open. * * @param server the server to start * @param timeoutSec the maximum time to wait * * @throws SocketTimeoutException is the timeout has been reached */ public static void start(IServer server, int timeoutSec) throws SocketTimeoutException { final CountDownLatch startedSignal = new CountDownLatch(1); // create and add startup listener IServerListener startupListener = new IServerListener() { public void onInit() { startedSignal.countDown(); }; public void onDestroy() {}; }; server.addListener(startupListener); // start server within a dedicated thread Thread t = new Thread(server); t.setName("xServer"); t.start(); // wait until server has been started (onInit has been called) boolean isStarted = false; try { isStarted = startedSignal.await(timeoutSec, TimeUnit.SECONDS); } catch (InterruptedException ie) { // Restore the interrupted status Thread.currentThread().interrupt(); throw new RuntimeException("start signal doesn't occured. " + ie.toString()); } // timeout occurred? if (!isStarted) { throw new SocketTimeoutException("start timeout (" + DataConverter.toFormatedDuration((long) timeoutSec * 1000) + ")"); } // update thread name t.setName(SERVER_TRHREAD_PREFIX + ":" + server.getLocalPort()); // remove the startup listener server.removeListener(startupListener); } /** * (deep) copy of the byte buffer array * * @param buffers the byte buffer array * @return the copy */ public static ByteBuffer[] copy(ByteBuffer[] buffers) { if (buffers == null) { return null; } ByteBuffer[] copy = new ByteBuffer[buffers.length]; for (int i = 0; i < copy.length; i++) { copy[i] = copy(buffers[i]); } return copy; } /** * Returns a synchronized (thread-safe) connection backed by the specified connection. All methods * of the wapper are synchronized based on the underyling connection. * * @param con the connection to be "wrapped" in a synchronized connection * @return the synchronized (thread-safe) connection */ public static INonBlockingConnection synchronizedConnection(INonBlockingConnection con) { if (con instanceof SynchronizedNonBlockingConnection) { return con; } else { return new SynchronizedNonBlockingConnection(con); } } /** * Returns a synchronized (thread-safe) connection backed by the specified connection. All methods * of the wapper are synchronized based on the underyling connection. * * @param con the connection to be "wrapped" in a synchronized connection * @return the synchronized (thread-safe) connection */ public static IBlockingConnection synchronizedConnection(IBlockingConnection con) { if (con instanceof SynchronizedBlockingConnection) { return con; } else { return new SynchronizedBlockingConnection(con); } } /** * duplicates the byte buffer array * * @param buffers the byte buffer array * @return the copy */ static ByteBuffer[] duplicate(ByteBuffer[] buffers) { if (buffers == null) { return null; } ByteBuffer[] copy = new ByteBuffer[buffers.length]; for (int i = 0; i < copy.length; i++) { copy[i] = duplicate(buffers[i]); } return copy; } /** * duplicate the given buffer * * @param buffer the buffer to copy * @return the copy */ static ByteBuffer duplicate(ByteBuffer buffer) { if (buffer == null) { return null; } return buffer.duplicate(); } /** * copies the given buffer * * @param buffer the buffer to copy * @return the copy */ static ByteBuffer copy(ByteBuffer buffer) { if (buffer == null) { return null; } return ByteBuffer.wrap(DataConverter.toBytes(buffer)); } /** * creates and registers a mbean for the given server on the platform MBeanServer * * @param server the server to register * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IServer server) throws JMException { return registerMBean(server, DEFAULT_DOMAIN); } /** * creates and registers a mbean for the given server on the platform MBeanServer * under the given domain name * * @param server the server to register * @param domain the domain name to use * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IServer server, String domain) throws JMException { return registerMBean(server, domain, ManagementFactory.getPlatformMBeanServer()); } /** * creates and registers a mbean for the given server on the given MBeanServer * under the given domain name * * @param mbeanServer the mbean server to use * @param server the server to register * @param domain the domain name to use * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IServer server, String domain, MBeanServer mbeanServer) { try { return ServerMBeanProxyFactory.createAndRegister(server, domain, mbeanServer); } catch (Exception e) { throw new RuntimeException(DataConverter.toString(e)); } } /** * creates and registers a mbean for the given connection pool on the platform MBeanServer * * @param pool the pool to register * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IConnectionPool pool) throws JMException { return registerMBean(pool, DEFAULT_DOMAIN); } /** * creates and registers a mbean for the given connection pool on the platform MBeanServer * under the given domain name * * @param pool the pool to register * @param domain the domain name to use * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IConnectionPool pool, String domain) throws JMException { return registerMBean(pool, domain, ManagementFactory.getPlatformMBeanServer()); } /** * creates and registers a mbean for the given pool on the given MBeanServer * under the given domain name * * @param mbeanServer the mbean server to use * @param pool the pool to register * @param domain the domain name to use * @return the objectName * @throws JMException if an jmx exception occurs */ public static ObjectName registerMBean(IConnectionPool pool, String domain, MBeanServer mbeanServer) throws JMException { return ConnectionPoolMBeanProxyFactory.createAndRegister(pool, domain, mbeanServer); } /** * * checks if the current version matchs with the required version by using the maven version style * * @param currentVersion the current version * @param requiredVersion the required version * @return true if the version matchs */ public static boolean matchVersion(String currentVersion, String requiredVersion) { try { requiredVersion = requiredVersion.trim(); currentVersion = currentVersion.split("-")[0].trim(); // remove qualifier // dedicated version notation? if (requiredVersion.indexOf(",") == -1) { requiredVersion = requiredVersion.split("-")[0].trim(); // remove qualifier return currentVersion.equalsIgnoreCase(requiredVersion); // .. no it is range notation } else { String[] range = requiredVersion.split(","); for (int i = 0; i < range.length; i++) { range[i] = range[i].trim(); } if (range.length < 2) { return false; } String requiredMinVersion = range[0].substring(1, range[0].length()).trim(); requiredMinVersion = requiredMinVersion.split("-")[0]; // remove qualifier String requiredMaxVersion = range[1].substring(0, range[1].length() - 1).trim(); requiredMaxVersion = requiredMaxVersion.split("-")[0]; // remove qualifier // check min version if (range[0].startsWith("[") && (!currentVersion.equalsIgnoreCase(requiredMinVersion) && !isGreater(currentVersion, requiredMinVersion))) { return false; } // check max version if (range[1].endsWith(")")) { return isSmaller(currentVersion, requiredMaxVersion); } else if (range[1].endsWith("]")) { return (currentVersion.equalsIgnoreCase(requiredMaxVersion)|| isSmaller(currentVersion, requiredMaxVersion)); } } return false; } catch (Throwable t) { return false; } } private static boolean isGreater(String version, String required) { String[] numsVersion = version.split("\\."); String[] numsRequired = required.split("\\."); for (int i = 0; i < numsVersion.length; i++) { int numVersion = Integer.parseInt(numsVersion[i]); if (numsRequired.length <= i) { return true; } int numRequired = Integer.parseInt(numsRequired[i]); if (numVersion > numRequired) { return true; } } return false; } private static boolean isSmaller(String version, String required) { String[] numsVersion = version.split("\\."); String[] numsRequired = required.split("\\."); for (int i = 0; i < numsVersion.length; i++) { int numVersion = Integer.parseInt(numsVersion[i]); if (numsRequired.length < i) { return true; } int numRequired = Integer.parseInt(numsRequired[i]); if (numVersion < numRequired) { return true; } } return false; } static IOException toIOException(Throwable t) { return toIOException(t.getMessage(), t); } static IOException toIOException(String text, Throwable t) { IOException ioe = new IOException(text); ioe.setStackTrace(t.getStackTrace()); return ioe; } /** * get the implementation version * * @return the implementation version */ public static String getImplementationVersion() { if (implementationVersion == null) { readVersionFile(); } return implementationVersion; } /** * get the implementation date * * @return the implementation date */ public static String getImplementationDate() { if (implementationDate== null) { readVersionFile(); } return implementationDate; } private static void readVersionFile() { implementationVersion = ""; implementationDate = ""; InputStreamReader isr = null; LineNumberReader lnr = null; try { isr = new InputStreamReader(ConnectionUtils.class.getResourceAsStream("/org/xsocket/version.txt")); if (isr != null) { lnr = new LineNumberReader(isr); String line = null; do { line = lnr.readLine(); if (line != null) { if (line.startsWith("Implementation-Version=")) { implementationVersion = line.substring("Implementation-Version=".length(), line.length()).trim(); } else if (line.startsWith("Implementation-Date=")) { implementationDate = line.substring("Implementation-Date=".length(), line.length()).trim(); } } } while (line != null); lnr.close(); } } catch (IOException ioe) { implementationVersion = ""; implementationDate = ""; if (LOG.isLoggable(Level.FINE)) { LOG.fine("could not read version file. reason: " + ioe.toString()); } } finally { try { if (lnr != null) { lnr.close(); } if (isr != null) { isr.close(); } } catch (IOException ioe) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("exception occured by closing version.txt file stream " + ioe.toString()); } } } } static long computeSize(ByteBuffer[] buffers) { long size = 0; for (ByteBuffer byteBuffer : buffers) { size += byteBuffer.remaining(); } return size; } /** * creates a thread-safe new bound cache * * @param the map value type * @param maxSize the max size of the cache * @return the new map cache */ public static Map newMapCache(int maxSize) { return Collections.synchronizedMap(new MapCache(maxSize)); } static void injectServerField(Server server, Object handler) { Field[] fields = handler.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Resource.class)) { Resource res = field.getAnnotation(Resource.class); if ((field.getType() == IServer.class) || (res.type() == IServer.class) || (field.getType() == Server.class) || (res.type() == Server.class)) { try { field.setAccessible(true); field.set(handler, server); } catch (IllegalAccessException iae) { LOG.warning("could not inject server for attribute " + field.getName() + ". Reason " + iae.toString()); } } } } } /** * returns if current thread is dispatcher thread * * @return true, if current thread is a dispatcher thread */ public static boolean isDispatcherThread() { return Thread.currentThread().getName().startsWith(IoSocketDispatcher.DISPATCHER_PREFIX); } /** * returns if current thread is connector thread * @return true, if current thread is a connector thread */ static boolean isConnectorThread() { return Thread.currentThread().getName().startsWith(IoConnector.CONNECTOR_PREFIX); } static String printSelectionKey(SelectionKey key) { if (key != null) { try { int i = key.interestOps(); return printSelectionKeyValue(i) + " isValid=" + key.isValid(); } catch (CancelledKeyException cke) { return "canceled"; } } else { return ""; } } static String printSelectionKeyValue(int ops) { StringBuilder sb = new StringBuilder(); if ((ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) { sb.append("OP_ACCEPT, "); } if ((ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) { sb.append("OP_CONNECT, "); } if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { sb.append("OP_WRITE, "); } if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ) { sb.append("OP_READ, "); } String txt = sb.toString(); txt = txt.trim(); if (txt.length() > 0) { txt = txt.substring(0, txt.length() - 1); } return txt + " (" + ops + ")"; } static IHandlerInfo getHandlerInfo(IHandler handler) { if (handler instanceof HandlerChain) { return ((HandlerChain) handler).getHandlerInfo(); } else { HandlerInfo handlerInfo = handlerInfoCache.get(handler.getClass()); if (handlerInfo == null) { handlerInfo = new HandlerInfo(handler); handlerInfoCache.put(handler.getClass(), handlerInfo); } return handlerInfo; } } static CompletionHandlerInfo getCompletionHandlerInfo(IWriteCompletionHandler handler) { CompletionHandlerInfo completionHandlerInfo = completionHandlerInfoCache.get(handler.getClass()); if (completionHandlerInfo == null) { completionHandlerInfo = new CompletionHandlerInfo(handler); completionHandlerInfoCache.put(handler.getClass(), completionHandlerInfo); } return completionHandlerInfo; } private static boolean isMethodThreaded(Class clazz, String methodname, boolean dflt, Class... paramClass) { try { Method meth = clazz.getMethod(methodname, paramClass); Execution execution = meth.getAnnotation(Execution.class); if (execution != null) { if(execution.value() == Execution.NONTHREADED) { return false; } else { return true; } } else { return dflt; } } catch (NoSuchMethodException nsme) { return dflt; } } private static boolean isHandlerMultithreaded(Object handler) { Execution execution = handler.getClass().getAnnotation(Execution.class); if (execution != null) { if(execution.value() == Execution.NONTHREADED) { return false; } else { return true; } } else { return true; } } private static final class MapCache extends LinkedHashMap { private static final long serialVersionUID = 4513864504007457500L; private int maxSize = 0; MapCache(int maxSize) { this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Entry eldest) { return size() > maxSize; } } private static final class HandlerInfo implements IHandlerInfo { private boolean isConnectHandler = false; private boolean isDataHandler = false; private boolean isDisconnectHandler = false; private boolean isIdleTimeoutHandler = false; private boolean isConnectionTimeoutHandler = false; private boolean isConnectExceptionHandler = false; private boolean isLifeCycle = false; private boolean isConnectionScoped = false; private boolean isHandlerMultithreaded = false; private boolean isConnectHandlerMultithreaded = false; private boolean isDataHandlerMultithreaded = false; private boolean isDisconnectHandlerMultithreaded = false; private boolean isIdleTimeoutHandlerMultithreaded = false; private boolean isConnectionTimeoutHandlerMultithreaded = false; private boolean isConnectExceptionHandlerMultithreaded = false; private boolean isUnsynchronized = false; HandlerInfo(IHandler handler) { isConnectHandler = (handler instanceof IConnectHandler); isDataHandler = (handler instanceof IDataHandler); isDisconnectHandler = (handler instanceof IDisconnectHandler); isIdleTimeoutHandler = (handler instanceof IIdleTimeoutHandler); isConnectionTimeoutHandler = (handler instanceof IConnectionTimeoutHandler); isConnectExceptionHandler = (handler instanceof IConnectExceptionHandler); isLifeCycle = (handler instanceof ILifeCycle); isConnectionScoped = (handler instanceof IConnectionScoped); isUnsynchronized = (handler instanceof IUnsynchronized); isHandlerMultithreaded = ConnectionUtils.isHandlerMultithreaded(handler); if (isConnectHandler) { isConnectHandlerMultithreaded = isMethodThreaded(handler.getClass(), "onConnect", isHandlerMultithreaded, INonBlockingConnection.class); } if (isDataHandler) { isDataHandlerMultithreaded = isMethodThreaded(handler.getClass(), "onData", isHandlerMultithreaded, INonBlockingConnection.class); } if (isDisconnectHandler) { isDisconnectHandlerMultithreaded = isMethodThreaded(handler.getClass(), "onDisconnect", isHandlerMultithreaded, INonBlockingConnection.class); } if (isIdleTimeoutHandler) { isIdleTimeoutHandlerMultithreaded =isMethodThreaded(handler.getClass(), "onIdleTimeout", isHandlerMultithreaded, INonBlockingConnection.class); } if (isConnectionTimeoutHandler) { isConnectionTimeoutHandlerMultithreaded =isMethodThreaded(handler.getClass(), "onConnectionTimeout", isHandlerMultithreaded, INonBlockingConnection.class); } if (isConnectionTimeoutHandler) { isConnectHandlerMultithreaded = isMethodThreaded(handler.getClass(), "onConnectionTimeout", isHandlerMultithreaded, INonBlockingConnection.class); } if (isConnectExceptionHandler) { isConnectExceptionHandlerMultithreaded = isMethodThreaded(handler.getClass(), "onConnectException", isHandlerMultithreaded, INonBlockingConnection.class, IOException.class); } } public boolean isConnectHandler() { return isConnectHandler; } public boolean isDataHandler() { return isDataHandler; } public boolean isDisconnectHandler() { return isDisconnectHandler; } public boolean isIdleTimeoutHandler() { return isIdleTimeoutHandler; } public boolean isConnectionTimeoutHandler() { return isConnectionTimeoutHandler; } public boolean isLifeCycle() { return isLifeCycle; } public boolean isConnectionScoped() { return isConnectionScoped; } public boolean isConnectExceptionHandler() { return isConnectExceptionHandler; } public boolean isConnectExceptionHandlerMultithreaded() { return isConnectExceptionHandlerMultithreaded; } public boolean isUnsynchronized() { return isUnsynchronized; } public boolean isConnectHandlerMultithreaded() { return isConnectHandlerMultithreaded; } public boolean isDataHandlerMultithreaded() { return isDataHandlerMultithreaded; } public boolean isDisconnectHandlerMultithreaded() { return isDisconnectHandlerMultithreaded; } public boolean isIdleTimeoutHandlerMultithreaded() { return isIdleTimeoutHandlerMultithreaded; } public boolean isConnectionTimeoutHandlerMultithreaded() { return isConnectionTimeoutHandlerMultithreaded; } } static final class CompletionHandlerInfo { private boolean isOnWrittenMultithreaded = false; private boolean isOnExceptionMultithreaded = false; private boolean isUnsynchronized = false; public CompletionHandlerInfo(IWriteCompletionHandler handler) { isUnsynchronized = (handler instanceof IUnsynchronized); boolean isHandlerMultithreaded = ConnectionUtils.isHandlerMultithreaded(handler); isOnWrittenMultithreaded = isMethodThreaded(handler.getClass(), "onWritten", isHandlerMultithreaded, int.class); isOnExceptionMultithreaded = isMethodThreaded(handler.getClass(), "onException", isHandlerMultithreaded, IOException.class); } public boolean isUnsynchronized() { return isUnsynchronized; } public boolean isOnWrittenMultithreaded() { return isOnWrittenMultithreaded; } public boolean isOnExceptionMutlithreaded() { return isOnExceptionMultithreaded; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy