diff --git a/okapi-core/src/main/kotlin/com/softwaremill/okapi/core/OutboxProcessor.kt b/okapi-core/src/main/kotlin/com/softwaremill/okapi/core/OutboxProcessor.kt index 84cc82d..6006e3c 100644 --- a/okapi-core/src/main/kotlin/com/softwaremill/okapi/core/OutboxProcessor.kt +++ b/okapi-core/src/main/kotlin/com/softwaremill/okapi/core/OutboxProcessor.kt @@ -18,7 +18,7 @@ import java.time.Duration * fire-flush-await — making per-entry timing meaningless). Use * [OutboxProcessorListener.onBatchProcessed] when you need batch-level timing. */ -class OutboxProcessor( +class OutboxProcessor @JvmOverloads constructor( private val store: OutboxStore, private val entryProcessor: OutboxEntryProcessor, private val listener: OutboxProcessorListener? = null, diff --git a/okapi-core/src/test/java/com/softwaremill/okapi/core/JavaOutboxProcessorConstruction.java b/okapi-core/src/test/java/com/softwaremill/okapi/core/JavaOutboxProcessorConstruction.java new file mode 100644 index 0000000..17234de --- /dev/null +++ b/okapi-core/src/test/java/com/softwaremill/okapi/core/JavaOutboxProcessorConstruction.java @@ -0,0 +1,26 @@ +package com.softwaremill.okapi.core; + +/** + * Java-side guard for {@code @JvmOverloads} on {@link OutboxProcessor}'s constructor. + * + *

These call sites compile only if {@code OutboxProcessor} exposes the shorter, + * default-omitting constructors to Java. Drop {@code @JvmOverloads} and okapi-core's test + * compilation fails right here — which is the point: a Kotlin-only test keeps compiling + * (Kotlin fills defaults at the call site) and would hide the regression. {@code + * OutboxProcessorJavaInteropTest} calls these so the guard also runs. + */ +public final class JavaOutboxProcessorConstruction { + + private JavaOutboxProcessorConstruction() {} + + /** {@code OutboxProcessor(store, entryProcessor)} — listener and clock default. */ + public static OutboxProcessor withStoreAndProcessor(OutboxStore store, OutboxEntryProcessor entryProcessor) { + return new OutboxProcessor(store, entryProcessor); + } + + /** {@code OutboxProcessor(store, entryProcessor, listener)} — clock defaults. */ + public static OutboxProcessor withListener( + OutboxStore store, OutboxEntryProcessor entryProcessor, OutboxProcessorListener listener) { + return new OutboxProcessor(store, entryProcessor, listener); + } +} diff --git a/okapi-core/src/test/kotlin/com/softwaremill/okapi/core/OutboxProcessorJavaInteropTest.kt b/okapi-core/src/test/kotlin/com/softwaremill/okapi/core/OutboxProcessorJavaInteropTest.kt new file mode 100644 index 0000000..64649b7 --- /dev/null +++ b/okapi-core/src/test/kotlin/com/softwaremill/okapi/core/OutboxProcessorJavaInteropTest.kt @@ -0,0 +1,46 @@ +package com.softwaremill.okapi.core + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.nulls.shouldNotBeNull +import java.time.Clock +import java.time.Instant + +/** + * Runs the Java call sites in [JavaOutboxProcessorConstruction], which compile only when + * [OutboxProcessor]'s constructor carries `@JvmOverloads`. The real guard is that Java source + * compiling — okapi-core has no other Java interop test, which is why the missing annotation + * regressed unnoticed. The stubs are inert: the constructor only stores its arguments. + */ +class OutboxProcessorJavaInteropTest : + FunSpec({ + val store = + object : OutboxStore { + override fun persist(entry: OutboxEntry) = entry + + override fun claimPending(limit: Int) = emptyList() + + override fun updateAfterProcessing(entry: OutboxEntry) = entry + + override fun removeDeliveredBefore(time: Instant, limit: Int) = 0 + + override fun findOldestCreatedAt(statuses: Set) = emptyMap() + + override fun countByStatuses() = emptyMap() + } + val entryProcessor = + OutboxEntryProcessor( + deliverer = + object : MessageDeliverer { + override val type = "stub" + + override fun deliver(entry: OutboxEntry) = DeliveryResult.Success + }, + retryPolicy = RetryPolicy(maxRetries = 3), + clock = Clock.systemUTC(), + ) + + test("Java can construct OutboxProcessor with trailing defaults omitted (@JvmOverloads)") { + JavaOutboxProcessorConstruction.withStoreAndProcessor(store, entryProcessor).shouldNotBeNull() + JavaOutboxProcessorConstruction.withListener(store, entryProcessor, null).shouldNotBeNull() + } + })