Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ba7e149
rm schema validation
christiangoerdes Mar 19, 2026
185287c
add IncludeList for configuration
christiangoerdes Mar 19, 2026
f71f1cb
implement recursive parsing for included files
christiangoerdes Mar 19, 2026
3aed931
extract methods and add IncludeContext
christiangoerdes Mar 19, 2026
5bdd15f
add documenation
christiangoerdes Mar 19, 2026
9691e61
generate IncludeList.java
christiangoerdes Mar 19, 2026
c81dcf3
improve errors
christiangoerdes Mar 19, 2026
29f8b46
sort files from dir
christiangoerdes Mar 19, 2026
8345a29
add test for include list
christiangoerdes Mar 23, 2026
290122f
add example
christiangoerdes Mar 23, 2026
c35a9ee
Merge branch 'master' into yaml-include-list
christiangoerdes Mar 24, 2026
a034659
fix rootPath problem
christiangoerdes Mar 24, 2026
74a5d1b
Merge remote-tracking branch 'origin/yaml-include-list' into yaml-inc…
christiangoerdes Mar 24, 2026
ec536b3
add license
christiangoerdes Mar 24, 2026
1f41034
add return type
christiangoerdes Mar 24, 2026
6467f5a
add error for duplicate component ids
christiangoerdes Mar 24, 2026
9d9df44
add SourceMetadata to BeanDefinition
christiangoerdes Mar 24, 2026
1eefa60
add BeanDefinition to Interceptors and Proxies
christiangoerdes Mar 25, 2026
dba7f7f
replace router base location with bean baselocation
christiangoerdes Mar 25, 2026
107a0cc
replace router base location with bean baselocation
christiangoerdes Mar 25, 2026
82fe3fe
add null check for tests
christiangoerdes Mar 25, 2026
de63e38
fix path resolving
christiangoerdes Mar 25, 2026
eaa2699
fix path resolving
christiangoerdes Mar 25, 2026
eab9a1e
rm unused method
christiangoerdes Mar 25, 2026
06fe3cf
add test for sourceMetadata
christiangoerdes Mar 25, 2026
3919a7a
improve example
christiangoerdes Mar 25, 2026
81c700f
rn example dir
christiangoerdes Mar 25, 2026
fbb0418
enable using file URIs
christiangoerdes Mar 25, 2026
a175076
add and improve error for multiple configuration and global defs
christiangoerdes Mar 25, 2026
2a0ca3c
add filename to error
christiangoerdes Mar 25, 2026
aafa3cc
load double includes only once
christiangoerdes Mar 25, 2026
cdbc48f
add win absolut path check
christiangoerdes Mar 25, 2026
c3c447b
Merge branch 'master' into yaml-include-list-base-path-resolving
christiangoerdes Mar 30, 2026
7c7f8a2
optimize imports
christiangoerdes Mar 30, 2026
788d2c8
add getBeanDefinition for bean
christiangoerdes Mar 30, 2026
ead6d34
remove BeanDefinitionAware and related references across the codebase
christiangoerdes Mar 30, 2026
eb5a556
add documentation
christiangoerdes Mar 30, 2026
14c8aca
refactor to parsing package
christiangoerdes Mar 30, 2026
205bd17
requested changes
christiangoerdes Mar 30, 2026
a44de34
add BeanDefinitions record
christiangoerdes Mar 30, 2026
e38c094
refactor GenericYamlParser and related parsing logic
christiangoerdes Mar 30, 2026
83f3ebf
rm SourceContext.java
christiangoerdes Mar 30, 2026
c1d0aa1
rm basepath from SourceMetadata
christiangoerdes Mar 30, 2026
f499c47
rm redundancies
christiangoerdes Mar 30, 2026
cd9c0de
improve code
christiangoerdes Mar 30, 2026
0a61aeb
fix annot tests
christiangoerdes Mar 31, 2026
af96988
merge SourceMetadataSupport.java with SourceMetadata
christiangoerdes Mar 31, 2026
268b38b
add error for findSingleSetterOrNullForAnnotation
christiangoerdes Mar 31, 2026
2d3ed4d
rm dead code
christiangoerdes Mar 31, 2026
a854737
use same session
christiangoerdes Mar 31, 2026
953eec2
ensure router is set before accessing baselocation
christiangoerdes Mar 31, 2026
2620e94
improve OSUtil and add test
christiangoerdes Mar 31, 2026
b5eb96c
fix path to example folder
christiangoerdes Mar 31, 2026
e93916a
add license
christiangoerdes Mar 31, 2026
e116854
Merge branch 'master' into yaml-include-list-base-path-resolving-pars…
christiangoerdes Apr 1, 2026
86a3b24
Merge branch 'master' into yaml-include-list-base-path-resolving-pars…
christiangoerdes Apr 2, 2026
03485b0
Merge branch 'master' into yaml-include-list-base-path-resolving-pars…
christiangoerdes Apr 10, 2026
5ee2e4c
use soapProxy baselocation
christiangoerdes Apr 10, 2026
64e40de
Merge branch 'master' into yaml-include-list-base-path-resolving-pars…
christiangoerdes Apr 13, 2026
124001b
Merge branch 'master' into yaml-include-list-base-path-resolving-pars…
predic8 Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,8 @@ public void process(Model m) throws IOException {
return; // we will be called again to handle the newly generated class.
if (new BeanClassGenerator(processingEnv).writeJava(m))
return; // we will be called again to handle the newly generated class.
if (new IncludeListClassGenerator(processingEnv).writeJava(m))
return; // we will be called again to handle the newly generated class.
new Schemas(processingEnv).writeXSD(m);
new KubernetesBootstrapper(processingEnv).boot(m);
new JsonSchemaGenerator(processingEnv).write(m);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,29 @@
package com.predic8.membrane.annot.beanregistry;

import com.predic8.membrane.annot.Grammar;
import com.predic8.membrane.annot.yaml.GenericYamlParser;
import com.predic8.membrane.annot.yaml.WatchAction;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;

import static com.predic8.membrane.annot.yaml.WatchAction.ADDED;
import static com.predic8.membrane.annot.yaml.parsing.GenericYamlParser.parseMembraneResources;

/**
* This is the definition side of a {@link BeanRegistryImplementation}. You can start the bean registry
* and send it a series of change events.
*/
public interface BeanCollector {

default List<BeanDefinition> parseYamlBeanDefinitions(InputStream yamls, Grammar grammar) throws IOException {
List<BeanDefinition> bds = GenericYamlParser.parseMembraneResources(yamls, grammar);
return parseYamlBeanDefinitions(yamls, grammar, null);
}

default List<BeanDefinition> parseYamlBeanDefinitions(InputStream yamls, Grammar grammar, Path rootSourceFile) throws IOException {
List<BeanDefinition> bds = parseMembraneResources(yamls, grammar, rootSourceFile);
for (BeanDefinition bd : bds) {
handle(new BeanDefinitionChanged(WatchAction.ADDED, bd), false);
handle(new BeanDefinitionChanged(ADDED, bd), false);
}
return bds;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
import com.predic8.membrane.annot.Grammar;
import com.predic8.membrane.annot.bean.BeanFactory;
import com.predic8.membrane.annot.yaml.*;
import com.predic8.membrane.annot.yaml.parsing.GenericYamlParser;
import org.jetbrains.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.*;

import static com.predic8.membrane.annot.yaml.parsing.GenericYamlParser.readMembraneObject;

public class BeanContainer {
private static final Logger log = LoggerFactory.getLogger(BeanContainer.class);

Expand Down Expand Up @@ -80,20 +83,33 @@ public String toString() {

private synchronized @NotNull Object define(BeanRegistryImplementation registry, Grammar grammar) {
log.debug("defining bean: {}", definition.getNode());
BeanDefinitionContext.push(definition);
try {
Object created;
if ("bean".equals(definition.getKind())) {
return new BeanFactory(registry).create(definition.getNode().path("bean"));
created = new BeanFactory(registry).create(definition.getNode().path("bean"));
} else {
created = readMembraneObject(definition.getKind(),
grammar,
definition.getNode(),
registry);
}
return GenericYamlParser.readMembraneObject(definition.getKind(),
grammar,
definition.getNode(),
registry);

registry.rememberBeanDefinition(created, definition);
return created;
} catch (ConfigurationParsingException e) {
if (e.getSourceFile() == null
&& definition.getSourceMetadata() != null
&& definition.getSourceMetadata().sourceFile() != null) {
e.setSourceFile(definition.getSourceMetadata().sourceFile().toAbsolutePath().normalize());
}
throw e;
}
catch (Exception e) {
log.error("Could not instantiate bean: {}", definition.getNode(), e);
throw new RuntimeException(e);
} finally {
BeanDefinitionContext.pop();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import com.fasterxml.jackson.databind.*;
import com.predic8.membrane.annot.yaml.*;
import com.predic8.membrane.annot.yaml.parsing.source.SourceMetadata;

import static com.predic8.membrane.annot.yaml.parsing.source.SourceMetadata.empty;

/**
* Immutable.
Expand All @@ -28,6 +31,7 @@ public class BeanDefinition {
private final String uid;
private final JsonNode node;
private final String kind;
private final SourceMetadata sourceMetadata;

/**
* Only called from K8S.
Expand All @@ -44,18 +48,24 @@ private BeanDefinition(JsonNode node) {
throw new IllegalArgumentException("name is null");
namespace = metadata.get("namespace").asText();
uid = metadata.get("uid").asText();
sourceMetadata = empty();
}

public static BeanDefinitionChanged create4Kubernetes(WatchAction action, JsonNode node) {
return new BeanDefinitionChanged(action, new BeanDefinition(node));
}

public BeanDefinition(String kind, String name, String namespace, String uid, JsonNode node) {
this(kind, name, namespace, uid, node, empty());
}

public BeanDefinition(String kind, String name, String namespace, String uid, JsonNode node, SourceMetadata sourceMetadata) {
this.kind = kind;
this.name = name;
this.namespace = namespace;
this.uid = uid;
this.node = node;
this.sourceMetadata = sourceMetadata == null ? empty() : sourceMetadata;
}

public JsonNode getNode() {
Expand All @@ -78,6 +88,14 @@ public String getKind() {
return kind;
}

/**
* Retrieves the source metadata associated with this bean definition.
* @return the {@link SourceMetadata} containing information about the source files for this bean definition.
*/
public SourceMetadata getSourceMetadata() {
return sourceMetadata;
}

public String getScope() {
if (node == null)
return null;
Expand All @@ -103,14 +121,22 @@ public boolean isPrototype() {
return PROTOTYPE.equals(getScope());
}

public String formatConfigLocation() {
if (sourceMetadata != null && sourceMetadata.sourceFile() != null) {
return sourceMetadata.sourceFile().toString();
}
return name;
}

@Override
public String toString() {
return "BeanDefinition{" +
"name='" + name + '\'' +
", namespace='" + namespace + '\'' +
", uid='" + uid + '\'' +
", node=" + node +
", kind='" + kind + '\'' +
'}';
", namespace='" + namespace + '\'' +
", uid='" + uid + '\'' +
", node=" + node +
", kind='" + kind + '\'' +
", sourceMetadata=" + sourceMetadata +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* Copyright 2026 predic8 GmbH, www.predic8.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

package com.predic8.membrane.annot.beanregistry;

import java.util.ArrayDeque;
import java.util.Deque;

public final class BeanDefinitionContext {
private static final ThreadLocal<Deque<BeanDefinition>> STACK = ThreadLocal.withInitial(ArrayDeque::new);

private BeanDefinitionContext() {
}

public static void push(BeanDefinition beanDefinition) {
STACK.get().push(beanDefinition);
}

public static void pop() {
Deque<BeanDefinition> stack = STACK.get();
if (!stack.isEmpty()) {
stack.pop();
}
if (stack.isEmpty()) {
STACK.remove();
}
}

public static BeanDefinition current() {
Deque<BeanDefinition> stack = STACK.get();
return stack.isEmpty() ? null : stack.peek();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* Copyright 2026 predic8 GmbH, www.predic8.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

package com.predic8.membrane.annot.beanregistry;

import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Wraps parsed bean definitions and provides simple validation helpers for multi-file configs.
*/
public record BeanDefinitions(List<BeanDefinition> definitions) {

public BeanDefinitions {
definitions = List.copyOf(definitions);
}

public @NotNull Optional<BeanDefinition> getConfigDefinition() {
List<BeanDefinition> configurationDefinitions = definitions.stream()
.filter(bd -> "configuration".equals(bd.getKind()))
.toList();

if (configurationDefinitions.size() > 1) {
throw new IllegalStateException("Found multiple 'configuration' definitions (%d). Only one is allowed. Found at: %s"
.formatted(configurationDefinitions.size(), configurationDefinitions.stream()
.map(BeanDefinition::formatConfigLocation)
.collect(Collectors.joining(", "))));
}

return configurationDefinitions.stream().findFirst();
}

public void ensureSingleGlobalDefinition() {
List<BeanDefinition> globalDefinitions = definitions.stream()
.filter(bd -> "global".equals(bd.getKind()))
.toList();

if (globalDefinitions.size() > 1) {
throw new IllegalStateException("Found multiple 'global' definitions (%d). Only one is allowed. Found at: %s"
.formatted(globalDefinitions.size(), globalDefinitions.stream()
.map(BeanDefinition::formatConfigLocation)
.collect(Collectors.joining(", "))));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import com.predic8.membrane.annot.*;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.*;

Expand Down Expand Up @@ -48,6 +47,21 @@ public interface BeanRegistry {
*/
<T> Optional<T> getBean(String name, Class<T> clazz);

/**
* Retrieves the {@code BeanDefinition} corresponding to the given object.
* @param obj the object for which the {@code BeanDefinition} is to be retrieved
* @return the {@code BeanDefinition} corresponding to the given object
*/
BeanDefinition getBeanDefinition(Object obj);

/**
* Associates the given bean instance with its {@code BeanDefinition}.
*
* @param bean the bean instance
* @param definition the bean definition
*/
void rememberBeanDefinition(Object bean, BeanDefinition definition);

/**
* Registers a bean with the specified name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import java.util.function.*;

import static com.predic8.membrane.annot.yaml.WatchAction.*;
import static java.util.Collections.synchronizedMap;
import static java.util.Collections.synchronizedSet;
import static java.util.UUID.randomUUID;

/**
* TODO:
Expand All @@ -53,8 +56,11 @@ public class BeanRegistryImplementation implements BeanRegistry, BeanCollector,
// uid -> bean container
private final Map<String, BeanContainer> bcs = new ConcurrentHashMap<>(); // Order is not critical. Order is determined by uidsToActivate

// TODO optimize this for dynamic changes
private final Map<Object, BeanDefinition> beanDefinitions = synchronizedMap(new IdentityHashMap<>());

@GuardedBy("uidsToActivate")
private final Set<UidAction> uidsToActivate = Collections.synchronizedSet(new LinkedHashSet<>()); // keeps order
private final Set<UidAction> uidsToActivate = synchronizedSet(new LinkedHashSet<>()); // keeps order

/**
* Protects the initialization of beans, which are unique per class.
Expand Down Expand Up @@ -193,19 +199,29 @@ public <T> Optional<T> getBean(String beanname, Class<T> clazz) {
.map(clazz::cast);
}

@Override
public @Nullable BeanDefinition getBeanDefinition(Object obj) {
if (obj == null) return null;

if (obj instanceof BeanDefinition beanDefinition)
return beanDefinition;

return beanDefinitions.get(obj);
}

public void register(String beanName, Object bean) {
if (bean == null)
throw new IllegalArgumentException("bean must not be null");

var uuid = UUID.randomUUID().toString();
handleBeanContainerChange(ADDED, new BeanContainer(
new BeanDefinition(
"component",
computeBeanName(beanName, uuid),
null,
uuid,
null),
bean));
var uuid = randomUUID().toString();
BeanDefinition definition = new BeanDefinition(
"component",
computeBeanName(beanName, uuid),
null,
uuid,
null);
rememberBeanDefinition(bean, definition);
handleBeanContainerChange(ADDED, new BeanContainer(definition, bean));
singletonBeans.put(uuid, bean);
// the return value of 'put' is ignored, since bean registration with
// random keys should not yield duplicates anyway.
Expand Down Expand Up @@ -249,6 +265,14 @@ public void close() {
});
}

@Override
public void rememberBeanDefinition(Object bean, BeanDefinition definition) {
if (bean == null || definition == null)
return;

beanDefinitions.put(bean, definition);
}

/**
* Checks whether any registered Observer is interested in the given bean.
*/
Expand Down
Loading
Loading