Merge pull request #3910 from eclipse/jetty-9.4.x-3840-pathresource-byterange

Issue #3840 Static resource byte-range support performance
This commit is contained in:
Joakim Erdfelt 2019-07-30 20:04:06 -05:00 committed by GitHub
commit 95298d89e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 717 additions and 40 deletions

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@ -47,9 +46,10 @@ import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.resource.HttpContentRangeWriter;
import org.eclipse.jetty.server.resource.RangeWriter;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartOutputStream;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
@ -779,9 +779,6 @@ public class ResourceService
ctp = "multipart/byteranges; boundary=";
response.setContentType(ctp + multi.getBoundary());
InputStream in = content.getResource().getInputStream();
long pos = 0;
// calculate the content-length
int length = 0;
String[] header = new String[ranges.size()];
@ -801,39 +798,17 @@ public class ResourceService
length += 2 + 2 + multi.getBoundary().length() + 2 + 2;
response.setContentLength(length);
i = 0;
for (InclusiveByteRange ibr : ranges)
try (RangeWriter rangeWriter = HttpContentRangeWriter.newRangeWriter(content))
{
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
long start = ibr.getFirst();
long size = ibr.getSize();
if (in != null)
i = 0;
for (InclusiveByteRange ibr : ranges)
{
// Handle non cached resource
if (start < pos)
{
in.close();
in = content.getResource().getInputStream();
pos = 0;
}
if (pos < start)
{
in.skip(start - pos);
pos = start;
}
IO.copy(in, multi, size);
pos += size;
multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]});
rangeWriter.writeTo(multi, ibr.getFirst(), ibr.getSize());
i++;
}
else
// Handle cached resource
content.getResource().writeTo(multi, start, size);
i++;
}
if (in != null)
in.close();
multi.close();
}
return true;

View File

@ -0,0 +1,64 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
/**
* ByteBuffer based RangeWriter
*/
public class ByteBufferRangeWriter implements RangeWriter
{
private final ByteBuffer buffer;
private boolean closed = false;
public ByteBufferRangeWriter(ByteBuffer buffer)
{
this.buffer = buffer.asReadOnlyBuffer();
}
@Override
public void close() throws IOException
{
closed = true;
}
@Override
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
{
if (skipTo > Integer.MAX_VALUE)
{
throw new IllegalArgumentException("Unsupported skipTo " + skipTo + " > " + Integer.MAX_VALUE);
}
if (length > Integer.MAX_VALUE)
{
throw new IllegalArgumentException("Unsupported length " + skipTo + " > " + Integer.MAX_VALUE);
}
ByteBuffer src = buffer.slice();
src.position((int)skipTo);
src.limit(Math.addExact((int)skipTo, (int)length));
BufferUtil.writeTo(src, outputStream);
}
}

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Objects;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Range Writer selection for HttpContent
*/
public class HttpContentRangeWriter
{
private static final Logger LOG = Log.getLogger(HttpContentRangeWriter.class);
/**
* Obtain a new RangeWriter for the supplied HttpContent.
*
* @param content the HttpContent to base RangeWriter on
* @return the RangeWriter best suited for the supplied HttpContent
*/
public static RangeWriter newRangeWriter(HttpContent content)
{
Objects.requireNonNull(content, "HttpContent");
// Try direct buffer
ByteBuffer buffer = content.getDirectBuffer();
if (buffer == null)
{
buffer = content.getIndirectBuffer();
}
if (buffer != null)
{
return new ByteBufferRangeWriter(buffer);
}
try
{
ReadableByteChannel channel = content.getReadableByteChannel();
if (channel != null)
{
if (channel instanceof SeekableByteChannel)
{
SeekableByteChannel seekableByteChannel = (SeekableByteChannel)channel;
return new SeekableByteChannelRangeWriter(seekableByteChannel);
}
if (LOG.isDebugEnabled())
LOG.debug("Skipping non-SeekableByteChannel option " + channel + " from content " + content);
channel.close();
}
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Skipping ReadableByteChannel option", e);
}
return new InputStreamRangeWriter(() -> content.getInputStream());
}
}

