diff --git a/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasics.java b/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasics.java index 0f3b6df..f6d4ba6 100644 --- a/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasics.java +++ b/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasics.java @@ -5,7 +5,6 @@ import io.nats.client.*; import io.nats.client.api.StorageType; -import io.nats.client.api.StreamInfo; import io.nats.client.support.DateTimeUtils; import io.synadia.sm.ScheduleManagement; import io.synadia.sm.ScheduledMessageBuilder; @@ -13,7 +12,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static io.synadia.examples.ScheduleUtils.report; +import static io.synadia.examples.ScheduleExampleUtils.report; /** * Example: build and publish a few scheduled messages using @@ -57,26 +56,25 @@ public static void main(String[] args) { try { jsm.deleteStream(STREAM); } catch (Exception ignore) {} // Use the utility to properly create a schedulable stream - StreamInfo si = ScheduleManagement.createSchedulableStream(jsm, STREAM, StorageType.Memory, STREAM_SUBJECTS); - report("Created stream", si.getConfiguration()); + ScheduleManagement.createSchedulableStream(jsm, STREAM, StorageType.Memory, STREAM_SUBJECTS); CountDownLatch latch = new CountDownLatch(4); Dispatcher d = connection.createDispatcher(); // subscribe to the subject that receives the schedule message js.subscribe(SCHEDULES, d, m -> { - report("SCHEDULED (received)", m); + report("MONITORING via '" + SCHEDULES + "'", m); m.ack(); }, false); // subscribe to the target subject js.subscribe(TARGETS, d, m -> { - report("TARGETED (received)", m); + report("TARGETED via '" + TARGETS + "'", m); m.ack(); latch.countDown(); }, false); - report("SCHEDULE-NOW (publishing)"); + report("SCHEDULING " + SCHEDULE_PREFIX + "now"); new ScheduledMessageBuilder() .scheduleSubject(SCHEDULE_PREFIX + "now") .targetSubject(TARGET_PREFIX + "now") @@ -84,7 +82,7 @@ public static void main(String[] args) { .data("Schedule-Now") .scheduleMessage(js); - report("SCHEDULE-AT (publishing)"); + report("SCHEDULING " + SCHEDULE_PREFIX + "at"); new ScheduledMessageBuilder() .scheduleSubject(SCHEDULE_PREFIX + "at") .targetSubject(TARGET_PREFIX + "at") @@ -92,15 +90,19 @@ public static void main(String[] args) { .data("Scheduled-At") .scheduleMessage(js); - report("SCHEDULE-EVERY (publishing)"); + report("SCHEDULING " + SCHEDULE_PREFIX + "every"); new ScheduledMessageBuilder() - .scheduleSubject(SCHEDULE_PREFIX + "at") - .targetSubject(TARGET_PREFIX + "at") + .scheduleSubject(SCHEDULE_PREFIX + "every") + .targetSubject(TARGET_PREFIX + "every") .scheduleEvery(1, TimeUnit.SECONDS) .data("Every Second") .scheduleMessage(js); latch.await(); + + // The "every" schedule keeps firing until it is removed. + report("CANCEL " + SCHEDULE_PREFIX + "every", + ScheduleManagement.cancelSchedule(jsm, SCHEDULE_PREFIX + "every", STREAM)); } } catch (Exception e) { diff --git a/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasicsAlternate.java b/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasicsAlternate.java index 15ebd84..b7fc51a 100644 --- a/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasicsAlternate.java +++ b/schedule-message/src/examples/java/io/synadia/examples/ScheduleBasicsAlternate.java @@ -13,12 +13,14 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static io.synadia.examples.ScheduleUtils.report; +import static io.synadia.examples.ScheduleExampleUtils.report; /** * Example: same scenario as {@link ScheduleBasics}, but built using * {@link io.synadia.sm.ScheduledMessageBuilder#build()} and then published * via {@link io.nats.client.JetStream#publish(io.nats.client.Message)}. + * There is really no reason to do this unless you specifically want to log the + * actual message. */ public class ScheduleBasicsAlternate { @@ -66,13 +68,13 @@ public static void main(String[] args) { // subscribe to the subject that receives the schedule message js.subscribe(SCHEDULES, d, m -> { - report("SCHEDULED (received)", m); + report("MONITORING via '" + SCHEDULES + "'", m); m.ack(); }, false); // subscribe to the target subject js.subscribe(TARGETS, d, m -> { - report("TARGETED (received)", m); + report("TARGETED via '" + TARGETS + "'", m); m.ack(); latch.countDown(); }, false); @@ -83,7 +85,7 @@ public static void main(String[] args) { .scheduleImmediate() .data("Schedule-Now") .build(); - report("SCHEDULE-NOW (publishing)", m); + report("SCHEDULING " + SCHEDULE_PREFIX + "now", m); js.publish(m); m = new ScheduledMessageBuilder() @@ -92,19 +94,23 @@ public static void main(String[] args) { .scheduleAt(DateTimeUtils.gmtNow().plusSeconds(5)) .data("Scheduled-At") .build(); - report("SCHEDULE-AT (publishing)", m); + report("SCHEDULING " + SCHEDULE_PREFIX + "at", m); js.publish(m); m = new ScheduledMessageBuilder() - .scheduleSubject(SCHEDULE_PREFIX + "at") - .targetSubject(TARGET_PREFIX + "at") + .scheduleSubject(SCHEDULE_PREFIX + "every") + .targetSubject(TARGET_PREFIX + "every") .scheduleEvery(1, TimeUnit.SECONDS) .data("Every Second") .build(); - report("SCHEDULE-EVERY (publishing)", m); + report("SCHEDULING " + SCHEDULE_PREFIX + "every", m); js.publish(m); latch.await(); + + // The "every" schedule keeps firing until it is removed. + report("CANCEL " + SCHEDULE_PREFIX + "every", + ScheduleManagement.cancelSchedule(jsm, SCHEDULE_PREFIX + "every", STREAM)); } } catch (Exception e) { diff --git a/schedule-message/src/examples/java/io/synadia/examples/ScheduleCancel.java b/schedule-message/src/examples/java/io/synadia/examples/ScheduleCancel.java new file mode 100644 index 0000000..866fef7 --- /dev/null +++ b/schedule-message/src/examples/java/io/synadia/examples/ScheduleCancel.java @@ -0,0 +1,117 @@ +// Copyright (c) 2025-2026 Synadia Communications Inc. All Rights Reserved. +// See LICENSE and NOTICE file for details. + +package io.synadia.examples; + +import io.nats.client.*; +import io.nats.client.api.StorageType; +import io.synadia.sm.ScheduleManagement; +import io.synadia.sm.ScheduleManagement.Result; +import io.synadia.sm.ScheduledMessageBuilder; + +import java.time.Duration; + +import static io.synadia.examples.ScheduleExampleUtils.report; + +/** + * Example: stop a running schedule with each + * {@link ScheduleManagement#cancelSchedule cancelSchedule} overload. + *
+ *
+ * NATS schedules use a six-field cron form (second minute hour day month + * day-of-week) per ADR-51. The expressions below fire on short intervals + * so the example completes quickly; the time-zone-bound expression behaves + * identically here because it does not pin a time of day, but the call shape + * is the same one you would use for {@code "0 30 9 * * *"} ("9:30 every day + * in New York"). + */ +public class ScheduleCron { + + /** Stream name used by this example. */ + public static final String STREAM = "schedules-enabled"; + + /** Prefix for all schedule subjects in this example. */ + public static final String SCHEDULE_PREFIX = "schedule."; + + /** Prefix for all target subjects in this example. */ + public static final String TARGET_PREFIX = "target."; + + private static final String SCHEDULES = SCHEDULE_PREFIX + ">"; + private static final String TARGETS = TARGET_PREFIX + "*"; + + /** Subject patterns the example stream accepts. */ + public static final String[] STREAM_SUBJECTS = new String[]{SCHEDULES, TARGETS}; + + private ScheduleCron() {} + + /** + * Example entry point. + * @param args ignored + */ + public static void main(String[] args) { + try { + Options options = new Options.Builder() + .server("nats://localhost:4222") + .errorListener(new ErrorListener() {}) + .build(); + + try (Connection connection = Nats.connect(options)) { + JetStreamManagement jsm = connection.jetStreamManagement(); + JetStream js = connection.jetStream(); + + // delete the stream in case it existed, just for a fresh example + try { jsm.deleteStream(STREAM); } catch (Exception ignore) {} + + ScheduleManagement.createSchedulableStream(jsm, STREAM, StorageType.Memory, STREAM_SUBJECTS); + + CountDownLatch latch = new CountDownLatch(4); + Dispatcher d = connection.createDispatcher(); + + js.subscribe(TARGETS, d, m -> { + report("TARGETED via '" + TARGETS + "'", m); + m.ack(); + latch.countDown(); + }, false); + + String cronSubject = SCHEDULE_PREFIX + "cron"; + String cronTzSubject = SCHEDULE_PREFIX + "cron-tz"; + + // Six-field cron: every two seconds. + report("SCHEDULING " + cronSubject + " with cron '*/2 * * * * *'"); + new ScheduledMessageBuilder() + .scheduleSubject(cronSubject) + .targetSubject(TARGET_PREFIX + "cron") + .scheduleCron("*/2 * * * * *") + .data("Cron-Every-2s") + .scheduleMessage(js); + + // Same expression, evaluated in a specific IANA time zone. + report("SCHEDULING " + cronTzSubject + " with cron '*/3 * * * * *' (America/New_York)"); + new ScheduledMessageBuilder() + .scheduleSubject(cronTzSubject) + .targetSubject(TARGET_PREFIX + "cron-tz") + .scheduleCron("*/3 * * * * *", "America/New_York") + .data("Cron-Every-3s-NY") + .scheduleMessage(js); + + latch.await(); + + // Cron schedules keep firing until they are removed. + report("CANCEL " + cronSubject, ScheduleManagement.cancelSchedule(jsm, cronSubject, STREAM)); + report("CANCEL " + cronTzSubject, ScheduleManagement.cancelSchedule(jsm, cronTzSubject, STREAM)); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/schedule-message/src/examples/java/io/synadia/examples/ScheduleEveryAndIn.java b/schedule-message/src/examples/java/io/synadia/examples/ScheduleEveryAndIn.java new file mode 100644 index 0000000..195abf4 --- /dev/null +++ b/schedule-message/src/examples/java/io/synadia/examples/ScheduleEveryAndIn.java @@ -0,0 +1,128 @@ +// Copyright (c) 2025-2026 Synadia Communications Inc. All Rights Reserved. +// See LICENSE and NOTICE file for details. + +package io.synadia.examples; + +import io.nats.client.*; +import io.nats.client.api.StorageType; +import io.synadia.sm.ScheduleManagement; +import io.synadia.sm.ScheduledMessageBuilder; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static io.synadia.examples.ScheduleExampleUtils.report; + +/** + * Example: the remaining {@code scheduleEvery(...)} and {@code scheduleIn(...)} + * overloads on {@link ScheduledMessageBuilder} that the {@link ScheduleBasics} + * example does not exercise: + *
+ * The builder writes its own {@code Nats-Schedule-*} headers after copying + * user headers, so user headers are preserved on the message that the schedule + * publishes to the target subject. + */ +public class ScheduleHeadersAndCopy { + + /** Stream name used by this example. */ + public static final String STREAM = "schedules-enabled"; + + /** Prefix for all schedule subjects in this example. */ + public static final String SCHEDULE_PREFIX = "schedule."; + + /** Prefix for all target subjects in this example. */ + public static final String TARGET_PREFIX = "target."; + + private static final String SCHEDULES = SCHEDULE_PREFIX + ">"; + private static final String TARGETS = TARGET_PREFIX + "*"; + + /** Subject patterns the example stream accepts. */ + public static final String[] STREAM_SUBJECTS = new String[]{SCHEDULES, TARGETS}; + + private ScheduleHeadersAndCopy() {} + + /** + * Example entry point. + * @param args ignored + */ + public static void main(String[] args) { + try { + Options options = new Options.Builder() + .server("nats://localhost:4222") + .errorListener(new ErrorListener() {}) + .build(); + + try (Connection connection = Nats.connect(options)) { + JetStreamManagement jsm = connection.jetStreamManagement(); + JetStream js = connection.jetStream(); + + // delete the stream in case it existed, just for a fresh example + try { jsm.deleteStream(STREAM); } catch (Exception ignore) {} + + ScheduleManagement.createSchedulableStream(jsm, STREAM, StorageType.Memory, STREAM_SUBJECTS); + + CountDownLatch latch = new CountDownLatch(2); + Dispatcher d = connection.createDispatcher(); + + js.subscribe(TARGETS, d, m -> { + report("TARGETED via '" + TARGETS + "'", m); + m.ack(); + latch.countDown(); + }, false); + + // 1) Attach custom headers to a scheduled message. + Headers userHeaders = new Headers(); + userHeaders.put("X-Trace-Id", "abc-123"); + userHeaders.put("X-Origin", "ScheduleHeadersAndCopy"); + + String withHeadersSubject = SCHEDULE_PREFIX + "with-headers"; + report("SCHEDULING " + withHeadersSubject + " with custom headers"); + new ScheduledMessageBuilder() + .scheduleSubject(withHeadersSubject) + .targetSubject(TARGET_PREFIX + "with-headers") + .scheduleImmediate() + .headers(userHeaders) + .data("Has-User-Headers") + .scheduleMessage(js); + + // 2) Seed a schedule from an existing message via copy(). + // copy() pulls subject, data, and headers; the schedule subject + // here is taken from the source message, while the target subject + // is set explicitly. + Headers seedHeaders = new Headers(); + seedHeaders.put("X-Seeded-From", "template"); + Message seed = new NatsMessage(SCHEDULE_PREFIX + "copied", null, seedHeaders, "Seed-Data".getBytes()); + report("SEED message (template for copy())", seed); + + String copiedSubject = SCHEDULE_PREFIX + "copied"; + report("SCHEDULING " + copiedSubject + " built via copy(seed)"); + new ScheduledMessageBuilder() + .copy(seed) + .targetSubject(TARGET_PREFIX + "copied") + .scheduleImmediate() + .scheduleMessage(js); + + latch.await(); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/schedule-message/src/examples/java/io/synadia/examples/SchedulePredefined.java b/schedule-message/src/examples/java/io/synadia/examples/SchedulePredefined.java new file mode 100644 index 0000000..46d2b00 --- /dev/null +++ b/schedule-message/src/examples/java/io/synadia/examples/SchedulePredefined.java @@ -0,0 +1,86 @@ +// Copyright (c) 2025-2026 Synadia Communications Inc. All Rights Reserved. +// See LICENSE and NOTICE file for details. + +package io.synadia.examples; + +import io.nats.client.*; +import io.nats.client.api.StorageType; +import io.synadia.sm.PredefinedSchedules; +import io.synadia.sm.ScheduleManagement; +import io.synadia.sm.ScheduledMessageBuilder; + +import static io.synadia.examples.ScheduleExampleUtils.report; + +/** + * Example: build a schedule for every entry of {@link PredefinedSchedules} + * ({@code @hourly}, {@code @daily}, {@code @weekly}, ...). The shortest + * predefined interval is {@code @hourly}, so this example would not fire + * within a reasonable test run; instead it publishes each schedule, reports + * what got stored on the stream, then cancels it. + */ +public class SchedulePredefined { + + /** Stream name used by this example. */ + public static final String STREAM = "schedules-enabled"; + + /** Prefix for all schedule subjects in this example. */ + public static final String SCHEDULE_PREFIX = "schedule."; + + /** Prefix for all target subjects in this example. */ + public static final String TARGET_PREFIX = "target."; + + private static final String SCHEDULES = SCHEDULE_PREFIX + ">"; + private static final String TARGETS = TARGET_PREFIX + "*"; + + /** Subject patterns the example stream accepts. */ + public static final String[] STREAM_SUBJECTS = new String[]{SCHEDULES, TARGETS}; + + private SchedulePredefined() {} + + /** + * Example entry point. + * @param args ignored + */ + public static void main(String[] args) { + try { + Options options = new Options.Builder() + .server("nats://localhost:4222") + .errorListener(new ErrorListener() {}) + .build(); + + try (Connection connection = Nats.connect(options)) { + JetStreamManagement jsm = connection.jetStreamManagement(); + JetStream js = connection.jetStream(); + + // delete the stream in case it existed, just for a fresh example + try { jsm.deleteStream(STREAM); } catch (Exception ignore) {} + + ScheduleManagement.createSchedulableStream(jsm, STREAM, StorageType.Memory, STREAM_SUBJECTS); + + for (PredefinedSchedules p : PredefinedSchedules.values()) { + String scheduleSubject = SCHEDULE_PREFIX + p.name().toLowerCase(); + String targetSubject = TARGET_PREFIX + p.name().toLowerCase(); + + // Show what is being sent to the schedule subject. + Message m = new ScheduledMessageBuilder() + .scheduleSubject(scheduleSubject) + .targetSubject(targetSubject) + .schedule(p) + .data("Predefined-" + p.name()) + .build(); + report("SCHEDULING " + p.name(), m); + + js.publish(m); + + // Predefined schedules fire no more often than hourly, + // so cancel each one immediately to keep the stream tidy. + report("CANCEL " + scheduleSubject, + ScheduleManagement.cancelSchedule(jsm, scheduleSubject, STREAM)); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/schedule-message/src/examples/java/io/synadia/examples/SchedulePublishAndCancel.java b/schedule-message/src/examples/java/io/synadia/examples/SchedulePublishAndCancel.java new file mode 100644 index 0000000..8fb6099 --- /dev/null +++ b/schedule-message/src/examples/java/io/synadia/examples/SchedulePublishAndCancel.java @@ -0,0 +1,169 @@ +// Copyright (c) 2025-2026 Synadia Communications Inc. All Rights Reserved. +// See LICENSE and NOTICE file for details. + +package io.synadia.examples; + +import io.nats.client.*; +import io.nats.client.api.PublishAck; +import io.nats.client.api.StorageType; +import io.nats.client.impl.Headers; +import io.synadia.sm.ScheduleManagement; +import io.synadia.sm.ScheduledMessageBuilder; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; + +import static io.synadia.examples.ScheduleExampleUtils.report; + +/** + * Example: the three atomic publish-and-stop calls on {@link ScheduleManagement}. + * Each one publishes a message to the {@code targetSubject} and stops the named + * schedule as a single atomic step. + *
+ *
* The {@code targetSubject} must not equal {@code scheduleSubject}. This constraint * is enforced by the server, not by this method, so passing equal subjects surfaces * as a {@link JetStreamApiException} with error code {@code 10212} from the publish * call. * - * @param jsm the JetStream management context (its - * {@code jetStream()} context is used to publish) - * @param scheduleSubject the schedule subject to stop - * @param targetSubject the subject to publish to; this may be the - * original schedule's target subject (to publish - * early) or any other subject. Must not equal - * {@code scheduleSubject} — see above - * @param data the message body; may be {@code null} - * @param userHeaders extra headers to include on the published - * message; may be {@code null}. The - * {@code Nats-Scheduler} and - * {@code Nats-Schedule-Next} headers are always - * set by this method and override any conflicting - * keys from {@code userHeaders} - * @param publishOnlyIfScheduleExists when {@code true}, the publish is sent with an - * expected-last-subject-sequence guard so it - * only succeeds if the schedule message is still - * present; when {@code false}, the publish is - * sent unconditionally - * @return the {@link PublishAck} from the server, or {@code null} when - * {@code publishOnlyIfScheduleExists} is {@code true} and the schedule for the - * subject could not be located. The lookup requires exactly one matching - * stream; if zero or more than one stream covers {@code scheduleSubject}, the - * method returns {@code null} without publishing + * @param jsm the JetStream management context (its {@code jetStream()} + * context is used to publish) + * @param scheduleSubject the schedule subject to stop + * @param targetSubject the subject to publish to; this may be the original + * schedule's target subject (to publish early) or any other + * subject. Must not equal {@code scheduleSubject} — see above + * @param data the message body; may be {@code null} + * @param userHeaders extra headers to include on the published message; may be + * {@code null}. The {@code Nats-Scheduler} and + * {@code Nats-Schedule-Next} headers are always set by this + * method and override any conflicting keys from + * {@code userHeaders} + * @return the {@link PublishAck} from the server * @throws JetStreamApiException if the server returned an error * @throws IOException if the request could not be sent */ - public static @Nullable PublishAck publishAndCancelSchedule(JetStreamManagement jsm, String scheduleSubject, String targetSubject, - byte @Nullable[] data, @Nullable Headers userHeaders, boolean publishOnlyIfScheduleExists) throws JetStreamApiException, IOException { - if (publishOnlyIfScheduleExists) { - String streamName = findStreamLenient(jsm, scheduleSubject); - if (streamName != null) { - long seq = getScheduleSequence(jsm, streamName, scheduleSubject); - if (seq != -1) { - return publishAndCancelSchedule(jsm, scheduleSubject, seq, targetSubject, data, userHeaders); - } - } - return null; - } - + public static PublishAck publishAndCancelSchedule(JetStreamManagement jsm, String scheduleSubject, String targetSubject, + byte @Nullable[] data, @Nullable Headers userHeaders) throws JetStreamApiException, IOException { Headers h = makeHeaders(scheduleSubject, userHeaders); return jsm.jetStream().publish(targetSubject, h, data); } + /** + * Guarded variant of + * {@link #publishAndCancelSchedule(JetStreamManagement, String, String, byte[], Headers)}: + * looks up the schedule message first and only publishes if it is still present, using + * the {@code Nats-Expected-Last-Subject-Sequence} precondition so the operation is + * atomic with any concurrent fire. + *
+ * The lookup requires exactly one stream to cover {@code scheduleSubject}; if + * zero or more than one stream matches, the method returns {@code null} without + * publishing. + * + * @param jsm the JetStream management context + * @param scheduleSubject the schedule subject to stop + * @param targetSubject the subject to publish to; must not equal + * {@code scheduleSubject} (server rejects with code + * {@code 10212}) + * @param data the message body; may be {@code null} + * @param userHeaders extra headers to include on the published message; may be + * {@code null}. The {@code Nats-Scheduler} and + * {@code Nats-Schedule-Next} headers are always set by this + * method and override any conflicting keys from + * {@code userHeaders} + * @return the {@link PublishAck} from the server, or {@code null} if the schedule + * for {@code scheduleSubject} could not be located (no schedule message present, + * or the stream lookup was ambiguous) + * @throws JetStreamApiException if the server returned an error + * @throws IOException if the request could not be sent + */ + public static @Nullable PublishAck publishAndCancelScheduleIfExists(JetStreamManagement jsm, String scheduleSubject, String targetSubject, + byte @Nullable[] data, @Nullable Headers userHeaders) throws JetStreamApiException, IOException { + String streamName = findStreamLenient(jsm, scheduleSubject); + if (streamName != null) { + long seq = getScheduleSequence(jsm, streamName, scheduleSubject); + if (seq != -1) { + return publishAndCancelSchedule(jsm, scheduleSubject, seq, targetSubject, data, userHeaders); + } + } + return null; + } + /** * Atomic publish-and-stop guarded by an explicit existence check. Same headers as * the simpler overload, but additionally sets: diff --git a/schedule-message/src/test/java/io/synadia/sm/ScheduleManagementTests.java b/schedule-message/src/test/java/io/synadia/sm/ScheduleManagementTests.java index 561f1c1..fc630bd 100644 --- a/schedule-message/src/test/java/io/synadia/sm/ScheduleManagementTests.java +++ b/schedule-message/src/test/java/io/synadia/sm/ScheduleManagementTests.java @@ -182,7 +182,8 @@ public void testCancelBySubjectInStream_wildcard_noMatches() throws Exception { ScheduleManagement.cancelSchedule(jsm, f.schedPrefix + ".*", f.stream)); } - // -- publishAndCancelSchedule(jsm, sched, tgt, data, publishOnlyIfExists) -- + // -- publishAndCancelSchedule(jsm, sched, tgt, data, userHeaders) ---------- + // -- publishAndCancelScheduleIfExists(jsm, sched, tgt, data, userHeaders) -- @Test public void testPublishAndCancel_unconditional_success() throws Exception { @@ -192,7 +193,7 @@ public void testPublishAndCancel_unconditional_success() throws Exception { scheduleInTheFuture(schedSubject, tgtSubject, "body"); PublishAck ack = ScheduleManagement.publishAndCancelSchedule( - jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null, false); + jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null); assertNotNull(ack); assertFalse(scheduleExists(f.stream, schedSubject)); @@ -205,8 +206,8 @@ public void testPublishAndCancel_ifExists_whenPresent() throws Exception { String tgtSubject = f.tgt("p2"); scheduleInTheFuture(schedSubject, tgtSubject, "body"); - PublishAck ack = ScheduleManagement.publishAndCancelSchedule( - jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null, true); + PublishAck ack = ScheduleManagement.publishAndCancelScheduleIfExists( + jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null); assertNotNull(ack); assertFalse(scheduleExists(f.stream, schedSubject)); @@ -218,8 +219,8 @@ public void testPublishAndCancel_ifExists_whenMissing() throws Exception { String schedSubject = f.sched("p3"); String tgtSubject = f.tgt("p3"); - PublishAck ack = ScheduleManagement.publishAndCancelSchedule( - jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null, true); + PublishAck ack = ScheduleManagement.publishAndCancelScheduleIfExists( + jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), null); assertNull(ack); } @@ -267,7 +268,7 @@ public void testPublishAndCancel_targetEqualsScheduleSubject_serverRejects() thr JetStreamApiException ex = assertThrows(JetStreamApiException.class, () -> ScheduleManagement.publishAndCancelSchedule( - jsm, sameSubject, sameSubject, "x".getBytes(), null, false)); + jsm, sameSubject, sameSubject, "x".getBytes(), null)); assertEquals(10212, ex.getApiErrorCode()); } @@ -293,7 +294,7 @@ public void testPublishAndCancel_userHeadersCannotOverrideRequired() throws Exce userHeaders.put("X-Custom", "carry-me"); PublishAck ack = ScheduleManagement.publishAndCancelSchedule( - jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), userHeaders, false); + jsm, schedSubject, tgtSubject, "cancel-now".getBytes(), userHeaders); assertNotNull(ack); // The schedule was actually cancelled — proves the required headers won.