Skip to content

Commit 525fe99

Browse files
add comments and rename K8sHelperGenerator.java to Grammar
1 parent f58f8b2 commit 525fe99

File tree

13 files changed

+102
-61
lines changed

13 files changed

+102
-61
lines changed

annot/src/main/java/com/predic8/membrane/annot/K8sHelperGenerator.java renamed to annot/src/main/java/com/predic8/membrane/annot/Grammar.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import java.util.List;
1818

19-
public interface K8sHelperGenerator {
19+
public interface Grammar {
2020

2121
Class<?> getElement(String key);
2222

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class BeanCache implements BeanRegistry {
2929
private static final Logger log = LoggerFactory.getLogger(BeanCache.class);
3030

3131
private final BeanCacheObserver router;
32-
private final K8sHelperGenerator k8sHelperGenerator;
32+
private final Grammar grammar;
3333
private final ConcurrentHashMap<String, Object> uuidMap = new ConcurrentHashMap<>();
3434
private final ArrayBlockingQueue<ChangeEvent> changeEvents = new ArrayBlockingQueue<>(1000);
3535
private Thread thread;
@@ -47,9 +47,9 @@ record StaticConfigurationLoaded() implements ChangeEvent {
4747
private final Map<String, BeanDefinition> bds = new ConcurrentHashMap<>();
4848
private final Set<String> uidsToActivate = ConcurrentHashMap.newKeySet();
4949

50-
public BeanCache(BeanCacheObserver router, K8sHelperGenerator k8sHelperGenerator) {
50+
public BeanCache(BeanCacheObserver router, Grammar grammar) {
5151
this.router = router;
52-
this.k8sHelperGenerator = k8sHelperGenerator;
52+
this.grammar = grammar;
5353
}
5454

5555
public void start() {
@@ -83,7 +83,7 @@ public Object define(BeanDefinition bd) throws IOException, ParsingException {
8383
log.debug("defining bean: {}", bd.getNode());
8484

8585
return GenericYamlParser.readMembraneObject(bd.getKind(),
86-
k8sHelperGenerator,
86+
grammar,
8787
bd.getNode(),
8888
this);
8989
}

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

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import com.networknt.schema.Schema;
2121
import com.networknt.schema.SchemaLocation;
2222
import com.networknt.schema.SchemaRegistry;
23-
import com.predic8.membrane.annot.K8sHelperGenerator;
23+
import com.predic8.membrane.annot.Grammar;
2424
import org.jetbrains.annotations.NotNull;
2525
import org.slf4j.Logger;
2626
import org.slf4j.LoggerFactory;
@@ -47,19 +47,27 @@ public class GenericYamlParser {
4747
private static final String EMPTY_DOCUMENT_WARNING = "Skipping empty document. Maybe there are two --- separators but no configuration in between.";
4848

4949
/**
50-
* Parses Membrane resources from a YAML input stream.
51-
* @param resource the input stream to parse. The method takes care of closing the stream.
52-
* @param generator the K8s helper generator
53-
* @param observer the bean cache observer
54-
* @return the bean registry
55-
*/
56-
public static BeanRegistry parseMembraneResources(@NotNull InputStream resource, K8sHelperGenerator generator, BeanCacheObserver observer) throws IOException {
50+
* Entry point used by the runtime to consume a YAML stream and turn it into
51+
* a {@link BeanRegistry} that the router can work with.
52+
* <ul>
53+
* <li>Reads the entire stream as UTF-8.</li>
54+
* <li>Splits multi-document YAML ("---" separators).</li>
55+
* <li>Validates each document against the JSON Schema provided by {@code grammar}.</li>
56+
* <li>Emits helpful line/column locations for malformed multi-document input.</li>
57+
* </ul>
58+
* The returned registry is fully populated and {@link BeanCache#fireConfigurationLoaded()} has been called.
59+
* @param resource the input stream to parse. The method takes care of closing the stream.
60+
* @param grammar the grammar to use for type resolution and schema location
61+
* @param observer the bean cache observer
62+
* @return the bean registry
63+
*/
64+
public static BeanRegistry parseMembraneResources(@NotNull InputStream resource, Grammar grammar, BeanCacheObserver observer) throws IOException {
5765
BeanCache registry;
5866
try (resource) {
59-
registry = new BeanCache(observer, generator);
67+
registry = new BeanCache(observer, grammar);
6068
registry.start();
6169

62-
new GenericYamlParser(generator, new String(resource.readAllBytes(), UTF_8))
70+
new GenericYamlParser(grammar, new String(resource.readAllBytes(), UTF_8))
6371
.getBeanDefinitions()
6472
.forEach(bd -> registry.handle(WatchAction.ADDED, bd));
6573

@@ -78,7 +86,19 @@ public static BeanRegistry parseMembraneResources(@NotNull InputStream resource,
7886

7987
List<BeanDefinition> beanDefs = new ArrayList<>();
8088

81-
public GenericYamlParser(K8sHelperGenerator generator, String yaml) throws IOException {
89+
/**
90+
* Parses one or more YAML documents into bean definitions.
91+
* <p>
92+
* The input string may contain multiple YAML documents separated by '---'. Each non-empty
93+
* document is validated against the schema provided by {@link Grammar} and then
94+
* turned into a {@link BeanDefinition}. Validation errors are mapped back to line/column
95+
* numbers using {@link JsonLocationMap} to produce helpful error messages.
96+
* </p>
97+
* @param grammar provides schema location and Java type resolution
98+
* @param yaml the raw YAML content (may contain multi-document stream)
99+
* @throws IOException if schema loading or validation fails
100+
*/
101+
public GenericYamlParser(Grammar grammar, String yaml) throws IOException {
82102
JsonLocationMap jsonLocationMap = new JsonLocationMap();
83103
List<JsonNode> rootNodes = jsonLocationMap.parseWithLocations(yaml);
84104
for (int i = 0; i < rootNodes.size(); i++) {
@@ -89,7 +109,7 @@ public GenericYamlParser(K8sHelperGenerator generator, String yaml) throws IOExc
89109
continue;
90110
}
91111
try {
92-
validate(generator, rootNodes.get(i));
112+
validate(grammar, rootNodes.get(i));
93113
} catch (YamlSchemaValidationException e) {
94114
JsonLocation location = jsonLocationMap.getLocationMap().get(
95115
e.getErrors().getFirst().getInstanceNode());
@@ -117,8 +137,8 @@ private static String getBeanType(JsonNode jsonNode) {
117137
return jsonNode.fieldNames().next();
118138
}
119139

120-
public static void validate(K8sHelperGenerator generator, JsonNode input) throws IOException, YamlSchemaValidationException {
121-
Schema schema = SchemaRegistry.withDefaultDialect(DRAFT_2020_12, builder -> {}).getSchema(SchemaLocation.of(generator.getSchemaLocation()));
140+
public static void validate(Grammar grammar, JsonNode input) throws IOException, YamlSchemaValidationException {
141+
Schema schema = SchemaRegistry.withDefaultDialect(DRAFT_2020_12, builder -> {}).getSchema(SchemaLocation.of(grammar.getSchemaLocation()));
122142
schema.initializeValidators();
123143
List<Error> errors = schema.validate(input);
124144
if (!errors.isEmpty()) {
@@ -127,14 +147,26 @@ public static void validate(K8sHelperGenerator generator, JsonNode input) throws
127147
}
128148

129149

130-
public static Object readMembraneObject(String kind, K8sHelperGenerator generator, JsonNode node, BeanRegistry registry) throws ParsingException {
150+
/**
151+
* Parse a top-level Membrane resource of the given {@code kind}.
152+
* <p>Ensures the node contains exactly one key (the kind), resolves the Java class via the
153+
* grammar and delegates to {@link #parse(ParsingContext, Class, JsonNode)}.</p>
154+
*/
155+
public static Object readMembraneObject(String kind, Grammar grammar, JsonNode node, BeanRegistry registry) throws ParsingException {
131156
ensureSingleKey(node);
132-
Class<?> clazz = generator.getElement(kind);
157+
Class<?> clazz = grammar.getElement(kind);
133158
if (clazz == null)
134159
throw new ParsingException("Did not find java class for kind '%s'.".formatted(kind), node);
135-
return GenericYamlParser.parse(new ParsingContext(kind, registry, generator), clazz, node.get(kind));
160+
return GenericYamlParser.parse(new ParsingContext(kind, registry, grammar), clazz, node.get(kind));
136161
}
137162

163+
/**
164+
* Creates and populates an instance of {@code clazz} from the given YAML/JSON node.
165+
* - Arrays: only valid for {@code @MCElement(noEnvelope=true)}; items are parsed and passed to the single {@code @MCChildElement} list setter.
166+
* - Objects: each field is mapped to a setter resolved by {@link MethodSetter#getMethodSetter(ParsingContext, Class, String)};
167+
* values are produced by {@link #resolveSetterValue(MethodSetter, ParsingContext, JsonNode, String)}. A top-level {@code "$ref"} injects a previously defined bean.
168+
* All failures are wrapped in a {@link ParsingException} with location information.
169+
*/
138170
public static <T> T parse(ParsingContext ctx, Class<T> clazz, JsonNode node) throws ParsingException {
139171
try {
140172
T obj = clazz.getConstructor().newInstance();
@@ -207,6 +239,10 @@ private static List<Object> parseListIncludingStartEvent(ParsingContext context,
207239
return res;
208240
}
209241

242+
/**
243+
* Parses a single-item map node like { kind: {...} } by extracting the only key and
244+
* delegating to {@link #parseMapToObj(ParsingContext, JsonNode, String)}.
245+
*/
210246
private static Object parseMapToObj(ParsingContext context, JsonNode node) throws ParsingException {
211247
ensureSingleKey(node);
212248
String key = node.fieldNames().next();

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ private static boolean equalsAttributeName(Method method, String key) {
6060
|| annotation.attributeName().equals(key);
6161
}
6262

63+
/**
64+
* Returns the single {@code @MCChildElement} setter for a class annotated with
65+
* {@code @MCElement(noEnvelope=true)}.
66+
* <ul>
67+
* <li>Class must be {@code noEnvelope=true}.</li>
68+
* <li>No {@code @MCAttribute} setters are allowed.</li>
69+
* <li>Exactly one child setter must exist and it must accept a {@link java.util.Collection}.</li>
70+
* </ul>
71+
*/
6372
public static <T> Method getSingleChildSetter(Class<T> clazz) {
6473
MCElement annotation = clazz.getAnnotation(MCElement.class);
6574
if (annotation == null || !annotation.noEnvelope()) {

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public MethodSetter(Method setter, Class<?> beanClass) {
2020
this.beanClass = beanClass;
2121
}
2222

23+
/**
24+
* Resolves which setter on {@code clazz} should handle the given YAML field {@code key} and,
25+
* if needed, which bean class that field represents.
26+
* Throws a {@link RuntimeException} if neither a matching setter nor a resolvable bean class can be found.
27+
*/
2328
public static <T> @NotNull MethodSetter getMethodSetter(ParsingContext ctx, Class<T> clazz, String key) {
2429
Method setter = findSetterForKey(clazz, key);
2530
// MCChildElements which are not lists are directly declared as beans,
@@ -31,9 +36,9 @@ public MethodSetter(Method setter, Class<?> beanClass) {
3136
Class<?> beanClass = null;
3237
if (setter == null) {
3338
try {
34-
beanClass = ctx.k8sHelperGenerator().getLocal(ctx.context(), key);
39+
beanClass = ctx.grammar().getLocal(ctx.context(), key);
3540
if (beanClass == null)
36-
beanClass = ctx.k8sHelperGenerator().getElement(key);
41+
beanClass = ctx.grammar().getElement(key);
3742
if (beanClass != null)
3843
setter = getChildSetter(clazz, beanClass);
3944
} catch (Exception e) {

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22

33
import com.predic8.membrane.annot.*;
44

5-
public record ParsingContext(String context, BeanRegistry registry, K8sHelperGenerator k8sHelperGenerator) {
5+
/**
6+
* Immutable parsing state passed down while traversing YAML.
7+
* - context: current element scope used for local type resolution in {@link Grammar}.
8+
* - registry: access to already materialized beans (e.g., for $ref/reference attributes).
9+
* - grammar: resolves element names to Java classes via local/global lookups.
10+
*/
11+
public record ParsingContext(String context, BeanRegistry registry, Grammar grammar) {
612

713
ParsingContext updateContext(String context) {
8-
return new ParsingContext(context, registry, k8sHelperGenerator);
14+
return new ParsingContext(context, registry, grammar);
915
}
1016

1117
public Class<?> resolveClass(String key) {
12-
Class<?> clazz = k8sHelperGenerator.getLocal(context, key);
18+
Class<?> clazz = grammar.getLocal(context, key);
1319
if (clazz == null)
14-
clazz = k8sHelperGenerator.getElement(key);
20+
clazz = grammar.getElement(key);
1521
if (clazz == null)
1622
throw new RuntimeException("Did not find java class for key '%s'.".formatted(key));
1723
return clazz;

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414

1515
package com.predic8.membrane.annot.util;
1616

17-
import com.predic8.membrane.annot.K8sHelperGenerator;
17+
import com.predic8.membrane.annot.Grammar;
1818
import com.predic8.membrane.annot.yaml.*;
19-
import org.yaml.snakeyaml.Yaml;
2019

2120
import java.io.IOException;
22-
import java.io.InputStreamReader;
2321
import java.lang.reflect.InvocationTargetException;
2422
import java.util.concurrent.CountDownLatch;
2523

@@ -29,7 +27,7 @@ public class YamlParser {
2927
private final BeanRegistry beanRegistry;
3028

3129
public YamlParser(String resourceName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, InterruptedException, YamlSchemaValidationException {
32-
K8sHelperGenerator generator = (K8sHelperGenerator) getClass().getClassLoader()
30+
Grammar generator = (Grammar) getClass().getClassLoader()
3331
.loadClass("com.predic8.membrane.demo.config.spring.K8sHelperGeneratorAutoGenerated")
3432
.getConstructor()
3533
.newInstance();

core/src/main/java/com/predic8/membrane/core/Router.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import com.predic8.membrane.annot.yaml.WatchAction;
2424
import com.predic8.membrane.core.RuleManager.RuleDefinitionSource;
2525
import com.predic8.membrane.core.config.spring.BaseLocationApplicationContext;
26-
import com.predic8.membrane.core.config.spring.K8sHelperGeneratorAutoGenerated;
26+
import com.predic8.membrane.core.config.spring.GrammarAutoGenerated;
2727
import com.predic8.membrane.core.config.spring.TrackingApplicationContext;
2828
import com.predic8.membrane.core.config.spring.TrackingFileSystemXmlApplicationContext;
2929
import com.predic8.membrane.core.exceptions.SpringConfigurationErrorHandler;
@@ -720,6 +720,6 @@ else if (bd.getAction() == WatchAction.MODIFIED)
720720

721721
@Override
722722
public boolean isActivatable(BeanDefinition bd) {
723-
return Proxy.class.isAssignableFrom(new K8sHelperGeneratorAutoGenerated().getElement(bd.getKind()));
723+
return Proxy.class.isAssignableFrom(new GrammarAutoGenerated().getElement(bd.getKind()));
724724
}
725725
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
package com.predic8.membrane.core.cli;
1616

1717
import com.predic8.membrane.core.*;
18-
import com.predic8.membrane.core.config.spring.K8sHelperGeneratorAutoGenerated;
18+
import com.predic8.membrane.core.config.spring.GrammarAutoGenerated;
1919
import com.predic8.membrane.core.config.spring.TrackingFileSystemXmlApplicationContext;
2020
import com.predic8.membrane.core.exceptions.*;
2121
import com.predic8.membrane.core.openapi.serviceproxy.*;
@@ -159,7 +159,7 @@ private static Router initRouterByYAML(String location) throws Exception {
159159
router.setAsynchronousInitialization(true);
160160
router.start();
161161

162-
parseMembraneResources(router.getResolverMap().resolve(location), new K8sHelperGeneratorAutoGenerated(), router);
162+
parseMembraneResources(router.getResolverMap().resolve(location), new GrammarAutoGenerated(), router);
163163

164164
return router;
165165
}

core/src/main/java/com/predic8/membrane/core/config/spring/k8s/Envelope.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,20 @@
1414
package com.predic8.membrane.core.config.spring.k8s;
1515

1616
import com.fasterxml.jackson.databind.JsonNode;
17-
import com.predic8.membrane.annot.K8sHelperGenerator;
18-
import com.predic8.membrane.core.config.spring.K8sHelperGeneratorAutoGenerated;
17+
import com.predic8.membrane.annot.Grammar;
18+
import com.predic8.membrane.core.config.spring.GrammarAutoGenerated;
1919
import com.predic8.membrane.annot.yaml.BeanRegistry;
20-
import com.predic8.membrane.annot.yaml.GenericYamlParser;
21-
import org.yaml.snakeyaml.events.Event;
22-
import org.yaml.snakeyaml.events.MappingEndEvent;
23-
import org.yaml.snakeyaml.events.MappingStartEvent;
24-
import org.yaml.snakeyaml.events.ScalarEvent;
2520

2621
import java.util.*;
2722

28-
import static com.predic8.membrane.annot.yaml.YamlLoader.readObj;
29-
import static com.predic8.membrane.annot.yaml.YamlLoader.readString;
30-
3123
public class Envelope {
3224
String kind;
3325
String apiVersion;
3426
Metadata metadata;
3527
JsonNode spec;
3628
final Map<String, Object> additionalProperties = new HashMap<>();
3729

38-
private static final K8sHelperGenerator K8S_HELPER = new K8sHelperGeneratorAutoGenerated();
30+
private static final Grammar K8S_HELPER = new GrammarAutoGenerated();
3931

4032
public void parse(JsonNode node, BeanRegistry registry) {
4133
kind = node.get("kind").asText();

0 commit comments

Comments
 (0)