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

com.proofpoint.http.client.jetty.DynamicBodySourceContentProvider Maven / Gradle / Ivy

The newest version!
package com.proofpoint.http.client.jetty;

import com.proofpoint.http.client.DynamicBodySource;
import com.proofpoint.http.client.DynamicBodySource.Writer;
import com.proofpoint.tracetoken.TraceToken;
import com.proofpoint.tracetoken.TraceTokenScope;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.content.InputStreamContentSource;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;

import static com.proofpoint.tracetoken.TraceTokenManager.getCurrentTraceToken;
import static com.proofpoint.tracetoken.TraceTokenManager.registerTraceToken;
import static java.lang.Math.max;
import static java.lang.Math.min;

class DynamicBodySourceContentProvider
        implements Request.Content
{
    private static final int BUFFER_SIZE = 4096;
    private static final RetainableByteBuffer INITIAL = RetainableByteBuffer.wrap(ByteBuffer.allocate(0));
    private static final RetainableByteBuffer DONE = RetainableByteBuffer.wrap(ByteBuffer.allocate(0));

    private final ByteBufferPool.Sized bufferPool = new ByteBufferPool.Sized(null, false, BUFFER_SIZE);
    private final DynamicBodySource dynamicBodySource;
    private final Queue chunks = new BlockingArrayQueue<>(4, 64);
    private final AtomicLong bytesWritten;
    private final TraceToken traceToken;
    private final SerializedInvoker invoker = new SerializedInvoker(InputStreamContentSource.class);

    private final AutoLock lock = new AutoLock();
    private Writer writer;
    private Runnable demandCallback;
    private Content.Chunk errorChunk;
    private boolean closed;

    DynamicBodySourceContentProvider(DynamicBodySource dynamicBodySource, AtomicLong bytesWritten)
    {
        this.dynamicBodySource = dynamicBodySource;
        this.bytesWritten = bytesWritten;
        traceToken = getCurrentTraceToken();
    }

    @Override
    public long getLength()
    {
        return dynamicBodySource.getLength();
    }

    @Override
    public Content.Chunk read()
    {
        try (AutoLock ignored = lock.lock()) {
            if (errorChunk != null) {
                return errorChunk;
            }
            if (closed) {
                return Content.Chunk.EOF;
            }
            if (writer == null) {
                try (TraceTokenScope ignored2 = registerTraceToken(traceToken)) {
                    writer = dynamicBodySource.start(new DynamicBodySourceOutputStream(bufferPool, chunks));
                }
                catch (Throwable x) {
                    return failure(x);
                }
            }
        }

        RetainableByteBuffer chunk = chunks.poll();
        if (chunk == null) {
            try (TraceTokenScope ignored = registerTraceToken(traceToken)) {
                while (chunk == null) {
                    try {
                        writer.write();
                    }
                    catch (Throwable x) {
                        return failure(x);
                    }
                    chunk = chunks.poll();
                }
            }
        }

        if (chunk == DONE) {
            close();
            if (errorChunk != null) {
                return errorChunk;
            }
            return Content.Chunk.EOF;
        }
        ByteBuffer buffer = chunk.getByteBuffer();
        bytesWritten.addAndGet(buffer.position());
        buffer.flip();
        return Content.Chunk.asChunk(buffer, false, chunk);
    }

    private void close()
    {
        try (AutoLock ignored = lock.lock()) {
            closed = true;
        }
        if (writer instanceof AutoCloseable closeableWriter) {
            try (TraceTokenScope ignored = registerTraceToken(traceToken)) {
                closeableWriter.close();
            }
            catch (Throwable x) {
                try (AutoLock ignored = lock.lock()) {
                    if (errorChunk == null) {
                        errorChunk = Content.Chunk.from(x);
                    }
                }
            }
        }
        for (RetainableByteBuffer chunk = chunks.poll(); chunk != null; chunk = chunks.poll()) {
            chunk.release();
        }
    }

    @Override
    public void demand(Runnable demandCallback)
    {
        try (AutoLock ignored = lock.lock()) {
            if (this.demandCallback != null) {
                throw new IllegalStateException("demand pending");
            }
            this.demandCallback = demandCallback;
        }
        invoker.run(this::invokeDemandCallback);
    }

    private void invokeDemandCallback()
    {
        Runnable demandCallback;
        try (AutoLock ignored = lock.lock()) {
            demandCallback = this.demandCallback;
            this.demandCallback = null;
        }
        if (demandCallback != null) {
            ExceptionUtil.run(demandCallback, this::fail);
        }
    }

    @Override
    public void fail(Throwable failure)
    {
        failure(failure);
    }

    private Content.Chunk failure(Throwable failure)
    {
        Content.Chunk error;
        try (AutoLock ignored = lock.lock()) {
            error = errorChunk;
            if (error == null) {
                error = errorChunk = Content.Chunk.from(failure);
            }
            close();
        }
        return error;
    }

    private static class DynamicBodySourceOutputStream
            extends OutputStream
    {
        private RetainableByteBuffer lastChunk = INITIAL;
        private final ByteBufferPool.Sized bufferPool;
        private final Queue chunks;

        private DynamicBodySourceOutputStream(ByteBufferPool.Sized bufferPool, Queue chunks)
        {
            this.bufferPool = bufferPool;
            this.chunks = chunks;
        }

        @Override
        public void write(int b)
        {
            if (!chunks.isEmpty() && lastChunk.hasRemaining()) {
                lastChunk.getByteBuffer().put((byte) b);
            }
            else {
                lastChunk = bufferPool.acquire();
                lastChunk.getByteBuffer().clear().put((byte) b);
                chunks.add(lastChunk);
            }
        }

        @Override
        public void write(byte[] b, int off, int len)
        {
            if (!chunks.isEmpty() && lastChunk.hasRemaining()) {
                int toCopy = min(len, lastChunk.remaining());
                lastChunk.getByteBuffer().put(b, off, toCopy);
                if (toCopy == len) {
                    return;
                }
                off += toCopy;
                len -= toCopy;
            }

            lastChunk = bufferPool.acquire(max(BUFFER_SIZE, len), false);
            lastChunk.getByteBuffer().clear().put(b, off, len);
            chunks.add(lastChunk);
        }

        @Override
        public void close()
        {
            lastChunk = DONE;
            chunks.add(DONE);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy