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

org.eclipse.jetty.io.IOResources Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// 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;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;

import org.eclipse.jetty.io.content.ByteBufferContentSource;
import org.eclipse.jetty.io.content.InputStreamContentSource;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.resource.MemoryResource;
import org.eclipse.jetty.util.resource.Resource;

/**
 * Common IO operations for {@link Resource} content.
 */
public class IOResources
{
    /**
     * 

Reads the contents of a Resource into a RetainableByteBuffer.

*

The resource must not be a directory, must exists and there must be * a way to access its contents.

*

Multiple optimized methods are used to access the resource's contents but if they all fail, * {@link Resource#newInputStream()} is used as a fallback.

* * @param resource the resource to be read. * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. * @param direct the directness of the buffers. * @return a {@link RetainableByteBuffer} containing the resource's contents. * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. */ public static RetainableByteBuffer toRetainableByteBuffer(Resource resource, ByteBufferPool bufferPool, boolean direct) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); // Optimize for MemoryResource. if (resource instanceof MemoryResource memoryResource) return RetainableByteBuffer.wrap(ByteBuffer.wrap(memoryResource.getBytes())); long longLength = resource.length(); if (longLength > Integer.MAX_VALUE) throw new IllegalArgumentException("Resource length exceeds 2 GiB: " + resource); int length = (int)longLength; bufferPool = bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool; // Optimize for PathResource. Path path = resource.getPath(); if (path != null) { RetainableByteBuffer retainableByteBuffer = bufferPool.acquire(length, direct); try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path)) { long totalRead = 0L; ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer(); int pos = BufferUtil.flipToFill(byteBuffer); while (totalRead < length) { int read = seekableByteChannel.read(byteBuffer); if (read == -1) break; totalRead += read; } BufferUtil.flipToFlush(byteBuffer, pos); return retainableByteBuffer; } catch (IOException e) { retainableByteBuffer.release(); throw new RuntimeIOException(e); } } // Fallback to InputStream. try (InputStream inputStream = resource.newInputStream()) { if (inputStream == null) throw new IllegalArgumentException("Resource does not support InputStream: " + resource); ByteBufferAggregator aggregator = new ByteBufferAggregator(bufferPool, direct, length > -1 ? length : 4096, length > -1 ? length : Integer.MAX_VALUE); byte[] byteArray = new byte[4096]; while (true) { int read = inputStream.read(byteArray); if (read == -1) break; aggregator.aggregate(ByteBuffer.wrap(byteArray, 0, read)); } return aggregator.takeRetainableByteBuffer(); } catch (IOException e) { throw new RuntimeIOException(e); } } /** *

Gets a {@link Content.Source} with the contents of a resource.

*

The resource must not be a directory, must exists and there must be * a way to access its contents.

*

Multiple optimized methods are used to access the resource's contents but if they all fail, * {@link Resource#newInputStream()} is used as a fallback.

* * @param resource the resource from which to get a {@link Content.Source}. * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. * @return the {@link Content.Source}. * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. */ public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); // Try to find an optimized content source. Path path = resource.getPath(); if (path != null) { return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), path, 0, -1); } if (resource instanceof MemoryResource memoryResource) { byte[] bytes = memoryResource.getBytes(); return new ByteBufferContentSource(ByteBuffer.wrap(bytes)); } // Fallback to wrapping InputStream. try { return new InputStreamContentSource(resource.newInputStream()); } catch (IOException e) { throw new RuntimeIOException(e); } } /** *

Gets a {@link Content.Source} with a range of the contents of a resource.

*

The resource must not be a directory, must exists and there must be * a way to access its contents.

*

Multiple optimized methods are used to access the resource's contents but if they all fail, * {@link Resource#newInputStream()} is used as a fallback.

* * @param resource the resource from which to get a {@link Content.Source}. * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. * @param first the first byte from which to read from. * @param length the length of the content to read. * @return the {@link Content.Source}. * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. */ public static Content.Source asContentSource(Resource resource, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); // Try using the resource's path if possible, as the nio API is async and helps to avoid buffer copies. Path path = resource.getPath(); if (path != null) { return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), path, first, length); } // Try an optimization for MemoryResource. if (resource instanceof MemoryResource memoryResource) return Content.Source.from(ByteBuffer.wrap(memoryResource.getBytes())); // Fallback to InputStream. try { InputStream inputStream = resource.newInputStream(); if (inputStream == null) throw new IllegalArgumentException("Resource does not support InputStream: " + resource); return Content.Source.from(new ByteBufferPool.Sized(bufferPool, direct, bufferSize), inputStream, first, length); } catch (IOException e) { throw new RuntimeIOException(e); } } /** *

Gets an {@link InputStream} with the contents of a resource.

*

The resource must not be a directory, must exist and must return non-null to {@link Resource#newInputStream()}.

* * @param resource the resource from which to get an {@link InputStream}. * @return the {@link InputStream}. * @throws IllegalArgumentException if the resource is a directory or does not exist or {@link Resource#newInputStream()} returns null. */ public static InputStream asInputStream(Resource resource) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); try { InputStream inputStream = resource.newInputStream(); if (inputStream == null) throw new IllegalArgumentException("Resource does not support InputStream: " + resource); return inputStream; } catch (IOException e) { throw new RuntimeIOException(e); } } /** *

Performs an asynchronous copy of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.

*

The resource must not be a directory, must exist and there must be a way to access its contents.

*

Multiple optimized methods are used to access the resource's contents but if they all fail, * {@link #asContentSource(Resource, ByteBufferPool, int, boolean)} is used as a fallback to perform the * {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.

* * @param resource the resource to copy from. * @param sink the sink to copy to. * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. * @param callback the callback to notify when the copy is done. * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. */ public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, Callback callback) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); // Save a Content.Source allocation for resources with a Path. Path path = resource.getPath(); if (path != null) { try { new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, callback).iterate(); } catch (Throwable x) { callback.failed(x); } return; } // Directly write the byte array if the resource is a MemoryResource. if (resource instanceof MemoryResource memoryResource) { byte[] bytes = memoryResource.getBytes(); sink.write(true, ByteBuffer.wrap(bytes), callback); return; } // Fallback to Content.Source. Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct); Content.copy(source, sink, callback); } /** *

Performs an asynchronous copy of a subset of the contents of a resource to a sink, using the given buffer pool and buffer characteristics.

*

The resource must not be a directory, must exist and there must be a way to access its contents.

*

Multiple optimized methods are used to access the resource's contents but if they all fail, * {@link #asContentSource(Resource, ByteBufferPool, int, boolean, long, long)} is used as a fallback to perform the * {@link Content#copy(Content.Source, Content.Sink, Callback) copy}.

* * @param resource the resource to copy from. * @param sink the sink to copy to. * @param bufferPool the {@link ByteBufferPool} to get buffers from. null means allocate new buffers as needed. * @param bufferSize the size of the buffer to be used for the copy. Any value < 1 means use a default value. * @param direct the directness of the buffers, this parameter is ignored if {@code bufferSize} is < 1. * @param first the first byte of the resource to start from. * @param length the length of the resource's contents to copy. * @param callback the callback to notify when the copy is done. * @throws IllegalArgumentException if the resource is a directory or does not exist or there is no way to access its contents. */ public static void copy(Resource resource, Content.Sink sink, ByteBufferPool bufferPool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IllegalArgumentException { if (resource.isDirectory() || !resource.exists()) throw new IllegalArgumentException("Resource must exist and cannot be a directory: " + resource); // Save a Content.Source allocation for resources with a Path. Path path = resource.getPath(); if (path != null) { try { new PathToSinkCopier(path, sink, bufferPool, bufferSize, direct, first, length, callback).iterate(); } catch (Throwable x) { callback.failed(x); } return; } // Directly write the byte array if the resource is a MemoryResource. if (resource instanceof MemoryResource memoryResource) { byte[] bytes = memoryResource.getBytes(); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); if (first >= 0) byteBuffer.position((int)first); if (length >= 0) byteBuffer.limit((int)(byteBuffer.position() + length)); sink.write(true, byteBuffer, callback); return; } // Fallback to Content.Source. Content.Source source = asContentSource(resource, bufferPool, bufferSize, direct, first, length); Content.copy(source, sink, callback); } private static class PathToSinkCopier extends IteratingNestedCallback { private final SeekableByteChannel channel; private final Content.Sink sink; private final ByteBufferPool pool; private final int bufferSize; private final boolean direct; private long remainingLength; private RetainableByteBuffer retainableByteBuffer; private boolean terminated; public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, Callback callback) throws IOException { this(path, sink, pool, bufferSize, direct, -1L, -1L, callback); } public PathToSinkCopier(Path path, Content.Sink sink, ByteBufferPool pool, int bufferSize, boolean direct, long first, long length, Callback callback) throws IOException { super(callback); this.channel = Files.newByteChannel(path); if (first > -1) channel.position(first); this.sink = sink; this.pool = pool == null ? ByteBufferPool.NON_POOLING : pool; this.bufferSize = bufferSize <= 0 ? 4096 : bufferSize; this.direct = direct; this.remainingLength = length; } @Override public InvocationType getInvocationType() { return InvocationType.NON_BLOCKING; } @Override protected Action process() throws Throwable { if (terminated) return Action.SUCCEEDED; if (retainableByteBuffer == null) retainableByteBuffer = pool.acquire(bufferSize, direct); ByteBuffer byteBuffer = retainableByteBuffer.getByteBuffer(); BufferUtil.clearToFill(byteBuffer); if (remainingLength >= 0 && remainingLength < Integer.MAX_VALUE) byteBuffer.limit((int)Math.min(byteBuffer.capacity(), remainingLength)); boolean eof = false; while (byteBuffer.hasRemaining() && !eof) { int read = channel.read(byteBuffer); if (read == -1) eof = true; else if (remainingLength >= 0) remainingLength -= read; } BufferUtil.flipToFlush(byteBuffer, 0); terminated = eof || remainingLength == 0; sink.write(terminated, byteBuffer, this); return Action.SCHEDULED; } @Override protected void onCompleteSuccess() { if (retainableByteBuffer != null) retainableByteBuffer.release(); IO.close(channel); super.onCompleteSuccess(); } @Override protected void onCompleteFailure(Throwable x) { if (retainableByteBuffer != null) retainableByteBuffer.release(); IO.close(channel); super.onCompleteFailure(x); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy