diff --git a/annot/pom.xml b/annot/pom.xml index 76f74f68e2..8f83de2e0f 100644 --- a/annot/pom.xml +++ b/annot/pom.xml @@ -12,19 +12,21 @@ 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. - --> + --> + 4.0.0 service-proxy-annot ${project.artifactId} jar - - org.membrane-soa - service-proxy-parent - ../pom.xml - 7.0.5 - + + org.membrane-soa + service-proxy-parent + ../pom.xml + 7.0.5 + @@ -87,6 +89,11 @@ 3.0 test + + org.mockito + mockito-core + test + diff --git a/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java b/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java index ae8aceabd2..3c5390144e 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java +++ b/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java @@ -26,4 +26,22 @@ public interface BeanRegistry { Grammar getGrammar(); List getBeans(Class clazz); + + /** + * Retrieves a single bean of the specified type. + * + * @param clazz the class of the bean to retrieve + * @param the bean type + * @return Optional containing the bean if exactly one exists, empty otherwise + * @throws RuntimeException if multiple beans of the specified type exist + */ + Optional getBean(Class clazz); + + /** + * Registers a bean with the specified name. + * + * @param beanName the name to register the bean under + * @param bean instance to register (must not be null) + */ + void register(String beanName, Object bean); } diff --git a/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java b/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java index 7d0abbcd1a..bdd6e52fc7 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java +++ b/annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java @@ -173,4 +173,22 @@ public List getBeans(Class clazz) { .map(clazz::cast) .toList(); } + + public Optional getBean(Class 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); + log.error(msg); + throw new RuntimeException(msg); + } + return beans.size() == 1 ? Optional.of(beans.getFirst()) : Optional.empty(); + } + + public void register(String beanName, Object bean) { + 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); + } } diff --git a/annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java b/annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java index 9a19b7f2ea..d586e4d2b4 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java +++ b/annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java @@ -105,12 +105,7 @@ public static List parseMembraneResources(@NotNull InputStream r try (resource) { return parseToBeanDefinitions(resource, grammar); } catch (JsonParseException e) { - throw new IOException( - "Invalid YAML: multiple configurations must be separated by '---' " - + "(at line " + e.getLocation().getLineNr() - + ", column " + e.getLocation().getColumnNr() + ").", - e - ); + throw new IOException("Invalid YAML: multiple configurations must be separated by '---' (at line %d, column %d).".formatted(e.getLocation().getLineNr(), e.getLocation().getColumnNr()), e); } } diff --git a/annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java b/annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java new file mode 100644 index 0000000000..3a6b387bb7 --- /dev/null +++ b/annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java @@ -0,0 +1,62 @@ +/* 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.annot.beanregistry; + +import org.junit.jupiter.api.*; +import org.mockito.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BeanRegistryImplementationTest { + + private BeanRegistryImplementation registry; + private BeanRegistryAware aware; + + @BeforeEach + void setup() { + aware = Mockito.mock(BeanRegistryAware.class); + registry = new BeanRegistryImplementation(null, aware, null); + } + + @Test + void register() { + A a1 = new A("a1"); + A a2 = new A("a2"); + A a3 = new A("a3"); + registry.register("bean1", a1); + registry.register("bean2", a2); + registry.register("bean3", a3); + List as = registry.getBeans(A.class); + assertEquals(3, as.size()); + assertEquals(Set.of(a1,a2,a3),new HashSet<>(as)); + } + + @Test + void getBean() { + A a1 = new A("a1"); + registry.register("bean1", a1); + assertEquals(a1, registry.getBean(A.class).orElseThrow()); + } + + class A { + String value; + + public A(String value) { + this.value = value; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/Router.java b/core/src/main/java/com/predic8/membrane/core/Router.java index 640280cdc2..e0f5ff2e1d 100644 --- a/core/src/main/java/com/predic8/membrane/core/Router.java +++ b/core/src/main/java/com/predic8/membrane/core/Router.java @@ -105,7 +105,6 @@ public class Router implements Lifecycle, ApplicationContextAware, BeanRegistryA protected final FlowController flowController; protected ExchangeStore exchangeStore = new LimitedMemoryExchangeStore(); protected Transport transport; - protected GlobalInterceptor globalInterceptor = new GlobalInterceptor(); protected final ResolverMap resolverMap; protected final DNSCache dnsCache = new DNSCache(); private final KubernetesWatcher kubernetesWatcher = new KubernetesWatcher(this); @@ -497,7 +496,7 @@ public void setBeanName(String s) { */ @MCChildElement(order = 2) public void setGlobalInterceptor(GlobalInterceptor globalInterceptor) { - this.globalInterceptor = globalInterceptor; + registry.register("globalInterceptor", globalInterceptor); } public String getId() { @@ -530,10 +529,6 @@ public FlowController getFlowController() { return flowController; } - public GlobalInterceptor getGlobalInterceptor() { - return globalInterceptor; - } - public synchronized void setAsynchronousInitialization(boolean asynchronousInitialization) { this.asynchronousInitialization = asynchronousInitialization; notifyAll(); diff --git a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java index 171ac6b386..d5155ad378 100644 --- a/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java +++ b/core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java @@ -173,7 +173,7 @@ private static Router initRouterByYAML(MembraneCommandLine commandLine, String o } private static Router initRouterByYAML(String location) throws Exception { - var router = new HttpRouter(); + var router = new Router(); router.setBaseLocation(location); router.setAsynchronousInitialization(true); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java index 476def64d3..d09fd3c491 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java @@ -22,7 +22,7 @@ * @description The global chain applies plugins to all endpoints, enabling centralized features * such as global user authentication, logging, and other cross-cutting concerns. */ -@MCElement(name = "global", excludeFromFlow = true) +@MCElement(name = "global", excludeFromFlow = true, component = false, topLevel = true) public class GlobalInterceptor extends AbstractFlowWithChildrenInterceptor { @Override diff --git a/core/src/main/java/com/predic8/membrane/core/transport/Transport.java b/core/src/main/java/com/predic8/membrane/core/transport/Transport.java index 2359d18564..8d1a15cca7 100644 --- a/core/src/main/java/com/predic8/membrane/core/transport/Transport.java +++ b/core/src/main/java/com/predic8/membrane/core/transport/Transport.java @@ -33,8 +33,8 @@ public abstract class Transport { * SSL and Non-SSL are mixed here, maybe split that in future */ protected final Set menuListeners = new HashSet<>(); - private List interceptors = new Vector<>(); + private Router router; private boolean reverseDNS = true; @@ -62,7 +62,7 @@ public void init(Router router) throws Exception { interceptors.add(getExchangeStoreInterceptor()); interceptors.add(getInterceptor(DispatchingInterceptor.class)); interceptors.add(getInterceptor(ReverseProxyingInterceptor.class)); - interceptors.add(router.getGlobalInterceptor()); + router.getRegistry().getBean(GlobalInterceptor.class).ifPresent(i -> interceptors.add(i )); interceptors.add(getInterceptor(UserFeatureInterceptor.class)); interceptors.add(getInterceptor(InternalRoutingInterceptor.class)); interceptors.add(getInterceptor(HTTPClientInterceptor.class)); diff --git a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java index 9e28337587..e0216123fd 100644 --- a/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java +++ b/core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java @@ -346,21 +346,16 @@ public List getBeans() { return List.of(); } - @Override public void parseYamls(InputStream yamls, Grammar grammar) throws IOException { BeanCollector.super.parseYamls(yamls, grammar); } @Override - public void handle(ChangeEvent changeEvent, boolean isLast) { - - } + public void handle(ChangeEvent changeEvent, boolean isLast) {} @Override - public void start() { - - } + public void start() {} @Override public Grammar getGrammar() { @@ -371,6 +366,14 @@ public Grammar getGrammar() { public List getBeans(Class clazz) { return List.of(); } + + @Override + public Optional getBean(Class clazz) { + return Optional.empty(); + } + + @Override + public void register(String beanName, Object object) {} } private static APIProxy parse(String yaml, BeanRegistry reg) { diff --git a/core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java b/core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java index ad91d08b7f..5ac41b380c 100755 --- a/core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java +++ b/core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java @@ -14,6 +14,7 @@ package com.predic8.membrane.core.transport.http; +import com.predic8.membrane.annot.beanregistry.*; import com.predic8.membrane.core.*; import com.predic8.membrane.core.exchangestore.*; import com.predic8.membrane.core.interceptor.GlobalInterceptor; @@ -39,14 +40,15 @@ public class HttpTransportTest { private final GlobalInterceptor globalInterceptor = new GlobalInterceptor(); @BeforeEach - public void before() throws Exception { + void before() throws Exception { when(resolverMap.getHTTPSchemaResolver()).thenReturn(httpSchemaResolver); when(router.getResolverMap()).thenReturn(resolverMap); when(router.getRuleManager()).thenReturn(ruleManager); when(router.getExchangeStore()).thenReturn(exchangeStore); - when(router.getGlobalInterceptor()).thenReturn(globalInterceptor); when(router.getHttpClientFactory()).thenReturn(new HttpClientFactory(null)); when(router.getStatistics()).thenReturn(statistics); + BeanRegistryImplementation value = new BeanRegistryImplementation(null, router, null); // Do not inline! Otherwise mocking is not possible! + when(router.getRegistry()).thenReturn(value); transport = new HttpTransport(); transport.init(router);