View File

@ -0,0 +1,125 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.eclipse.jetty.util.IO;
/**
* Default Range Writer for InputStream
*/
public class InputStreamRangeWriter implements RangeWriter
{
public static final int NO_PROGRESS_LIMIT = 3;
public interface InputStreamSupplier
{
InputStream newInputStream() throws IOException;
}
private final InputStreamSupplier inputStreamSupplier;
private boolean closed = false;
private InputStream inputStream;
private long pos;
/**
* Create InputStremRangeWriter
*
* @param inputStreamSupplier Supplier of the InputStream. If the stream needs to be regenerated, such as the next
* requested range being before the current position, then the current InputStream is closed and a new one obtained
* from this supplier.
*/
public InputStreamRangeWriter(InputStreamSupplier inputStreamSupplier)
{
this.inputStreamSupplier = inputStreamSupplier;
}
@Override
public void close() throws IOException
{
closed = true;
if (inputStream != null)
{
inputStream.close();
}
}
@Override
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
{
if (closed)
{
throw new IOException("RangeWriter is closed");
}
if (inputStream == null)
{
inputStream = inputStreamSupplier.newInputStream();
pos = 0;
}
if (skipTo < pos)
{
inputStream.close();
inputStream = inputStreamSupplier.newInputStream();
pos = 0;
}
if (pos < skipTo)
{
long skipSoFar = pos;
long actualSkipped;
int noProgressLoopLimit = NO_PROGRESS_LIMIT;
// loop till we reach desired point, break out on lack of progress.
while (noProgressLoopLimit > 0 && skipSoFar < skipTo)
{
actualSkipped = inputStream.skip(skipTo - skipSoFar);
if (actualSkipped == 0)
{
noProgressLoopLimit--;
}
else if (actualSkipped > 0)
{
skipSoFar += actualSkipped;
noProgressLoopLimit = NO_PROGRESS_LIMIT;
}
else
{
// negative values means the stream was closed or reached EOF
// either way, we've hit a state where we can no longer
// fulfill the requested range write.
throw new IOException("EOF reached before InputStream skip destination");
}
}
if (noProgressLoopLimit <= 0)
{
throw new IOException("No progress made to reach InputStream skip position " + (skipTo - pos));
}
pos = skipTo;
}
IO.copy(inputStream, outputStream, length);
pos += length;
}
}

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
/**
* Interface for writing sections (ranges) of a single resource (SeekableByteChannel, Resource, etc) to an outputStream.
*/
public interface RangeWriter extends Closeable
{
/**
* Write the specific range (start, size) to the outputStream.
*
* @param outputStream the stream to write to
* @param skipTo the offset / skip-to / seek-to / position in the resource to start the write from
* @param length the size of the section to write
*/
void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException;
}

View File

@ -0,0 +1,66 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
public class SeekableByteChannelRangeWriter implements RangeWriter
{
private final SeekableByteChannel channel;
private final int bufSize;
private final ByteBuffer buffer;
public SeekableByteChannelRangeWriter(SeekableByteChannel seekableByteChannel)
{
this.channel = seekableByteChannel;
this.bufSize = IO.bufferSize;
this.buffer = BufferUtil.allocate(this.bufSize);
}
@Override
public void close() throws IOException
{
this.channel.close();
}
@Override
public void writeTo(OutputStream outputStream, long skipTo, long length) throws IOException
{
this.channel.position(skipTo);
// copy from channel to output stream
long readTotal = 0;
while (readTotal < length)
{
BufferUtil.clearToFill(buffer);
int size = (int)Math.min(bufSize, length - readTotal);
buffer.limit(size);
int readLen = channel.read(buffer);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, outputStream);
readTotal += readLen;
}
}
}

