Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 116 additions & 2 deletions src/main/java/org/apache/commons/io/build/AbstractOrigin.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URLConnection;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
Expand All @@ -43,8 +44,10 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;

import org.apache.commons.io.Charsets;
import org.apache.commons.io.IORandomAccessFile;
Expand Down Expand Up @@ -961,6 +964,80 @@ public Reader getReader(final Charset charset) throws IOException {
*/
public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {

/**
* Options for connect and read from a URI.
*
* @since 2.22.0
*/
public static final class URIOpenOption implements OpenOption {

/**
* Builds URIOpenOption.
*/
public static class Builder extends AbstractSupplier<URIOpenOption, Builder> {

private Duration connectTimeout;
private Duration readTimeout;

/**
* Constructs a new instance.
*/
public Builder() {
// empty
}

@Override
public URIOpenOption get() {
return new URIOpenOption(this);
}

/**
* Sets the connect timeout duration.
*
* @param connectTimeout the connect timeout duration.
* @return {@code this instance}.
*/
public Builder setConnectTimeout(final Duration connectTimeout) {
this.connectTimeout = connectTimeout;
return asThis();
}

/**
* Sets the read timeout duration.
*
* @param readTimeout the read timeout duration.
* @return {@code this instance}.
*/
public Builder setReadTimeout(final Duration readTimeout) {
this.readTimeout = readTimeout;
return asThis();
}
}

/**
* Creates a new builder.
*
* @return a new builder.
*/
public static Builder builder() {
return new Builder();
}

private final Duration connectTimeout;

private final Duration readTimeout;

private URIOpenOption(final Builder builder) {
connectTimeout = builder.connectTimeout;
readTimeout = builder.readTimeout;
}

@Override
public String toString() {
return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]";
}
}

private static final String SCHEME_HTTPS = "https";
private static final String SCHEME_HTTP = "http";

Expand All @@ -974,12 +1051,17 @@ public URIOrigin(final URI origin) {
super(origin);
}

/**
* {@inheritDoc}
*
* @see URIOpenOption
*/
@Override
protected Channel getChannel(final OpenOption... options) throws IOException {
final URI uri = get();
final String scheme = uri.getScheme();
if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
return Channels.newChannel(uri.toURL().openStream());
return Channels.newChannel(getInputStream(uri, options));
}
return Files.newByteChannel(getPath(), options);
}
Expand All @@ -989,20 +1071,52 @@ public File getFile() {
return getPath().toFile();
}

/**
* {@inheritDoc}
* <p>
* Set timeouts with a {@link URIOpenOption}.
* </p>
*
* @see URIOpenOption
* @see URLConnection#setConnectTimeout(int)
* @see URLConnection#setReadTimeout(int)
*/
@Override
public InputStream getInputStream(final OpenOption... options) throws IOException {
final URI uri = get();
final String scheme = uri.getScheme();
if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
return uri.toURL().openStream();
return getInputStream(uri, options);
}
return Files.newInputStream(getPath(), options);
}

private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException {
final URLConnection connection = uri.toURL().openConnection();
if (options != null) {
Stream.of(options).forEach(option -> {
if (option instanceof URIOpenOption) {
final URIOpenOption connOption = (URIOpenOption) option;
if (connOption.connectTimeout != null) {
connection.setConnectTimeout(toMillis(connOption.connectTimeout));
}
if (connOption.readTimeout != null) {
connection.setReadTimeout(toMillis(connOption.readTimeout));
}
}
});
}
return connection.getInputStream();
}

@Override
public Path getPath() {
return Paths.get(get());
}

private int toMillis(final Duration duration) {
return Math.toIntExact(duration.toMillis());
}
}

/**
Expand Down
62 changes: 53 additions & 9 deletions src/test/java/org/apache/commons/io/build/URIOriginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.function.Supplier;

import org.apache.commons.io.build.AbstractOrigin.URIOrigin;
import org.apache.commons.io.build.AbstractOrigin.URIOrigin.URIOpenOption;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.MethodSource;

/**
* Tests {@link URIOrigin}.
Expand All @@ -41,6 +47,31 @@
*/
class URIOriginTest extends AbstractOriginTest<URI, URIOrigin> {

// @formatter:off
private static final URIOpenOption URI_OPEN_OPTION = URIOpenOption.builder()
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.get();
// @formatter:on

static String[] fixtures() {
return new String[] { "http://1.1.1.1", // IP
"http://google.com", // HTTP
"https://apache.org" // HTTPS
};
}

private void checkRead(final InputStream in) throws IOException {
assertNotEquals(-1, in.read());
}

private void checkRead(final Channel in, final Supplier<String> message) throws IOException {
if (in instanceof ReadableByteChannel) {
final ReadableByteChannel rbc = (ReadableByteChannel) in;
assertNotEquals(-1, rbc.read(ByteBuffer.allocate(1)), message);
}
}

@Override
protected URIOrigin newOriginRo() {
return new URIOrigin(Paths.get(FILE_NAME_RO).toUri());
Expand All @@ -58,23 +89,36 @@ protected void resetOriginRw() throws IOException {
Files.write(rwPath, ArrayUtils.EMPTY_BYTE_ARRAY, StandardOpenOption.CREATE);
}

@Test
void testGetChannelFileURI() throws Exception {
final AbstractOrigin.URIOrigin origin = getOriginRo().asThis();
try (Channel in = origin.getChannel()) {
checkRead(in, origin::toString);
}
}

@ParameterizedTest
@ValueSource(strings = {
"http://apache.com",
"https://apache.com"
})
void testGetInputStream(final String uri) throws Exception {
@MethodSource("fixtures")
void testGetInputStrea(final String uri) throws Exception {
final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri));
try (InputStream in = origin.getInputStream()) {
assertNotEquals(-1, in.read());
try (Channel in = origin.getChannel(URI_OPEN_OPTION)) {
checkRead(in, uri::toString);
}
}

@ParameterizedTest
@MethodSource("fixtures")
void testGetInputStream(final String uri) throws Exception {
final AbstractOrigin.URIOrigin origin = new AbstractOrigin.URIOrigin(new URI(uri));
try (InputStream in = origin.getInputStream(URI_OPEN_OPTION)) {
checkRead(in);
}
}
@Test
void testGetInputStreamFileURI() throws Exception {
final AbstractOrigin.URIOrigin origin = getOriginRo().asThis();
try (InputStream in = origin.getInputStream()) {
assertNotEquals(-1, in.read());
checkRead(in);
}
}
}
Loading