Skip to content

Commit 38acfa6

Browse files
committed
Removed the ContextFilter and replaced it with a standard function
1 parent 12d8460 commit 38acfa6

6 files changed

Lines changed: 77 additions & 38 deletions

File tree

lib/src/main/java/com/codeheadsystems/microbus/Bus.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.codeheadsystems.microbus;
22

3+
import java.util.Map;
34
import java.util.function.Consumer;
5+
import java.util.function.Function;
46

57
public interface Bus<D> {
68

@@ -10,9 +12,9 @@ public interface Bus<D> {
1012
*
1113
* @param filler for a context.
1214
*/
13-
void register(ContextFiller<D> filler);
15+
void registerContextFiller(Function<D, Map<String, Object>> filler);
1416

15-
void unregister(ContextFiller<D> filler);
17+
void unregisterContextFiller(Function<D, Map<String, Object>> filler);
1618

1719
void publish(Message<D> message);
1820

lib/src/main/java/com/codeheadsystems/microbus/ContextFiller.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

lib/src/main/java/com/codeheadsystems/microbus/DefaultUUIDSupplier.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import java.util.UUID;
66
import java.util.function.Supplier;
77

8+
/**
9+
* Uses a normal random object for the UUID. Create your own with a SecureRandom if needed.
10+
*/
811
public class DefaultUUIDSupplier implements Supplier<UUID> {
912

1013
private final TimeBasedEpochGenerator generator;

lib/src/main/java/com/codeheadsystems/microbus/MicroBus.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@
77
import java.util.concurrent.ExecutorService;
88
import java.util.concurrent.Executors;
99
import java.util.function.Consumer;
10+
import java.util.function.Function;
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
1213

14+
/**
15+
* Basic implementation for a buss.
16+
*
17+
* @param <D> data used for the messages. This can be as simple or complex as you want.
18+
*/
1319
public class MicroBus<D> implements Bus<D> {
1420

1521
private static final Logger LOGGER = LoggerFactory.getLogger(MicroBus.class);
1622

1723
private final List<Consumer<Message<D>>> subscribers = new CopyOnWriteArrayList<>();
18-
private final List<ContextFiller<D>> contextFillers = new CopyOnWriteArrayList<>();
24+
private final List<Function<D, Map<String, Object>>> contextFillers = new CopyOnWriteArrayList<>();
1925
private final ContextFactory contextFactory;
2026
private final ExecutorService executorService;
2127

@@ -31,13 +37,13 @@ public MicroBus(ContextFactory contextFactory,
3137
}
3238

3339
@Override
34-
public void register(ContextFiller<D> filler) {
40+
public void registerContextFiller(Function<D, Map<String, Object>> filler) {
3541
LOGGER.debug("register({})", filler);
3642
contextFillers.add(Objects.requireNonNull(filler));
3743
}
3844

3945
@Override
40-
public void unregister(ContextFiller<D> filler) {
46+
public void unregisterContextFiller(Function<D, Map<String, Object>> filler) {
4147
LOGGER.debug("unregister({})", filler);
4248
contextFillers.remove(filler);
4349
}
@@ -67,9 +73,12 @@ public void unsubscribe(Consumer<Message<D>> subscriber) {
6773
private void dispatch(Message<D> message) {
6874
contextFactory.withRunnable(() -> {
6975
LOGGER.debug("[{}] dispatch({})", contextFactory.contextUuid(), message.uuid());
76+
// Fill up the context with any fillers first. Store the results we get.
7077
Map<String, Object> properties = contextFactory.currentContext()
71-
.map(ctx -> fillContext(message, ctx))
72-
.orElseThrow();
78+
.map(ctx -> fillContext(message, ctx))
79+
.orElseThrow(); // should never happen.
80+
// Now call each subscriber with the message and note context.
81+
// Remember we have to copy the properties from the current context if we're doing async stuff.
7382
for (Consumer<Message<D>> subscriber : subscribers) {
7483
if (message.async()) {
7584
dispatchAsync(message, subscriber, properties);
@@ -111,10 +120,10 @@ private void callSubscriber(final Message<D> message,
111120
private Map<String, Object> fillContext(final Message<D> message,
112121
final Context context) {
113122
LOGGER.debug("[{}] fillContext({})", contextFactory.contextUuid(), message.uuid());
114-
for (ContextFiller<D> filler : contextFillers) {
123+
for (Function<D, Map<String, Object>> filler : contextFillers) {
115124
try {
116125
LOGGER.trace("[{}] filling context from {} for {}", contextFactory.contextUuid(), filler, message.uuid());
117-
filler.fillContext(message.data(), context);
126+
context.properties().putAll(filler.apply(message.data()));
118127
} catch (Exception e) {
119128
LOGGER.error("ContextFiller threw exception for message {}: {}", message.uuid(), filler, e);
120129
}

lib/src/test/java/com/codeheadsystems/microbus/ContextFactoryTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import static org.junit.jupiter.api.Assertions.*;
44

5+
import java.lang.reflect.Field;
56
import java.util.Optional;
7+
import java.util.Stack;
68
import java.util.UUID;
79
import java.util.function.Supplier;
810
import org.junit.jupiter.api.BeforeEach;
@@ -145,4 +147,38 @@ void clearAllContexts_insideWithSupplier_finallySurvivesEmptyStack() {
145147
assertEquals(Optional.empty(), contextFactory.currentContext());
146148
}
147149

150+
@Test
151+
void withContextSet_emptyStack_logsWarningButDoesNotThrow() {
152+
// The finally block handles the case where the stack was cleared during execution.
153+
// This can't happen through the public API alone, so we use reflection.
154+
contextFactory.withRunnable(() -> {
155+
getStack().clear();
156+
});
157+
assertEquals(Optional.empty(), contextFactory.currentContext());
158+
}
159+
160+
@Test
161+
void withContextSet_mismatchedContext_logsWarningButDoesNotThrow() {
162+
// The finally block handles the case where a different context is on top of the stack.
163+
// This can't happen through the public API alone, so we use reflection.
164+
contextFactory.withRunnable(() -> {
165+
Stack<Context> stack = getStack();
166+
stack.pop();
167+
stack.push(new Context(UUID.randomUUID()));
168+
});
169+
assertEquals(Optional.empty(), contextFactory.currentContext());
170+
}
171+
172+
@SuppressWarnings("unchecked")
173+
private Stack<Context> getStack() {
174+
try {
175+
Field field = ContextFactory.class.getDeclaredField("threadLocalContext");
176+
field.setAccessible(true);
177+
ThreadLocal<Stack<Context>> threadLocal = (ThreadLocal<Stack<Context>>) field.get(contextFactory);
178+
return threadLocal.get();
179+
} catch (Exception e) {
180+
throw new RuntimeException(e);
181+
}
182+
}
183+
148184
}

lib/src/test/java/com/codeheadsystems/microbus/MicroBusTest.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import java.util.ArrayList;
66
import java.util.List;
7+
import java.util.Map;
78
import java.util.UUID;
89
import java.util.concurrent.CountDownLatch;
910
import java.util.concurrent.ExecutorService;
1011
import java.util.concurrent.Executors;
1112
import java.util.concurrent.TimeUnit;
1213
import java.util.function.Consumer;
14+
import java.util.function.Function;
1315
import org.junit.jupiter.api.BeforeEach;
1416
import org.junit.jupiter.api.Test;
1517

@@ -184,13 +186,13 @@ void subscribe_nullSubscriber_throws() {
184186
}
185187

186188
@Test
187-
void register_nullFiller_throws() {
188-
assertThrows(NullPointerException.class, () -> bus.register(null));
189+
void registerContextFiller_nullFiller_throws() {
190+
assertThrows(NullPointerException.class, () -> bus.registerContextFiller(null));
189191
}
190192

191193
@Test
192-
void register_fillerPopulatesContextBeforeSubscribers() {
193-
bus.register((data, context) -> context.properties().put("key", data.toUpperCase()));
194+
void registerContextFiller_fillerPopulatesContextBeforeSubscribers() {
195+
bus.registerContextFiller(data -> Map.of("key", data.toUpperCase()));
194196

195197
List<Object> captured = new ArrayList<>();
196198
bus.subscribe(msg -> captured.add(contextFactory.currentContext().orElseThrow().properties().get("key")));
@@ -201,9 +203,9 @@ void register_fillerPopulatesContextBeforeSubscribers() {
201203
}
202204

203205
@Test
204-
void register_multipleFillers_allInvoked() {
205-
bus.register((data, context) -> context.properties().put("a", "1"));
206-
bus.register((data, context) -> context.properties().put("b", "2"));
206+
void registerContextFiller_multipleFillers_allInvoked() {
207+
bus.registerContextFiller(data -> Map.of("a", "1"));
208+
bus.registerContextFiller(data -> Map.of("b", "2"));
207209

208210
List<Object> capturedA = new ArrayList<>();
209211
List<Object> capturedB = new ArrayList<>();
@@ -220,9 +222,9 @@ void register_multipleFillers_allInvoked() {
220222
}
221223

222224
@Test
223-
void register_fillerException_doesNotStopOtherFillersOrSubscribers() {
224-
bus.register((data, context) -> { throw new RuntimeException("filler boom"); });
225-
bus.register((data, context) -> context.properties().put("survived", true));
225+
void registerContextFiller_fillerException_doesNotStopOtherFillersOrSubscribers() {
226+
bus.registerContextFiller(data -> { throw new RuntimeException("filler boom"); });
227+
bus.registerContextFiller(data -> Map.of("survived", true));
226228

227229
List<Object> captured = new ArrayList<>();
228230
bus.subscribe(msg -> captured.add(contextFactory.currentContext().orElseThrow().properties().get("survived")));
@@ -233,15 +235,15 @@ void register_fillerException_doesNotStopOtherFillersOrSubscribers() {
233235
}
234236

235237
@Test
236-
void unregister_removesFiller() {
237-
ContextFiller<String> filler = (data, context) -> context.properties().put("key", "value");
238-
bus.register(filler);
238+
void unregisterContextFiller_removesFiller() {
239+
Function<String, Map<String, Object>> filler = data -> Map.of("key", "value");
240+
bus.registerContextFiller(filler);
239241

240242
List<Object> captured = new ArrayList<>();
241243
bus.subscribe(msg -> captured.add(contextFactory.currentContext().orElseThrow().properties().get("key")));
242244

243245
bus.publish(new Message<>("first", false, UUID_1));
244-
bus.unregister(filler);
246+
bus.unregisterContextFiller(filler);
245247
bus.publish(new Message<>("second", false, UUID_1));
246248

247249
assertEquals(2, captured.size());
@@ -250,9 +252,9 @@ void unregister_removesFiller() {
250252
}
251253

252254
@Test
253-
void register_async_fillerPopulatesContext() throws Exception {
255+
void registerContextFiller_async_fillerPopulatesContext() throws Exception {
254256
CountDownLatch latch = new CountDownLatch(1);
255-
bus.register((data, context) -> context.properties().put("async-key", data));
257+
bus.registerContextFiller(data -> Map.of("async-key", data));
256258

257259
List<Object> captured = new ArrayList<>();
258260
bus.subscribe(msg -> {

0 commit comments

Comments
 (0)