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

org.xlightweb.client.DuplicatingBodyForwarder Maven / Gradle / Ivy

Go to download

xLightweb is a lightweight, high performance, scalable web network library

There is a newer version: 2.13.2
Show newest version
/*
 *  Copyright (c) xlightweb.org, 2008 - 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.xlightweb.org/
 */
package org.xlightweb.client;

import java.io.Closeable;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.BodyDataSink;
import org.xlightweb.IBodyDataHandler;
import org.xlightweb.IBodyDestroyListener;
import org.xlightweb.NonBlockingBodyDataSource;




/**
 * A Forwarder which duplicates the data and forwards it to a primary and a secondary sink 
 * 
 * @author [email protected]
 */
final class DuplicatingBodyForwarder implements IBodyDataHandler {
    
    private static final Logger LOG = Logger.getLogger(DuplicatingBodyForwarder.class.getName());
    
    private static final ByteBuffer NULL_BYTE_BUFFER = ByteBuffer.allocate(0);
	
	private final NonBlockingBodyDataSource bodyDataSource;
	private final ISink primarySink;
	private final AtomicBoolean isPrimarySinkClosed = new AtomicBoolean(false); 
	private final ISink secondarySink;
	private final AtomicBoolean isSecondarySinkClosed = new AtomicBoolean(false);
	
	
    /**
     * DuplicatingBodyForwarder is unsynchronized by config. See HttpUtils$getExecutionMode
     */

	

