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

org.xsocket.connection.http.AbstractHttpConnection Maven / Gradle / Ivy

There is a newer version: 2.0-beta-1
Show newest version
/*
 *  Copyright (c) xsocket.org, 2006 - 2008. 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.http;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.BufferUnderflowException;
import java.nio.channels.ClosedChannelException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IConnectionTimeoutHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.IHandler;
import org.xsocket.connection.IIdleTimeoutHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnectionPool;
import org.xsocket.connection.http.AbstractHttpMessage.BodyType;
import org.xsocket.connection.http.client.HttpClientConnection;
import org.xsocket.connection.http.server.HttpServerConnection;
import org.xsocket.connection.spi.DefaultIoProvider;





/**
 * 
 * Implemenation base for the {@link HttpClientConnection} and {@link HttpServerConnection}
 * 
 * @author [email protected]
 */
public abstract class AbstractHttpConnection implements IHttpConnection {

	private static final Logger LOG = Logger.getLogger(AbstractHttpConnection.class.getName());
		
	protected static final int MAX_HEADER_LENGTH = Integer.MAX_VALUE;

	protected static final String DEFAULT_BODY_ENCODING = AbstractMessageHeader.DEFAULT_BODY_ENCODING;
	
	private static Executor defaultWorkerPool = null;

	
	private final AtomicBoolean isReceiving = new AtomicBoolean(true);

	
	// open flag
	private boolean isOpen = true;
	
	
	// timer support 
	private static final Timer TIMER = new Timer("xHttpTimer", true);

		
	// underlying connection
	private INonBlockingConnection tcpConnection = null;
	
	
	// protocol handler
	private final ProtocolHandler protocolHandler = new ProtocolHandler();
	
		
	// flags
	private boolean isPersistent = false;
	
	
	// sent transaction flag
	private final AtomicBoolean isWriteTransactionRunning = new AtomicBoolean(false);
	
	
	// attached parser
	private IDataHandler bodyParser = null;
	
	// output body close listener support
	private final AtomicReference bodyDataSink = new AtomicReference();
	
	
	// attachment
	private Object attachment = null;
	
	
	
	// call back processing
	private final LinkedList taskQueue = new LinkedList();
	private final MultithreadedTaskProcessor multithreadedTaskProcessor = new MultithreadedTaskProcessor();

	

	/**
	 * constructor 
	 * 
	 * @param nativeConnection   the underlying tcp connection
	 * @throws IOException if an exception occurs
	 */
	protected AbstractHttpConnection(INonBlockingConnection nativeConnection) throws IOException {
		this.tcpConnection = nativeConnection;
		
		nativeConnection.setAutoflush(false);
		nativeConnection.setFlushmode(FlushMode.ASYNC);
		nativeConnection.setAttachment(this);

		protocolHandler.onConnect(nativeConnection);
		nativeConnection.setHandler(protocolHandler);
	}


	/**
	 * retrieve a http connection based on a given native conection (http connection will be attached 
	 * to the native connection within the http connection constructor)
	 * 
	 * @param nativeConnection  the native connection
	 * @return the http connection
	 */
	static AbstractHttpConnection getHttpConnection(INonBlockingConnection nativeConnection) {
		return (AbstractHttpConnection) nativeConnection.getAttachment();
	}
	
	
	
