Skip to content

Commit 9805b06

Browse files
authored
move BeanCacheObservers into the registry (#2556)
* move BeanCacheObservers into the registry * minor fixes * refactor
1 parent 3b30a54 commit 9805b06

13 files changed

Lines changed: 134 additions & 75 deletions

File tree

annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanCacheObserver.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@ public interface BeanCacheObserver {
3535
* Called for an add/modify/delete event of a bean.
3636
*
3737
* @param bd the bean definition changed event
38-
* @param bean the current instance (on ADD/MODIFY) or {@code null} (on DELETE)
38+
* @param newBean the current instance (on ADD/MODIFY) or {@code null} (on DELETE)
3939
* @param oldBean the previous instance (on MODIFY) or {@code null}
4040
* @throws IOException if handling the event performs I/O and it fails
4141
*
4242
*/
43-
void handleBeanEvent(BeanDefinitionChanged bd, Object bean, Object oldBean) throws IOException;
43+
void handleBeanEvent(BeanDefinitionChanged bd, Object newBean, Object oldBean) throws IOException;
4444

4545
/**
4646
* Whether beans of the given definition should be considered activatable/usable
4747
* by the runtime.
4848
*
49-
* @param bd the bean definition
49+
* @param bd the bean container
5050
* @return {@code true} if activatable, {@code false} otherwise
5151
*/
52-
boolean isActivatable(BeanDefinition bd);
52+
boolean isActivatable(BeanContainer bd);
5353
}

annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanContainer.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public class BeanContainer {
2727
private static final Logger log = LoggerFactory.getLogger(BeanContainer.class);
2828

2929
private final BeanDefinition definition;
30+
/**
31+
* The class of the bean.
32+
*/
33+
private final Class clazz;
3034
/**
3135
* Constructed bean after initialization.
3236
*/
@@ -35,16 +39,18 @@ public class BeanContainer {
3539
/**
3640
* Creates a BeanDefinition where the bean has not yet been instantiated and initialized.
3741
*/
38-
public BeanContainer(BeanDefinition definition) {
42+
public BeanContainer(BeanDefinition definition, Grammar grammar) {
3943
this.definition = definition;
44+
this.clazz = GenericYamlParser.decideClazz(definition.getKind(), grammar, definition.getNode());
4045
}
4146

4247
/**
4348
* Creates a BeanDefinition where the bean has already been instantiated and initialized.
4449
*/
45-
public BeanContainer(BeanDefinition definition, Object singleton) {
50+
public BeanContainer(BeanDefinition definition, @NotNull Object singleton) {
4651
this.definition = definition;
4752
this.singleton.set(singleton);
53+
this.clazz = singleton.getClass();
4854
}
4955

5056
/**
@@ -95,6 +101,7 @@ public String toString() {
95101
return define(registry, grammar);
96102
}
97103

104+
98105
// Singleton: ensure define() runs at most once per BeanContainer.
99106
synchronized (this) {
100107
Object existing = getSingleton();
@@ -117,4 +124,10 @@ private static boolean isPrototypeScope(BeanDefinition bd) {
117124
);
118125
}
119126

127+
/**
128+
* Checks whether this bean definition will produce a bean which can be assigned to the clazz.
129+
*/
130+
public <T> boolean produces(Class<T> clazz) {
131+
return clazz.isAssignableFrom(this.clazz);
132+
}
120133
}

annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
import org.slf4j.*;
2121

2222
import javax.annotation.concurrent.*;
23+
import java.io.IOException;
2324
import java.lang.reflect.Method;
2425
import java.util.*;
2526
import java.util.concurrent.*;
2627
import java.util.function.*;
2728

29+
import static com.predic8.membrane.annot.yaml.WatchAction.ADDED;
30+
2831
/**
2932
* TODO:
3033
* - More Tests
@@ -41,7 +44,7 @@ public class BeanRegistryImplementation implements BeanRegistry, BeanCollector,
4144

4245
private static final Logger log = LoggerFactory.getLogger(BeanRegistryImplementation.class);
4346

44-
private final BeanCacheObserver observer;
47+
private final List<BeanCacheObserver> observers = Collections.synchronizedList(new ArrayList<>());
4548
private final Grammar grammar;
4649

4750
// uid -> bean
@@ -67,44 +70,54 @@ record UidAction(String uid, WatchAction action) {
6770
record PreDestroyCallback(Object bean, Method method) {
6871
}
6972

70-
public BeanRegistryImplementation(BeanCacheObserver observer, BeanRegistryAware registryAware, Grammar grammar) {
71-
this.observer = observer;
73+
public BeanRegistryImplementation(Grammar grammar) {
7274
this.grammar = grammar;
73-
registryAware.setRegistry(this);
7475
}
7576

7677
@Override
7778
public void start() {
79+
for (BeanContainer bc : bcs.values()) {
80+
bc.getOrCreate(this, grammar);
81+
}
7882
}
7983

8084
@Override
8185
public void handle(ChangeEvent changeEvent, boolean isLast) {
8286
if (changeEvent instanceof StaticConfigurationLoaded) {
8387
activationRun();
84-
observer.handleAsynchronousInitializationResult(uidsToActivate.isEmpty());
88+
handleAsynchronousInitializationResult(uidsToActivate.isEmpty());
8589
}
8690
if (changeEvent instanceof BeanDefinitionChanged(WatchAction action, BeanDefinition bd)) {
8791
// Keep the latest BeanDefinition for all actions so activationRun
8892
// can see both metadata and the action (including DELETED).
89-
bcs.put(bd.getUid(), new BeanContainer(bd));
90-
91-
if (!bd.isComponent() && observer.isActivatable(bd)) {
92-
uidsToActivate.add(new UidAction(bd.getUid(), action));
93-
}
93+
handleBeanContainerChange(action, new BeanContainer(bd, grammar));
9494
if (isLast)
9595
activationRun();
9696
}
9797
}
9898

99+
private void handleBeanContainerChange(WatchAction action, BeanContainer bc) {
100+
bcs.put(bc.getDefinition().getUid(), bc);
101+
102+
if (bc.produces(BeanCacheObserver.class)) {
103+
observers.add((BeanCacheObserver) bc.getOrCreate(this, grammar));
104+
log.debug("Registered BeanRegistry observer: " + bc);
105+
}
106+
107+
if (!bc.getDefinition().isComponent() && isActivatable(bc)) {
108+
uidsToActivate.add(new UidAction(bc.getDefinition().getUid(), action));
109+
}
110+
}
111+
112+
99113
private void activationRun() {
100114
Set<UidAction> uidsToRemove = new HashSet<>();
101115
for (UidAction uidAction : uidsToActivate) {
102116
BeanContainer bc = bcs.get(uidAction.uid);
103117
try {
104118
Object bean = bc.getOrCreate(this, grammar);
105119

106-
// e.g., inform router about a new ApiProxy or GlobalInterceptor
107-
observer.handleBeanEvent(new BeanDefinitionChanged(uidAction.action, bc.getDefinition()), bean, getOldBean(uidAction.action, bc.getDefinition()));
120+
handleBeanEvent(new BeanDefinitionChanged(uidAction.action, bc.getDefinition()), bean, getOldBean(uidAction.action, bc.getDefinition()));
108121

109122
if (uidAction.action.isAdded() || uidAction.action.isModified())
110123
singletonBeans.put(bc.getDefinition().getUid(), bean);
@@ -155,6 +168,7 @@ public Grammar getGrammar() {
155168
@Override
156169
public <T> List<T> getBeans(Class<T> clazz) {
157170
return bcs.values().stream()
171+
.filter(bd -> bd.produces(clazz))
158172
.map(bc -> bc.getOrCreate(this, grammar))
159173
.filter(Objects::nonNull)
160174
.filter(clazz::isInstance)
@@ -183,15 +197,14 @@ public void register(String beanName, Object bean) {
183197
throw new IllegalArgumentException("bean must not be null");
184198

185199
var uuid = UUID.randomUUID().toString();
186-
bcs.put(uuid,
187-
new BeanContainer(
188-
new BeanDefinition(
189-
"component",
190-
computeBeanName(beanName, uuid),
191-
null,
192-
uuid,
193-
null),
194-
bean));
200+
handleBeanContainerChange(ADDED, new BeanContainer(
201+
new BeanDefinition(
202+
"component",
203+
computeBeanName(beanName, uuid),
204+
null,
205+
uuid,
206+
null),
207+
bean));
195208
singletonBeans.put(uuid, bean);
196209
// the return value of 'put' is ignored, since bean registration with
197210
// random keys should not yield duplicates anyway.
@@ -235,4 +248,38 @@ public void close() {
235248
});
236249
}
237250

251+
/**
252+
* Checks whether any registered Observer is interested in the given bean.
253+
*/
254+
private boolean isActivatable(BeanContainer bd) {
255+
synchronized (observers) {
256+
for (BeanCacheObserver observer : observers) {
257+
if (observer.isActivatable(bd)) return true;
258+
}
259+
}
260+
return false;
261+
}
262+
263+
/**
264+
* Notifies all registered Observers about the result of the asynchronous initialization.
265+
*/
266+
private void handleAsynchronousInitializationResult(boolean empty) {
267+
synchronized (observers) {
268+
for (BeanCacheObserver observer : observers) {
269+
observer.handleAsynchronousInitializationResult(empty);
270+
}
271+
}
272+
}
273+
274+
/**
275+
* Notifies all registered Observers about a bean change.
276+
*/
277+
private void handleBeanEvent(BeanDefinitionChanged event, Object newBean, @Nullable Object oldBean) throws IOException {
278+
synchronized (observers) {
279+
for (BeanCacheObserver observer : observers)
280+
// e.g., inform router about a new ApiProxy or GlobalInterceptor
281+
observer.handleBeanEvent(event, newBean, oldBean);
282+
}
283+
}
284+
238285
}

annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,18 @@ private static void validate(Grammar grammar, JsonNode input) throws YamlSchemaV
141141
* grammar and delegates to {@link #createAndPopulateNode(ParsingContext, Class, JsonNode)}.</p>
142142
*/
143143
public static <R extends BeanRegistry & BeanLifecycleManager> Object readMembraneObject(String kind, Grammar grammar, JsonNode node, R registry) throws ParsingException {
144+
return createAndPopulateNode(new ParsingContext<>(kind, registry, grammar), decideClazz(kind, grammar, node), node.get(kind));
145+
}
146+
147+
/**
148+
* Detects the class that will be selected to represent the node in Java.
149+
*/
150+
public static Class<?> decideClazz(String kind, Grammar grammar, JsonNode node) {
144151
ensureSingleKey(node);
145152
Class<?> clazz = grammar.getElement(kind);
146153
if (clazz == null)
147154
throw new ParsingException("Did not find java class for kind '%s'.".formatted(kind), node);
148-
return createAndPopulateNode(new ParsingContext<>(kind, registry, grammar), clazz, node.get(kind));
155+
return clazz;
149156
}
150157

151158
/**
@@ -306,6 +313,9 @@ private static Object parseMapToObj(ParsingContext<?> ctx, JsonNode node, String
306313
* registered within the registry.
307314
*/
308315
private static <T> T handlePostConstructAndPreDestroy(ParsingContext<?> ctx, T bean) {
316+
if (bean instanceof BeanRegistryAware beanRegistryAware) {
317+
beanRegistryAware.setRegistry(ctx.registry());
318+
}
309319
ReflectionUtils.doWithMethods(bean.getClass(), method -> {
310320
if (method.isAnnotationPresent(PostConstruct.class)) {
311321
try {

annot/src/test/java/com/predic8/membrane/annot/YAMLBeanParsingTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,15 @@ void prototypeResolveReference() {
101101

102102
@Test
103103
void missingClassFailsFastOnResolve() {
104-
BeanRegistry r = parse("""
104+
var ex = assertThrows(RuntimeException.class, () -> {
105+
BeanRegistry r = parse("""
105106
components:
106107
x:
107108
bean:
108109
scope: singleton
109110
""");
110-
111-
var ex = assertThrows(RuntimeException.class, () -> r.resolve("#/components/x"));
112-
assertAnyErrorContains(ex, "Missing/blank 'class'");
111+
});
112+
assertAnyErrorContains(ex, "Missing/blank 'class' in bean spec.");
113113
}
114114

115115
@Test

annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ class BeanRegistryImplementationTest {
2929
@BeforeEach
3030
void setup() {
3131
aware = Mockito.mock(BeanRegistryAware.class);
32-
registry = new BeanRegistryImplementation(null, aware, null);
32+
registry = new BeanRegistryImplementation(null);
33+
registry.register("aware", aware);
3334
}
3435

3536
@Test

annot/src/test/java/com/predic8/membrane/annot/util/YamlParser.java

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ public class YamlParser {
3737
*/
3838
private final BeanRegistry beanRegistry;
3939

40-
private static class TestRouter implements BeanRegistryAware {
41-
42-
@Override
43-
public void setRegistry(BeanRegistry registry) {
44-
45-
}
46-
}
47-
4840
public YamlParser(String resourceName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, InterruptedException {
4941
Grammar generator = getGrammar();
5042

@@ -55,11 +47,11 @@ public YamlParser(String resourceName) throws ClassNotFoundException, NoSuchMeth
5547
String normalized = resourceName.startsWith("/") ?
5648
resourceName.substring(1) : resourceName;
5749

58-
BeanRegistryImplementation impl = new BeanRegistryImplementation(getLatchObserver(cdl), new TestRouter(), generator);
50+
BeanRegistryImplementation impl = new BeanRegistryImplementation(generator);
5951
impl.parseYamls(requireNonNull(cl.getResourceAsStream(normalized)), generator);
60-
beanRegistry = impl;
52+
impl.start();
6153

62-
cdl.await();
54+
beanRegistry = impl;
6355
}
6456

6557
private @NotNull Grammar getGrammar()
@@ -78,28 +70,6 @@ public YamlParser(String resourceName) throws ClassNotFoundException, NoSuchMeth
7870
return (Grammar) grammarClass.getConstructor().newInstance();
7971
}
8072

81-
/**
82-
* Used to get notification about termination of parsing
83-
*/
84-
private static @NotNull BeanCacheObserver getLatchObserver(CountDownLatch cdl) {
85-
return new BeanCacheObserver() {
86-
@Override
87-
public void handleAsynchronousInitializationResult(boolean empty) {
88-
cdl.countDown();
89-
}
90-
91-
@Override
92-
public void handleBeanEvent(BeanDefinitionChanged bdc, Object bean, Object oldBean) {
93-
94-
}
95-
96-
@Override
97-
public boolean isActivatable(BeanDefinition bd) {
98-
return true;
99-
}
100-
};
101-
}
102-
10373
/**
10474
* Called by reflection from the YAML parser in CompilerHelper
10575
* @return BeanRegistry

core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,15 @@ private static Router initRouterByYAML(String location) throws Exception {
163163
router.getConfiguration().setBaseLocation(location);
164164

165165
GrammarAutoGenerated grammar = new GrammarAutoGenerated();
166-
BeanRegistryImplementation reg = new BeanRegistryImplementation(router, router, grammar);
166+
BeanRegistryImplementation reg = new BeanRegistryImplementation(grammar);
167+
reg.register("router", router);
167168

168169
getConfigDefinition(reg.parseYamlBeanDefinitions(router.getResolverMap().resolve(location), grammar))
169170
.ifPresent(beanDefinition -> router.applyConfiguration((Configuration) reg.resolve(beanDefinition.getName())));
170171

171172
reg.finishStaticConfiguration();
172173

174+
reg.start();
173175
router.start();
174176
logStartupMessage();
175177
return router;

0 commit comments

Comments
 (0)