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

org.yamcs.client.base.BulkRestDataSender Maven / Gradle / Ivy

The newest version!
package org.yamcs.client.base;

import java.nio.channels.ClosedChannelException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.yamcs.client.ClientException;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;

/**
 * Used to post large quantities of data to yamcs. The data is sent using HTTP chuncked encoding
 */
public class BulkRestDataSender extends SimpleChannelInboundHandler {
    ChannelHandlerContext ctx;
    CompletableFuture completeRequestCf = new CompletableFuture<>();
    volatile ClientException clientException = null;
    int count = 0;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
    }

    /**
     * send the next chunk of data. The caller is blocked if it sends data faster that can be transfered to the server.
     * 
     * @param data
     * @throws ClientException
     *             when there is an exception sending the data. The exception is also thrown if no data can be sent for
     *             10 seconds
     */
    public void sendData(byte[] data) throws ClientException {
        ByteBuf buf = Unpooled.wrappedBuffer(data);
        sendData(buf);
    }

    public void sendData(ByteBuf buf) throws ClientException {
        if (clientException != null) {
            throw clientException;
        }

        count++;
        try {
            Channel ch = ctx.channel();
            if (!ch.isOpen()) {
                throw new ClosedChannelException();
            }

            ChannelFuture writeFuture = ctx.writeAndFlush(new DefaultHttpContent(buf));
            if (!ch.isWritable()) {
                boolean writeCompleted = writeFuture.await(600, TimeUnit.SECONDS);
                if (!writeCompleted) {
                    throw new ClientException("Channel did not become writable in 60 seconds");
                }
            }
        } catch (Exception e) {
            completeRequestCf.completeExceptionally(e);
            if (e instanceof ClientException) {
                throw (ClientException) e;
            } else {
                throw new ClientException(e.toString(), e);
            }
        }
    }

    /**
     * Complete the request by a final empty chunck and return the response from the server.
     * 
     * @return a CompletableFuture that completes once the response from the server has been received.
     */
    public CompletableFuture completeRequest() {
        if (completeRequestCf.isDone()) {
            return completeRequestCf;
        }

        Channel ch = ctx.channel();
        if (!ch.isOpen()) {
            completeRequestCf.completeExceptionally(new ClosedChannelException());
        }
        ChannelFuture writeFuture = ctx.writeAndFlush(new DefaultLastHttpContent());
        writeFuture.addListener(f -> {
            if (!f.isSuccess()) {
                completeRequestCf.completeExceptionally(f.cause());
            }
        });
        return completeRequestCf;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
        HttpResponseStatus status = msg.status();
        if (status.equals(HttpResponseStatus.OK)) {
            byte[] b = HttpClient.getByteArray(msg.content());
            completeRequestCf.complete(b);
        } else {
            clientException = HttpClient.decodeException(msg);
            completeRequestCf.completeExceptionally(clientException);
        }
    }

    /**
     *
     * This handler expects to receive a 100 Continue message which means that the request is ok and the sender can
     * start streaming data if this is received, a new bulk sender will be created and added to the pipeline and the
     * CompletableFuture will be completed with the new object if any other response is received, the CompletableFuture
     * will be completed exceptionally.
     */
    static class ContinuationHandler extends SimpleChannelInboundHandler {
        CompletableFuture cf;

        public ContinuationHandler(CompletableFuture cf) {
            this.cf = cf;
        }

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, HttpResponse msg) {
            if (msg.status().equals(HttpResponseStatus.CONTINUE)) {
                ChannelPipeline pipeline = ctx.pipeline();
                pipeline.remove(this);
                pipeline.addLast(new HttpObjectAggregator(512 * 1024));
                BulkRestDataSender brds = new BulkRestDataSender();
                pipeline.addLast(brds);
                cf.complete(brds);
            } else {
                cf.completeExceptionally(new ClientException("Cannot continue the bulk load: " + msg.status()));
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy