package vanilla.java.chronicle.tcp;
import vanilla.java.chronicle.Chronicle;
import vanilla.java.chronicle.Excerpt;
import vanilla.java.chronicle.impl.IndexedChronicle;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This listens to a ChronicleSource and copies new entries. This SInk can be any number of excerpt behind the source and can be restart many times without losing data.
* <p/>
* Can be used as a component or run as a stand alone service.
*
* @author peter.lawrey
*/
public class ChronicleSink<C extends Chronicle> implements Closeable {
private final C chronicle;
private final SocketAddress address;
private final ExcerptListener<C> listener;
private final ExecutorService service;
private final String name;
private final Logger logger;
private volatile boolean closed = false;
public ChronicleSink(C chronicle, String hostname, int port) {
this(chronicle, hostname, port, NullExcerptListener.INSTANCE);
}
public ChronicleSink(C chronicle, String hostname, int port, ExcerptListener<C> listener) {
this.chronicle = chronicle;
this.listener = listener;
this.address = new InetSocketAddress(hostname, port);
name = chronicle.name() + '@' + hostname + ':' + port;
logger = Logger.getLogger(getClass().getName() + '.' + chronicle);
service = Executors.newSingleThreadExecutor(new NamedThreadFactory(name));
service.execute(new Sink());
}
public static void main(String... args) throws IOException {
if (args.length < 3) {
System.err.println("Usage: java " + ChronicleSink.class.getName() + " {chronicle-base-path} {hostname} {port}");
System.exit(-1);
}
int dataBitsHintSize = Integer.getInteger("dataBitsHintSize", 24);
String def = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "Big" : "Little";
ByteOrder byteOrder = System.getProperty("byteOrder", def).equalsIgnoreCase("Big") ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
String basePath = args[0];
String hostname = args[1];
int port = Integer.parseInt(args[2]);
IndexedChronicle ic = new IndexedChronicle(basePath, dataBitsHintSize, byteOrder);
ChronicleSink cs = new ChronicleSink(ic, hostname, port);
}
class Sink implements Runnable {
Excerpt excerpt = chronicle.createExcerpt();
@Override
public void run() {
SocketChannel sc = null;
while (!closed) {
if (sc == null || !sc.isOpen())
sc = createConnection();
else
readNextExcerpt(sc);
}
}
private SocketChannel createConnection() {
while (!closed) {
try {
SocketChannel sc = SocketChannel.open(address);
ByteBuffer bb = ByteBuffer.allocate(8);
bb.putLong(0, chronicle.size());
while (bb.remaining() > 0 && sc.write(bb) > 0) ;
if (bb.remaining() > 0) throw new EOFException();
return sc;
} catch (IOException e) {
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE, "Failed to connect to " + address + " retrying", e);
else if (logger.isLoggable(Level.INFO))
logger.log(Level.INFO, "Failed to connect to " + address + " retrying " + e);
}
}
return null;
}
private void readNextExcerpt(SocketChannel sc) {
ByteBuffer bb = TcpUtil.createBuffer(1, chronicle);
try {
while (!closed) {
readHeader(sc, bb);
long index = bb.getLong(0);
long size = bb.getLong(8);
if (index != chronicle.size())
throw new StreamCorruptedException("Expected index " + chronicle.size() + " but got " + index);
if (size > Integer.MAX_VALUE || size < 0)
throw new StreamCorruptedException("size was " + size);
if (size > bb.capacity())
bb = TcpUtil.createBuffer((int) size, chronicle);
bb.position(0);
bb.limit((int) size);
while (bb.remaining() > 0 && sc.read(bb) > 0) ;
if (bb.remaining() > 0) throw new EOFException();
bb.flip();
excerpt.startExcerpt((int) size);
excerpt.write(bb);
excerpt.finish();
excerpt.index(index);
listener.onExcerpt(excerpt);
}
} catch (IOException e) {
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE, "Lost connection to " + address + " retrying", e);
else if (logger.isLoggable(Level.INFO))
logger.log(Level.INFO, "Lost connection to " + address + " retrying " + e);
}
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE, "Disconnected from " + address);
}
private void readHeader(SocketChannel sc, ByteBuffer bb) throws IOException {
bb.position(0);
bb.limit(TcpUtil.HEADER_SIZE);
while (bb.remaining() > 0 && sc.read(bb) > 0) ;
if (bb.remaining() > 0) throw new EOFException();
}
}
@Override
public void close() throws IOException {
closed = true;
service.shutdownNow();
chronicle.close();
}
}