org.eclipse.jetty.io.content.ContentSourceInputStream 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 org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.IO;
/**
* An {@link InputStream} that is backed by a {@link Content.Source}.
* The read methods are implemented by calling {@link Content.Source#read()}.
* Any {@link Content.Chunk}s read are released once all their content
* has been read.
*
*/
public class ContentSourceInputStream extends InputStream
{
private final Blocker.Shared blocking = new Blocker.Shared();
private final Content.Source content;
private byte[] oneByte;
private Content.Chunk chunk;
public ContentSourceInputStream(Content.Source content)
{
this.content = content;
}
@Override
public int read() throws IOException
{
if (oneByte == null)
oneByte = new byte[1];
int read = read(oneByte, 0, 1);
return read < 0 ? -1 : oneByte[0] & 0xFF;
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
while (true)
{
if (chunk != null)
{
if (Content.Chunk.isFailure(chunk))
{
Content.Chunk failure = chunk;
chunk = Content.Chunk.next(failure);
throw IO.rethrow(failure.getFailure());
}
ByteBuffer byteBuffer = chunk.getByteBuffer();
if (chunk.isLast() && !byteBuffer.hasRemaining())
return -1;
int l = Math.min(byteBuffer.remaining(), len);
byteBuffer.get(b, off, l);
if (!byteBuffer.hasRemaining())
{
chunk.release();
chunk = chunk.isLast() ? Content.Chunk.EOF : null;
}
return l;
}
// Skip empty chunks.
while (true)
{
chunk = content.read();
if (chunk == null || chunk.hasRemaining() || chunk.isLast() || Content.Chunk.isFailure(chunk))
break;
}
if (chunk == null)
{
try (Blocker.Runnable callback = blocking.runnable())
{
content.demand(callback);
callback.block();
}
}
}
}
@Override
public int available() throws IOException
{
ByteBuffer available = chunk == null ? null : chunk.getByteBuffer();
if (available != null)
return available.remaining();
return 0;
}
@Override
public void close() throws IOException
{
// If we have already reached a real EOF or a persistent failure, close is a noop.
if (chunk == Content.Chunk.EOF || Content.Chunk.isFailure(chunk, true))
return;
boolean contentSkipped = false;
// If we have a chunk here, then it needs to be released
if (chunk != null)
{
contentSkipped = chunk.hasRemaining();
chunk.release();
chunk = Content.Chunk.next(chunk);
}
// If we don't have a chunk and have not skipped content, try one read looking for EOF
if (!contentSkipped && chunk == null)
{
chunk = content.read();
// If we read a chunk
if (chunk != null)
{
// Handle a failure as read would
if (Content.Chunk.isFailure(chunk))
{
Content.Chunk failure = chunk;
chunk = Content.Chunk.next(failure);
if (!failure.isLast())
content.fail(failure.getFailure());
throw IO.rethrow(failure.getFailure());
}
contentSkipped = chunk.hasRemaining();
chunk.release();
chunk = Content.Chunk.next(chunk);
}
}
// if we are now really at EOF without skipping content, then nothing more to do
if (!contentSkipped && chunk != null && chunk.isLast())
return;
// Otherwise this is an abnormal close before EOF
Throwable closed = new IOException("closed before EOF");
chunk = Content.Chunk.from(closed);
content.fail(closed);
}
}