org.eclipse.jetty.io.content.InputStreamContentSource Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.io.content;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Objects;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;
/**
*
* A {@link Content.Source} that is backed by an {@link InputStream}.
* Data is read from the {@link InputStream} into a buffer that is optionally acquired
* from a {@link ByteBufferPool}, and converted to a {@link Content.Chunk} that is
* returned from {@link #read()}. If no {@link ByteBufferPool} is provided, then
* a {@link ByteBufferPool.NonPooling} is used.
*
*/
public class InputStreamContentSource implements Content.Source
{
private final AutoLock lock = new AutoLock();
private final SerializedInvoker invoker = new SerializedInvoker();
private final InputStream inputStream;
private final ByteBufferPool bufferPool;
private int bufferSize = 4096;
private boolean useDirectByteBuffers;
private Runnable demandCallback;
private Content.Chunk errorChunk;
private boolean closed;
public InputStreamContentSource(InputStream inputStream)
{
this(inputStream, null);
}
public InputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool)
{
this.inputStream = Objects.requireNonNull(inputStream);
this.bufferPool = bufferPool != null ? bufferPool : ByteBufferPool.NON_POOLING;
}
public int getBufferSize()
{
return bufferSize;
}
public void setBufferSize(int bufferSize)
{
this.bufferSize = bufferSize;
}
public boolean isUseDirectByteBuffers()
{
return useDirectByteBuffers;
}
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
{
this.useDirectByteBuffers = useDirectByteBuffers;
}
@Override
public Content.Chunk read()
{
try (AutoLock ignored = lock.lock())
{
if (errorChunk != null)
return errorChunk;
if (closed)
return Content.Chunk.EOF;
}
RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), useDirectByteBuffers);
try
{
ByteBuffer buffer = streamBuffer.getByteBuffer();
int read = fillBufferFromInputStream(inputStream, buffer.array());
if (read < 0)
{
streamBuffer.release();
close();
return Content.Chunk.EOF;
}
else
{
buffer.limit(read);
return Content.Chunk.asChunk(buffer, false, streamBuffer);
}
}
catch (Throwable x)
{
streamBuffer.release();
return failure(x);
}
}
protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException
{
return inputStream.read(buffer, 0, buffer.length);
}
private void close()
{
try (AutoLock ignored = lock.lock())
{
closed = true;
}
IO.close(inputStream);
}
@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)
runDemandCallback(demandCallback);
}
private void runDemandCallback(Runnable demandCallback)
{
try
{
demandCallback.run();
}
catch (Throwable x)
{
fail(x);
}
}
@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);
}
IO.close(inputStream);
return error;
}
}