View File

@ -0,0 +1,156 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.resource;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class RangeWriterTest
{
public static final String DATA = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ!@#$%^&*()_+/.,[]";
public static Path initDataFile() throws IOException
{
Path testDir = MavenTestingUtils.getTargetTestingPath(RangeWriterTest.class.getSimpleName());
FS.ensureEmpty(testDir);
Path dataFile = testDir.resolve("data.dat");
try (BufferedWriter writer = Files.newBufferedWriter(dataFile, UTF_8, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))
{
writer.write(DATA);
writer.flush();
}
return dataFile;
}
public static Stream<Arguments> impls() throws IOException
{
Resource resource = new PathResource(initDataFile());
return Stream.of(
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, true))),
Arguments.of(new ByteBufferRangeWriter(BufferUtil.toBuffer(resource, false))),
Arguments.of(new SeekableByteChannelRangeWriter((SeekableByteChannel)resource.getReadableByteChannel())),
Arguments.of(new InputStreamRangeWriter(() -> resource.getInputStream()))
);
}
@ParameterizedTest
@MethodSource("impls")
public void testSimpleRange(RangeWriter rangeWriter) throws IOException
{
ByteArrayOutputStream outputStream;
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 50);
assertThat("Range: 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
}
@ParameterizedTest
@MethodSource("impls")
public void testSameRange_MultipleTimes(RangeWriter rangeWriter) throws IOException
{
ByteArrayOutputStream outputStream;
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 50);
assertThat("Range(a): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 50);
assertThat("Range(b): 10 (len=50)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 60)));
}
@ParameterizedTest
@MethodSource("impls")
public void testMultipleRanges_Ordered(RangeWriter rangeWriter) throws IOException
{
ByteArrayOutputStream outputStream;
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 20);
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 35, 10);
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 55, 10);
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
}
@ParameterizedTest
@MethodSource("impls")
public void testMultipleRanges_Overlapping(RangeWriter rangeWriter) throws IOException
{
ByteArrayOutputStream outputStream;
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 20);
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 15, 20);
assertThat("Range(b): 15 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(15, 15 + 20)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 20, 20);
assertThat("Range(b): 20 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(20, 20 + 20)));
}
@ParameterizedTest
@MethodSource("impls")
public void testMultipleRanges_ReverseOrder(RangeWriter rangeWriter) throws IOException
{
ByteArrayOutputStream outputStream;
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 55, 10);
assertThat("Range(b): 55 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(55, 55 + 10)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 35, 10);
assertThat("Range(b): 35 (len=10)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(35, 35 + 10)));
outputStream = new ByteArrayOutputStream();
rangeWriter.writeTo(outputStream, 10, 20);
assertThat("Range(a): 10 (len=20)", new String(outputStream.toByteArray(), UTF_8), is(DATA.substring(10, 10 + 20)));
}
}

View File

@ -19,17 +19,19 @@
package org.eclipse.jetty.util.resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
@ -40,6 +42,7 @@ import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@ -58,6 +61,7 @@ public class PathResource extends Resource
private final Path path;
private final Path alias;
private final URI uri;
private final boolean belongsToDefaultFileSystem;
private final Path checkAliasPath()
{
@ -196,6 +200,7 @@ public class PathResource extends Resource
assertValidPath(path);
this.uri = this.path.toUri();
this.alias = checkAliasPath();
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
}
/**
@ -216,6 +221,7 @@ public class PathResource extends Resource
childPath += "/";
this.uri = URIUtil.addPath(parent.uri, childPath);
this.alias = checkAliasPath();
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
}
/**
@ -256,6 +262,7 @@ public class PathResource extends Resource
this.path = path.toAbsolutePath();
this.uri = path.toUri();
this.alias = checkAliasPath();
this.belongsToDefaultFileSystem = this.path.getFileSystem() == FileSystems.getDefault();
}
/**
@ -365,6 +372,8 @@ public class PathResource extends Resource
@Override
public File getFile() throws IOException
{
if (!belongsToDefaultFileSystem)
return null;
return path.toFile();
}
@ -379,9 +388,8 @@ public class PathResource extends Resource
@Override
public InputStream getInputStream() throws IOException
{
// Use a FileInputStream rather than Files.newInputStream(path)
// since it produces a stream with a fast skip implementation
return new FileInputStream(getFile());
// TODO: investigate if SPARSE use for default FileSystem usages is worth it
return Files.newInputStream(path, StandardOpenOption.READ);
}
@Override
@ -393,7 +401,8 @@ public class PathResource extends Resource
@Override
public ReadableByteChannel getReadableByteChannel() throws IOException
{
return FileChannel.open(path, StandardOpenOption.READ);
// TODO: investigate if SPARSE use for default FileSystem usages is worth it
return Files.newByteChannel(path, StandardOpenOption.READ);
}
@Override
@ -559,6 +568,43 @@ public class PathResource extends Resource
}
}
/**
* @param outputStream the output stream to write to
* @param start First byte to write
* @param count Bytes to write or -1 for all of them.
* @throws IOException if unable to copy the Resource to the output
*/
@Override
public void writeTo(OutputStream outputStream, long start, long count)
throws IOException
{
long length = count;
if (count < 0)
{
length = Files.size(path) - start;
}
try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ))
{
ByteBuffer buffer = BufferUtil.allocate(IO.bufferSize);
channel.position(start);
// copy from channel to output stream
long readTotal = 0;
while (readTotal < length)
{
BufferUtil.clearToFill(buffer);
int size = (int)Math.min(IO.bufferSize, length - readTotal);
buffer.limit(size);
int readLen = channel.read(buffer);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, outputStream);
readTotal += readLen;
}
}
}
@Override
public String toString()
{

View File

@ -0,0 +1,124 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util.resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
public class PathResourceTest
{
@Test
public void testNonDefaultFileSystem_GetInputStream() throws URISyntaxException, IOException
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
System.err.println("URI = " + uri);
Map<String, Object> env = new HashMap<>();
env.put("multi-release", "runtime");
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
assertThat(manifestPath, is(not(nullValue())));
PathResource resource = new PathResource(manifestPath);
try (InputStream inputStream = resource.getInputStream())
{
assertThat("InputStream", inputStream, is(not(nullValue())));
}
}
}
@Test
public void testNonDefaultFileSystem_GetReadableByteChannel() throws URISyntaxException, IOException
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
System.err.println("URI = " + uri);
Map<String, Object> env = new HashMap<>();
env.put("multi-release", "runtime");
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
assertThat(manifestPath, is(not(nullValue())));
PathResource resource = new PathResource(manifestPath);
try (ReadableByteChannel channel = resource.getReadableByteChannel())
{
assertThat("ReadableByteChannel", channel, is(not(nullValue())));
}
}
}
@Test
public void testNonDefaultFileSystem_GetFile() throws URISyntaxException, IOException
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
URI uri = new URI("jar", exampleJar.toUri().toASCIIString(), null);
System.err.println("URI = " + uri);
Map<String, Object> env = new HashMap<>();
env.put("multi-release", "runtime");
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path manifestPath = zipfs.getPath("/META-INF/MANIFEST.MF");
assertThat(manifestPath, is(not(nullValue())));
PathResource resource = new PathResource(manifestPath);
File file = resource.getFile();
assertThat("File should be null for non-default FileSystem", file, is(nullValue()));
}
}
@Test
public void testDefaultFileSystem_GetFile() throws Exception
{
Path exampleJar = MavenTestingUtils.getTestResourcePathFile("example.jar");
PathResource resource = new PathResource(exampleJar);
File file = resource.getFile();
assertThat("File for default FileSystem", file, is(exampleJar.toFile()));
}
}