org.eclipse.jetty.io.internal.ByteChannelContentSource 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.internal;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
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.BufferUtil;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;
/**
* A {@link Content.Source} backed by a {@link ByteChannel}.
* Any calls to {@link #demand(Runnable)} are immediately satisfied.
*/
public class ByteChannelContentSource implements Content.Source
{
private final AutoLock lock = new AutoLock();
private final SerializedInvoker _invoker = new SerializedInvoker();
private final ByteBufferPool.Sized _byteBufferPool;
private ByteChannel _byteChannel;
private final long _offset;
private final long _length;
private RetainableByteBuffer _buffer;
private long _totalRead;
private Runnable demandCallback;
private Content.Chunk _terminal;
public ByteChannelContentSource(SeekableByteChannel seekableByteChannel, long offset, long length)
{
this(null, seekableByteChannel, offset, length);
}
public ByteChannelContentSource(ByteBufferPool.Sized byteBufferPool, SeekableByteChannel seekableByteChannel, long offset, long length)
{
this(byteBufferPool, (ByteChannel)seekableByteChannel, offset, length);
if (offset >= 0 && seekableByteChannel != null)
{
try
{
seekableByteChannel.position(offset);
}
catch (IOException e)
{
// lock not needed in constructor
lockedSetTerminal(Content.Chunk.from(e, true));
}
}
}
public ByteChannelContentSource(ByteChannel byteChannel)
{
this(null, byteChannel, -1L, -1L);
}
public ByteChannelContentSource(ByteBufferPool.Sized byteBufferPool, ByteChannel byteChannel)
{
this(byteBufferPool, byteChannel, -1L, -1L);
}
private ByteChannelContentSource(ByteBufferPool.Sized byteBufferPool, ByteChannel byteChannel, long offset, long length)
{
_byteBufferPool = Objects.requireNonNullElse(byteBufferPool, ByteBufferPool.SIZED_NON_POOLING);
_byteChannel = byteChannel;
_offset = offset < 0 ? 0 : offset;
_length = length;
}
protected ByteChannel open() throws IOException
{
return _byteChannel;
}
@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);
}
protected void lockedSetTerminal(Content.Chunk terminal)
{
if (_terminal == null)
_terminal = Objects.requireNonNull(terminal);
else
ExceptionUtil.addSuppressedIfNotAssociated(_terminal.getFailure(), terminal.getFailure());
IO.close(_byteChannel);
if (_buffer != null)
_buffer.release();
_buffer = null;
}
private void lockedEnsureOpenOrTerminal()
{
if (_terminal == null && (_byteChannel == null || !_byteChannel.isOpen()))
{
try
{
_byteChannel = open();
if (_byteChannel == null || !_byteChannel.isOpen())
lockedSetTerminal(Content.Chunk.from(new ClosedChannelException(), true));
else if (_offset >= 0 && _byteChannel instanceof SeekableByteChannel seekableByteChannel)
seekableByteChannel.position(_offset);
}
catch (IOException e)
{
lockedSetTerminal(Content.Chunk.from(e, true));
}
}
}
@Override
public Content.Chunk read()
{
try (AutoLock ignored = lock.lock())
{
lockedEnsureOpenOrTerminal();
if (_terminal != null)
return _terminal;
if (_buffer == null)
{
_buffer = _byteBufferPool.acquire();
}
else if (_buffer.isRetained())
{
_buffer.release();
_buffer = _byteBufferPool.acquire();
}
try
{
ByteBuffer byteBuffer = _buffer.getByteBuffer();
BufferUtil.clearToFill(byteBuffer);
if (_length >= 0)
byteBuffer.limit((int)Math.min(_buffer.capacity(), _length - _totalRead));
int read = _byteChannel.read(byteBuffer);
BufferUtil.flipToFlush(byteBuffer, 0);
if (read == 0)
return null;
if (read > 0)
{
_totalRead += read;
_buffer.retain();
if (_length < 0 || _totalRead < _length)
return Content.Chunk.asChunk(byteBuffer, false, _buffer);
Content.Chunk last = Content.Chunk.asChunk(byteBuffer, true, _buffer);
lockedSetTerminal(Content.Chunk.EOF);
return last;
}
lockedSetTerminal(Content.Chunk.EOF);
}
catch (Throwable t)
{
lockedSetTerminal(Content.Chunk.from(t, true));
}
}
return _terminal;
}
@Override
public void fail(Throwable failure)
{
try (AutoLock ignored = lock.lock())
{
lockedSetTerminal(Content.Chunk.from(failure, true));
}
}
@Override
public long getLength()
{
return _length;
}
@Override
public boolean rewind()
{
try (AutoLock ignored = lock.lock())
{
// We can remove terminal condition for a rewind that is likely to occur
if (_terminal != null && !Content.Chunk.isFailure(_terminal) && (_byteChannel == null || _byteChannel instanceof SeekableByteChannel))
_terminal = null;
lockedEnsureOpenOrTerminal();
if (_terminal != null || _byteChannel == null || !_byteChannel.isOpen())
return false;
if (_offset >= 0 && _byteChannel instanceof SeekableByteChannel seekableByteChannel)
{
try
{
seekableByteChannel.position(_offset);
_totalRead = 0;
return true;
}
catch (Throwable t)
{
lockedSetTerminal(Content.Chunk.from(t, true));
}
}
return false;
}
}
/**
* A {@link ByteChannelContentSource} for a {@link Path}
*/
public static class PathContentSource extends ByteChannelContentSource
{
private final Path _path;
public PathContentSource(Path path)
{
this(null, path, 0, -1);
}
public PathContentSource(ByteBufferPool.Sized byteBufferPool, Path path)
{
this(byteBufferPool, path, 0, -1);
}
public PathContentSource(ByteBufferPool.Sized byteBufferPool, Path path, long offset, long length)
{
super(byteBufferPool, null, offset, length < 0 ? size(path) : length);
_path = path;
}
public Path getPath()
{
return _path;
}
@Override
protected ByteChannel open() throws IOException
{
return Files.newByteChannel(_path, StandardOpenOption.READ);
}
private static long size(Path path)
{
try
{
return Files.size(path);
}
catch (IOException e)
{
return -1L;
}
}
}
}