Package org.xlightweb

Source Code of org.xlightweb.NonBlockingBodyDataSource$BodyDataHandlerAdapter

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. 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.xlightweb.org/
*/
package org.xlightweb;


import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
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.xlightweb.AbstractHttpConnection.IMultimodeExecutor;
import org.xlightweb.HttpUtils.CompletionHandlerInfo;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.IDataSource;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.AbstractNonBlockingStream;
import org.xsocket.connection.ConnectionUtils;
import org.xsocket.connection.IWriteCompletionHandler;
import org.xsocket.connection.IConnection.FlushMode;



/**
* data source base implementation
* @author grro@xlightweb.org
*/
public abstract class NonBlockingBodyDataSource implements IDataSource, ReadableByteChannel, Closeable {
 
    private static final Logger LOG = Logger.getLogger(NonBlockingBodyDataSource.class.getName());
   
   
    @SuppressWarnings("unchecked")
    private static final Map<Class, Integer> bodyDataHandlerExecutionModeCache = ConnectionUtils.newMapCache(25);
   
    private final AtomicBoolean isDestroyed = new AtomicBoolean(false);
    private final AtomicBoolean isComplete = new AtomicBoolean(false);
  private final AtomicReference<IOException> exceptionRef = new AtomicReference<IOException>();
 
  private final NonBlockingStream nonBlockingStream = new NonBlockingStream();
  private final AtomicReference<ISink> sourceRef = new AtomicReference<ISink>(nonBlockingStream);
   
  private final AtomicReference<IBodyDataHandler> bodyHandlerRef = new AtomicReference<IBodyDataHandler>(null);
  private final ArrayList<IBodyCloseListener> closeListeners = new ArrayList<IBodyCloseListener>();
  private final ArrayList<IBodyCompleteListener> completeListeners = new ArrayList<IBodyCompleteListener>();
  private IBodyDestroyListener destroyListener = null;
 
  private final IMultimodeExecutor executor;
 
 
 
     // life cycle management
    private static final long MIN_WATCHDOG_PERIOD_MILLIS = 10 * 1000;
    public static final long DEFAULT_RECEIVE_TIMEOUT_MILLIS = Long.MAX_VALUE;

    private long bodyDataReceiveTimeoutMillis = DEFAULT_RECEIVE_TIMEOUT_MILLIS;
    private long creationTimeMillis = 0;
    private long lastTimeDataReceivedMillis = System.currentTimeMillis();
    private TimeoutWatchDogTask watchDogTask;

    // statistics
    private int dataReceived = 0;
   
 
  NonBlockingBodyDataSource(String encoding, IMultimodeExecutor executor) {
      nonBlockingStream.setEncoding(encoding);
      this.executor = executor;
    }

  NonBlockingBodyDataSource(String encoding, IMultimodeExecutor executor, ByteBuffer[] data) throws IOException {
        nonBlockingStream.setEncoding(encoding);
        this.executor = executor;
       
        if (data != null) {
            append(data);
        }
       
        isComplete.set(true);
    }


 
  /**
     * appends data
     *
     * @param isContentImmutable if the buffer is immutable
     * @param data               the data to append
     */
    int append(ByteBuffer data) throws IOException {
        int added = sourceRef.get().append(data);
        dataReceived += added;
       
        return added;
    }

   
    /**
     * appends data
     *
     * @param isContentImmutable if the buffer is immutable
     * @param data               the data to append
     */
    int append(ByteBuffer[] data) throws IOException {
        int added = sourceRef.get().append(data);
        dataReceived += added;
       
        return added;
    }
   
   
    /**
     * appends data
     *
     * @param isContentImmutable if the buffer is immutable
     * @param data               the data to append
     */
    int append(ByteBuffer[] data, IWriteCompletionHandler completionHandler) throws IOException {
        int added = 0;
       
        ISink sink = sourceRef.get();
        if (sink != null) {
            if (completionHandler != null) {
                added = sink.append(data, completionHandler, false);
            } else {
                added = sink.append(data);
            }
           
            dataReceived += added;
            return added;
           
        } else {
            return 0;
        }
    }
   
    
    
    final boolean isMoreInputDataExpected() {
       
        // no, if data source is complete
        if (isComplete.get()) {
            return false;
        }
      
        // no, if data source is destroyed
        if (isDestroyed.get()) {
            return false;
        }
       
        // ... yes
        return true;
    }
   
   
    void setComplete() throws IOException {
        isComplete.set(true);

        ISink sink = sourceRef.get();
        sink.setComplete();
       
        callCompleteListeners();
       
        terminateWatchDog();
    }
   
 
  /**
     * return true, if all body data has been received
     * 
     * @return true, if all body data has been received
     * @throws IOException if an error exists
     */
    final boolean isCompleteReceived() throws IOException {
        if (isComplete.get()) {
            return true;           
        } else {
            throwExceptionIfExist();
            return false;
        }
    }

    final boolean isComplete() {
        return isComplete.get();
    }

    final int getSizeDataReceived() {
        return dataReceived;
    }
   
    final boolean isDestroyed() {
        return isDestroyed.get();
    }
       
