diff --git a/src/main/java/loci/common/S3Handle.java b/src/main/java/loci/common/S3Handle.java index 9862007e..6a323c69 100644 --- a/src/main/java/loci/common/S3Handle.java +++ b/src/main/java/loci/common/S3Handle.java @@ -40,15 +40,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.util.regex.Pattern; -import io.minio.MinioClient; -import io.minio.errors.MinioException; -import io.minio.ObjectStat; -import org.xmlpull.v1.XmlPullParserException; +import loci.common.services.DependencyException; +import loci.common.services.S3ClientService; +import loci.common.services.S3ClientServiceException; +import loci.common.services.S3ClientStat; +import loci.common.services.ServiceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -103,11 +102,11 @@ class DelayedObjectNotFound extends IOException { /** remaining path, or key, for this accessed resource */ private final String path; - /** Minio client */ - private MinioClient s3Client; + /** S3 client */ + private S3ClientService s3Client; /** Remote file stat */ - private ObjectStat stat; + private S3ClientStat stat; /** Is this a directory (currently only buckets are considered directories */ private boolean isBucket; @@ -228,11 +227,7 @@ else if (scheme.startsWith("s3+")) { try { this.initialize(); } - catch ( - MinioException | - InvalidKeyException | - NoSuchAlgorithmException | - XmlPullParserException e) { + catch (S3ClientServiceException e) { this.objectNotFound = e; LOGGER.debug("Object not found: [{}] {}", this, e); } @@ -245,32 +240,33 @@ else if (scheme.startsWith("s3+")) { * @throws IOException if there was an error connecting to the server */ protected void connect() throws IOException { + final String appName = "Bio-Formats"; + // TODO: Replace "dev" with a version + final String appVersion = "dev"; try { - s3Client = new MinioClient(server, port, accessKey, secretKey); - // TODO: Replace "dev" with a version - s3Client.setAppInfo("Bio-Formats", "dev"); + ServiceFactory factory = new ServiceFactory(); + s3Client = factory.getInstance(S3ClientService.class); + s3Client.initialize(server, port, accessKey, secretKey, appName, appVersion); } - catch (MinioException e) { + catch (S3ClientServiceException e) { throw new IOException(String.format( "Failed to connect: %s", this), e); } + catch (DependencyException e) { + throw new IOException(String.format( + "S3 requires additional dependencies: %s", this), e); + } LOGGER.trace("connected: server:{} port:{}", server, port); } /** * Check bucket or object exists * @throws IOException if unable to get the object - * @throws MinioException if unable to get the object - * @throws InvalidKeyException if unable to get the object - * @throws NoSuchAlgorithmException if unable to get the object - * @throws XmlPullParserException if unable to get the object + * @throws S3ClientServiceException if unable to get the object */ protected void initialize() throws IOException, - MinioException, - InvalidKeyException, - NoSuchAlgorithmException, - XmlPullParserException { + S3ClientServiceException { if (path == null) { isBucket = s3Client.bucketExists(bucket); } @@ -347,16 +343,11 @@ protected void downloadObject(Path destination) throws HandleException, IOExcept if (path == null) { throw new HandleException("Download path=null not allowed"); } + Files.createDirectories(destination.getParent()); try { - Files.createDirectories(destination.getParent()); s3Client.getObject(bucket, path, destination.toString()); } - catch ( - IOException | - InvalidKeyException | - MinioException | - NoSuchAlgorithmException | - XmlPullParserException e) { + catch (S3ClientServiceException e) { throw new HandleException("Download failed " + toString(), e); } } @@ -437,11 +428,7 @@ protected void resetStream(long offset) throws IOException { fp = offset; mark = offset; } - catch ( - InvalidKeyException | - MinioException | - NoSuchAlgorithmException | - XmlPullParserException e) { + catch (S3ClientServiceException e) { throw new IOException(String.format( "failed to load s3: %s\n\t%s", uri, this), e); } diff --git a/src/main/java/loci/common/services/S3ClientService.java b/src/main/java/loci/common/services/S3ClientService.java new file mode 100644 index 00000000..55e046e0 --- /dev/null +++ b/src/main/java/loci/common/services/S3ClientService.java @@ -0,0 +1,97 @@ +/* + * #%L + * S3 client service + * %% + * Copyright (C) 2019 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package loci.common.services; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An S3 client + */ +public interface S3ClientService extends Service { + + /** + * Initialise the S3 client + * @param server servername + * @param port port + * @param accessKey access key + * @param secretKey secret key + * @param appName user agent application name + * @param appVersion user agent application version + * @throws S3ClientServiceException if an S3 error occurred + */ + void initialize(String server, int port, String accessKey, String secretKey, + String appName, String appVersion) + throws S3ClientServiceException; + + /** + * Check whether a bucket exists + * @param bucket Bucket name + * @return true if bucket exists + * @throws S3ClientServiceException if an S3 error occurred + * @throws IOException if an S3 error occurred + */ + boolean bucketExists(String bucket) throws S3ClientServiceException, IOException; + + /** + * Stat the object + * @param bucket Bucket name + * @param object Object path + * @return S3ClientStat object + * @throws S3ClientServiceException if an S3 error occurred + * @throws IOException if an S3 error occurred + */ + S3ClientStat statObject(String bucket, String object) throws S3ClientServiceException, IOException; + + /** + * Read an object + * @param bucket Bucket name + * @param object Object path + * @param offset Start reading at this offset + * @return InputStream to the object + * @throws S3ClientServiceException if an S3 error occurred + * @throws IOException if an S3 error occurred + */ + InputStream getObject(String bucket, String object, long offset) throws S3ClientServiceException, IOException; + + /** + * Download an object + * @param bucket Bucket name + * @param object Object path + * @param filename Destination file + * @throws S3ClientServiceException if an S3 error occurred + * @throws IOException if an S3 error occurred + */ + void getObject(String bucket, String object, String filename) throws S3ClientServiceException, IOException; + +} diff --git a/src/main/java/loci/common/services/S3ClientServiceException.java b/src/main/java/loci/common/services/S3ClientServiceException.java new file mode 100644 index 00000000..2c2d4a80 --- /dev/null +++ b/src/main/java/loci/common/services/S3ClientServiceException.java @@ -0,0 +1,67 @@ +/* + * #%L + * S3 client stat object + * %% + * Copyright (C) 2019 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package loci.common.services; + +/** + * Exception thrown when internal error specific to the S3 client is raised + */ +public class S3ClientServiceException extends ServiceException +{ + /** + * Constructor. + * @param message Error message. + */ + public S3ClientServiceException(String message) + { + super(message); + } + + /** + * Constructor. + * @param message Error message. + * @param cause Upstream exception. + */ + public S3ClientServiceException(String message, Throwable cause) + { + super(message, cause); + } + + /** + * Constructor. + * @param cause Upstream exception. + */ + public S3ClientServiceException(Throwable cause) + { + super(cause); + } +} diff --git a/src/main/java/loci/common/services/S3ClientServiceImpl.java b/src/main/java/loci/common/services/S3ClientServiceImpl.java new file mode 100644 index 00000000..3b12b04c --- /dev/null +++ b/src/main/java/loci/common/services/S3ClientServiceImpl.java @@ -0,0 +1,132 @@ +/* + * #%L + * S3 client service + * %% + * Copyright (C) 2019 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package loci.common.services; +import io.minio.MinioClient; +import io.minio.errors.MinioException; +import io.minio.ObjectStat; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +/** + * An S3 client + */ +public class S3ClientServiceImpl extends AbstractService implements S3ClientService { + + /** + * Minio client + */ + private MinioClient s3Client; + + /** + * Default constructor. + */ + public S3ClientServiceImpl() { + checkClassDependency(io.minio.MinioClient.class); + } + + // -- S3ClientService methods + + @Override + public void initialize(String server, int port, String accessKey, String secretKey, + String appName, String appVersion) + throws S3ClientServiceException { + try { + s3Client = new MinioClient(server, port, accessKey, secretKey); + s3Client.setAppInfo(appName, appVersion); + } catch (MinioException e) { + throw new S3ClientServiceException(e); + } + } + + @Override + public boolean bucketExists(String bucket) throws S3ClientServiceException, IOException { + try { + return s3Client.bucketExists(bucket); + } + catch ( + MinioException | + InvalidKeyException | + NoSuchAlgorithmException | + XmlPullParserException e) { + throw new S3ClientServiceException(e); + } + } + + @Override + public S3ClientStat statObject(String bucket, String object) throws S3ClientServiceException, IOException { + try { + ObjectStat mcstat = s3Client.statObject(bucket, object); + return new S3ClientStat(mcstat.length()); + } + catch ( + MinioException | + InvalidKeyException | + NoSuchAlgorithmException | + XmlPullParserException e) { + throw new S3ClientServiceException(e); + } + } + + @Override + public InputStream getObject(String bucket, String object, long offset) throws S3ClientServiceException, IOException { + try { + return s3Client.getObject(bucket, object, offset); + } + catch ( + InvalidKeyException | + MinioException | + NoSuchAlgorithmException | + XmlPullParserException e) { + throw new S3ClientServiceException(e); + } + } + + @Override + public void getObject(String bucket, String object, String filename) throws S3ClientServiceException, IOException { + try { + s3Client.getObject(bucket, object, filename); + } + catch ( + InvalidKeyException | + MinioException | + NoSuchAlgorithmException | + XmlPullParserException e) { + throw new S3ClientServiceException(e); + } + } + +} diff --git a/src/main/java/loci/common/services/S3ClientStat.java b/src/main/java/loci/common/services/S3ClientStat.java new file mode 100644 index 00000000..feceb1b4 --- /dev/null +++ b/src/main/java/loci/common/services/S3ClientStat.java @@ -0,0 +1,59 @@ +/* + * #%L + * S3 client stat object + * %% + * Copyright (C) 2019 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package loci.common.services; + +/** + * Object stat (attributes) information + */ +public class S3ClientStat { + /** + * Create stat object storing length of object + * @param length + */ + S3ClientStat(long length) { + this.length = length; + } + + /** + * Length (size) of object + */ + private final long length; + + /** + * Get length (size) of object + * @return length + */ + public long length() { + return this.length; + }; +} \ No newline at end of file diff --git a/src/main/java/loci/common/services/services.properties b/src/main/java/loci/common/services/services.properties index 18f1deb8..ee701222 100644 --- a/src/main/java/loci/common/services/services.properties +++ b/src/main/java/loci/common/services/services.properties @@ -58,6 +58,8 @@ loci.formats.services.JPEGTurboService=loci.formats.services.JPEGTurboServiceImp loci.formats.services.WlzService=loci.formats.services.WlzServiceImpl loci.formats.services.EXIFService=loci.formats.services.EXIFServiceImpl loci.formats.services.JPEGXRService=loci.formats.services.JPEGXRServiceImpl +# Minio S3 client service +loci.common.services.S3ClientService=loci.common.services.S3ClientServiceImpl # OME Codecs JAI Image I/O service ome.codecs.services.JAIIIOService=ome.codecs.services.JAIIIOServiceImpl # OME Codecs LuraWave decoder service (depends on stubs.jar for compilation) diff --git a/src/test/java/loci/common/utests/LocationTest.java b/src/test/java/loci/common/utests/LocationTest.java index dc6c5371..02a24ea8 100644 --- a/src/test/java/loci/common/utests/LocationTest.java +++ b/src/test/java/loci/common/utests/LocationTest.java @@ -43,6 +43,8 @@ import loci.common.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.SkipException; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -55,6 +57,7 @@ public class LocationTest { private static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows"); + private static final Logger LOGGER = LoggerFactory.getLogger(LocationTest.class); // -- Fields -- @@ -202,10 +205,10 @@ public void checkProperties() throws IOException { runS3RemoteTests = TestUtilities.getPropValueInt("testng.runS3RemoteTests") > 0; if (!runHttpRemoteTests) { - System.err.println("WARNING: HTTP tests are disabled!"); + LOGGER.warn("HTTP tests are disabled!"); } if (!runS3RemoteTests) { - System.err.println("WARNING: S3 tests are disabled!"); + LOGGER.warn("S3 tests are disabled!"); } } diff --git a/src/test/java/loci/common/utests/S3ClientServiceTest.java b/src/test/java/loci/common/utests/S3ClientServiceTest.java new file mode 100644 index 00000000..fa0bb657 --- /dev/null +++ b/src/test/java/loci/common/utests/S3ClientServiceTest.java @@ -0,0 +1,142 @@ +/* + * #%L + * Common package for I/O and related utilities + * %% + * Copyright (C) 2005 - 2016 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package loci.common.utests; + +import loci.common.services.DependencyException; +import loci.common.services.S3ClientService; +import loci.common.services.S3ClientServiceException; +import loci.common.services.S3ClientServiceImpl; +import loci.common.services.S3ClientStat; +import loci.common.services.ServiceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.SkipException; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +/** + * Unit tests for the loci.common.service.S3ClientServiceImpl class. + * + * @see loci.common.URLHandle + */ +@Test(groups="readTests") +public class S3ClientServiceTest { + + // -- Fields -- + + private static boolean runS3RemoteTests; + private Path TEMPDIR; + private S3ClientService s3; + + private static final Logger LOGGER = LoggerFactory.getLogger(S3ClientServiceTest.class); + + // -- Setup methods -- + + @BeforeClass + public void setup() throws IOException { + TEMPDIR = Files.createTempDirectory("S3ClientServiceTest-"); + TEMPDIR.toFile().deleteOnExit(); + + runS3RemoteTests = TestUtilities.getPropValueInt("testng.runS3RemoteTests") > 0; + + if (!runS3RemoteTests) { + LOGGER.warn("S3 tests are disabled!"); + } + } + + @BeforeMethod + public void setupMethod() throws S3ClientServiceException, SkipException { + if (!runS3RemoteTests) { + throw new SkipException("S3 tests are disabled"); + } + s3 = new S3ClientServiceImpl(); + s3.initialize("http://localhost", 31836, null, null, "S3ClientServiceTest", "0.0.0"); + } + + // -- Test methods -- + + @Test + public void testServiceLookup() throws DependencyException { + ServiceFactory factory = new ServiceFactory(); + S3ClientService s3client = factory.getInstance(S3ClientService.class); + assertTrue(S3ClientServiceImpl.class.isInstance(s3client)); + } + + @Test + public void testBucketExists() throws S3ClientServiceException, IOException { + assertTrue(s3.bucketExists("bioformats.test.public")); + } + + @Test(expectedExceptions = { S3ClientServiceException.class }) + public void testBucketNotExists() throws S3ClientServiceException, IOException { + s3.bucketExists("this.bucket.does.not.exist"); + } + + @Test + public void testStatObject() throws S3ClientServiceException, IOException { + S3ClientStat stat = s3.statObject("bioformats.test.public", "2MBfile.txt"); + assertEquals(2097152, stat.length()); + } + + @Test + public void testGetObject() throws S3ClientServiceException, IOException { + InputStream in = s3.getObject("bioformats.test.public", "2MBfile.txt", 1380896); + String line = new BufferedReader(new InputStreamReader(in)).readLine(); + assertEquals(". 43154", line); + in.close(); + } + + @Test + public void testGetObjectFile() throws S3ClientServiceException, IOException { + final String filepath = TEMPDIR + "/2MBfile.txt"; + s3.getObject("bioformats.test.public", "2MBfile.txt", filepath); + Path path = Paths.get(filepath); + assertEquals(2097152, Files.size(path)); + String[] lines = Files.newBufferedReader(path).lines().toArray(String[]::new); + assertEquals(". 1", lines[0]); + assertEquals(". 65536", lines[lines.length - 1]); + } + +} \ No newline at end of file diff --git a/src/test/java/loci/common/utests/S3HandleTest.java b/src/test/java/loci/common/utests/S3HandleTest.java index 8b86428e..fc49416f 100644 --- a/src/test/java/loci/common/utests/S3HandleTest.java +++ b/src/test/java/loci/common/utests/S3HandleTest.java @@ -34,6 +34,8 @@ import loci.common.S3Handle; import loci.common.StreamHandle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.SkipException; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -60,6 +62,8 @@ public class S3HandleTest { private static boolean runS3RemoteTests; private Path TEMPDIR; + private static final Logger LOGGER = LoggerFactory.getLogger(S3HandleTest.class); + private static final String s3public = "s3+http://localhost:31836"; private static final String s3private = "s3+http://accesskey:secretkey@localhost:31836"; @@ -73,7 +77,7 @@ public void setup() throws IOException { runS3RemoteTests = TestUtilities.getPropValueInt("testng.runS3RemoteTests") > 0; if (!runS3RemoteTests) { - System.err.println("WARNING: S3 tests are disabled!"); + LOGGER.warn("S3 tests are disabled!"); } }