	protected static boolean hasChunkedBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.CHUNKED;
	}

	protected static boolean hasBoundBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.BOUND;
	}
	
	protected static boolean hasConnectionTerminatedBody(AbstractHttpMessage message) throws IOException {
		return message.getBodyType() == BodyType.CONNECTION_TERMINATED;
	}
	
	/**
	 * returns the underlying native tcp connection
	 * 
	 * @return the tcp connetion
	 */
	protected final INonBlockingConnection getUnderlyingConnection() {
		return tcpConnection;
	}

	

	
	/**
	 * returns the default worker pool
	 * 
	 * @return the default worker pool
	 */
	synchronized static Executor getDefaultWorkerpool() {
		if (defaultWorkerPool == null) {
			defaultWorkerPool = Executors.newCachedThreadPool(new DefaultThreadFactory());
		}
		
		return defaultWorkerPool;
	}

	
	protected static void schedule(TimerTask task, long delay, long period) {
		TIMER.schedule(task, delay, period);
	}
	

	/**
	 * process a task non threaded
	 * 
	 * @param task the task to process
	 */
	protected final void processNonThreaded(Runnable task) {
	
		synchronized (taskQueue) {
			
			// no running worker -> process non threaded
			if (taskQueue.isEmpty()) {
				task.run();
			
			// worker is currently running -> add task to queue (the task will be handled by the running worker)
			} else {
				taskQueue.addLast(task);
			}				
		}
	}

	
	/**
	 * process a task multi threaded
	 * 
	 * @param task the task to process
	 */
	protected final void processMultiThreaded(Runnable task) {
		synchronized (taskQueue) {
			
			// no running worker
			if (taskQueue.isEmpty()) {
				taskQueue.addLast(task);

				getUnderlyingConnection().getWorkerpool().execute(multithreadedTaskProcessor);				
			
			// worker is currently running -> add task to queue (the task will be handled by the running worker)
			} else {
				taskQueue.addLast(task);
			}				
		}
	}



	
	
	/**
	 * {@inheritDoc}
	 */
	public final boolean isSecure() {
		return tcpConnection.isSecure();
	}
	
	
	/**
	 * returns if the connection is persistent 
	 * 
	 * @return true, if the connection is persistent
	 */
	public final boolean isPersistent() {
		return isPersistent;
	}
	
	
	/**
	 * sets the persistent flag
	 * 
	 * @param isPersistent the persistent flag 
	 */
	protected final void setPersistent(boolean isPersistent) {
		this.isPersistent = isPersistent;
	}
	
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setOption(String name, Object value) throws IOException {
		tcpConnection.setOption(name, value);
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final Object getOption(String name) throws IOException {
		return tcpConnection.getOption(name);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public final Map getOptions() {
		return tcpConnection.getOptions();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void setConnectionTimeoutMillis(long timeoutMillis) {
		tcpConnection.setConnectionTimeoutMillis(timeoutMillis);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final long getConnectionTimeoutMillis() {
		return tcpConnection.getConnectionTimeoutMillis();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setIdleTimeoutMillis(long timeoutMillis) {
		tcpConnection.setIdleTimeoutMillis(timeoutMillis);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final long getIdleTimeoutMillis() {
		return tcpConnection.getIdleTimeoutMillis();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final boolean isOpen() {
		if (!isOpen) {
			return false;
		}
		
		return tcpConnection.isOpen();
	}
	
	

	/**
	 * {@inheritDoc}
	 */
	public final String getId() {
		return tcpConnection.getId();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToConnectionTimeout() {
		return tcpConnection.getRemainingMillisToConnectionTimeout();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public long getRemainingMillisToIdleTimeout() {
		return tcpConnection.getRemainingMillisToIdleTimeout();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public void close() throws IOException {

		Runnable closeTask = new Runnable() {
			
			public void run() {
				doClose();	
			}		
		};

		processNonThreaded(closeTask);
	}
	
	
	private void doClose() {
		try {
			if (isResusable()) {
				tcpConnection.close();
				
			} else {
				
				if (LOG.isLoggable(Level.FINE)) {
					if (getBodyParser() != null) {
						LOG.fine("destroying connection instead of closing it (BodyParser is set)");
					}
				}
				
				// destroy (underlying tcp) connection by using connection pool. The underlying connection could be a pooled one)
				// The connection pool detects automatically if the connection is pooled or not. The connection will be 
				// closed (logically) anyway
				NonBlockingConnectionPool.destroy(tcpConnection);
			}
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by closing htttp connection " + getId() + " " + ioe.toString());
			}
		}			
	}
	
	
	protected boolean isResusable() {
		return (getBodyParser() == null) && isPersistent; 
	}
	

	protected final void closeSilence() {
		try {
			close();
		} catch (IOException ioe) {
			if (LOG.isLoggable(Level.FINE)) {
				LOG.fine("error occured by closing connection " + getId() + " " + ioe.toString());
			}
			
			try {
				NonBlockingConnectionPool.destroy(tcpConnection);
			} catch (IOException ignore) { }
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final void setAttachment(Object obj) {
		this.attachment = obj;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final Object getAttachment() {
		return attachment;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final InetAddress getLocalAddress() {
		return tcpConnection.getLocalAddress();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getLocalPort() {
		return tcpConnection.getLocalPort();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final InetAddress getRemoteAddress() {
		return tcpConnection.getRemoteAddress();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final int getRemotePort() {
		return tcpConnection.getRemotePort();
	}
	
	

	/**
	 * destroy the connection
	 */
	protected void destroy() {
		
		
		Runnable destroyTask = new Runnable() {
			
			public void run() {
				// destroy (underlying tcp) connection by using connection pool. The underlying connection could be a pooled one)
				// The connection pool detects automatically if the connection is pooled or not. The connection will be 
				// closed (logically) anyway
				try {
					NonBlockingConnectionPool.destroy(tcpConnection);
				} catch (IOException ioe) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("error occured by destroying htttp connection " + getId() + " " + ioe.toString());
					}
				}				
			}		
		};

		processNonThreaded(destroyTask);
	}
	
	
	protected final void sendMessageBody(BodyDataSink bodyDataSink, NonBlockingBodyDataSource bodyDataSource) throws IOException {
		
		try {
			bodyDataSink.setFlushmode(FlushMode.ASYNC);
			bodyDataSink.setAutoflush(false);
	
			// is body complete?
			if (bodyDataSource.isComplete()) {
				int available = bodyDataSource.available();
				if (available > 0) {
					bodyDataSink.write(bodyDataSource.readBytesByLength(available));
				}
				bodyDataSink.close();	
	
			// .. no
			} else {
				BodyDataForwardHandler forwardHandler = new BodyDataForwardHandler(bodyDataSink);
				bodyDataSource.setDataHandler(forwardHandler);
				bodyDataSink.flush();
			}
			
		} catch (ClosedChannelException cce) {
			throw new IOException("http connection " + getId() + " is already closed " + cce.toString());
		}
	}


	
	

	protected final BodyDataSink newChunkedBody(AbstractMessageHeader header) throws IOException {
		return new ChunkedBodyDataSink(this, header, header.getCharacterEncoding());
	}

	protected final BodyDataSink newBoundBody(AbstractMessageHeader header) throws IOException {
		return new BoundBodyDataSink(this, header, header.getCharacterEncoding());		
	}
	
	/*protected final BodyDataSink newUnboundBody(AbstractMessageHeader header) throws IOException {
		return new UnboundBodyDataSink(this, header, header.getCharacterEncoding());		
	}*/
	
	
	protected void onBodyDataReceived() {
		
	}
	
	
	void setBodyDataSink(BodyDataSink bodyChannel) {
		isWriteTransactionRunning.set(true);
		bodyDataSink.set(bodyChannel);
	}
	
	
	boolean removeBodyDataSink(BodyDataSink bodyChannel) {
		BodyDataSink bds = bodyDataSink.get(); 
		if ((bds != null) && (bds == bodyChannel)) {
			bodyDataSink.set(null);
			isWriteTransactionRunning.set(false);
			return true;
		}
		
		return false;
	}
	

	
	
	protected final boolean isWriteTransactionRunning() {
		return isWriteTransactionRunning.get();
	}
	
	protected HttpResponseHeader newEmptyResponseHeader() { 
		return new HttpResponseHeader();
	}
	
	
	protected abstract void onMessage(INonBlockingConnection connection) throws BufferUnderflowException, IOException;
	
	
	
	protected final void addFullMessageBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		
		try {
			// chunked body
			if (AbstractMessageHeader.hasChunkedBody(message.getMessageHeader())) {
				ChunkedBodyDataSource body = new ChunkedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
				message.setBodyDataSource(body);
				setBodyParser(body);
				
				body.onData(connection);
	
				
			// length defined plain body
			} else if (AbstractMessageHeader.hasBoundBody(message.getMessageHeader())) {
	
				BoundBodyDataSource body = new BoundBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
				message.setBodyDataSource(body);
				setBodyParser(body);
				
				body.onData(connection);
				
	
			// connection terminated body
			} else if (AbstractMessageHeader.hasConnectionTerminatedBody(message.getMessageHeader())) {
	
				setPersistent(false);
				
				ConnectionTerminatedBodyDataSource body = new ConnectionTerminatedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
				message.setBodyDataSource(body);
				setBodyParser(body);
				
				body.onData(connection);
				
			}
			
		} catch (BufferUnderflowException ignore) { }
	}

	
	
	protected final void addSimpleMessageBodyParser(AbstractHttpMessage message, INonBlockingConnection connection) throws IOException {
		
		try {
			setPersistent(false);
				
			ConnectionTerminatedBodyDataSource body = new ConnectionTerminatedBodyDataSource(AbstractHttpConnection.this, message.getMessageHeader(), message.getCharacterEncoding());
			message.setBodyDataSource(body);
			setBodyParser(body);
				
			body.onData(connection);	
		} catch (BufferUnderflowException ignore) { }
	}
	

	/**
	 * disconnect call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected void onDisconnect() throws IOException {

		BodyDataSink bds = bodyDataSink.get();
		if (bds != null) {
			bds.onUnderlyingHttpConnectionClosed();
			bodyDataSink.set(null);
		}		
	}
	
	
	/**
	 * idle time out call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	private void onIdleTimeout() throws IOException {
		if (LOG.isLoggable(Level.FINE)) {
			LOG.fine("[" + getId()  + "] idle timeout reached (" + 
					 DataConverter.toFormatedDuration(tcpConnection.getIdleTimeoutMillis()) + 
					 "). terminate connection");
		}

		this.close();
	}
	
	
	/**
	 * connection timeout call back 
	 * 
	 * @throws IOException  if an exception occurs
	 */
	protected void onConnectionTimeout() throws IOException {
		this.close();
	}
	
	
	protected void onProtocolException(Throwable throwable) {
		if (LOG.isLoggable(Level.FINE)) {
			LOG.fine("[" + getId() + "] protocol error. closing connection " + throwable.toString());
		}
		
throwable.printStackTrace();
		
		destroy();
	}


	
	/**
	 * parse the encoding part of the given content type
	 * 
	 * @param contentType  the content type
	 * @return the encoding
	 */
	protected static final String parseEncoding(String contentType) {
		return AbstractMessageHeader.parseEncoding(contentType);
	}
	
	
	/**
	 * set the close flag for a body data sink
	 * 
	 * @param bodyDataSink  the body data sink
	 * @param isClose       the close flag
	 */
	protected static final void setCloseHttpConnectionAfterWritten(BodyDataSink bodyDataSink, boolean isClose) {
		bodyDataSink.setCloseHttpConnectionAfterWritten(isClose);
	}

	

		
	
	/**
	 * remove the current body parser
	 */
	void removeBodyParser() {
		bodyParser = null;
	}

	
	private IHandler getBodyParser() {
		return bodyParser;
	}


	
	private void setBodyParser(IDataHandler parser) {
		this.bodyParser = parser;
	}
	
	

	protected static String getReason(int statusCode) {
		switch (statusCode) {
		case 200:
			return "OK";

		case 404:
			return "Not found";
			
		case 500:
			return "Internal Server Error";
			
		case 501:
			return "Not Implemented";

		case 502:
			return "Bad Gateway";

		case 503:
			return "Service Unavailable";
			
		case 504:
			return "Gateway Timeout";
		
		case 505:
			return "HTTP Version Not Supported";

			
		default:
			return " ";
		}
	}
	


	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {		
		return tcpConnection.toString();
	}
	
	

	
	private void performPendingTasks() {
		assert (DefaultIoProvider.isDispatcherThread() == false);
		
		boolean taskToProcess = true;
		
		while(taskToProcess) {
			
			// get task from queue
			Runnable task = null;
			synchronized (taskQueue) {
				task = taskQueue.getFirst();
				assert (task != null) : "a task should always be available";
			}
			
			// perform it 
			task.run();
			
			
			// remove task
			synchronized (taskQueue) {
				
				// remaining tasks to process				
				if (taskQueue.size() > 1) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("more task to process. process next task");
					}
					taskQueue.remove(task);
					taskToProcess = true;
					
				} else {
					taskQueue.remove(task);
					taskToProcess = false;
				}
			}
		}
	}

	


	

	private final class MultithreadedTaskProcessor implements Runnable {
		
		public void run() {
			performPendingTasks();
		}
	}
	
	
	

	
	
	
	@Execution(Execution.NONTHREADED)
	private final class ProtocolHandler implements IDataHandler, IConnectHandler, IDisconnectHandler, IIdleTimeoutHandler, IConnectionTimeoutHandler {

		
		public boolean onData(INonBlockingConnection connection) throws BufferUnderflowException {
			
			try {
				if (connection.available() <= 0) {
					return true;
				}
		
				
				if (isReceiving.get()) {
					
					AbstractHttpConnection httpCon = getHttpConnection(connection);
					if (httpCon != null) {
						
						IDataHandler bodyParser = (IDataHandler) httpCon.getBodyParser();
						
						// body handling?
						if (bodyParser != null) {
							return bodyParser.onData(connection);
						} 
						
						AbstractHttpConnection.this.onMessage(connection);
						
					} else {
						connection.close();
					}
				}
				
			} catch (ClosedChannelException ignore) {
				
				
			} catch (BufferUnderflowException bue) {
				throw bue;				
				
			} catch (Exception ex) {
				onProtocolException(ex);
			}
			
			return true;
		}
		
		public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
			return true;
		}
		
		public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
			isOpen = false;
			
			IDisconnectHandler bodyParser = (IDisconnectHandler) getHttpConnection(connection).getBodyParser();

			// body handling?
			if (bodyParser != null) {
				bodyParser.onDisconnect(connection);
			}
			
			AbstractHttpConnection.this.onDisconnect();
			return true;
		}
		
		
		public boolean onConnectionTimeout(INonBlockingConnection connection) throws IOException {
			AbstractHttpConnection.this.onConnectionTimeout();
			return true;
		}
		
		
		public boolean onIdleTimeout(INonBlockingConnection connection) throws IOException {
			AbstractHttpConnection.this.onIdleTimeout();
			return true;
		}
	}
	
	
	private final class BodyDataForwardHandler implements IBodyDataHandler {

		private BodyDataSink bodyDataSink = null;
		private int forwarded = 0;
		
		
		public BodyDataForwardHandler(BodyDataSink bodyDataSink) {
			this.bodyDataSink = bodyDataSink;
		}
		
		
		@Execution(Execution.NONTHREADED)
		public boolean onData(NonBlockingBodyDataSource bodyDataSource) {

			
			try {
				int available = bodyDataSource.available();
				
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("[" + getId() + "] " + bodyDataSource.getClass().getSimpleName() + "available=" + available);
				}
				
				// data available
				if (available > 0) {
					long written = bodyDataSink.write(bodyDataSource.readBytesByLength(available));
					bodyDataSink.flush();
					
					forwarded += written;
					
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" + getId() + "] " + bodyDataSource.getClass().getSimpleName() + " " + written + " forwarded to data sink " + bodyDataSink.getClass().getSimpleName() + " (total forwarded=" + forwarded + ")");
					}
					
					
				// end of stream reached
				} else if (available == -1) {
					if (LOG.isLoggable(Level.FINE)) {
						LOG.fine("[" + getId() + "] " + bodyDataSource.getClass().getSimpleName() + " closing data sink " + bodyDataSink.getClass().getSimpleName() + " (total forwarded=" + forwarded + ")");
					}
					bodyDataSink.close();
				}
			
				return true;

			} catch (Exception ioe) {
				if (LOG.isLoggable(Level.FINE)) {
					LOG.fine("error occured by writing data sink " + bodyDataSource + " " + ioe.toString() + " closing connection " + getId());
				}
				destroy();
				bodyDataSink.destroy();
				return false;
			}
		}
	};
	
	
	
	private static class DefaultThreadFactory implements ThreadFactory {
		private static final AtomicInteger poolNumber = new AtomicInteger(1);
		private final ThreadGroup group;
		private final AtomicInteger threadNumber = new AtomicInteger(1);
		private final String namePrefix;

		DefaultThreadFactory() {
			SecurityManager s = System.getSecurityManager();
			group = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
			namePrefix = "xHttpNbcPool-" + poolNumber.getAndIncrement() + "-thread-";
        }

		public Thread newThread(Runnable r) {
			Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (!t.isDaemon()) {
                t.setDaemon(true);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy