/*
* 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.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.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 grro@xlightweb.org
*/
final class DuplicatingBodyForwarder implements IBodyDataHandler {
private static final Logger LOG = Logger.getLogger(DuplicatingBodyForwarder.class.getName());
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 = bodyDataSource.available();
} catch (IOException e) {
destroySinks();
return true;
}
if (available == -1) {
closeSinks();
return true;
} else if (available > 0) {
ByteBuffer[] buffers = bodyDataSource.readByteBufferByLength(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();
}
}
}