diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index de66e5945..1845faf53 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -33,7 +33,7 @@ jobs: export -- GPG_SIGNING_KEY_ID printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD" GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')" - ./gradlew publishAndReleaseToMavenCentral -Dorg.gradle.jvmargs="-Xmx8g" --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache + ./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache env: SONATYPE_USERNAME: ${{ secrets.LITHIC_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.LITHIC_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f70d9978a..73033badf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.102.0" + ".": "0.102.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index baabb56d9..e56961307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.102.1 (2025-08-22) + +Full Changelog: [v0.102.0...v0.102.1](https://github.com/lithic-com/lithic-java/compare/v0.102.0...v0.102.1) + +### Chores + +* **ci:** reduce log noise ([9e55acc](https://github.com/lithic-com/lithic-java/commit/9e55acc8f97887a2a0bb33fd298200fd1d5dc75a)) +* **client:** refactor closing / shutdown ([ffdb0de](https://github.com/lithic-com/lithic-java/commit/ffdb0dec8ae31a8f1a5894ae62c9330d9f5e69fe)) +* **internal:** support running formatters directly ([5803b62](https://github.com/lithic-com/lithic-java/commit/5803b62ba3fa4f6d9cf6f8c92d04f9fd0473a701)) +* remove memory upper bound from publishing step ([e75ad9a](https://github.com/lithic-com/lithic-java/commit/e75ad9a34161e81144bb936ca66113f14ccf659c)) + ## 0.102.0 (2025-08-18) Full Changelog: [v0.101.0...v0.102.0](https://github.com/lithic-com/lithic-java/compare/v0.101.0...v0.102.0) diff --git a/README.md b/README.md index 0af7bfeba..4b78e7961 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.102.0) -[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.102.0/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.102.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.lithic.api/lithic-java)](https://central.sonatype.com/artifact/com.lithic.api/lithic-java/0.102.1) +[![javadoc](https://javadoc.io/badge2/com.lithic.api/lithic-java/0.102.1/javadoc.svg)](https://javadoc.io/doc/com.lithic.api/lithic-java/0.102.1) @@ -13,7 +13,7 @@ The Lithic Java SDK is similar to the Lithic Kotlin SDK but with minor differenc -The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.102.0). +The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.lithic.api/lithic-java/0.102.1). @@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.lithic.com](https://docs.lithic ### Gradle ```kotlin -implementation("com.lithic.api:lithic-java:0.102.0") +implementation("com.lithic.api:lithic-java:0.102.1") ``` ### Maven @@ -33,7 +33,7 @@ implementation("com.lithic.api:lithic-java:0.102.0") com.lithic.api lithic-java - 0.102.0 + 0.102.1 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 80c9c68c1..223cdee64 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.lithic.api" - version = "0.102.0" // x-release-please-version + version = "0.102.1" // x-release-please-version } subprojects { diff --git a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt index 8b454be22..1872c42da 100644 --- a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt +++ b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClient.kt @@ -126,6 +126,8 @@ class LithicOkHttpClient private constructor() { * The executor to use for running [AsyncStreamResponse.Handler] callbacks. * * Defaults to a dedicated cached thread pool. + * + * This class takes ownership of the executor and shuts it down, if possible, when closed. */ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply { clientOptions.streamHandlerExecutor(streamHandlerExecutor) diff --git a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt index 6e2efccdd..82fbba48c 100644 --- a/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt +++ b/lithic-java-client-okhttp/src/main/kotlin/com/lithic/api/client/okhttp/LithicOkHttpClientAsync.kt @@ -126,6 +126,8 @@ class LithicOkHttpClientAsync private constructor() { * The executor to use for running [AsyncStreamResponse.Handler] callbacks. * * Defaults to a dedicated cached thread pool. + * + * This class takes ownership of the executor and shuts it down, if possible, when closed. */ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply { clientOptions.streamHandlerExecutor(streamHandlerExecutor) diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientAsyncImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientAsyncImpl.kt index 173776d11..d0a10d637 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientAsyncImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientAsyncImpl.kt @@ -288,7 +288,7 @@ class LithicClientAsyncImpl(private val clientOptions: ClientOptions) : LithicCl // get /v1/status withRawResponse().apiStatus(params, requestOptions).thenApply { it.parse() } - override fun close() = clientOptions.httpClient.close() + override fun close() = clientOptions.close() class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) : LithicClientAsync.WithRawResponse { diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientImpl.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientImpl.kt index 3be6e93bf..7be47434e 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientImpl.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/client/LithicClientImpl.kt @@ -270,7 +270,7 @@ class LithicClientImpl(private val clientOptions: ClientOptions) : LithicClient // get /v1/status withRawResponse().apiStatus(params, requestOptions).parse() - override fun close() = clientOptions.httpClient.close() + override fun close() = clientOptions.close() class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) : LithicClient.WithRawResponse { diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt index 022ff6526..114aecec6 100644 --- a/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/ClientOptions.kt @@ -13,6 +13,7 @@ import java.time.Clock import java.time.Duration import java.util.Optional import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicLong @@ -26,6 +27,8 @@ private constructor( * The HTTP client to use in the SDK. * * Use the one published in `lithic-java-client-okhttp` or implement your own. + * + * This class takes ownership of the client and closes it when closed. */ @get:JvmName("httpClient") val httpClient: HttpClient, /** @@ -47,6 +50,8 @@ private constructor( * The executor to use for running [AsyncStreamResponse.Handler] callbacks. * * Defaults to a dedicated cached thread pool. + * + * This class takes ownership of the executor and shuts it down, if possible, when closed. */ @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor, /** @@ -179,6 +184,8 @@ private constructor( * The HTTP client to use in the SDK. * * Use the one published in `lithic-java-client-okhttp` or implement your own. + * + * This class takes ownership of the client and closes it when closed. */ fun httpClient(httpClient: HttpClient) = apply { this.httpClient = PhantomReachableClosingHttpClient(httpClient) @@ -207,9 +214,14 @@ private constructor( * The executor to use for running [AsyncStreamResponse.Handler] callbacks. * * Defaults to a dedicated cached thread pool. + * + * This class takes ownership of the executor and shuts it down, if possible, when closed. */ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply { - this.streamHandlerExecutor = streamHandlerExecutor + this.streamHandlerExecutor = + if (streamHandlerExecutor is ExecutorService) + PhantomReachableExecutorService(streamHandlerExecutor) + else streamHandlerExecutor } /** @@ -466,4 +478,19 @@ private constructor( ) } } + + /** + * Closes these client options, relinquishing any underlying resources. + * + * This is purposefully not inherited from [AutoCloseable] because the client options are + * long-lived and usually should not be synchronously closed via try-with-resources. + * + * It's also usually not necessary to call this method at all. the default client automatically + * releases threads and connections if they remain idle, but if you are writing an application + * that needs to aggressively release unused resources, then you may call this method. + */ + fun close() { + httpClient.close() + (streamHandlerExecutor as? ExecutorService)?.shutdown() + } } diff --git a/lithic-java-core/src/main/kotlin/com/lithic/api/core/PhantomReachableExecutorService.kt b/lithic-java-core/src/main/kotlin/com/lithic/api/core/PhantomReachableExecutorService.kt new file mode 100644 index 000000000..590a30a0e --- /dev/null +++ b/lithic-java-core/src/main/kotlin/com/lithic/api/core/PhantomReachableExecutorService.kt @@ -0,0 +1,58 @@ +package com.lithic.api.core + +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit + +/** + * A delegating wrapper around an [ExecutorService] that shuts it down once it's only phantom + * reachable. + * + * This class ensures the [ExecutorService] is shut down even if the user forgets to do it. + */ +internal class PhantomReachableExecutorService(private val executorService: ExecutorService) : + ExecutorService { + init { + closeWhenPhantomReachable(this) { executorService.shutdown() } + } + + override fun execute(command: Runnable) = executorService.execute(command) + + override fun shutdown() = executorService.shutdown() + + override fun shutdownNow(): MutableList = executorService.shutdownNow() + + override fun isShutdown(): Boolean = executorService.isShutdown + + override fun isTerminated(): Boolean = executorService.isTerminated + + override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean = + executorService.awaitTermination(timeout, unit) + + override fun submit(task: Callable): Future = executorService.submit(task) + + override fun submit(task: Runnable, result: T): Future = + executorService.submit(task, result) + + override fun submit(task: Runnable): Future<*> = executorService.submit(task) + + override fun invokeAll( + tasks: MutableCollection> + ): MutableList> = executorService.invokeAll(tasks) + + override fun invokeAll( + tasks: MutableCollection>, + timeout: Long, + unit: TimeUnit, + ): MutableList> = executorService.invokeAll(tasks, timeout, unit) + + override fun invokeAny(tasks: MutableCollection>): T = + executorService.invokeAny(tasks) + + override fun invokeAny( + tasks: MutableCollection>, + timeout: Long, + unit: TimeUnit, + ): T = executorService.invokeAny(tasks, timeout, unit) +} diff --git a/lithic-java-proguard-test/build.gradle.kts b/lithic-java-proguard-test/build.gradle.kts index 06512330c..d4d64f681 100644 --- a/lithic-java-proguard-test/build.gradle.kts +++ b/lithic-java-proguard-test/build.gradle.kts @@ -37,8 +37,6 @@ val proguardJar by tasks.registering(proguard.gradle.ProGuardTask::class) { outjars(proguardJarPath) printmapping("${layout.buildDirectory.get()}/proguard-mapping.txt") - dontwarn() - val javaHome = System.getProperty("java.home") if (System.getProperty("java.version").startsWith("1.")) { // Before Java 9, the runtime classes were packaged in a single jar file. diff --git a/lithic-java-proguard-test/test.pro b/lithic-java-proguard-test/test.pro index 2405d4e26..e72ae1c08 100644 --- a/lithic-java-proguard-test/test.pro +++ b/lithic-java-proguard-test/test.pro @@ -5,4 +5,5 @@ -keep class org.junit.** { *; } # Many warnings don't apply for our testing purposes. +-dontnote -dontwarn \ No newline at end of file diff --git a/scripts/format b/scripts/format index 7c0be4d57..65db1769a 100755 --- a/scripts/format +++ b/scripts/format @@ -4,5 +4,18 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running formatters" -./gradlew format +if command -v ktfmt &> /dev/null; then + echo "==> Running ktfmt" + ./scripts/kotlin-format +else + echo "==> Running gradlew formatKotlin" + ./gradlew formatKotlin +fi + +if command -v palantir-java-format &> /dev/null; then + echo "==> Running palantir-java-format" + ./scripts/java-format +else + echo "==> Running gradlew formatJava" + ./gradlew formatJava +fi diff --git a/scripts/java-format b/scripts/java-format new file mode 100755 index 000000000..ad5febce9 --- /dev/null +++ b/scripts/java-format @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +find . -name "*.java" -not -path "./buildSrc/build/*" -print0 | xargs -0 -r palantir-java-format --palantir --replace "$@" diff --git a/scripts/kotlin-format b/scripts/kotlin-format new file mode 100755 index 000000000..3b8be9ea9 --- /dev/null +++ b/scripts/kotlin-format @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +find . -name "*.kt" -not -path "./buildSrc/build/*" -print0 | xargs -0 -r ktfmt --kotlinlang-style "$@" diff --git a/scripts/lint b/scripts/lint index aea8af713..dbc8f7760 100755 --- a/scripts/lint +++ b/scripts/lint @@ -5,4 +5,19 @@ set -e cd "$(dirname "$0")/.." echo "==> Running lints" -./gradlew lint + +if command -v ktfmt &> /dev/null; then + echo "==> Checking ktfmt" + ./scripts/kotlin-format --dry-run --set-exit-if-changed +else + echo "==> Running gradlew lintKotlin" + ./gradlew lintKotlin +fi + +if command -v palantir-java-format &> /dev/null; then + echo "==> Checking palantir-java-format" + ./scripts/java-format --dry-run --set-exit-if-changed +else + echo "==> Running gradlew lintJava" + ./gradlew lintJava +fi