	public DuplicatingBodyForwarder(final NonBlockingBodyDataSource bodyDataSource, final ISink primarySink, final ISink secondarySink) {
		this.bodyDataSource = bodyDataSource;
		this.primarySink = primarySink;
		this.secondarySink = secondarySink;
		
		
		IBodyDestroyListener destroyListenerPrimary = new IBodyDestroyListener() {
		    public void onDestroyed() {
		        isPrimarySinkClosed.set(true); 
		        handlePeerDestroy();
		    }
		};
		
		primarySink.setDestroyListener(destroyListenerPrimary);
		
		
		IBodyDestroyListener destroyListenerSecondary = new IBodyDestroyListener() {
		    public void onDestroyed() {
		        isSecondarySinkClosed.set(true);
		        handlePeerDestroy();
		    }
		};

		secondarySink.setDestroyListener(destroyListenerSecondary);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public boolean onData(final NonBlockingBodyDataSource bodyDataSource) throws BufferUnderflowException {
	    
	    try {

	        int available = 0;
	        do {
    	        try {
    	            available = HttpClientConnection.availableSilence(bodyDataSource);
    	        } catch (IOException e) {
    	            destroySinks();
    	            return true;
    	        }

                if (available == -1) {
                    closeSinks();
                    return true;
                    
                } else if (available == 0) {
                	if (isPrimarySinkClosed.get()) {
                        if (!isSecondarySinkClosed.get()) {
                            write(secondarySink, isSecondarySinkClosed, NULL_BYTE_BUFFER);
                        } else {
                            throw new ClosedChannelException();
                        }
                    } else {
                        if (!isSecondarySinkClosed.get()) {
                            write(secondarySink, isSecondarySinkClosed, NULL_BYTE_BUFFER);
                        }
                        write(primarySink, isPrimarySinkClosed, NULL_BYTE_BUFFER);
                    }                	
                	
                } else if (available > 0) {
                    ByteBuffer[] buffers = HttpClientConnection.readByteBufferByLengthSilence(bodyDataSource, available);
                    
                    for (ByteBuffer buffer : buffers) {
                        
                        if (isPrimarySinkClosed.get()) {
                            if (!isSecondarySinkClosed.get()) {
                                write(secondarySink, isSecondarySinkClosed, buffer);
                            } else {
                                throw new ClosedChannelException();
                            }
                        } else {
                            if (!isSecondarySinkClosed.get()) {
                                write(secondarySink, isSecondarySinkClosed, buffer.duplicate());
                            }
                            write(primarySink, isPrimarySinkClosed, buffer);
                        }
                    }
                }

	        } while (available > 0);
	        
        } catch (IOException e) {
            destroySinks();
        } 
        
		return true;
	}
	
	
	private void write(ISink sink, AtomicBoolean isClosed, ByteBuffer buffer) {
	    try {
	        sink.onData(buffer);	
	        
	    } catch (IOException ioe) {
	        if (LOG.isLoggable(Level.FINE)) {
	            LOG.fine("[" + sink.getId() + "] error occured by writing data to " + sink + " " + ioe.toString());
	        }
	        isClosed.set(true);
	        handlePeerDestroy();
	    }
	}
	
	
	private void handlePeerDestroy() {
	    if (isPrimarySinkClosed.get() && isSecondarySinkClosed.get()) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("both data sink are closed. Destroying data source");
            }	        
            bodyDataSource.destroy();
        }
	}
	
	
	
	
	
	private void destroySinks() {	   
	    if (!isPrimarySinkClosed.getAndSet(true)) {
	        primarySink.destroy();
	    }
	        
	    if (!isSecondarySinkClosed.getAndSet(true)) {
	        secondarySink.destroy();
	    }
	}
	    
	

    private void closeSinks() throws IOException {
        if (!isPrimarySinkClosed.getAndSet(true)) {
            primarySink.close();
        }
            
        if (!isSecondarySinkClosed.getAndSet(true)) {
            secondarySink.close();
        }
    }
	
    
   
	public static interface ISink extends Closeable {
	        
	    void onData(ByteBuffer data) throws IOException;
	    
	    void setDestroyListener(IBodyDestroyListener destroyListener);
	        
	    void destroy();
	    
	    String getId();
	}
	
	
	static final class BodyDataSinkAdapter implements ISink {
        
        private final BodyDataSink dataSink; 
            
        public BodyDataSinkAdapter(BodyDataSink dataSink) throws IOException {
            this.dataSink = dataSink;
        }
            
        public void onData(ByteBuffer data) throws IOException {
            dataSink.write(data);
        }
            
        public void close() throws IOException {
            dataSink.close();
        }
            
        public void destroy() {
            dataSink.destroy();
        }
            
        public void setDestroyListener(IBodyDestroyListener destroyListener) {
            dataSink.addDestroyListener(destroyListener);
        }
        
        public String getId() {
            return "wrapped" + dataSink.getId();
        }
    }
	
	
    static class InMemorySink implements ISink {
        
        /*
         * TODO: improve synchronization (make it less expensive) 
         */
        
        private final List buffers = new ArrayList();
        private final int maxBufferSize;
        private int bufferSize = 0;
         

        private IBodyDestroyListener destroyListener = null;
        private boolean isDestroyed = false;
        private boolean isClosed = false;
        
        private ISink forwardSink = null;
        
        
        public InMemorySink() {
            this(Integer.MAX_VALUE);
        }

        
        public InMemorySink(int maxBufferSize) {
            this.maxBufferSize = maxBufferSize;
        }
        
        public synchronized void onData(ByteBuffer data) throws IOException {
            if (forwardSink == null) {
                bufferSize += data.remaining();                
                buffers.add(data);
                
                if (bufferSize > maxBufferSize) {
                    onMaxBufferSizeExceeded();
                }
            } else {
                forwardSink.onData(data);
            }
        }
        
        void onMaxBufferSizeExceeded() {
            destroy();
        }
        
        
        public synchronized void close() throws IOException {
            if (forwardSink == null) {
                isClosed = true;
            } else {
                forwardSink.close();
            }
        }
        
        public synchronized boolean isDestroyed() {
            return isDestroyed;
        }
        
        public synchronized void destroy() {
            if (forwardSink == null) {
                isDestroyed = true;
                buffers.clear();
                callDestroyListener();
            } else {
                forwardSink.destroy();
            }
        }
        
        private void callDestroyListener() {
            IBodyDestroyListener ls = destroyListener;
            if (ls != null) {
                try {
                    ls.onDestroyed();
                } catch (IOException ioe) {
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine("Error occured by calling destroy listener");
                    }
                }
            }
        }
        
        public String getId() {
            return "";
        }
        
        public synchronized int getSize() {
            int size = 0;
            
            for (ByteBuffer buffer : buffers) {
                size += buffer.remaining();
            }
            
            return size;
        }
                
        public synchronized void setDestroyListener(IBodyDestroyListener destroyListener) {
            if (forwardSink == null) {
                this.destroyListener = destroyListener;
            } else {
                forwardSink.setDestroyListener(destroyListener);
            }
        }
        
        
        public synchronized boolean forwardTo(ISink sink) throws IOException {
            if (isDestroyed) {
                return false;
            }
            
            forwardSink = sink;
            
            if (destroyListener != null) {
                sink.setDestroyListener(destroyListener);
            }
            
                
            for (ByteBuffer buffer : buffers) {
                onData(buffer);
            }
                
            if (isClosed) {
                close();
            }
            
            return true;
        }
    }	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy