Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2756d0f
refactor: move globalInterceptor to beanFactory
christiangoerdes Dec 23, 2025
1e2bede
Merge branch 'master' into router-refactor-to-beanfactory
predic8 Dec 24, 2025
e76ce7f
refactor: streamline exception messages, improve logging, and enhance…
predic8 Dec 24, 2025
e21be9a
feat: global interceptor support for YAML
predic8 Dec 24, 2025
dfdca86
refactor: streamline imports, remove unused methods, and improve exce…
predic8 Dec 24, 2025
520164b
Merge branch 'master' into router-refactor-to-beanfactory
predic8 Dec 24, 2025
de82e1f
refactor: remove unused `setRouter` method from `Transport` and enhan…
predic8 Dec 24, 2025
cf52d74
Merge remote-tracking branch 'origin/router-refactor-to-beanfactory' …
predic8 Dec 24, 2025
adbe257
refactor: remove unused imports, simplify package structure, and clea…
predic8 Dec 25, 2025
21ae9a1
refactor: remove unused imports, simplify package structure, and clea…
predic8 Dec 25, 2025
6346a37
refactor: extend and streamline BeanRegistry integration in Router, i…
predic8 Dec 25, 2025
88af125
refactor: streamline imports in `GenericYamlParserTest`, improve read…
predic8 Dec 25, 2025
2df4c3b
Merge branch 'master' into router-refactor-to-beanfactory
predic8 Dec 25, 2025
4d3917e
refactor: simplify `registerIfAbsent` usage in `Router` and `BeanRegi…
predic8 Dec 25, 2025
d773f92
refactor: simplify `registerIfAbsent` by removing `name` parameter, s…
predic8 Dec 25, 2025
961a43e
Merge remote-tracking branch 'origin/router-refactor-to-beanfactory' …
predic8 Dec 25, 2025
5369b43
refactor: remove duplicate `openPorts` field from `Router`
predic8 Dec 25, 2025
a197d78
refactor: enhance concurrency handling in `BeanRegistryImplementation…
predic8 Dec 25, 2025
612cef6
refactor: replace `Router.init` with `Router.initByXML`, improve thre…
predic8 Dec 26, 2025
7be9eab
refactor: remove redundant `Exception` declarations, simplify `tearDo…
predic8 Dec 26, 2025
3d4c808
refactor: simplify Router lifecycle methods, streamline test setup/te…
predic8 Dec 26, 2025
c9dd5c4
refactor: streamline imports in test files and remove redundant `Exce…
predic8 Dec 26, 2025
bf9f618
refactor: improve thread-safety in `registerIfAbsent` by introducing …
predic8 Dec 26, 2025
4be156a
refactor: update test lifecycle methods to use `@BeforeAll`/`@AfterAl…
predic8 Dec 26, 2025
7564a5e
refactor: refactor `Router` lifecycle methods, remove `openPorts` han…
predic8 Dec 28, 2025
3e5715f
refactor: streamline imports, remove redundant declarations, and impr…
predic8 Dec 28, 2025
1c5f812
refactor: improve Router lifecycle management, update test setup/tear…
predic8 Dec 29, 2025
f873164
refactor: replace `Router.initByXML` with `RouterBootstrap.initByXML`…
predic8 Dec 29, 2025
e221d3a
refactor: make `router` field `final` in `RuleReinitializer` to ensur…
predic8 Dec 29, 2025
6dd1ab6
refactor: improve `Router` lifecycle methods, enhance thread-safety, …
predic8 Dec 29, 2025
2a6a7cc
refactor: remove unnecessary `Exception` declaration from `initOasi` …
predic8 Dec 29, 2025
be5387d
refactor: enhance `Router` lifecycle handling, improve thread-safety,…
predic8 Dec 29, 2025
86f1857
refactor: add TODO for replacing `backgroundInitializer` with virtual…
predic8 Dec 29, 2025
9d8d0bf
Merge branch 'master' into router-refactor-to-beanfactory
predic8 Dec 29, 2025
6ce17f2
merge
predic8 Dec 29, 2025
9955a07
refactor: replace `Router` with `HttpRouter` in test files and update…
predic8 Dec 30, 2025
0308efc
Merge branch 'master' into router-refactor-to-beanfactory
predic8 Dec 30, 2025
cc5fa67
refactor: improve thread-safety in `DefaultHotDeployer`, enhance logg…
predic8 Dec 30, 2025
27317a6
Merge remote-tracking branch 'origin/router-refactor-to-beanfactory' …
predic8 Dec 30, 2025
7a8e80c
refactor: replace `Router` with `IRouter` for better abstraction and …
predic8 Dec 30, 2025
6539c3d
refactor: introduce `AbstractRouter` for shared functionality, replac…
predic8 Dec 30, 2025
1afe5be
refactor: remove `IRouter` interface, replace with `DefaultRouter` or…
predic8 Dec 30, 2025
4b9b06f
refactor: replace `router.getConfig()` with `router.getConfiguration(…
predic8 Dec 30, 2025
df16814
refactor: replace `router.shutdown()` with `router.stop()` and introd…
predic8 Dec 30, 2025
feea582
refactor: improve thread-safety and synchronization in `BeanRegistryI…
predic8 Dec 31, 2025
1623f85
refactor: extend timeout in `MassivelyParallelTest` to improve test s…
predic8 Dec 31, 2025
4166d52
improved synchronization
rrayst Jan 1, 2026
8575853
doc
rrayst Jan 1, 2026
c169acf
small improvements
rrayst Jan 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,52 @@

package com.predic8.membrane.annot.beanregistry;

import com.predic8.membrane.annot.Grammar;
import com.predic8.membrane.annot.bean.BeanFactory;
import com.predic8.membrane.annot.yaml.GenericYamlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.*;
Comment thread
predic8 marked this conversation as resolved.

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

private final BeanDefinition definition;
/**
* Constructed bean after initialization.
*/
private volatile Object singleton;
private final AtomicReference<Object> singleton = new AtomicReference<>();
Comment thread
rrayst marked this conversation as resolved.

/**
* Creates a BeanDefinition where the bean has not yet been instantiated and initialized.
*/
public BeanContainer(BeanDefinition definition) {
this.definition = definition;
}

/**
* Creates a BeanDefinition where the bean has already been instantiated and initialized.
*/
public BeanContainer(BeanDefinition definition, Object singleton) {
this.definition = definition;
this.singleton.set(singleton);
}

public Object getSingleton() {
return singleton;
/**
* Only to be used within this class.
* Use {@link #getOrCreate(BeanRegistryImplementation, Grammar)} instead.
*/
private Object getSingleton() {
return singleton.get();
}

public void setSingleton(Object singleton) {
this.singleton = singleton;
/**
* Only to be used within this class.
* Use {@link #getOrCreate(BeanRegistryImplementation, Grammar)} instead.
*/
private void setSingleton(Object singleton) {
this.singleton.set(singleton);
}

public BeanDefinition getDefinition() {
Expand All @@ -40,6 +68,52 @@ public BeanDefinition getDefinition() {

@Override
public String toString() {
return "BeanContainer: %s of %s".formatted( definition.getName(),definition.getKind());
return "BeanContainer: %s of %s singleton: %s".formatted( definition.getName(),definition.getKind(),singleton.get());
}

private synchronized Object define(BeanRegistryImplementation registry, Grammar grammar) {
log.debug("defining bean: {}", definition.getNode());
try {
if ("bean".equals(definition.getKind())) {
return new BeanFactory(registry).create(definition.getNode().path("bean"));
}
return GenericYamlParser.readMembraneObject(definition.getKind(),
grammar,
definition.getNode(),
registry);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public Object getOrCreate(BeanRegistryImplementation registry, Grammar grammar) {
boolean prototype = isPrototypeScope(getDefinition());

// Prototypes are created anew every time.
if (prototype) {
return define(registry, grammar);
}

// Singleton: ensure define() runs at most once per BeanContainer.
synchronized (this) {
Object existing = getSingleton();
if (existing != null) {
return existing;
}

Object created = define(registry, grammar);
setSingleton(created);
return created;
}
}

private static boolean isPrototypeScope(BeanDefinition bd) {
if (!bd.isBean())
return bd.isPrototype();

return "PROTOTYPE".equalsIgnoreCase(
bd.getNode().path("bean").path("scope").asText("SINGLETON")
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import com.fasterxml.jackson.databind.*;
import com.predic8.membrane.annot.yaml.*;

/**
* Immutable.
*/
public class BeanDefinition {

public static final String PROTOTYPE = "prototype";
Expand Down Expand Up @@ -76,6 +79,8 @@ public String getKind() {
}

public String getScope() {
if (node == null)
return null;
JsonNode meta = node.get("metadata");
if (meta == null)
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.predic8.membrane.annot.*;

import java.util.*;
import java.util.function.*;

public interface BeanRegistry {

Expand Down Expand Up @@ -44,4 +45,15 @@ public interface BeanRegistry {
* @param bean instance to register (must not be null)
*/
void register(String beanName, Object bean);

/**
* Registers a bean of the specified type with the given name if it is not already registered.
* If a bean with the given name is already present, the existing instance is returned.
* Otherwise, the supplier is used to create and register a new instance.
* @param type the class type of the bean
* @param supplier a supplier that provides a new instance of the bean if not already registered
* @param <T> the generic type of the bean
* @return the existing or newly created and registered bean instance
*/
<T> T registerIfAbsent(Class<T> type, Supplier<T> supplier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,29 @@
limitations under the License. */
package com.predic8.membrane.annot.beanregistry;

import com.predic8.membrane.annot.Grammar;
import com.predic8.membrane.annot.bean.BeanFactory;
import com.predic8.membrane.annot.yaml.GenericYamlParser;
import com.predic8.membrane.annot.yaml.WatchAction;
import com.predic8.membrane.annot.*;
import com.predic8.membrane.annot.bean.*;
import com.predic8.membrane.annot.yaml.*;
import org.jetbrains.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.*;

import javax.annotation.concurrent.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.*;
import java.util.function.*;

/**
* TODO:
* - More Tests
* - Document
* - For TB Unclear: Lifecycle activation/resolve
* - oldbean
* - a.) Revert to second list (singletonBeans)
* - b.) Give proxies an unique id
* <p>
* For K8S UUID and name is needed cause name is only unique within a namespace.
*
*/
public class BeanRegistryImplementation implements BeanRegistry, BeanCollector {

private static final Logger log = LoggerFactory.getLogger(BeanRegistryImplementation.class);
Expand All @@ -36,31 +48,24 @@ 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

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

record UidAction(String uid, WatchAction action) {}
/**
* Protects the initialization of beans, which are unique per class.
*/
private final Object uniqueClassInitialization = new Object();

record UidAction(String uid, WatchAction action) {
}

public BeanRegistryImplementation(BeanCacheObserver observer, BeanRegistryAware registryAware, Grammar grammar) {
this.observer = observer;
this.grammar = grammar;
registryAware.setRegistry(this);
}

private Object define(BeanDefinition bd) {
log.debug("defining bean: {}", bd.getNode());
try {
if ("bean".equals(bd.getKind())) {
return new BeanFactory(this).create(bd.getNode().path("bean"));
}
return GenericYamlParser.readMembraneObject(bd.getKind(),
grammar,
bd.getNode(),
this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void start() {
}
Expand Down Expand Up @@ -89,10 +94,9 @@ private void activationRun() {
for (UidAction uidAction : uidsToActivate) {
BeanContainer bc = bcs.get(uidAction.uid);
try {
Object bean = define(bc.getDefinition());
bc.setSingleton(bean);
Object bean = bc.getOrCreate(this, grammar);

// e.g. inform router about new proxy
// e.g., inform router about a new ApiProxy or GlobalInterceptor
observer.handleBeanEvent(new BeanDefinitionChanged(uidAction.action, bc.getDefinition()), bean, getOldBean(uidAction.action, bc.getDefinition()));

if (uidAction.action.isAdded() || uidAction.action.isModified())
Expand Down Expand Up @@ -121,19 +125,7 @@ private void activationRun() {

@Override
public Object resolve(String url) {
BeanContainer bc = getFirstByName(url).orElseThrow(() -> new RuntimeException("Reference %s not found".formatted(url)));

boolean prototype = isPrototypeScope(bc.getDefinition());

if (!prototype && bc.getSingleton() != null)
return bc.getSingleton();

Object instance = define(bc.getDefinition());

if (!prototype)
bc.setSingleton(instance);

return instance;
return getFirstByName(url).orElseThrow(() -> new RuntimeException("Reference %s not found".formatted(url))).getOrCreate(this, grammar);
}

private @NotNull Optional<BeanContainer> getFirstByName(String url) {
Expand All @@ -143,7 +135,7 @@ public Object resolve(String url) {
@Override
public List<Object> getBeans() {
return bcs.values().stream().filter(bd -> !bd.getDefinition().isComponent())
.map(BeanContainer::getSingleton)
.map(bc -> bc.getOrCreate(this, grammar))
.filter(Objects::nonNull)
.toList();
}
Expand All @@ -153,19 +145,10 @@ public Grammar getGrammar() {
return grammar;
}

private static boolean isPrototypeScope(BeanDefinition bd) {
if (!bd.isBean())
return bd.isPrototype();

return "PROTOTYPE".equalsIgnoreCase(
bd.getNode().path("bean").path("scope").asText("SINGLETON")
);
}

@Override
public <T> List<T> getBeans(Class<T> clazz) {
return bcs.values().stream()
.map(BeanContainer::getSingleton)
.map(bc -> bc.getOrCreate(this, grammar))
.filter(Objects::nonNull)
.filter(clazz::isInstance)
.map(clazz::cast)
Expand All @@ -175,18 +158,44 @@ public <T> List<T> getBeans(Class<T> clazz) {
public <T> Optional<T> getBean(Class<T> clazz) {
var beans = getBeans(clazz);
if (beans.size() > 1) {
var msg = "One bean was asked. But found %d beans of %s".formatted(beans.size(),clazz);
var msg = "One bean was asked. But found %d beans of %s".formatted(beans.size(), clazz);
log.error(msg);
throw new RuntimeException(msg);
}
return beans.size() == 1 ? Optional.of(beans.getFirst()) : Optional.empty();
}

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

var uuid = UUID.randomUUID().toString();
BeanContainer bc = new BeanContainer(new BeanDefinition("component", beanName,null, uuid, null));
bc.setSingleton(bean);
singletonBeans.put(uuid,bean);
bcs.put(uuid, bc);
bcs.put(uuid,
new BeanContainer(
new BeanDefinition(
"component",
computeBeanName(beanName, uuid),
null,
uuid,
null),
bean));
singletonBeans.put(uuid, bean);
// the return value of 'put' is ignored, since bean registration with
// random keys should not yield duplicates anyway.
}

public <T> T registerIfAbsent(Class<T> type, Supplier<T> supplier) {
synchronized (uniqueClassInitialization) {
return getBean(type).orElseGet(() -> getBean(type).orElseGet(() -> {
T created = supplier.get();
register(null, created);
return created;
}));
}
}

private static @NotNull String computeBeanName(String beanName, String uuid) {
return beanName != null ? beanName : "#" + uuid;
}

}
31 changes: 31 additions & 0 deletions core/src/main/java/com/predic8/membrane/core/AbstractRouter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* Copyright 2025 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.core;

import com.predic8.membrane.core.proxies.*;
import org.slf4j.*;

public abstract class AbstractRouter implements Router, MainComponents {

private static final Logger log = LoggerFactory.getLogger(DefaultRouter.class);

protected void initProxies() {
log.debug("Initializing proxies.");
for (Proxy proxy : getRuleManager().getRules()) {
log.debug("Initializing proxy {}.", proxy.getName());
proxy.init(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class Configuration {

private boolean retryInit = false;

private String jmxRouterName;
private String jmxRouterName = "default";
Comment thread
predic8 marked this conversation as resolved.

private URIFactory uriFactory = new URIFactory(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private void defineRouter(BeanDefinitionRegistry beanDefinitionRegistry) {
if (!beanDefinitionRegistry.containsBeanDefinition("router")) {
beanDefinitionRegistry.registerBeanDefinition(
"router",
root().clazz(Router.class).addRef("transport", "transport").addRef("exchangeStore", "memoryExchangeStore").build());
root().clazz(DefaultRouter.class).addRef("transport", "transport").addRef("exchangeStore", "memoryExchangeStore").build());
}
}

Expand Down
Loading
Loading