From 882a4e21747296883e0a51fb775f0eefdbfe4798 Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Sat, 20 Jun 2026 07:44:27 +0530 Subject: [PATCH 1/2] Move to JDK 25 for build only Signed-off-by: Bala.FA --- .gitattributes | 15 +++++- .github/workflows/gradle.yml | 30 +++++------ .../java/io/minio/admin/MinioAdminClient.java | 6 ++- api/src/main/java/io/minio/Checksum.java | 1 + api/src/main/java/io/minio/Http.java | 2 +- api/src/main/java/io/minio/PartReader.java | 3 +- .../java/io/minio/errors/MinioException.java | 26 ++++++--- build.gradle | 50 +++++++++++------- functional/TestMinioClient.java | 8 ++- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 15 +++--- gradlew.bat | 36 +++++-------- spotbugs-filter.xml | 4 ++ 14 files changed, 120 insertions(+), 80 deletions(-) diff --git a/.gitattributes b/.gitattributes index fcadb2cf9..9cf876051 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,14 @@ -* text eol=lf +# Normalize text files to LF, but let Git auto-detect binaries so they are +# stored byte-for-byte (a blanket "* text" corrupts binaries like the Gradle +# wrapper jar by stripping CR bytes during line-ending normalization). +* text=auto eol=lf + +# Always treat these as binary regardless of auto-detection. +*.jar binary +*.zip binary +*.gz binary +*.class binary +*.png binary +*.jpg binary +*.gif binary +*.ico binary diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5eb62cc47..cb9c6af53 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,11 +29,11 @@ jobs: exit 1 fi - - name: Setup java 17 for building + - name: Setup java 25 for building uses: actions/setup-java@v5 with: distribution: temurin - java-version: '17' + java-version: '25' - name: Set environment variables on Ubuntu if: matrix.os == 'ubuntu-latest' @@ -59,7 +59,7 @@ jobs: ./gradlew.bat build - name: Setup java ${{ matrix.java-version }} for testing - if: matrix.java-version != '17' + if: matrix.java-version != '25' uses: actions/setup-java@v5 with: distribution: temurin @@ -69,20 +69,20 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | cd functional - curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar - curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar - javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java - java -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest - javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java - java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent - javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java - java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent + curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar + curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.2/spotbugs-annotations-4.10.2.jar + javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java + java -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest + javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java + java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent + javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java + java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent - name: Run tests on Windows if: matrix.os == 'windows-latest' run: | cd functional - curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar - curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar - javac -encoding UTF-8 -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java - java -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest + curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar + curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.2/spotbugs-annotations-4.10.2.jar + javac -encoding UTF-8 -cp "spotbugs-annotations-4.10.2.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java + java -cp "spotbugs-annotations-4.10.2.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest diff --git a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java index e5588d1bc..e6e702e39 100644 --- a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java +++ b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java @@ -453,8 +453,10 @@ public long getBucketQuota(String bucketName) throws MinioException { OBJECT_MAPPER .getTypeFactory() .constructMapType(HashMap.class, String.class, JsonNode.class); - return OBJECT_MAPPER.>readValue(response.body().bytes(), mapType) - .entrySet().stream() + return OBJECT_MAPPER + .>readValue(response.body().bytes(), mapType) + .entrySet() + .stream() .filter(entry -> "quota".equals(entry.getKey())) .findFirst() .map(entry -> Long.valueOf(entry.getValue().toString())) diff --git a/api/src/main/java/io/minio/Checksum.java b/api/src/main/java/io/minio/Checksum.java index 75c048b96..d1d15cb90 100644 --- a/api/src/main/java/io/minio/Checksum.java +++ b/api/src/main/java/io/minio/Checksum.java @@ -33,6 +33,7 @@ public class Checksum { /** MD5 hash of zero length byte array. */ public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg=="; + /** SHA-256 hash of zero length byte array. */ public static final String ZERO_SHA256_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; diff --git a/api/src/main/java/io/minio/Http.java b/api/src/main/java/io/minio/Http.java index 772ba721d..e6b6ac669 100644 --- a/api/src/main/java/io/minio/Http.java +++ b/api/src/main/java/io/minio/Http.java @@ -890,7 +890,7 @@ public static OkHttpClient setTimeout( .build(); } - /** HTTP body of {@link RandomAccessFile}, {@link ByteBuffer} or {@link byte} array. */ + /** HTTP body of {@link RandomAccessFile}, {@link ByteBuffer} or {@code byte} array. */ public static class Body { private okhttp3.RequestBody requestBody; private RandomAccessFile file; diff --git a/api/src/main/java/io/minio/PartReader.java b/api/src/main/java/io/minio/PartReader.java index 1eb03e208..6715f65ea 100644 --- a/api/src/main/java/io/minio/PartReader.java +++ b/api/src/main/java/io/minio/PartReader.java @@ -96,7 +96,8 @@ private void readOneByte() throws MinioException { int n = 0; try { - while ((n = file != null ? file.read(oneByte) : stream.read(oneByte)) == 0) ; + while ((n = file != null ? file.read(oneByte) : stream.read(oneByte)) == 0) + ; } catch (IOException e) { throw new MinioException(e); } diff --git a/api/src/main/java/io/minio/errors/MinioException.java b/api/src/main/java/io/minio/errors/MinioException.java index 31f3be597..2ec209d04 100644 --- a/api/src/main/java/io/minio/errors/MinioException.java +++ b/api/src/main/java/io/minio/errors/MinioException.java @@ -62,12 +62,26 @@ public String httpTrace() { /** Throws encapsulated exception. */ public void throwEncapsulatedException() - throws BucketPolicyTooLargeException, CertificateException, EOFException, - ErrorResponseException, FileNotFoundException, GeneralSecurityException, - InsufficientDataException, InternalException, InvalidKeyException, - InvalidResponseException, IOException, JsonMappingException, JsonParseException, - JsonProcessingException, KeyManagementException, KeyStoreException, MinioException, - NoSuchAlgorithmException, ServerException, XmlParserException { + throws BucketPolicyTooLargeException, + CertificateException, + EOFException, + ErrorResponseException, + FileNotFoundException, + GeneralSecurityException, + InsufficientDataException, + InternalException, + InvalidKeyException, + InvalidResponseException, + IOException, + JsonMappingException, + JsonParseException, + JsonProcessingException, + KeyManagementException, + KeyStoreException, + MinioException, + NoSuchAlgorithmException, + ServerException, + XmlParserException { Throwable e = getCause(); // Inherited by MinioException diff --git a/build.gradle b/build.gradle index dedf232cc..28649990d 100644 --- a/build.gradle +++ b/build.gradle @@ -15,14 +15,14 @@ */ /********************************/ -/* gradleVersion = '9.4.1' */ +/* gradleVersion = '9.6.0' */ /********************************/ plugins { - id 'com.gradleup.shadow' version '9.0.0' - id 'com.github.spotbugs' version '6.5.1' - id 'org.jreleaser' version '1.23.0' - id 'com.diffplug.spotless' version '6.13.0' + id 'com.gradleup.shadow' version '9.4.2' + id 'com.github.spotbugs' version '6.5.8' + id 'org.jreleaser' version '1.24.0' + id 'com.diffplug.spotless' version '8.7.0' } /* Root project definitions */ @@ -48,22 +48,26 @@ subprojects { dependencies { api 'com.carrotsearch.thirdparty:simple-xml-safe:2.7.1' api 'com.google.guava:guava:33.6.0-jre' - api 'com.squareup.okhttp3:okhttp:5.3.2' - api 'com.fasterxml.jackson.core:jackson-annotations:2.21' - api 'com.fasterxml.jackson.core:jackson-core:2.21.2' - api 'com.fasterxml.jackson.core:jackson-databind:2.21.2' + api 'com.squareup.okhttp3:okhttp:5.4.0' + api 'com.fasterxml.jackson.core:jackson-annotations:2.22' + api 'com.fasterxml.jackson.core:jackson-core:2.22.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.22.0' api 'org.bouncycastle:bcprov-jdk18on:1.84' api 'org.apache.commons:commons-compress:1.28.0' - api 'commons-codec:commons-codec:1.21.0' + api 'commons-codec:commons-codec:1.22.0' api 'org.xerial.snappy:snappy-java:1.1.10.8' - compileOnly 'com.github.spotbugs:spotbugs-annotations:4.9.8' + compileOnly 'com.github.spotbugs:spotbugs-annotations:4.10.2' - testImplementation 'com.squareup.okhttp3:mockwebserver:5.3.2' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' + testImplementation 'com.squareup.okhttp3:mockwebserver:5.4.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.14.4' } [compileJava, compileTestJava].each() { it.options.fork = true + // Build runs on JDK 25 (see java.toolchain) but enforces the Java 8 API surface and + // emits Java 8 bytecode via --release 8. --release also avoids the "source/target 8 is + // obsolete" warning; -Xlint:-options is kept as a belt-and-braces guard for -Werror. + it.options.release = 8 it.options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation', '-Xlint:-options', '-Werror', '-Xdiags:verbose'] it.options.encoding = 'UTF-8' } @@ -103,6 +107,10 @@ subprojects { check.dependsOn localeTest java { + // Compile/test/javadoc run on JDK 25; bytecode/API level pinned to Java 8 via release below. + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } @@ -112,7 +120,7 @@ subprojects { target '**/*.java' importOrder 'edu', 'com', 'io', 'java', 'javax', 'org', '' removeUnusedImports() - googleJavaFormat('1.7') + googleJavaFormat('1.35.0') } groovyGradle { target '*.gradle' @@ -226,7 +234,7 @@ project(':api') { } signing { - required { + required = { gradle.taskGraph.allTasks.any { it.name.contains('LocalMavenWithChecksums') } } sign publishing.publications.minioJava @@ -346,7 +354,7 @@ project(':adminapi') { } signing { - required { + required = { gradle.taskGraph.allTasks.any { it.name.contains('LocalMavenWithChecksums') } } sign publishing.publications.minioJava @@ -355,7 +363,7 @@ project(':adminapi') { project(':examples') { dependencies { - compileOnly 'me.tongfei:progressbar:0.9.5' + compileOnly 'me.tongfei:progressbar:0.10.2' compileOnly project(':api') } @@ -367,6 +375,9 @@ project(':examples') { main { java { srcDirs = ["$rootDir/examples"] + // srcDir is the module root, which also contains build/; keep generated + // files (e.g. Spotless lint output) out of compilation. + exclude 'build/**' } } } @@ -374,7 +385,7 @@ project(':examples') { project(':functional') { dependencies { - implementation 'org.junit.jupiter:junit-jupiter-api:5.14.3' + implementation 'org.junit.jupiter:junit-jupiter-api:5.14.4' implementation project(':api') implementation project(':adminapi') } @@ -387,6 +398,9 @@ project(':functional') { main { java { srcDirs = ["$rootDir/functional"] + // srcDir is the module root, which also contains build/; keep generated + // files (e.g. Spotless lint output) out of compilation. + exclude 'build/**' } } } diff --git a/functional/TestMinioClient.java b/functional/TestMinioClient.java index 28182b3c2..86769bac0 100644 --- a/functional/TestMinioClient.java +++ b/functional/TestMinioClient.java @@ -548,7 +548,9 @@ public void putObject() throws Exception { testPutObject( "[object name ends with '/']", - PutObjectArgs.builder().bucket(bucketName).object("path/to/" + getRandomName() + "/") + PutObjectArgs.builder() + .bucket(bucketName) + .object("path/to/" + getRandomName() + "/") .stream(new ContentInputStream(0), 0L, null) .contentType(CUSTOM_CONTENT_TYPE) .build(), @@ -1445,7 +1447,9 @@ public void testCopyObject( client.makeBucket(MakeBucketArgs.builder().bucket(args.source().bucket()).build()); try { PutObjectArgs.Builder builder = - PutObjectArgs.builder().bucket(args.source().bucket()).object(args.source().object()) + PutObjectArgs.builder() + .bucket(args.source().bucket()) + .object(args.source().object()) .stream(new ContentInputStream(1 * KB), 1L * KB, null); if (sse != null) builder.sse(sse); client.putObject(builder.build()); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 40229 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 delta 35349 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 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/spotbugs-filter.xml b/spotbugs-filter.xml index bc8accf49..27f43ade6 100644 --- a/spotbugs-filter.xml +++ b/spotbugs-filter.xml @@ -32,4 +32,8 @@ + + + + From f29cd3e9404d336d46363c1bf81cff15c48cce94 Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Sat, 27 Jun 2026 13:03:22 +0530 Subject: [PATCH 2/2] Fix bugs found during code audit across api, adminapi, functional, examples api: - Compose/copy >5GiB: send per-part x-amz-copy-source-range (was loop-invariant) - Checksum.CRC64NVME.update: bound the slicing loop by len, not p.length - messages/Filter: keep And(String,Map) tags; "exactly one" via new Utils.xor - Http BaseUrl: ELB endpoints derive the real region (was "com") - messages/Checksum.headers(): emit x-amz-checksum- - PromptObjectArgs prompt setter; ListPartsArgs.Builder extends ObjectArgs.Builder - maxKeys() null-safe; Arrays.hashCode for array-backed args - downloadObject error propagation + temp cleanup; snowball/appendObject RAF close - uploadPartsParallelly buffer return/abort; executeAsync 304 return - credential providers: AwsConfig/MinioClientConfig/MinioEnvironment NPE -> clean - GetPresignedObjectUrlArgs expiry overflow; ObjectLockConfiguration duration(); AccessControlPolicy null owner; ListenBucketNotificationArgs requires bucket; GetObjectAttributesArgs validation; LifecycleConfiguration via Utils.xor; VersioningConfiguration.excludeFolders() primitive; ReplicationConfiguration text adminapi: - updateServiceAccount newStatus nullable (no silent disable) - Status.fromString null-guard; thread signing Credentials through execute() - getBucketQuota asLong(); Crypto.encrypt exact-multiple chunk; map getters return empty map when absent functional: - PutObjectRunnable surfaces thread failures; notification tests assert; legal-hold tests fixed; stream/temp-file cleanup; TestUserAgent guards; MintLogger throws UncheckedIOException instead of blank log examples: - close getObject/Response/progress streams (try-with-resources); fix GetObjectProgressBar/SelectObjectContent object names; real file size as object size; provider isSuccessful() check; placeholder credentials Also add CLAUDE.md with build commands and architecture overview. Signed-off-by: Bala.FA --- .github/workflows/gradle.yml | 2 +- CLAUDE.md | 61 ++++++++ .../src/main/java/io/minio/admin/Crypto.java | 4 +- .../minio/admin/GetDataUsageInfoResponse.java | 22 ++- .../io/minio/admin/GetServerInfoResponse.java | 14 +- .../java/io/minio/admin/MinioAdminClient.java | 62 ++++++--- .../src/main/java/io/minio/admin/Status.java | 2 +- .../main/java/io/minio/AppendObjectArgs.java | 3 +- api/src/main/java/io/minio/BaseS3Client.java | 7 +- api/src/main/java/io/minio/Checksum.java | 5 +- .../io/minio/CompleteMultipartUploadArgs.java | 3 +- .../io/minio/GetObjectAttributesArgs.java | 6 + .../io/minio/GetPresignedObjectUrlArgs.java | 8 +- api/src/main/java/io/minio/Http.java | 2 +- .../java/io/minio/ListObjectVersionsArgs.java | 2 +- .../main/java/io/minio/ListObjectsV1Args.java | 2 +- .../main/java/io/minio/ListObjectsV2Args.java | 2 +- api/src/main/java/io/minio/ListPartsArgs.java | 2 +- .../minio/ListenBucketNotificationArgs.java | 4 +- .../main/java/io/minio/MinioAsyncClient.java | 130 ++++++++++++++---- .../main/java/io/minio/PromptObjectArgs.java | 2 +- .../java/io/minio/PutObjectAPIBaseArgs.java | 5 +- api/src/main/java/io/minio/PutObjectArgs.java | 2 +- .../io/minio/UploadSnowballObjectsArgs.java | 7 +- api/src/main/java/io/minio/Utils.java | 9 ++ .../minio/credentials/AwsConfigProvider.java | 2 + .../MinioClientConfigProvider.java | 2 +- .../credentials/MinioEnvironmentProvider.java | 17 ++- .../minio/messages/AccessControlPolicy.java | 3 +- .../main/java/io/minio/messages/Checksum.java | 2 +- .../main/java/io/minio/messages/Filter.java | 6 +- .../messages/LifecycleConfiguration.java | 4 +- .../messages/ObjectLockConfiguration.java | 4 +- .../messages/ReplicationConfiguration.java | 2 +- .../messages/VersioningConfiguration.java | 4 +- examples/GetObject.java | 21 ++- examples/GetObjectProgressBar.java | 39 +++--- examples/GetPartialObject.java | 19 ++- examples/GetPresignedPostFormData.java | 11 +- .../MinioClientWithAssumeRoleProvider.java | 8 +- .../MinioClientWithClientGrantsProvider.java | 3 + .../MinioClientWithWebIdentityProvider.java | 3 + examples/ProgressStream.java | 14 +- examples/PutObjectProgressBar.java | 16 ++- examples/PutObjectUiProgressBar.java | 4 +- examples/SelectObjectContent.java | 25 ++-- functional/MintLogger.java | 4 +- functional/PutObjectRunnable.java | 8 ++ functional/TestMinioClient.java | 38 ++--- functional/TestUserAgent.java | 25 +++- 50 files changed, 457 insertions(+), 195 deletions(-) create mode 100644 CLAUDE.md diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index cb9c6af53..325316685 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: - name: Check limited Guava usage if: matrix.os == 'ubuntu-latest' run: | - if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github "com.google.common.base.Objects" .; then + if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github --exclude=CLAUDE.md "com.google.common.base.Objects" .; then echo "Error: use java.util.Objects instead of com.google.common.base.Objects" exit 1 fi diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..b08df4cb9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,61 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +MinIO Java SDK — an S3-compatible client library (`io.minio:minio`) plus a MinIO +admin client (`io.minio:minio-admin`). Multi-module Gradle build. The public API +must remain **Java 8 source/bytecode compatible** even though the build runs on JDK 25. + +## Modules (`settings.gradle`) + +- `api` — the S3 client. Published as artifact `minio`. The core of the project. +- `adminapi` — MinIO server admin client (`io.minio.admin`). Published as `minio-admin`. Depends on `:api`. +- `functional` — integration tests run against a live S3/MinIO server (`FunctionalTest`, `TestMinioAdminClient`, etc.). Not published; sources live in `functional/` (not `src/`). +- `examples` — runnable usage examples. Sources live in `examples/` (not `src/`). + +## Commands + +```sh +./gradlew build # compile, run unit tests, spotless check, spotbugs — run before every PR +./gradlew spotlessApply # auto-format (google-java-format); fixes most lint failures +./gradlew :api:test # unit tests for one module +./gradlew :api:test --tests 'io.minio.MinioClientTest' # single test class +./gradlew :api:test --tests 'io.minio.MakeBucketArgsTest.method' # single test method +./gradlew :api:localeTest # re-runs tests under locale de-DE (part of `check`) +./gradlew runFunctionalTest # integration tests vs play.min.io by default +./gradlew runFunctionalTest -Pendpoint=http://localhost:9000 -PaccessKey=... -PsecretKey=... -Pregion=us-east-1 +``` + +Unit tests are JUnit 5 (Jupiter) in `*/src/test/java`. `functional/` tests hit a real server and are not part of `build`. + +## Build / lint gates (enforced in CI, fail the build) + +- **`--release 8` with `-Werror`** and `-Xlint:unchecked,deprecation`. Any new warning breaks the build. Do not use APIs newer than Java 8 in `api`/`adminapi` main code. +- **Spotless** (`googleJavaFormat`, import order `edu,com,io,java,javax,org`, no unused imports). Run `./gradlew spotlessApply` before committing. +- **SpotBugs** at MAX effort / lowest confidence threshold; suppressions go in `spotbugs-filter.xml`. +- **No `com.google.common.base.Objects`** — CI greps for it; use `java.util.Objects` instead. (Guava is otherwise available.) + +## Architecture + +**Client layering** (`api/src/main/java/io/minio/`): +- `BaseS3Client` (abstract) — HTTP plumbing, request signing, response handling. +- `MinioAsyncClient extends BaseS3Client` — the real implementation; every operation returns `CompletableFuture`. +- `MinioClient` — synchronous facade that **wraps a `MinioAsyncClient`** and blocks on its futures. It does not extend the async client. A new sync operation almost always means adding the async method first, then a blocking wrapper. +- Both are built via a `.builder()...build()` (endpoint, credentials, region, http client). + +**Args pattern** — every public operation takes one immutable `XxxArgs` object built via `XxxArgs.builder()`. The class hierarchy (see `arg-class-structure.txt`) is: +`BaseArgs` → `BucketArgs` (adds bucket/region) → `ObjectArgs` (adds object) → either `ObjectWriteArgs` (uploads: sse, tags, retention, ...) or `ObjectVersionArgs` → `ObjectReadArgs` (ssec) → `ObjectConditionalReadArgs` (offset/length/etag/since). When adding an operation, subclass the right level rather than re-declaring fields. + +**Other key packages:** +- `io.minio.messages` — S3 XML request/response models, (de)serialized with simple-xml-safe. +- `io.minio.credentials` — credential `Provider`s (static, env, IAM, assume-role, LDAP, web-identity, certificate, chained). +- `io.minio.errors` — exception hierarchy rooted at `MinioException`. +- `Http`, `Signer` — OkHttp transport and AWS SigV4 signing. + +## Conventions + +- Public-facing API changes should be reflected in `docs/API.md`. +- Releases are driven by JReleaser (`build.sh` shows the deploy invocation); the `version` lives in `build.gradle` (`-DEV` suffix unless `-Prelease`). +- Target branch for PRs is `master`. diff --git a/adminapi/src/main/java/io/minio/admin/Crypto.java b/adminapi/src/main/java/io/minio/admin/Crypto.java index 8902e6d74..46065e5a6 100644 --- a/adminapi/src/main/java/io/minio/admin/Crypto.java +++ b/adminapi/src/main/java/io/minio/admin/Crypto.java @@ -209,7 +209,9 @@ public static byte[] encrypt(byte[] payload, String password) throws MinioExcept boolean done = false; for (int nonceId = 1; !done; nonceId++) { int to = from + CHUNK_SIZE; - if (to > payload.length) { + // Use >= so a payload that is an exact multiple of CHUNK_SIZE marks its final full chunk as + // the last one, rather than emitting an extra empty trailing chunk (matches madmin-go/sio). + if (to >= payload.length) { additionalData = markAsLast(additionalData); to = payload.length; done = true; diff --git a/adminapi/src/main/java/io/minio/admin/GetDataUsageInfoResponse.java b/adminapi/src/main/java/io/minio/admin/GetDataUsageInfoResponse.java index 0bf3a1b5f..a3aa8dd67 100644 --- a/adminapi/src/main/java/io/minio/admin/GetDataUsageInfoResponse.java +++ b/adminapi/src/main/java/io/minio/admin/GetDataUsageInfoResponse.java @@ -75,7 +75,9 @@ public long objectsTotalSize() { } public Map objectsReplicationInfo() { - return Collections.unmodifiableMap(this.objectsReplicationInfo); + return this.objectsReplicationInfo == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.objectsReplicationInfo); } public long bucketsCount() { @@ -83,11 +85,15 @@ public long bucketsCount() { } public Map bucketsUsageInfo() { - return Collections.unmodifiableMap(this.bucketsUsageInfo); + return this.bucketsUsageInfo == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.bucketsUsageInfo); } public Map bucketsSizes() { - return Collections.unmodifiableMap(bucketsSizes); + return bucketsSizes == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(bucketsSizes); } public AllTierStats tierStats() { @@ -203,7 +209,9 @@ public long objectsCount() { } public Map objectsSizesHistogram() { - return Collections.unmodifiableMap(this.objectsSizesHistogram); + return this.objectsSizesHistogram == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.objectsSizesHistogram); } public long versionsCount() { @@ -215,7 +223,9 @@ public long objectReplicaTotalSize() { } public Map objectsReplicationInfo() { - return Collections.unmodifiableMap(this.objectsReplicationInfo); + return this.objectsReplicationInfo == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.objectsReplicationInfo); } } @@ -249,7 +259,7 @@ public static class AllTierStats { private Map tiers; public Map tiers() { - return Collections.unmodifiableMap(this.tiers); + return this.tiers == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.tiers); } } } diff --git a/adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java b/adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java index e7ddef460..187f46b9d 100644 --- a/adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java +++ b/adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java @@ -289,7 +289,9 @@ public String commitID() { } public Map network() { - return Collections.unmodifiableMap(this.network); + return this.network == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.network); } public List disks() { @@ -321,7 +323,9 @@ public GCStats gCStats() { } public Map minioEnvVars() { - return Collections.unmodifiableMap(this.minioEnvVars); + return this.minioEnvVars == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(this.minioEnvVars); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -613,11 +617,13 @@ public Integer totalErrorsTimeout() { } public Map lastMinute() { - return Collections.unmodifiableMap(lastMinute); + return lastMinute == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(lastMinute); } public Map apiCalls() { - return Collections.unmodifiableMap(apiCalls); + return apiCalls == null ? Collections.emptyMap() : Collections.unmodifiableMap(apiCalls); } public Long totalTokens() { diff --git a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java index e6e702e39..e9a82887f 100644 --- a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java +++ b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java @@ -167,10 +167,12 @@ private OkHttpClient getHttpClient(PrintWriter traceStream) { } private Response httpExecute( - Http.Method method, Command command, Multimap queryParamMap, byte[] body) + Http.Method method, + Command command, + Multimap queryParamMap, + byte[] body, + Credentials creds) throws IOException, MinioException { - Credentials creds = getCredentials(); - HttpUrl.Builder urlBuilder = this.baseUrl .newBuilder() @@ -224,7 +226,21 @@ private Response execute( Http.Method method, Command command, Multimap queryParamMap, byte[] body) throws MinioException { try { - return httpExecute(method, command, queryParamMap, body); + return httpExecute(method, command, queryParamMap, body, getCredentials()); + } catch (IOException e) { + throw new MinioException(e); + } + } + + private Response execute( + Http.Method method, + Command command, + Multimap queryParamMap, + byte[] body, + Credentials creds) + throws MinioException { + try { + return httpExecute(method, command, queryParamMap, body, creds); } catch (IOException e) { throw new MinioException(e); } @@ -258,7 +274,8 @@ public void addUser( Http.Method.PUT, Command.ADD_USER, ImmutableMultimap.of("accessKey", accessKey), - Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()))) { + Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()), + creds)) { } catch (JsonProcessingException e) { throw new MinioException(e); } @@ -292,8 +309,8 @@ public UserInfo getUserInfo(String accessKey) throws MinioException { * @throws MinioException thrown to indicate SDK exception. */ public Map listUsers() throws MinioException { - try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null)) { - Credentials creds = getCredentials(); + Credentials creds = getCredentials(); + try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null, creds)) { byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey()); MapType mapType = OBJECT_MAPPER @@ -459,8 +476,8 @@ public long getBucketQuota(String bucketName) throws MinioException { .stream() .filter(entry -> "quota".equals(entry.getKey())) .findFirst() - .map(entry -> Long.valueOf(entry.getValue().toString())) - .orElseThrow(() -> new IllegalArgumentException("found not quota")); + .map(entry -> entry.getValue().asLong()) + .orElseThrow(() -> new IllegalArgumentException("quota not found in response")); } catch (IOException e) { throw new MinioException(e); } @@ -676,7 +693,8 @@ public Credentials addServiceAccount( Http.Method.PUT, Command.ADD_SERVICE_ACCOUNT, null, - Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) { + Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()), + creds)) { byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey()); return OBJECT_MAPPER.readValue(jsonData, AddServiceAccountResponse.class).credentials(); } catch (JsonProcessingException e) { @@ -691,7 +709,7 @@ public Credentials addServiceAccount( * * @param accessKey Access key. * @param newSecretKey New secret key. - * @param newPolicy New policy as JSON string . + * @param newPolicy New policy as JSON string. * @param newStatus New service account status. * @param newName New service account name. * @param newDescription New description. @@ -702,7 +720,7 @@ public void updateServiceAccount( @Nonnull String accessKey, @Nullable String newSecretKey, @Nullable Map newPolicy, - @Nullable boolean newStatus, + @Nullable Boolean newStatus, @Nullable String newName, @Nullable String newDescription, @Nullable ZonedDateTime newExpiration) @@ -724,7 +742,7 @@ public void updateServiceAccount( serviceAccount.put("newSecretKey", newSecretKey); } if (newPolicy != null && !newPolicy.isEmpty()) serviceAccount.put("newPolicy", newPolicy); - serviceAccount.put("newStatus", newStatus ? "on" : "off"); + if (newStatus != null) serviceAccount.put("newStatus", newStatus ? "on" : "off"); if (newName != null && !newName.isEmpty()) serviceAccount.put("newName", newName); if (newDescription != null && !newDescription.isEmpty()) { serviceAccount.put("newDescription", newDescription); @@ -739,7 +757,8 @@ public void updateServiceAccount( Http.Method.POST, Command.UPDATE_SERVICE_ACCOUNT, ImmutableMultimap.of("accessKey", accessKey), - Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) { + Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()), + creds)) { } catch (JsonProcessingException e) { throw new MinioException(e); } @@ -777,13 +796,14 @@ public ListServiceAccountResponse listServiceAccount(@Nonnull String username) throw new IllegalArgumentException("user name must be provided"); } + Credentials creds = getCredentials(); try (Response response = execute( Http.Method.GET, Command.LIST_SERVICE_ACCOUNTS, ImmutableMultimap.of("user", username), - null)) { - Credentials creds = getCredentials(); + null, + creds)) { byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey()); return OBJECT_MAPPER.readValue(jsonData, ListServiceAccountResponse.class); } catch (IOException e) { @@ -804,13 +824,14 @@ public GetServiceAccountInfoResponse getServiceAccountInfo(@Nonnull String acces if (accessKey == null || accessKey.isEmpty()) { throw new IllegalArgumentException("access key must be provided"); } + Credentials creds = getCredentials(); try (Response response = execute( Http.Method.GET, Command.INFO_SERVICE_ACCOUNT, ImmutableMultimap.of("accessKey", accessKey), - null)) { - Credentials creds = getCredentials(); + null, + creds)) { byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey()); return OBJECT_MAPPER.readValue(jsonData, GetServiceAccountInfoResponse.class); } catch (IOException e) { @@ -824,7 +845,7 @@ private PolicyAssociationResponse attachDetachPolicy( @Nullable String user, @Nullable String group) throws MinioException { - if (!(user != null ^ group != null)) { + if (!Utils.xor(user, group)) { throw new IllegalArgumentException("either user or group must be provided"); } @@ -842,7 +863,8 @@ private PolicyAssociationResponse attachDetachPolicy( Http.Method.POST, command, null, - Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()))) { + Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()), + creds)) { return OBJECT_MAPPER.readValue( Crypto.decrypt(response.body().byteStream(), creds.secretKey()), PolicyAssociationResponse.class); diff --git a/adminapi/src/main/java/io/minio/admin/Status.java b/adminapi/src/main/java/io/minio/admin/Status.java index eb1d5026a..a2e403cd5 100644 --- a/adminapi/src/main/java/io/minio/admin/Status.java +++ b/adminapi/src/main/java/io/minio/admin/Status.java @@ -45,7 +45,7 @@ public static Status fromString(String statusString) { return DISABLED; } - if (statusString.isEmpty()) { + if (statusString == null || statusString.isEmpty()) { return null; } diff --git a/api/src/main/java/io/minio/AppendObjectArgs.java b/api/src/main/java/io/minio/AppendObjectArgs.java index 250af4b57..e60a84bcd 100644 --- a/api/src/main/java/io/minio/AppendObjectArgs.java +++ b/api/src/main/java/io/minio/AppendObjectArgs.java @@ -135,6 +135,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), filename, stream, data, length, chunkSize); + return Objects.hash( + super.hashCode(), filename, stream, Arrays.hashCode(data), length, chunkSize); } } diff --git a/api/src/main/java/io/minio/BaseS3Client.java b/api/src/main/java/io/minio/BaseS3Client.java index 586fe548e..f6103727a 100644 --- a/api/src/main/java/io/minio/BaseS3Client.java +++ b/api/src/main/java/io/minio/BaseS3Client.java @@ -406,12 +406,13 @@ private void onResponse(final Response response) throws IOException { if (!s3request.method().equals(Http.Method.HEAD) && (contentType == null || !Arrays.asList(contentType.split(";")).contains("application/xml"))) { - if (response.code() == 304 && response.body().contentLength() == 0) { + if (response.code() == 304 && errorXml.isEmpty()) { completableFuture.completeExceptionally( new ServerException( "server failed with HTTP status code " + response.code(), response.code(), traceBuilder.toString())); + return; } completableFuture.completeExceptionally( @@ -475,8 +476,8 @@ private void onResponse(final Response response) throws IOException { break; case 409: if (s3request.bucket() != null) { - code = NO_SUCH_BUCKET; - message = NO_SUCH_BUCKET_MESSAGE; + code = "Conflict"; + message = "Bucket not empty"; } else { code = "ResourceConflict"; message = "Request resource conflicts"; diff --git a/api/src/main/java/io/minio/Checksum.java b/api/src/main/java/io/minio/Checksum.java index d1d15cb90..a7dd44960 100644 --- a/api/src/main/java/io/minio/Checksum.java +++ b/api/src/main/java/io/minio/Checksum.java @@ -343,12 +343,13 @@ public CRC64NVME() {} @Override public void update(byte[] p, int off, int len) { + int limit = off + len; java.nio.ByteBuffer byteBuffer = java.nio.ByteBuffer.wrap(p, off, len); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); int offset = byteBuffer.position(); crc = ~crc; - while (p.length >= 64 && (p.length - offset) > 8) { + while (len >= 64 && (limit - offset) > 8) { long value = byteBuffer.getLong(); crc ^= value; crc = @@ -363,7 +364,7 @@ public void update(byte[] p, int off, int len) { offset = byteBuffer.position(); } - for (; offset < len; offset++) { + for (; offset < limit; offset++) { crc = CRC64_TABLE[(int) ((crc ^ (long) p[offset]) & 0xFF)] ^ (crc >>> 8); } diff --git a/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java b/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java index abf6dce31..8c56f9bf5 100644 --- a/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java +++ b/api/src/main/java/io/minio/CompleteMultipartUploadArgs.java @@ -132,6 +132,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), uploadId, parts, ssec, delayMs, maxRetries); + return Objects.hash( + super.hashCode(), uploadId, Arrays.hashCode(parts), ssec, delayMs, maxRetries); } } diff --git a/api/src/main/java/io/minio/GetObjectAttributesArgs.java b/api/src/main/java/io/minio/GetObjectAttributesArgs.java index b414bc64a..34657e21c 100644 --- a/api/src/main/java/io/minio/GetObjectAttributesArgs.java +++ b/api/src/main/java/io/minio/GetObjectAttributesArgs.java @@ -68,11 +68,17 @@ public Builder objectAttributes(List objectAttributes) { } public Builder maxParts(Integer maxParts) { + if (maxParts != null && maxParts < 1) { + throw new IllegalArgumentException("valid max parts must be provided"); + } operations.add(args -> args.maxParts = maxParts); return this; } public Builder partNumberMarker(Integer partNumberMarker) { + if (partNumberMarker != null && partNumberMarker < 1) { + throw new IllegalArgumentException("valid part number marker must be provided"); + } operations.add(args -> args.partNumberMarker = partNumberMarker); return this; } diff --git a/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java b/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java index ea221ea37..82ece9193 100644 --- a/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java +++ b/api/src/main/java/io/minio/GetPresignedObjectUrlArgs.java @@ -49,7 +49,7 @@ private void validateMethod(Http.Method method) { Utils.validateNotNull(method, "method"); } - private void validateExpiry(int expiry) { + private void validateExpiry(long expiry) { if (expiry < 1 || expiry > DEFAULT_EXPIRY_TIME) { throw new IllegalArgumentException( "expiry must be minimum 1 second to maximum " @@ -73,7 +73,11 @@ public Builder expiry(int expiry) { } public Builder expiry(int duration, TimeUnit unit) { - return expiry((int) unit.toSeconds(duration)); + // Validate the full-precision seconds before narrowing to int, otherwise a large duration + // could overflow into the valid range and pass the check with a truncated value. + long seconds = unit.toSeconds(duration); + validateExpiry(seconds); + return expiry((int) seconds); } @Override diff --git a/api/src/main/java/io/minio/Http.java b/api/src/main/java/io/minio/Http.java index e6b6ac669..d4f8f37e4 100644 --- a/api/src/main/java/io/minio/Http.java +++ b/api/src/main/java/io/minio/Http.java @@ -156,7 +156,7 @@ private void setAwsInfo(String host, boolean https) { if (!Utils.HOSTNAME_REGEX.matcher(host).find()) return; if (Utils.AWS_ELB_ENDPOINT_REGEX.matcher(host).find()) { - String[] tokens = host.split("\\.elb\\.amazonaws\\.com", 1)[0].split("\\."); + String[] tokens = host.split("\\.elb\\.amazonaws\\.com")[0].split("\\."); this.region = tokens[tokens.length - 1]; return; } diff --git a/api/src/main/java/io/minio/ListObjectVersionsArgs.java b/api/src/main/java/io/minio/ListObjectVersionsArgs.java index 3309a01d0..a6c895f2d 100644 --- a/api/src/main/java/io/minio/ListObjectVersionsArgs.java +++ b/api/src/main/java/io/minio/ListObjectVersionsArgs.java @@ -51,7 +51,7 @@ public String encodingType() { } public int maxKeys() { - return maxKeys; + return maxKeys != null ? maxKeys : 1000; } public String prefix() { diff --git a/api/src/main/java/io/minio/ListObjectsV1Args.java b/api/src/main/java/io/minio/ListObjectsV1Args.java index 9e086a787..9fc56e2c7 100644 --- a/api/src/main/java/io/minio/ListObjectsV1Args.java +++ b/api/src/main/java/io/minio/ListObjectsV1Args.java @@ -49,7 +49,7 @@ public String encodingType() { } public int maxKeys() { - return maxKeys; + return maxKeys != null ? maxKeys : 1000; } public String prefix() { diff --git a/api/src/main/java/io/minio/ListObjectsV2Args.java b/api/src/main/java/io/minio/ListObjectsV2Args.java index 9806ce7ee..abdcc2702 100644 --- a/api/src/main/java/io/minio/ListObjectsV2Args.java +++ b/api/src/main/java/io/minio/ListObjectsV2Args.java @@ -55,7 +55,7 @@ public String encodingType() { } public int maxKeys() { - return maxKeys; + return maxKeys != null ? maxKeys : 1000; } public String prefix() { diff --git a/api/src/main/java/io/minio/ListPartsArgs.java b/api/src/main/java/io/minio/ListPartsArgs.java index dff97394d..48a8fb390 100644 --- a/api/src/main/java/io/minio/ListPartsArgs.java +++ b/api/src/main/java/io/minio/ListPartsArgs.java @@ -41,7 +41,7 @@ public static Builder builder() { } /** Builder of {@link ListPartsArgs}. */ - public static final class Builder extends BucketArgs.Builder { + public static final class Builder extends ObjectArgs.Builder { public Builder uploadId(String uploadId) { Utils.validateNotEmptyString(uploadId, "upload ID"); operations.add(args -> args.uploadId = uploadId); diff --git a/api/src/main/java/io/minio/ListenBucketNotificationArgs.java b/api/src/main/java/io/minio/ListenBucketNotificationArgs.java index a56e86a3f..e5bb55364 100644 --- a/api/src/main/java/io/minio/ListenBucketNotificationArgs.java +++ b/api/src/main/java/io/minio/ListenBucketNotificationArgs.java @@ -52,9 +52,7 @@ private void validateEvents(String[] events) { } protected void validate(ListenBucketNotificationArgs args) { - if (args.bucketName != null) { - super.validate(args); - } + super.validate(args); validateEvents(args.events); } diff --git a/api/src/main/java/io/minio/MinioAsyncClient.java b/api/src/main/java/io/minio/MinioAsyncClient.java index 1e865626f..cb2f6bc4b 100644 --- a/api/src/main/java/io/minio/MinioAsyncClient.java +++ b/api/src/main/java/io/minio/MinioAsyncClient.java @@ -94,7 +94,10 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -343,11 +346,12 @@ private void downloadObject( GetObjectResponse getObjectResponse) throws MinioException { OutputStream os = null; + Path tempFilePath = null; try { Path filePath = Paths.get(filename); String tempFilename = filename + "." + Utils.encode(headObjectResponse.etag()) + ".part.minio"; - Path tempFilePath = Paths.get(tempFilename); + tempFilePath = Paths.get(tempFilename); if (Files.exists(tempFilePath)) Files.delete(tempFilePath); os = Files.newOutputStream(tempFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE); long bytesWritten = ByteStreams.copy(getObjectResponse, os); @@ -373,6 +377,17 @@ private void downloadObject( if (os != null) os.close(); } catch (IOException e) { throw new MinioException(e); + } finally { + // Remove the partially-written temp file if it was not moved to its destination. + if (tempFilePath != null) { + try { + Files.deleteIfExists(tempFilePath); + } catch (IOException e) { + // best-effort cleanup; log and continue. + Logger.getLogger(MinioAsyncClient.class.getName()) + .log(Level.WARNING, "failed to delete temporary file " + tempFilePath, e); + } + } } } } @@ -408,7 +423,7 @@ public CompletableFuture downloadObject(DownloadObjectArgs args) { downloadObject(filename, args.overwrite(), headObjectResponse, getObjectResponse); return null; } catch (MinioException e) { - return Utils.failedFuture(e); + throw new CompletionException(e); } }) .thenAccept(nullValue -> {}); @@ -728,7 +743,8 @@ private CompletableFuture uploadParts( final int finalPartNumber = partNumber; future = future.thenCombine( - uploadPartCopy(new UploadPartCopyArgs(args, uploadId, finalPartNumber, headers)), + uploadPartCopy( + new UploadPartCopyArgs(args, uploadId, finalPartNumber, finalHeaders)), (parts, response) -> { parts[response.partNumber() - 1] = response.part(); return parts; @@ -1861,13 +1877,23 @@ private CompletableFuture> uploadPartsParallelly( while (!errorOccurred.get()) { UploadPartArgs.Wrapper part = queue.take(); if (part.args() == null) break; // poison pill - UploadPartResponse response = uploadPart(part.args()).join(); - bufferPool.put(part.args().buffer()); - uploadResults.add(response); + try { + UploadPartResponse response = uploadPart(part.args()).join(); + uploadResults.add(response); + } finally { + // Always return the buffer to the pool, even on upload failure. + bufferPool.put(part.args().buffer()); + } } } catch (InterruptedException e) { + Thread.currentThread().interrupt(); errorOccurred.set(true); // signal to all threads exceptions.add(e); + } catch (RuntimeException e) { + // uploadPart().join() failures surface as CompletionException here; + // signal other threads and the reader so the upload aborts cleanly. + errorOccurred.set(true); + exceptions.add(e); } finally { doneLatch.countDown(); } @@ -1879,7 +1905,9 @@ private CompletableFuture> uploadPartsParallelly( } // Reader: submit initial buffer - queue.put( + offerUntilDoneOrError( + queue, + errorOccurred, new UploadPartArgs.Wrapper( new UploadPartArgs( args, @@ -1893,7 +1921,9 @@ private CompletableFuture> uploadPartsParallelly( while (partReader.partNumber() != partReader.partCount() && !errorOccurred.get()) { ByteBuffer buf = bufferPool.take(); partReader.read(buf); - queue.put( + offerUntilDoneOrError( + queue, + errorOccurred, new UploadPartArgs.Wrapper( new UploadPartArgs( args, @@ -1906,7 +1936,7 @@ private CompletableFuture> uploadPartsParallelly( // Signal all workers to stop with poison pills for (int i = 0; i < parallelUploads; i++) { - queue.put(new UploadPartArgs.Wrapper(null)); + offerUntilDoneOrError(queue, errorOccurred, new UploadPartArgs.Wrapper(null)); } doneLatch.await(); @@ -1930,6 +1960,20 @@ private CompletableFuture> uploadPartsParallelly( }); } + /** + * Offers an item to the queue, retrying until it is accepted or an error has been signalled by a + * worker. Prevents the reader from blocking forever on a full queue once all workers have died. + */ + private static void offerUntilDoneOrError( + BlockingQueue queue, + AtomicBoolean errorOccurred, + UploadPartArgs.Wrapper item) + throws InterruptedException { + while (!queue.offer(item, 200, TimeUnit.MILLISECONDS)) { + if (errorOccurred.get()) return; + } + } + private CompletableFuture putObject( PutObjectBaseArgs args, Object fileStreamData, @@ -3319,11 +3363,32 @@ public CompletableFuture uploadSnowballObjects( throw new IllegalArgumentException( "tarball size " + length + " is more than maximum allowed 5TiB"); } - try (RandomAccessFile file = new RandomAccessFile(args.stagingFilename(), "r")) { - return putObject(new PutObjectAPIArgs(args, file, length, headers)); + final RandomAccessFile file; + try { + file = new RandomAccessFile(args.stagingFilename(), "r"); } catch (IOException e) { throw new CompletionException(new MinioException(e)); } + return putObject(new PutObjectAPIArgs(args, file, length, headers)) + .exceptionally( + e -> { + e = e.getCause(); + try { + file.close(); + } catch (IOException ex) { + e.addSuppressed(new MinioException(ex)); + } + throw new CompletionException(e); + }) + .thenApply( + response -> { + try { + file.close(); + } catch (IOException e) { + throw new CompletionException(new MinioException(e)); + } + return response; + }); }); } @@ -3666,19 +3731,38 @@ public CompletableFuture appendObject(AppendObjectArgs args addSha256Checksum); } - RandomAccessFile file = new RandomAccessFile(args.filename(), "r"); + final RandomAccessFile file = new RandomAccessFile(args.filename(), "r"); return appendObject( - args, - writeOffset, - null, - null, - null, - args.length(), - file, - partSize, - hashers, - addContentSha256, - addSha256Checksum); + args, + writeOffset, + null, + null, + null, + args.length(), + file, + partSize, + hashers, + addContentSha256, + addSha256Checksum) + .exceptionally( + e -> { + e = e.getCause(); + try { + file.close(); + } catch (IOException ex) { + e.addSuppressed(new MinioException(ex)); + } + throw new CompletionException(e); + }) + .thenApply( + resp -> { + try { + file.close(); + } catch (IOException e) { + throw new CompletionException(new MinioException(e)); + } + return resp; + }); } catch (MinioException e) { return Utils.failedFuture(e); } catch (IOException e) { diff --git a/api/src/main/java/io/minio/PromptObjectArgs.java b/api/src/main/java/io/minio/PromptObjectArgs.java index 90dbf9ad9..a1296fb64 100644 --- a/api/src/main/java/io/minio/PromptObjectArgs.java +++ b/api/src/main/java/io/minio/PromptObjectArgs.java @@ -56,7 +56,7 @@ protected void validate(PromptObjectArgs args) { Utils.validateNotNull(args.promptArgs, "prompt argument"); } - public Builder offset(String prompt) { + public Builder prompt(String prompt) { Utils.validateNotEmptyString(prompt, "prompt"); operations.add(args -> args.prompt = prompt); return this; diff --git a/api/src/main/java/io/minio/PutObjectAPIBaseArgs.java b/api/src/main/java/io/minio/PutObjectAPIBaseArgs.java index a1217b493..e873ad7ac 100644 --- a/api/src/main/java/io/minio/PutObjectAPIBaseArgs.java +++ b/api/src/main/java/io/minio/PutObjectAPIBaseArgs.java @@ -120,8 +120,7 @@ public abstract static class Builder, A extends PutObjec extends ObjectArgs.Builder { protected void validate(A args) { super.validate(args); - if (!((args.file != null) != (args.buffer != null) != (args.data != null) - && !(args.file != null && args.buffer != null && args.data != null))) { + if (!Utils.xor(args.file, args.buffer, args.data)) { throw new IllegalArgumentException("only one of file, buffer or data must be provided"); } } @@ -172,6 +171,6 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), file, buffer, data, length, headers); + return Objects.hash(super.hashCode(), file, buffer, Arrays.hashCode(data), length, headers); } } diff --git a/api/src/main/java/io/minio/PutObjectArgs.java b/api/src/main/java/io/minio/PutObjectArgs.java index d870c8beb..3c318d65c 100644 --- a/api/src/main/java/io/minio/PutObjectArgs.java +++ b/api/src/main/java/io/minio/PutObjectArgs.java @@ -107,6 +107,6 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), stream, data); + return Objects.hash(super.hashCode(), stream, Arrays.hashCode(data)); } } diff --git a/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java b/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java index bf606f2ab..9441df170 100644 --- a/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java +++ b/api/src/main/java/io/minio/UploadSnowballObjectsArgs.java @@ -31,6 +31,12 @@ public class UploadSnowballObjectsArgs extends ObjectWriteArgs { private String stagingFilename; private boolean compression; + public UploadSnowballObjectsArgs() { + // The snowball tarball is uploaded under a transient, auto-generated object key; generate it + // here (once per instance) rather than mutating state inside validate(). + this.objectName = "snowball." + random.nextLong() + ".tar"; + } + public Iterable objects() { return this.objects; } @@ -56,7 +62,6 @@ private void validateObjects(Iterable objects) { @Override protected void validate(UploadSnowballObjectsArgs args) { - args.objectName = "snowball." + random.nextLong() + ".tar"; validateObjects(args.objects); super.validate(args); } diff --git a/api/src/main/java/io/minio/Utils.java b/api/src/main/java/io/minio/Utils.java index 1db11b43a..37fbd4b51 100644 --- a/api/src/main/java/io/minio/Utils.java +++ b/api/src/main/java/io/minio/Utils.java @@ -96,6 +96,15 @@ public static void validateNullOrNotEmptyString(String arg, String argName) { } } + /** Returns true if and only if exactly one of the given objects is non-null. */ + public static boolean xor(Object... objects) { + int count = 0; + for (Object object : objects) { + if (object != null && ++count > 1) return false; + } + return count == 1; + } + public static boolean isValidIPv4OrIPv6(String value) { return InetAddressValidator.getInstance().isValid(value); } diff --git a/api/src/main/java/io/minio/credentials/AwsConfigProvider.java b/api/src/main/java/io/minio/credentials/AwsConfigProvider.java index 0eb95c556..f886fb0d6 100644 --- a/api/src/main/java/io/minio/credentials/AwsConfigProvider.java +++ b/api/src/main/java/io/minio/credentials/AwsConfigProvider.java @@ -110,6 +110,8 @@ public Object put(Object key, Object value) { section = new Properties(); return result.put(header.substring(1, header.length() - 1), section); } + // Ignore key-value entries that appear before any [section] header. + if (section == null) return null; return section.put(key, value); } diff --git a/api/src/main/java/io/minio/credentials/MinioClientConfigProvider.java b/api/src/main/java/io/minio/credentials/MinioClientConfigProvider.java index 1c681d7c1..ba3101608 100644 --- a/api/src/main/java/io/minio/credentials/MinioClientConfigProvider.java +++ b/api/src/main/java/io/minio/credentials/MinioClientConfigProvider.java @@ -113,7 +113,7 @@ public static class Config { private Map> hosts; public Map get(String alias) { - return hosts.get(alias); + return hosts == null ? null : hosts.get(alias); } } } diff --git a/api/src/main/java/io/minio/credentials/MinioEnvironmentProvider.java b/api/src/main/java/io/minio/credentials/MinioEnvironmentProvider.java index 82df00ab4..5c16c4424 100644 --- a/api/src/main/java/io/minio/credentials/MinioEnvironmentProvider.java +++ b/api/src/main/java/io/minio/credentials/MinioEnvironmentProvider.java @@ -16,11 +16,24 @@ package io.minio.credentials; +import java.security.ProviderException; + /** Credential provider using MinIO server specific environment variables. */ public class MinioEnvironmentProvider extends EnvironmentProvider { @Override public Credentials fetch() { - return new Credentials( - getProperty("MINIO_ACCESS_KEY"), getProperty("MINIO_SECRET_KEY"), null, null); + String accessKey = getProperty("MINIO_ACCESS_KEY"); + if (accessKey == null) { + throw new ProviderException( + "Access key does not exist in MINIO_ACCESS_KEY environment variable"); + } + + String secretKey = getProperty("MINIO_SECRET_KEY"); + if (secretKey == null) { + throw new ProviderException( + "Secret key does not exist in MINIO_SECRET_KEY environment variable"); + } + + return new Credentials(accessKey, secretKey, null, null); } } diff --git a/api/src/main/java/io/minio/messages/AccessControlPolicy.java b/api/src/main/java/io/minio/messages/AccessControlPolicy.java index 6853bfb1e..aca599bfc 100644 --- a/api/src/main/java/io/minio/messages/AccessControlPolicy.java +++ b/api/src/main/java/io/minio/messages/AccessControlPolicy.java @@ -72,7 +72,8 @@ public String cannedAcl() { return "authenticated-read"; } if ("http://acs.amazonaws.com/groups/global/AllUsers".equals(uri)) return "public-read"; - if (owner.id() != null + if (owner != null + && owner.id() != null && grant.granteeId() != null && owner.id().equals(grant.granteeId())) { return "bucket-owner-read"; diff --git a/api/src/main/java/io/minio/messages/Checksum.java b/api/src/main/java/io/minio/messages/Checksum.java index d8b09c429..97fd1348a 100644 --- a/api/src/main/java/io/minio/messages/Checksum.java +++ b/api/src/main/java/io/minio/messages/Checksum.java @@ -95,7 +95,7 @@ public String checksumType() { private void addHeader(Http.Headers headers, String algorithm, String value) { if (value == null || value.isEmpty()) return; - headers.put("x-amz-checksum-algorithm-" + algorithm, value); + headers.put("x-amz-checksum-" + algorithm, value); headers.put("x-amz-checksum-algorithm", algorithm); } diff --git a/api/src/main/java/io/minio/messages/Filter.java b/api/src/main/java/io/minio/messages/Filter.java index 079ffa83f..510e87dac 100644 --- a/api/src/main/java/io/minio/messages/Filter.java +++ b/api/src/main/java/io/minio/messages/Filter.java @@ -69,7 +69,7 @@ public Filter( @Nullable @Element(name = "ObjectSizeLessThan", required = false) Long objectSizeLessThan, @Nullable @Element(name = "ObjectSizeGreaterThan", required = false) Long objectSizeGreaterThan) { - if (andOperator != null ^ prefix != null ^ tag != null) { + if (Utils.xor(andOperator, prefix, tag)) { this.andOperator = andOperator; this.prefix = prefix; this.tag = tag; @@ -156,9 +156,9 @@ public And( @Nullable Long objectSizeGreaterThan) { List tagList = null; if (tags != null) { - this.tags = new ArrayList<>(); + tagList = new ArrayList<>(); for (Map.Entry entry : tags.entrySet()) { - this.tags.add(new Tag(entry.getKey(), entry.getValue())); + tagList.add(new Tag(entry.getKey(), entry.getValue())); } } set(prefix, tagList, objectSizeLessThan, objectSizeGreaterThan); diff --git a/api/src/main/java/io/minio/messages/LifecycleConfiguration.java b/api/src/main/java/io/minio/messages/LifecycleConfiguration.java index e5694db3e..48de6f6fd 100644 --- a/api/src/main/java/io/minio/messages/LifecycleConfiguration.java +++ b/api/src/main/java/io/minio/messages/LifecycleConfiguration.java @@ -244,7 +244,7 @@ public Expiration( throw new IllegalArgumentException( "ExpiredObjectDeleteMarker must not be provided along with Date and Days"); } - } else if (date != null ^ days != null) { + } else if (Utils.xor(date, days)) { this.date = date; this.days = days; } else { @@ -358,7 +358,7 @@ public Transition( @Nullable @Element(name = "Date", required = false) Time.S3Time date, @Nullable @Element(name = "Days", required = false) Integer days, @Nullable @Element(name = "StorageClass", required = false) String storageClass) { - if (date != null ^ days != null) { + if (Utils.xor(date, days)) { this.date = date; this.days = days; } else { diff --git a/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java b/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java index 2e10682fa..5d9869e57 100644 --- a/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java +++ b/api/src/main/java/io/minio/messages/ObjectLockConfiguration.java @@ -139,7 +139,7 @@ public RetentionDurationUnit unit() { } public int duration() { - return days; + return days == null ? 0 : days; } @Override @@ -165,7 +165,7 @@ public RetentionDurationUnit unit() { } public int duration() { - return years; + return years == null ? 0 : years; } @Override diff --git a/api/src/main/java/io/minio/messages/ReplicationConfiguration.java b/api/src/main/java/io/minio/messages/ReplicationConfiguration.java index 066aa177c..eb17428cd 100644 --- a/api/src/main/java/io/minio/messages/ReplicationConfiguration.java +++ b/api/src/main/java/io/minio/messages/ReplicationConfiguration.java @@ -129,7 +129,7 @@ public Rule( id = id.trim(); if (id.isEmpty()) throw new IllegalArgumentException("ID must be non-empty string"); if (id.length() > 255) - throw new IllegalArgumentException("ID must be exceed 255 characters"); + throw new IllegalArgumentException("ID must not exceed 255 characters"); } this.status = Objects.requireNonNull(status, "Status must not be null"); diff --git a/api/src/main/java/io/minio/messages/VersioningConfiguration.java b/api/src/main/java/io/minio/messages/VersioningConfiguration.java index 8358b9834..52db0068f 100644 --- a/api/src/main/java/io/minio/messages/VersioningConfiguration.java +++ b/api/src/main/java/io/minio/messages/VersioningConfiguration.java @@ -90,8 +90,8 @@ public List excludedPrefixes() { return excludedPrefixes; } - public Boolean excludeFolders() { - return excludeFolders; + public boolean excludeFolders() { + return excludeFolders != null && excludeFolders; } @Override diff --git a/examples/GetObject.java b/examples/GetObject.java index 11da0f2fa..f64f8acbc 100644 --- a/examples/GetObject.java +++ b/examples/GetObject.java @@ -38,19 +38,18 @@ public static void main(String[] args) throws IOException, MinioException { // .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY") // .build(); - // Get input stream to have content of 'my-object' from 'my-bucket' - InputStream stream = + // Get input stream to have content of 'my-object' from 'my-bucket'. + // try-with-resources closes the stream (releasing the network connection) even on read error. + try (InputStream stream = minioClient.getObject( - GetObjectArgs.builder().bucket("my-bucket").object("my-object").build()); + GetObjectArgs.builder().bucket("my-bucket").object("my-object").build())) { - // Read the input stream and print to the console till EOF. - byte[] buf = new byte[16384]; - int bytesRead; - while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { - System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + // Read the input stream and print to the console till EOF. + byte[] buf = new byte[16384]; + int bytesRead; + while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { + System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + } } - - // Close the input stream. - stream.close(); } } diff --git a/examples/GetObjectProgressBar.java b/examples/GetObjectProgressBar.java index eeeb8636d..5d9e66f67 100644 --- a/examples/GetObjectProgressBar.java +++ b/examples/GetObjectProgressBar.java @@ -52,30 +52,29 @@ public static void main(String[] args) throws IOException, MinioException { // Get object stat information. StatObjectResponse stat = minioClient.statObject( - StatObjectArgs.builder().bucket("testbucket").object("resumes/4.original.pdf").build()); + StatObjectArgs.builder().bucket("my-bucket").object("my-object").build()); // Get input stream to have content of 'my-object' from 'my-bucket' - InputStream is = - new ProgressStream( - "Downloading .. ", - stat.size(), - minioClient.getObject( - GetObjectArgs.builder().bucket("my-bucket").object("my-object").build())); - Path path = Paths.get("my-filename"); - OutputStream os = Files.newOutputStream(path, StandardOpenOption.CREATE); - - long bytesWritten = ByteStreams.copy(is, os); - is.close(); - os.close(); + try (InputStream is = + new ProgressStream( + "Downloading .. ", + stat.size(), + minioClient.getObject( + GetObjectArgs.builder().bucket("my-bucket").object("my-object").build())); + OutputStream os = + Files.newOutputStream( + path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + long bytesWritten = ByteStreams.copy(is, os); - if (bytesWritten != stat.size()) { - throw new IOException( - path - + ": unexpected data written. expected = " - + stat.size() - + ", written = " - + bytesWritten); + if (bytesWritten != stat.size()) { + throw new IOException( + path + + ": unexpected data written. expected = " + + stat.size() + + ", written = " + + bytesWritten); + } } } } diff --git a/examples/GetPartialObject.java b/examples/GetPartialObject.java index 65555f973..eeea7baa8 100644 --- a/examples/GetPartialObject.java +++ b/examples/GetPartialObject.java @@ -40,23 +40,22 @@ public static void main(String[] args) throws IOException, MinioException { // Get input stream to have content of 'my-object' from 'my-bucket' starts from // byte position 1024 and length 4096. - InputStream stream = + // try-with-resources closes the stream (releasing the network connection) even on read error. + try (InputStream stream = minioClient.getObject( GetObjectArgs.builder() .bucket("my-bucket") .object("my-object") .offset(1024L) .length(4096L) - .build()); + .build())) { - // Read the input stream and print to the console till EOF. - byte[] buf = new byte[16384]; - int bytesRead; - while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { - System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + // Read the input stream and print to the console till EOF. + byte[] buf = new byte[16384]; + int bytesRead; + while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { + System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + } } - - // Close the input stream. - stream.close(); } } diff --git a/examples/GetPresignedPostFormData.java b/examples/GetPresignedPostFormData.java index 2665c1230..b8bd3cd9e 100644 --- a/examples/GetPresignedPostFormData.java +++ b/examples/GetPresignedPostFormData.java @@ -77,11 +77,12 @@ public static void main(String[] args) throws IOException, MinioException { .post(multipartBuilder.build()) .build(); OkHttpClient httpClient = new OkHttpClient().newBuilder().build(); - Response response = httpClient.newCall(request).execute(); - if (response.isSuccessful()) { - System.out.println("Pictures/avatar.png is uploaded successfully using POST object"); - } else { - System.out.println("Failed to upload Pictures/avatar.png"); + try (Response response = httpClient.newCall(request).execute()) { + if (response.isSuccessful()) { + System.out.println("Pictures/avatar.png is uploaded successfully using POST object"); + } else { + System.out.println("Failed to upload Pictures/avatar.png"); + } } // Print curl command usage to upload file /tmp/userpic.jpg. diff --git a/examples/MinioClientWithAssumeRoleProvider.java b/examples/MinioClientWithAssumeRoleProvider.java index 45a1c4aa6..b971b9b5b 100644 --- a/examples/MinioClientWithAssumeRoleProvider.java +++ b/examples/MinioClientWithAssumeRoleProvider.java @@ -25,9 +25,9 @@ public class MinioClientWithAssumeRoleProvider { public static void main(String[] args) throws MinioException { Provider provider = new AssumeRoleProvider( - "https://play.minio.io:9000/", // STS endpoint usually point to MinIO server. - "minio", // Access key. - "minio123", // Secret key. + "https://STS-HOST:STS-PORT/", // STS endpoint usually point to MinIO server. + "YOUR-ACCESSKEY", // Access key. + "YOUR-SECRETACCESSKEY", // Secret key. null, // Duration seconds if available. null, // Policy if available. null, // Region if available. @@ -38,7 +38,7 @@ public static void main(String[] args) throws MinioException { MinioClient minioClient = MinioClient.builder() - .endpoint("https://play.minio.io:9000") + .endpoint("https://MINIO-HOST:MINIO-PORT") .credentialsProvider(provider) .build(); diff --git a/examples/MinioClientWithClientGrantsProvider.java b/examples/MinioClientWithClientGrantsProvider.java index 8636dc3be..543951fb3 100644 --- a/examples/MinioClientWithClientGrantsProvider.java +++ b/examples/MinioClientWithClientGrantsProvider.java @@ -52,6 +52,9 @@ static Jwt getJwt( OkHttpClient client = new OkHttpClient(); try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new ProviderException("STS endpoint failed with HTTP status " + response.code()); + } ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.setVisibility( diff --git a/examples/MinioClientWithWebIdentityProvider.java b/examples/MinioClientWithWebIdentityProvider.java index 10ed3983a..b104dc8d4 100644 --- a/examples/MinioClientWithWebIdentityProvider.java +++ b/examples/MinioClientWithWebIdentityProvider.java @@ -56,6 +56,9 @@ static Jwt getJwt( OkHttpClient client = new OkHttpClient(); try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new ProviderException("STS endpoint failed with HTTP status " + response.code()); + } ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.setVisibility( diff --git a/examples/ProgressStream.java b/examples/ProgressStream.java index 28de24252..d739fba66 100644 --- a/examples/ProgressStream.java +++ b/examples/ProgressStream.java @@ -68,28 +68,30 @@ public void close() throws IOException { @Override public int read() throws IOException { - this.pb.step(); - return this.in.read(); + int b = this.in.read(); + if (b >= 0) this.pb.step(); // Only advance when a byte was actually read (not at EOF). + return b; } @Override public int read(byte[] toStore) throws IOException { int readBytes = this.in.read(toStore); - this.pb.stepBy(readBytes); // Update progress bar. + if (readBytes > 0) this.pb.stepBy(readBytes); // Don't step by the -1 EOF sentinel. return readBytes; } @Override public int read(byte[] toStore, int off, int len) throws IOException { int readBytes = this.in.read(toStore, off, len); - this.pb.stepBy(readBytes); + if (readBytes > 0) this.pb.stepBy(readBytes); return readBytes; } @Override public long skip(long n) throws IOException { - this.pb.stepTo(n); - return this.in.skip(n); + long skipped = this.in.skip(n); + this.pb.stepBy(skipped); // Advance by the bytes actually skipped, not an absolute position. + return skipped; } @Override diff --git a/examples/PutObjectProgressBar.java b/examples/PutObjectProgressBar.java index 4101a7ff7..51f1e473a 100644 --- a/examples/PutObjectProgressBar.java +++ b/examples/PutObjectProgressBar.java @@ -43,14 +43,16 @@ public static void main(String[] args) throws IOException, MinioException { String objectName = "my-object"; String bucketName = "my-bucket"; - InputStream pis = + // Use the actual file length as the object size; available() is only a hint, not a reliable + // size. + long size = Files.size(Paths.get("my-filename")); + try (InputStream pis = new BufferedInputStream( - new ProgressStream("Uploading... ", Files.newInputStream(Paths.get("my-filename")))); - minioClient.putObject( - PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( - pis, (long) pis.available(), null) - .build()); - pis.close(); + new ProgressStream("Uploading... ", Files.newInputStream(Paths.get("my-filename"))))) { + minioClient.putObject( + PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(pis, size, null) + .build()); + } System.out.println("my-object is uploaded successfully"); } } diff --git a/examples/PutObjectUiProgressBar.java b/examples/PutObjectUiProgressBar.java index ecb3bf546..55da17d0a 100644 --- a/examples/PutObjectUiProgressBar.java +++ b/examples/PutObjectUiProgressBar.java @@ -77,9 +77,11 @@ private void uploadFile(String filename) throws MinioException { new ProgressMonitorInputStream(this, "Uploading... " + file.getAbsolutePath(), bis); pmis.getProgressMonitor().setMillisToPopup(10); + // Use the actual file length as the object size; available() is only a hint, not a reliable + // size. minioClient.putObject( PutObjectArgs.builder().bucket("bank").object("my-object").stream( - pmis, (long) pmis.available(), null) + pmis, file.length(), null) .build()); System.out.println("my-object is uploaded successfully"); } catch (FileNotFoundException e) { diff --git a/examples/SelectObjectContent.java b/examples/SelectObjectContent.java index ab4a4ebdb..31fceeb73 100644 --- a/examples/SelectObjectContent.java +++ b/examples/SelectObjectContent.java @@ -65,24 +65,27 @@ public static void main(String[] args) throws IOException, MinioException { OutputSerialization.newCSV( null, null, null, OutputSerialization.QuoteFields.ASNEEDED, null); - SelectResponseStream stream = + try (SelectResponseStream stream = minioClient.selectObjectContent( SelectObjectContentArgs.builder() .bucket("my-bucket") - .object("my-objectName") + .object("my-object") .sqlExpression(sqlExpression) .inputSerialization(is) .outputSerialization(os) .requestProgress(true) - .build()); + .build())) { - byte[] buf = new byte[512]; - int bytesRead = stream.read(buf, 0, buf.length); - System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); - Stats stats = stream.stats(); - System.out.println("bytes scanned: " + stats.bytesScanned()); - System.out.println("bytes processed: " + stats.bytesProcessed()); - System.out.println("bytes returned: " + stats.bytesReturned()); - stream.close(); + byte[] buf = new byte[512]; + int bytesRead; + while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { + System.out.print(new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + } + + Stats stats = stream.stats(); + System.out.println("bytes scanned: " + stats.bytesScanned()); + System.out.println("bytes processed: " + stats.bytesProcessed()); + System.out.println("bytes returned: " + stats.bytesReturned()); + } } } diff --git a/functional/MintLogger.java b/functional/MintLogger.java index ad5fcaa06..37462d311 100644 --- a/functional/MintLogger.java +++ b/functional/MintLogger.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.UncheckedIOException; @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public class MintLogger { @@ -77,9 +78,8 @@ public String toString() { JsonInclude.Value.empty().withValueInclusion(JsonInclude.Include.NON_NULL)) .writeValueAsString(this); } catch (JsonProcessingException e) { - e.printStackTrace(); + throw new UncheckedIOException("unable to serialize MintLogger entry", e); } - return ""; } /** Return Alert. */ diff --git a/functional/PutObjectRunnable.java b/functional/PutObjectRunnable.java index 8e4c82580..e1ccf10b6 100644 --- a/functional/PutObjectRunnable.java +++ b/functional/PutObjectRunnable.java @@ -25,6 +25,7 @@ class PutObjectRunnable implements Runnable { MinioClient client; String bucketName; String filename; + private volatile Exception exception; public PutObjectRunnable(MinioClient client, String bucketName, String filename) { this.client = client; @@ -32,6 +33,11 @@ public PutObjectRunnable(MinioClient client, String bucketName, String filename) this.filename = filename; } + /** Returns the exception that failed this run, or null if it succeeded. */ + public Exception exception() { + return exception; + } + public void run() { StringBuffer traceBuffer = new StringBuffer(); @@ -48,6 +54,8 @@ public void run() { traceBuffer.append("[" + filename + "]: delete object\n"); client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(filename).build()); } catch (Exception e) { + // Record the failure so the test thread can detect it after join(); also print for tracing. + this.exception = e; System.err.print(traceBuffer.toString()); e.printStackTrace(); } diff --git a/functional/TestMinioClient.java b/functional/TestMinioClient.java index 86769bac0..4c8dc06dd 100644 --- a/functional/TestMinioClient.java +++ b/functional/TestMinioClient.java @@ -492,9 +492,11 @@ public void testThreadedPutObject() throws Exception { try { int count = 7; Thread[] threads = new Thread[count]; + PutObjectRunnable[] runnables = new PutObjectRunnable[count]; for (int i = 0; i < count; i++) { - threads[i] = new Thread(new PutObjectRunnable(client, bucketName, createFile6Mb())); + runnables[i] = new PutObjectRunnable(client, bucketName, createFile6Mb()); + threads[i] = new Thread(runnables[i]); } for (int i = 0; i < count; i++) threads[i].start(); @@ -502,6 +504,11 @@ public void testThreadedPutObject() throws Exception { // Waiting for threads to complete. for (int i = 0; i < count; i++) threads[i].join(); + // Fail the test if any thread's upload/cleanup threw. + for (int i = 0; i < count; i++) { + if (runnables[i].exception() != null) throw runnables[i].exception(); + } + // All threads are completed. mintSuccessLog(methodName, testTags, startTime); } catch (Exception e) { @@ -951,11 +958,11 @@ public void testDownloadObject( if (sse != null) builder.sse(sse); client.putObject(builder.build()); client.downloadObject(args); - Files.delete(Paths.get(args.filename())); mintSuccessLog(methodName, testTags, startTime); } catch (Exception e) { handleException(methodName, testTags, startTime, e); } finally { + Files.deleteIfExists(Paths.get(args.filename())); client.removeObject( RemoveObjectArgs.builder().bucket(args.bucket()).object(args.object()).build()); } @@ -1333,10 +1340,11 @@ public void testPutPresignedUrl( String urlString = client.getPresignedObjectUrl(args); try { writeObject(urlString, data); - InputStream is = + try (InputStream is = client.getObject( - GetObjectArgs.builder().bucket(args.bucket()).object(args.object()).build()); - data = readAllBytes(is); + GetObjectArgs.builder().bucket(args.bucket()).object(args.object()).build())) { + data = readAllBytes(is); + } String checksum = getSha256Sum(new ByteArrayInputStream(data), data.length); Assertions.assertEquals( expectedChecksum, @@ -1943,6 +1951,9 @@ public void composeObject() throws Exception { } } catch (Exception e) { handleException(methodName, null, startTime, e); + // Setup failed; skip the dependent tests (object names may be null). The finally below + // still cleans up any objects that were created. + return; } composeObjectTests(object1Mb, object6Mb, object6MbSsec); @@ -2022,14 +2033,8 @@ public void disableObjectLegalHold() throws Exception { new ContentInputStream(1 * KB), 1L * KB, null) .build()); + checkObjectLegalHold(bucketNameWithLock, objectName, true); checkObjectLegalHold(bucketNameWithLock, objectName, false); - client.enableObjectLegalHold( - EnableObjectLegalHoldArgs.builder() - .bucket(bucketNameWithLock) - .object(objectName) - .build()); - checkObjectLegalHold(bucketNameWithLock, objectName, false); - mintSuccessLog(methodName, null, startTime); } finally { if (objectInfo != null) { client.removeObject( @@ -2071,7 +2076,6 @@ public void isObjectLegalHoldEnabled() throws Exception { Assertions.assertFalse(result, "object legal hold: expected: false, got: " + result); checkObjectLegalHold(bucketNameWithLock, objectName, true); checkObjectLegalHold(bucketNameWithLock, objectName, false); - mintSuccessLog(methodName, null, startTime); } finally { if (objectInfo != null) { client.removeObject( @@ -2650,7 +2654,7 @@ public void getBucketNotification() throws Exception { || !EventType.OBJECT_CREATED_PUT .toString() .equals(config.queueConfigurations().get(0).events().get(0))) { - System.out.println( + throw new Exception( "config: expected: " + Xml.marshal(expectedConfig) + ", got: " + Xml.marshal(config)); } } finally { @@ -2703,7 +2707,7 @@ public void deleteBucketNotification() throws Exception { client.getBucketNotification( GetBucketNotificationArgs.builder().bucket(bucketName).build()); if (config.queueConfigurations().size() != 0) { - System.out.println("config: expected: , got: " + Xml.marshal(config)); + throw new Exception("config: expected: , got: " + Xml.marshal(config)); } } finally { client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); @@ -2825,10 +2829,10 @@ public void selectObjectContent() throws Exception { Assertions.assertNotNull(stats, "stats is null"); Assertions.assertTrue( stats.bytesScanned() == 256, - "stats.bytesScanned mismatch; expected: 258, got: " + stats.bytesScanned()); + "stats.bytesScanned mismatch; expected: 256, got: " + stats.bytesScanned()); Assertions.assertTrue( stats.bytesProcessed() == 256, - "stats.bytesProcessed mismatch; expected: 258, got: " + stats.bytesProcessed()); + "stats.bytesProcessed mismatch; expected: 256, got: " + stats.bytesProcessed()); Assertions.assertTrue( stats.bytesReturned() == 222, "stats.bytesReturned mismatch; expected: 222, got: " + stats.bytesReturned()); diff --git a/functional/TestUserAgent.java b/functional/TestUserAgent.java index d14bba570..2034e9131 100644 --- a/functional/TestUserAgent.java +++ b/functional/TestUserAgent.java @@ -17,27 +17,38 @@ import io.minio.BucketExistsArgs; import io.minio.MinioClient; +import io.minio.errors.MinioException; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.Scanner; -@edu.umd.cs.findbugs.annotations.SuppressFBWarnings( - value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public class TestUserAgent { public static void main(String[] args) throws Exception { - MinioClient client = MinioClient.builder().endpoint("http://httpbin.org").build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - client.traceOn(baos); - client.bucketExists(BucketExistsArgs.builder().bucket("any-bucket-name-works").build()); - client.traceOff(); + // try-with-resources closes the client so its OkHttp dispatcher/connection-pool threads shut + // down and the JVM exits promptly instead of waiting out the idle keep-alive. + try (MinioClient client = MinioClient.builder().endpoint("http://httpbin.org").build()) { + client.setRetry(null, null, null); + client.traceOn(baos); + try { + client.bucketExists(BucketExistsArgs.builder().bucket("any-bucket-name-works").build()); + } catch (MinioException e) { + // ignore + } + client.traceOff(); + } String expectedVersion = System.getProperty("version"); + if (expectedVersion == null || expectedVersion.isEmpty()) { + throw new Exception("system property 'version' must be set"); + } String version = null; try (Scanner scanner = new Scanner(new String(baos.toByteArray(), StandardCharsets.UTF_8))) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("User-Agent:")) { - version = line.split("/")[1]; + String[] tokens = line.split("/"); + if (tokens.length > 1) version = tokens[1]; break; } }