org.sfs.io.AsyncFileWriterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sfs.io.AsyncFileWriterImpl.java

Source

/*
 * Copyright 2016 The Simple File Server Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.sfs.io;

import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.logging.Logger;
import org.sfs.SfsVertx;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class AsyncFileWriterImpl implements AsyncFileWriter {

    private final Logger log;

    private final AsynchronousFileChannel ch;
    private final Context context;

    private Handler<Throwable> exceptionHandler;
    private Handler<Void> endHandler;

    private WriteQueueSupport<AsyncFileWriter> writeQueueSupport;

    private long startPosition;
    private long writePos;
    private long lastWriteTime;
    private boolean ended = false;

    public AsyncFileWriterImpl(long startPosition, WriteQueueSupport<AsyncFileWriter> writeQueueSupport,
            Context context, AsynchronousFileChannel dataFile, Logger log) {
        this.log = log;
        this.startPosition = startPosition;
        this.writePos = startPosition;
        this.ch = dataFile;
        this.context = context;
        this.writeQueueSupport = writeQueueSupport;
        this.lastWriteTime = System.currentTimeMillis();
    }

    protected void checkNotEnded() {
        Preconditions.checkState(!ended, "WriteStream ended");
    }

    @Override
    public AsyncFileWriterImpl endHandler(Handler<Void> endHandler) {
        this.endHandler = endHandler;
        handleEnd();
        return this;
    }

    @Override
    public void end(Buffer buffer) {
        checkNotEnded();
        ended = true;
        int length = buffer.length();
        doWrite(buffer, writePos, event -> {
            if (event.succeeded()) {
                handleEnd();
            } else {
                handleException(event.cause());
            }
        });
        writePos += length;
        lastWriteTime = System.currentTimeMillis();
    }

    @Override
    public void end() {
        checkNotEnded();
        ended = true;
        handleEnd();
    }

    @Override
    public long writePosition() {
        return writePos;
    }

    @Override
    public long startPosition() {
        return startPosition;
    }

    @Override
    public long lastWriteTime() {
        return lastWriteTime;
    }

    @Override
    public AsyncFileWriterImpl write(Buffer buffer) {
        checkNotEnded();
        int length = buffer.length();
        doWrite(buffer, writePos, event -> {
            if (event.succeeded()) {
                handleEnd();
            } else {
                handleException(event.cause());
            }
        });
        writePos += length;
        lastWriteTime = System.currentTimeMillis();
        return this;
    }

    private AsyncFileWriterImpl doWrite(Buffer buffer, long position, Handler<AsyncResult<Void>> handler) {
        Preconditions.checkNotNull(buffer, "buffer");
        Preconditions.checkArgument(position >= 0, "position must be >= 0");
        Handler<AsyncResult<Void>> wrapped = ar -> {
            if (ar.succeeded()) {
                if (handler != null) {
                    handler.handle(ar);
                }
            } else {
                if (handler != null) {
                    handler.handle(ar);
                } else {
                    handleException(ar.cause());
                }
            }
        };
        ByteBuf buf = buffer.getByteBuf();
        if (buf.nioBufferCount() > 1) {
            doWrite(buf.nioBuffers(), position, wrapped);
        } else {
            ByteBuffer bb = buf.nioBuffer();
            doWrite(bb, position, bb.limit(), wrapped);
        }
        return this;
    }

    private void doWrite(ByteBuffer[] buffers, long position, Handler<AsyncResult<Void>> handler) {
        AtomicInteger cnt = new AtomicInteger();
        AtomicBoolean sentFailure = new AtomicBoolean();
        for (ByteBuffer b : buffers) {
            int limit = b.limit();
            doWrite(b, position, limit, ar -> {
                if (ar.succeeded()) {
                    if (cnt.incrementAndGet() == buffers.length) {
                        handler.handle(ar);
                    }
                } else {
                    if (sentFailure.compareAndSet(false, true)) {
                        handler.handle(ar);
                    }
                }
            });
            position += limit;
        }
    }

    private void doWrite(ByteBuffer buff, long position, int toWrite, Handler<AsyncResult<Void>> handler) {
        if (toWrite <= 0) {
            handler.handle(Future.succeededFuture());
        } else {
            incrementWritesOutstanding(toWrite);
            writeInternal(buff, position, handler);
        }
    }

    @Override
    public AsyncFileWriterImpl setWriteQueueMaxSize(int maxSize) {
        // do nothing
        return this;
    }

    @Override
    public boolean writeQueueFull() {
        return writeQueueSupport.writeQueueFull();
    }

    @Override
    public boolean writeQueueEmpty() {
        return writeQueueSupport.writeQueueEmpty(this);
    }

    public void incrementWritesOutstanding(int delta) {
        writeQueueSupport.incrementWritesOutstanding(this, delta);
    }

    public void decrementWritesOutstanding(int delta) {
        writeQueueSupport.decrementWritesOutstanding(this, delta);
    }

    protected void removeWritesOutstandingCounter() {
        writeQueueSupport.remove(this);
    }

    @Override
    public AsyncFileWriterImpl drainHandler(Handler<Void> handler) {
        writeQueueSupport.drainHandler(context, handler);
        return this;
    }

    @Override
    public AsyncFileWriterImpl exceptionHandler(Handler<Throwable> handler) {
        this.exceptionHandler = handler;
        return this;
    }

    private void handleException(Throwable t) {
        if (exceptionHandler != null && t instanceof Exception) {
            exceptionHandler.handle(t);
        } else {
            log.error("Unhandled exception", t);

        }
    }

    private void handleEnd() {
        if (ended) {
            if (endHandler != null) {
                Handler<Void> h = endHandler;
                endHandler = null;
                writeQueueSupport.emptyHandler(this, context, h);
            }
        }
    }

    private void writeInternal(ByteBuffer buff, long position, Handler<AsyncResult<Void>> handler) {
        try {
            ch.write(buff, position, null, new java.nio.channels.CompletionHandler<Integer, Object>() {

                public void completed(Integer bytesWritten, Object attachment) {

                    long pos = position;

                    if (buff.hasRemaining()) {
                        // partial write
                        pos += bytesWritten;
                        // resubmit
                        writeInternal(buff, pos, handler);
                    } else {
                        // It's been fully written
                        context.runOnContext((v) -> {
                            decrementWritesOutstanding(buff.limit());
                            handler.handle(Future.succeededFuture());
                        });
                    }
                }

                public void failed(Throwable exc, Object attachment) {
                    removeWritesOutstandingCounter();
                    if (exc instanceof Exception) {
                        context.runOnContext((v) -> handler.handle(Future.succeededFuture()));
                    } else {
                        log.error("Error occurred", exc);
                    }
                }
            });
        } catch (RuntimeException e) {
            removeWritesOutstandingCounter();
            throw e;
        }
    }
}