    final void setException(IOException ioe) {
       
        IOException oldException = exceptionRef.get();
        if (oldException != null) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] warning a exception alreday exits. ignore exception (old: " + oldException + ", new: " + ioe);   
            }
            return;
        }
       
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" + getId() + "] set exception " + ioe);   
        }
       
        exceptionRef.set(ioe);        
        callBodyDataHandler(true);
        destroy(ioe.toString());
    }

   

    /**
     * destroys the data source
     */
    public void destroy() {
        destroy("user initiated");
    }
       
   

    /**
     * destroys the data source
     */
    void destroy(String reason) {
        terminateWatchDog();
        getExecutor().processNonthreaded(new DestroyTask(reason));
    }

   
    private final class DestroyTask implements Runnable {
       
        private final String reason;
       
        public DestroyTask(String reason) {
            this.reason = reason;
        }
       
        public void run() {
            performDestroy(reason);
        }
    }
   
    private void performDestroy(String reason) {
       
        if (!isDestroyed.getAndSet(true)) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] destroying data source");
            }
               
            onDestroy(reason);
           
            if (destroyListener != null) {
                try {
                    destroyListener.onDestroyed();
                } catch (IOException ioe) {
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("[" + getId() + "] error occured by calling destroy listener " + destroyListener + " " + ioe.toString());
                    }
                }           
            }
           
            sourceRef.get().destroy();
        }
    }
   
   
    abstract void onDestroy(String reason);
   

   
    /**
     * returns the id
     *
     * @return the id
     */
    abstract String getId();

   
   
    /**
     * adds a complete listener
     *
     * @param listener  the complete listener
     */
    public void addCompleteListener(IBodyCompleteListener listener) {
   
        synchronized (completeListeners) {
            completeListeners.add(listener);
        }
       
        if (isComplete.get()) {
            callCompleteListener(listener);
        }
    }
   
   
    @SuppressWarnings("unchecked")
    private void callCompleteListeners() {
        List<IBodyCompleteListener> completeListenersCopy = null;
        synchronized (completeListeners) {
            if (!completeListeners.isEmpty()) {
                completeListenersCopy = (List<IBodyCompleteListener>) completeListeners.clone();
            }
        }
       
        if (completeListenersCopy != null) {
            for (IBodyCompleteListener listener : completeListenersCopy) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("calling complete listener " + listener.getClass().getName() + "#" + listener.hashCode());
                }
               
                removeCompleteListener(listener);
                callCompleteListener(listener);
            }
        }          
    }

   
    private void callCompleteListener(final IBodyCompleteListener listener) {
       
        Runnable task = new Runnable() {
            public void run() {
                try {
                    listener.onComplete();
                } catch (IOException ioe) {
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("[" + getId() + "] Error occured by calling complete listener " + listener + " " + ioe.toString());
                    }
                    destroy(ioe.toString());
                }
            }
        };
       
        if (HttpUtils.isBodyCompleteListenerMutlithreaded(listener)) {
            getExecutor().processMultithreaded(task);
        } else {
            getExecutor().processNonthreaded(task);
        }
    }
   
    private boolean removeCompleteListener(IBodyCompleteListener listener) {
        synchronized (completeListeners) {
            return completeListeners.remove(listener);
        }
    }

   



    final void setDestroyListener(IBodyDestroyListener destroyListener) {
        assert (this.destroyListener == null);
        this.destroyListener = destroyListener;
    }

    final void addCloseListener(IBodyCloseListener closeListener) {
        synchronized (closeListeners) {
            closeListeners.add(closeListener);
        }
    }
   
    @SuppressWarnings("unchecked")
    private void callCloseListener() {
        ArrayList<IBodyCloseListener> closeListenersCopy = null;
        synchronized (closeListeners) {
            closeListenersCopy = (ArrayList<IBodyCloseListener>) closeListeners.clone();
        }
       
        for (IBodyCloseListener bodyCloseListener : closeListenersCopy) {
            removeCloseListener(bodyCloseListener);
            try {
                bodyCloseListener.onClose();
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + getId() + "] error occured by calling " + bodyCloseListener + " " + ioe.toString());
                }
            }
        }
    }

   
    /**
     * remove a close listener
     *
     * @param closeListener a close listener
     * @return true, if the listener is removed
     */
    final boolean removeCloseListener(IBodyCloseListener closeListener) {
        synchronized (closeListeners) {
            return closeListeners.remove(closeListener);
        }
    }


   
   
    /**
     * set the body handler
     *
     * @param bodyDataHandler  the body handler
     */
    public final void setDataHandler(IBodyDataHandler bodyDataHandler) {
        IBodyDataHandler bodyDataHandlerAdapter = newBodyDataHandlerAdapter(bodyDataHandler);
        bodyHandlerRef.set(bodyDataHandlerAdapter);
        callBodyDataHandler(true);
    }
 
    final void setSystemDataHandler(IBodyDataHandler bodyDataHandler) {
        bodyHandlerRef.set(bodyDataHandler);
        callBodyDataHandler(true);
    }

   
    final void callBodyDataHandler(boolean force) {
       
        IBodyDataHandler bodyDataHandler = bodyHandlerRef.get();
        if (bodyDataHandler != null) {
            if ((getSize() != 0) || force) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + getId() + "] calling body data handler " + bodyDataHandler.getClass().getName() + "#" + bodyDataHandler.hashCode());
                }
                bodyDataHandler.onData(this);
            } else {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("body data handler " + bodyDataHandler.getClass().getName() + "#" + bodyDataHandler.hashCode() +
                             " will not be called (size == 0)");
                }
            }
        } else {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("no body data handler assigned");
            }
        }
    }

    private int getSize() {
        int size = nonBlockingStream.getSize();
        if ((size == 0) && isComplete.get()) {
            return -1;
        }
       
        return size;
    }
   
   
    private int getVersion() throws IOException {
        return nonBlockingStream.getReadBufferVersion();
    }

   

    /**
     * returns the body data handler or <code>null</code> if no data handler is assigned
     *
     * @return the body data handler or <code>null</code> if no data handler is assigned
     */
    public IBodyDataHandler getDataHandler() {
        IBodyDataHandler bodyDataHandler = bodyHandlerRef.get();
        if (bodyDataHandler instanceof BodyDataHandlerAdapter) {
            return ((BodyDataHandlerAdapter) bodyDataHandler).getDelegate();
        } else {
            return bodyDataHandler;
        }
    }

   
   
    /**
     * set the body data receive timeout
     *
     * @param bodyDataReceiveTimeoutMillis the timeout
     */
    public final void setBodyDataReceiveTimeoutMillis(long bodyDataReceiveTimeoutMillis) {
        if (bodyDataReceiveTimeoutMillis <= 0) {
            if (!isComplete.get()) {
                setException(new ReceiveTimeoutException(bodyDataReceiveTimeoutMillis));
            }
            return;
        }
       
        creationTimeMillis = System.currentTimeMillis();
       
        if (this.bodyDataReceiveTimeoutMillis != bodyDataReceiveTimeoutMillis) {
            this.bodyDataReceiveTimeoutMillis = bodyDataReceiveTimeoutMillis;
       
            if (bodyDataReceiveTimeoutMillis == Long.MAX_VALUE) {
                terminateWatchDog();

            } else{
               
                long watchdogPeriod = 100;
                if (bodyDataReceiveTimeoutMillis > 1000) {
                    watchdogPeriod = bodyDataReceiveTimeoutMillis / 10;
                }
               
                if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
                    watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
                }
               
                updateWatchDog(watchdogPeriod);
            }
        }
    }
   
   
    private synchronized void updateWatchDog(long watchDogPeriod) {
        terminateWatchDog();

        watchDogTask = new TimeoutWatchDogTask(this);
        AbstractHttpConnection.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
    }

   
    private synchronized void terminateWatchDog() {
        if (watchDogTask != null) {
            watchDogTask.cancel();
            watchDogTask = null;
        }
    }
   
   
    private void checkTimeouts() {
       
        if (isComplete.get() || isDestroyed.get()) {
            terminateWatchDog();
            return;
        }
       
        long currentTimeMillis = System.currentTimeMillis();
           
        if (currentTimeMillis > (lastTimeDataReceivedMillis + bodyDataReceiveTimeoutMillis) &&
            currentTimeMillis > (creationTimeMillis + bodyDataReceiveTimeoutMillis)) {
           
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] receive timeout reached. set exception");
            }

            if (!isComplete.get()) {
                setException(new ReceiveTimeoutException());
            }
            destroy("receive timeout reached");
        }
    }   
   
 
    /**
     * returns the available bytes
     *
     * @return the number of available bytes, possibly zero, or -1 if the channel has reached end-of-stream
     *
     * @throws ProtocolException if a protocol error occurs
     * @throws IOException if some other exception occurs
     */
    public final int available() throws ProtocolException, IOException  {
       
        // if an exception is pending -> throwing it
        IOException ioe = exceptionRef.get();
        if (ioe != null) {
           
            // ClosedChannelException should not occur here. Anyway, handle it because available() should never throw a ClosedChannelException
            if (!(ioe instanceof ClosedChannelException)) {
                throw ioe;
            }
        }
       
        // retrieve the available data size
        int available = nonBlockingStream.getSize();
       
       
        // non data available?
        if ((available == 0)) {
           
            // if body is complete return -1 to signal end-of-stream
            if (isComplete.get()) {
                return -1;
               
            } else {
               
                // is destroyed?
                if (isDestroyed.get()) {
                    close();
                    throw new ClosedChannelException();
                } else {
                    return 0;
                }
            }
           
        } else {
            return available;
        }
    }

   
    /**
     * returns the current content size
     *
     * @return the current content size
     */
    int size() {
        int available = nonBlockingStream.getSize();
        if ((available <= 0) && isComplete.get()) {
            return -1;
        } else {
            return available;
        }
    }
   
   
   
    /**
     * closes the body data source
     *
     *  @throws IOException if an exception occurs
     */
    public final void close() throws IOException {
       
        if (isOpen()) {
            sourceRef.get().close();
           
            try {
                onClose();
                callCloseListener();    // call close listener only in case of success closing
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + getId() + "] error occured by closing connection. destroying it " + ioe.toString());
               
                setException(ioe);
            }
        }
    }
   

    abstract void onClose() throws IOException;
   


    final void closeSilence() {
        try {
            close();
        } catch (IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] Error occured by closing connection " + ioe.toString());
            }
        }
    }
    

    /**
     * returns true, if the body data source is open
     *
     *  @return  true, if the body data source is open
     */
    public final boolean isOpen() {
        return sourceRef.get().isOpen();
    }
   
 
 
 
  final void throwExceptionIfExist() throws IOException {
      IOException ioe = exceptionRef.get();
      if (ioe != null) {
          throw ioe;
      }
  }

 
  /**
     * see {@link ReadableByteChannel#read(ByteBuffer)}
     */
  public final int read(ByteBuffer buffer) throws IOException {
      throwExceptionIfExist();
     
      int size = buffer.remaining();
        int available = available();
       
        if (available == -1) {
            close();
            return -1;
        }
       
        if (available == 0) {
            return 0;
        }
       
        if (available > 0) {
            if (available < size) {
                size = available;
            }

            if (size > 0) {
                ByteBuffer[] bufs = readByteBufferByLength(size);
                copyBuffers(bufs, buffer);
            }          
        }        
        return size;
  }    

 
    private void copyBuffers(ByteBuffer[] source, ByteBuffer target) {
        for (ByteBuffer buf : source) {
            if (buf.hasRemaining()) {
                target.put(buf);
            }
        }
    }
   
   
    boolean isForwarding() {
        return (sourceRef.get() != nonBlockingStream);
    }
   
   
   
    void forwardTo(BodyDataSink bodyDataSink) throws IOException {

        // reset the source
        ForwardSink forwardSink = new ForwardSink(bodyDataSink);
        sourceRef.set(forwardSink);
       
        // after reseting the source, check if meanwhile the body is complete or data is received by previous sink 
        if (isComplete.get()) {
            forwardSink.close();
        } else {
            forwardSink.appendThreaded(null, null);
        }
    }
 
   
    private final class ForwardSink implements ISink {
       
        private final BodyDataSink delegate;
        private final AtomicInteger concurrentTasks = new AtomicInteger();
       
        private final AtomicBoolean isClosed = new AtomicBoolean();
       
       
        public ForwardSink(BodyDataSink delegate) throws IOException {
            this.delegate = delegate;
           
            delegate.setFlushmode(FlushMode.ASYNC);
            delegate.setAutoflush(true);
        }

       
        final int appendThreaded(final ByteBuffer[] buffers, final IWriteCompletionHandler writeCompletionHandler) {
           
            concurrentTasks.incrementAndGet();
           
            int size = HttpUtils.computeRemaining(buffers);
           
            Runnable task = new Runnable() {
               
                public void run() {
                    try {
                        append(buffers, writeCompletionHandler, true);
                    } catch (ClosedChannelException ignore) {
                       
                    } catch (IOException ioe) {
                        if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("error occured by append null buffer " + ioe.toString());
                        }
                       
                    } finally {
                        concurrentTasks.decrementAndGet();
                    }
                }
            };
            getExecutor().processNonthreaded(task);
           
            return size;
        }
       
       
        private void retrievePrevious() throws IOException {
            int size = nonBlockingStream.getSize();
            if (size > 0) {
                ByteBuffer[] buffers = nonBlockingStream.readByteBufferByLength(size);
                delegate.write(buffers);
            } else if (size == -1) {
                close();
            }
        }
       
       
        public int append(ByteBuffer buffer) throws IOException {
            if (concurrentTasks.get() > 0) {
                return appendThreaded(new ByteBuffer[] { buffer }, null);
            }
           
            retrievePrevious();
           
            delegate.getPendingWriteDataSize();
            return delegate.write(buffer);
        }
       
        public int append(ByteBuffer[] buffers) throws IOException {
            if (concurrentTasks.get() > 0) {
                return appendThreaded(buffers, null);
            }

            retrievePrevious();
           
            delegate.getPendingWriteDataSize();
            return (int) delegate.write(buffers);
        }
       
        public int append(ByteBuffer[] buffers, IWriteCompletionHandler writeCompletionHandler, boolean force) throws IOException {
            if ((force == false) && (concurrentTasks.get() > 0)) {
                return appendThreaded(buffers, writeCompletionHandler);
            }
           
            retrievePrevious();
           
            delegate.getPendingWriteDataSize();
            int size = 0;
            if (buffers != null) {
                for(ByteBuffer buffer : buffers) {
                    size += buffer.remaining();
                }
            }
           
            delegate.write(buffers, writeCompletionHandler);
           
            return size;
        }
       
       
        public void setComplete() throws IOException {
           
            if (concurrentTasks.get() > 0) {
                concurrentTasks.incrementAndGet();
                Runnable task = new Runnable() {
                   
                    public void run() {
                        try {
                            performSetComplete();
                        } finally {
                            concurrentTasks.decrementAndGet();
                        }
                    }
                };
                getExecutor().processNonthreaded(task);

            } else {
                performSetComplete();
            }
        }

       
        private void performSetComplete() {
            try {
                retrievePrevious();
                delegate.close();
            } catch (IOException ioe) {
                LOG.fine("error occured by setting complete " + ioe.toString());
            }
        }
       
       
        public void close() throws IOException {
           
            if (!isClosed.getAndSet(true)) {
                if (concurrentTasks.get() > 0) {
                    concurrentTasks.incrementAndGet();
                    Runnable task = new Runnable() {
                       
                        public void run() {
                            try {
                                performClose();
                            } finally {
                                concurrentTasks.decrementAndGet();
                            }
                        }
                    };
                    getExecutor().processNonthreaded(task);
                   
                } else {
                    performClose();
                }
            }
        }

        private void performClose() {
            try  {
                retrievePrevious();
                delegate.close();
            } catch (IOException ioe) {
                LOG.fine("error occured by closing " + ioe.toString());
            }
        }
       
       
        public boolean isOpen() {
            return delegate.isOpen();
        }
       
       
        public void destroy() {
            if (concurrentTasks.get() > 0) {
                concurrentTasks.incrementAndGet();
                Runnable task = new Runnable() {
                   
                    public void run() {
                        try {
                            delegate.destroy();
                        } finally {
                            concurrentTasks.decrementAndGet();
                        }
                    }
                };
                getExecutor().processNonthreaded(task);
               
            } else {
                delegate.destroy();
            }
        }
    }
   
   
 

    /**
     * transfer the available data of the this source channel to the given data sink
     *
     * @param dataSink   the data sink
     *
     * @return the number of transfered bytes
     * @throws ClosedChannelException If either this channel or the target channel is closed
     * @throws IOException If some other I/O error occurs
     */
    public long transferTo(BodyDataSink dataSink) throws ProtocolException, IOException, ClosedChannelException {
        return transferTo(dataSink, available());
    }
       
       
   

    /**
     * transfer the data of the this source channel to the given data sink
     *
     * @param dataSink            the data sink
     * @param length              the size to transfer
     *
     * @return the number of transfered bytes
     * @throws ClosedChannelException If either this channel or the target channel is closed
     * @throws IOException If some other I/O error occurs
     */
    public long transferTo(final BodyDataSink dataSink, final int length) throws ProtocolException, IOException, ClosedChannelException {
        return transferTo((WritableByteChannel) dataSink, length);
    }
   
   

    /**
     * transfer the data of the this source channel to the given data sink
     *
     * @param dataSink   the data sink
     * @param length     the size to transfer
     *
     * @return the number of transfered bytes
     * @throws ClosedChannelException If either this channel or the target channel is closed
     * @throws IOException If some other I/O error occurs
     */
    public final long transferTo(WritableByteChannel target, int length) throws IOException, ClosedChannelException {
        throwExceptionIfExist();
       
        if (length > 0) {
            long written = 0;

            ByteBuffer[] buffers = readByteBufferByLength(length);
            for (ByteBuffer buffer : buffers) {
                while(buffer.hasRemaining()) {
                    written += target.write(buffer);
                }
            }
            return written;

        } else {
            return 0;
        }
    }

   


    /**
     * suspend the (underlying connection of the) body data source
     * 
     * @throws IOException if an error occurs
     */
    abstract void suspend() throws IOException;

   
    /**
     * returns true, if the body data source is suspended
     *
     * @return true, if the body data source is suspended
     */
    abstract boolean isSuspended();

    /**
     * resume the (underlying connection of the) body data source
     *
     * @throws IOException if an error occurs
     */
    abstract void resume() throws IOException;
   
   
    /**
     * read a ByteBuffer by using a delimiter
     *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
     *
     * @param delimiter   the delimiter
     * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown 
     * @return the ByteBuffer
     * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found    
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available 
     */
  public final ByteBuffer[] readByteBufferByDelimiter(String delimiter, int maxLength) throws IOException,MaxReadSizeExceededException {
        throwExceptionIfExist();
        return nonBlockingStream.readByteBufferByDelimiter(delimiter, maxLength);
  }


    
    /**
     * read a ByteBuffer 
     *
     * @param length   the length could be negative, in this case a empty array will be returned
     * @return the ByteBuffer
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final ByteBuffer[] readByteBufferByLength(int length) throws IOException, BufferUnderflowException {
        throwExceptionIfExist();
        return nonBlockingStream.readByteBufferByLength(length);
    }

 

    /**
     * returns the body encoding
     *
     * @return the body encoding
     */
    String getEncoding() {
        return nonBlockingStream.getEncoding();
    }
   
    private ByteBuffer readSingleByteBufferByLength(int length) throws IOException {
        return DataConverter.toByteBuffer(readByteBufferByLength(length));
    }
   
   
    /**
     * read a ByteBuffer by using a delimiter. The default encoding will be used to decode the delimiter
     * To avoid memory leaks the {@link IReadWriteableConnection#readByteBufferByDelimiter(String, int)} method is generally preferable 
     * <br>
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
     *
     * @param delimiter   the delimiter
     * @return the ByteBuffer
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final ByteBuffer[] readByteBufferByDelimiter(String delimiter) throws IOException {
        return readByteBufferByDelimiter(delimiter, Integer.MAX_VALUE);
    }

   
   
    /**
     * read a byte array by using a delimiter
     *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
     *
     * @param delimiter   the delimiter 
     * @return the read bytes
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final byte[] readBytesByDelimiter(String delimiter) throws IOException {
        return DataConverter.toBytes(readByteBufferByDelimiter(delimiter));
    }

   
    /**
     * read a byte array by using a delimiter
     *
     * For performance reasons, the ByteBuffer readByteBuffer method is
     * generally preferable to get bytes
     *
     * @param delimiter   the delimiter
     * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown
     * @return the read bytes
     * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found    
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final byte[] readBytesByDelimiter(String delimiter, int maxLength) throws IOException, MaxReadSizeExceededException {
        return DataConverter.toBytes(readByteBufferByDelimiter(delimiter, maxLength));
    }

   
    /**
     * read bytes by using a length definition
     * 
     * @param length the amount of bytes to read 
     * @return the read bytes
     * @throws IOException If some other I/O error occurs
     * @throws IllegalArgumentException, if the length parameter is negative
     * @throws BufferUnderflowException if not enough data is available
     */
    public final byte[] readBytesByLength(int length) throws IOException {
        return DataConverter.toBytes(readByteBufferByLength(length));
    }

   
    /**
     * read a string by using a delimiter
     *
     * @param delimiter   the delimiter
     * @return the string
     * @throws IOException If some other I/O error occurs
     * @throws UnsupportedEncodingException if the default encoding is not supported
     * @throws BufferUnderflowException if not enough data is available
     */
    public final String readStringByDelimiter(String delimiter) throws IOException, UnsupportedEncodingException {
        return DataConverter.toString(readByteBufferByDelimiter(delimiter), getEncoding());
    }

   
    /**
     * read a string by using a delimiter
     *
     * @param delimiter   the delimiter
     * @param maxLength   the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown
     * @return the string
     * @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found    
     * @throws IOException If some other I/O error occurs
     * @throws UnsupportedEncodingException If the given encoding is not supported
     * @throws BufferUnderflowException if not enough data is available
     */
    public final String readStringByDelimiter(String delimiter, int maxLength) throws IOException,UnsupportedEncodingException, MaxReadSizeExceededException {
        return DataConverter.toString(readByteBufferByDelimiter(delimiter, maxLength), getEncoding());
    }

   
    /**
     * read a string by using a length definition
     *
     * @param length the amount of bytes to read 
     * @return the string
     * @throws IOException If some other I/O error occurs
     * @throws UnsupportedEncodingException if the given encoding is not supported
     * @throws IllegalArgumentException, if the length parameter is negative
     * @throws BufferUnderflowException if not enough data is available
     */
    public final String readStringByLength(int length) throws IOException, BufferUnderflowException {
        return DataConverter.toString(readByteBufferByLength(length), getEncoding());
    }

   
    /**
     * read a double
     *
     * @return the double value
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final double readDouble() throws IOException {
        return readSingleByteBufferByLength(8).getDouble();
    }

   

    /**
     * read a long
     *
     * @return the long value
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final long readLong() throws IOException {
        return readSingleByteBufferByLength(8).getLong();
    }

   
    /**
     * read an int
     *
     * @return the int value
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final int readInt() throws IOException {
        return readSingleByteBufferByLength(4).getInt();
    }

   
    /**
     * read a short value
     *
     * @return the short value
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final short readShort() throws IOException {
        return DataConverter.toByteBuffer(readBytesByLength(2)).getShort();
    }

   

    /**
     * read a byte
     *
     * @return the byte value
     * @throws IOException If some other I/O error occurs
     * @throws BufferUnderflowException if not enough data is available
     */
    public final byte readByte() throws IOException {
        return DataConverter.toByteBuffer(readBytesByLength(1)).get();
    }
   
    /**
     * Marks the read position in the connection. Subsequent calls to resetToReadMark() will attempt
     * to reposition the connection to this point.
     *
     */
    public final void markReadPosition() {
        nonBlockingStream.markReadPosition();
    }


    /**
     * Resets to the marked read position. If the connection has been marked,
     * then attempt to reposition it at the mark.
     *
     * @return true, if reset was successful
     */
    public final boolean resetToReadMark() {
        return nonBlockingStream.resetToReadMark();
    }


   
    /**
     * remove the read mark
     */
    public final void removeReadMark() {
        nonBlockingStream.removeReadMark();
    }

   
    /**
     * Returns the index of the first occurrence of the given string.
     *
     * @param str any string
     * @return if the string argument occurs as a substring within this object, then
     *         the index of the first character of the first such substring is returned;
     *         if it does not occur as a substring, -1 is returned.
     * @throws IOException If some other I/O error occurs
     */
    public final int indexOf(String str) throws IOException {
        throwExceptionIfExist();
        return nonBlockingStream.indexOf(str);
    }
   


    /**
     * Returns the index  of the first occurrence of the given string.
     *
     * @param str          any string
     * @param encoding     the encoding to use
     * @return if the string argument occurs as a substring within this object, then
     *         the index of the first character of the first such substring is returned;
     *         if it does not occur as a substring, -1 is returned.
     * @throws IOException If some other I/O error occurs
     */
    public final int indexOf(String str, String encoding) throws IOException, MaxReadSizeExceededException {
        throwExceptionIfExist();
        return nonBlockingStream.indexOf(str, encoding);
    }
   
    /**
     * get the version of read buffer. The version number increases, if
     * the read buffer queue has been modified
     *
     * @return the read buffer version
     * @throws IOException if an exception occurs
     */
    public int getReadBufferVersion() throws IOException {
        throwExceptionIfExist();
        return nonBlockingStream.getReadBufferVersion();
    }
   
   
  

   
    /**
     * returns body data receive timeout
     *
     * @return the body data receive timeout or <code>null</code>
     */
    public long getBodyDataReceiveTimeoutMillis() {
        return bodyDataReceiveTimeoutMillis;
    }
   
   
    /**
     * copies the body content
     * 
     * @return the copy
     */
    final ByteBuffer[] copyContent() {
        return nonBlockingStream.copyContent();
    }
   
   
    final IMultimodeExecutor getExecutor() {
        return executor;
    }
   
   
   
    private BodyDataHandlerAdapter newBodyDataHandlerAdapter(IBodyDataHandler bodyDataHandler) {
       
        if (bodyDataHandler == null) {
            return null;
        }

        Integer executionMode = bodyDataHandlerExecutionModeCache.get(bodyDataHandler.getClass());
       
        if (executionMode == null) {
            executionMode = IBodyDataHandler.DEFAULT_EXECUTION_MODE;
                       
            Execution execution = bodyDataHandler.getClass().getAnnotation(Execution.class);
            if (execution != null) {
                executionMode = execution.value();
            }
                   
            try {
                Method meth = bodyDataHandler.getClass().getMethod("onData", new Class[] { NonBlockingBodyDataSource.class });
                execution = meth.getAnnotation(Execution.class);
                if (execution != null) {
                    executionMode = execution.value();
                }
                       
            } catch (NoSuchMethodException nsme) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
                }
            }
           
            bodyDataHandlerExecutionModeCache.put(bodyDataHandler.getClass(), executionMode);
        }
       
        return new BodyDataHandlerAdapter(bodyDataHandler, executionMode);
    }
   
   
    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        try {
            return nonBlockingStream.toString();
        } catch (Exception e) {
            return "[" + getId() + "] error occured by performing toString: " + DataConverter.toString(e);
        }
    }
   
   
    String toDiagnosticString() {
        StringBuilder sb = new StringBuilder();
       
        sb.append("isDestroyed=" + isDestroyed.get());
        sb.append(" isComplete=" + isComplete.get());
        sb.append(" exception=" + exceptionRef.get());
        sb.append(" nonblockingStream#isOpen=" + nonBlockingStream.isOpen());
        sb.append(" nonblockingStream#isMoreInputDataExpected=" + nonBlockingStream.isMoreInputDataExpected());
        sb.append(" nonblockingStream#Size=" + nonBlockingStream.getSize());
        sb.append(" source=" + sourceRef.get());
       
        return sb.toString();
    }
   

    private static interface ISink {
       
        boolean isOpen();
       
        void close() throws IOException;
       
        void destroy();
       
        int append(ByteBuffer data) throws IOException;
       
        int append(ByteBuffer[] data) throws IOException;
       
        int append(ByteBuffer[] data, IWriteCompletionHandler completionHandler, boolean force) throws IOException;
       
        void setComplete() throws IOException;
    }
   
   
 
    private final class NonBlockingStream extends AbstractNonBlockingStream implements ISink {
       
        public void destroy() {
            drainReadQueue();
            callBodyDataHandler(true);
        }
       
        public void setComplete() {
            callBodyDataHandler(true);
        }
       
        @Override
        protected boolean isDataWriteable() {
            return false;
        }
       
        int getSize() {
            return getReadQueueSize();
        }

        @Override
        protected boolean isMoreInputDataExpected() {
            return NonBlockingBodyDataSource.this.isMoreInputDataExpected();
        }

        public boolean isOpen() {
            try {
                return (super.available() != -1);
            } catch (IOException ioe) {   // BUG in xSocket: AbstractNonBlockingStream isOpen() method declares an exception which will never been thrown
               return false;
            }
        }
       
        public int append(ByteBuffer buffer) {
           
            int size = 0;
            if (buffer != null) {
                size = buffer.remaining();
                appendDataToReadBuffer(new ByteBuffer[] { buffer }, size);
            }

            callBodyDataHandler(false);
           
            return size;
        }

       
        public int append(ByteBuffer[] buffer) {
            int size = 0;

            if (buffer != null) {
                for (ByteBuffer byteBuffer : buffer) {
                    size += byteBuffer.remaining();
                }
               
                appendDataToReadBuffer(buffer, size);
            }

            callBodyDataHandler(false);
           
            return size;
        }
       
              
       
        public int append(ByteBuffer[] buffers, IWriteCompletionHandler completionHandler, boolean force) {

            int size = 0;
           
            if (buffers != null) {
                size += append(buffers);
            }
              
            if (completionHandler != null) {
                new WriteCompletionHolder(completionHandler, executor, buffers).callOnWritten();
            }
           
            callBodyDataHandler(true);
           
            return size;
        }

       
        ByteBuffer[] copyContent() {
            return super.copyReadQueue();
        }
       
        @Override
        public String toString() {
            return printReadBuffer(NonBlockingBodyDataSource.this.getEncoding());
        }
    } 
   
   
    private final class BodyDataHandlerAdapter implements IBodyDataHandler {
       
        private final IBodyDataHandler delegate;
        private final int executionMode;
       
        BodyDataHandlerAdapter(IBodyDataHandler bodyDataHandler, int executionMode) {
            assert (bodyDataHandler != null);
           
            this.delegate = bodyDataHandler;
            this.executionMode = executionMode;
        }
   
       
        IBodyDataHandler getDelegate() {
            return delegate;
        }
       
        public boolean onData(final NonBlockingBodyDataSource bodyDataSource) throws BufferUnderflowException {
           
            Runnable task = new Runnable() {
                public void run() {
                    performOnData(bodyDataSource);
                }
            };
 
            if (executionMode == Execution.MULTITHREADED) {
                bodyDataSource.getExecutor().processMultithreaded(task);
            } else {
                bodyDataSource.getExecutor().processNonthreaded(task);
            }
               
            return true;
        }
       
        private boolean performOnData(NonBlockingBodyDataSource bodyDataSource) {
           
            try {
                // get pre version
                int preVersion = getVersion();
               
                // perform call
                boolean success = delegate.onData(bodyDataSource);

                // get post version
                int postVersion = getVersion();

               
                // should call be repeated?
                if (success && (preVersion != postVersion) && ((getSize() != 0))) {
                    // yes, initiate it
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("[" + getId() + "] re-initiate calling body data handler (read queue size=" + getSize());
                    }
                    callBodyDataHandler(false);
                }
                   
            } catch (BufferUnderflowException bue) {
                // swallow it
           
            } catch (Exception e) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + bodyDataSource.getId() + "] error occured by calling onData of " + delegate.getClass().getName() + "#" + delegate.hashCode() + " " + e.toString() + " destroying body data source");
                }
                bodyDataSource.destroy(e.toString());
            }
           
            return true;
        }
    }
   
   
    private static final class TimeoutWatchDogTask extends TimerTask {
       
        private WeakReference<NonBlockingBodyDataSource> dataSourceRef = null;
       
        public TimeoutWatchDogTask(NonBlockingBodyDataSource dataSource) {
            dataSourceRef = new WeakReference<NonBlockingBodyDataSource>(dataSource);
        }
   
       
        @Override
        public void run() {
            try {
                NonBlockingBodyDataSource dataSource = dataSourceRef.get();
               
                if (dataSource == null)  {
                    this.cancel();
                   
                } else {
                    dataSource.checkTimeouts();
                }
            } catch (Exception e) {
                // eat and log exception
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("error occured by checking timeouts " + e.toString());
                }
            }
        }      
    }
   
   
    public static final class WriteCompletionHolder implements Runnable {
       
        private final IWriteCompletionHandler handler;
        private final CompletionHandlerInfo handlerInfo;
        private final IMultimodeExecutor executor;
        private final int size;

       

        public WriteCompletionHolder(IWriteCompletionHandler handler, IMultimodeExecutor executor, ByteBuffer[] bufs) {
            this.handler = handler;
            this.executor = executor;
            this.handlerInfo = HttpUtils.getCompletionHandlerInfo(handler);
            this.size = computeSize(bufs);
        }

       
        private static int computeSize(ByteBuffer[] bufs) {
            if (bufs == null) {
                return 0;
            }
           
            int i = 0;
            for (ByteBuffer byteBuffer : bufs) {
                i += byteBuffer.remaining();
            }
           
            return i;
        }
       
     
        void performOnWritten(boolean isForceMultithreaded) {
            executor.processMultithreaded(this);
        }
     
     
        public void run() {
            callOnWritten();
        }

        private void callOnWritten() {
           
            try {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("data (size=" + size + " bytes) has been written. calling " + handler.getClass().getSimpleName() + "#" + handler.hashCode() " onWritten method");
                }
                handler.onWritten(size);
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("error occured by calling onWritten " + ioe.toString() + " closing connection");
                }
               
                performOnException(ioe);
            }
        }

     
        void performOnException(final IOException ioe) {
            if (handlerInfo.isOnExceptionMutlithreaded()) {
                Runnable task = new Runnable() {
                    public void run() {
                        callOnException(ioe);
                    }
                };
                executor.processMultithreaded(task);
             
            } else {
                Runnable task = new Runnable() {
                    public void run() {
                        callOnException(ioe);
                    }
                };
                executor.processNonthreaded(task);
            }
        }
     
     
        private void callOnException(IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("calling " + handler.getClass().getSimpleName() + "#" + handler.hashCode() " onException with " + ioe.toString());
            }

            handler.onException(ioe);
        }
    }
}
TOP

Related Classes of org.xlightweb.NonBlockingBodyDataSource$BodyDataHandlerAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.