This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karaf.git


The following commit(s) were added to refs/heads/main by this push:
     new d54f9a806 fix(#625): Invalidate OsgiTypeConverter delegate when new 
TypeConverterLoader arrives (#684)
d54f9a806 is described below

commit d54f9a806cee9c2323e4b341680f7608915959f7
Author: JB Onofré <[email protected]>
AuthorDate: Thu Mar 12 19:53:30 2026 +0100

    fix(#625): Invalidate OsgiTypeConverter delegate when new 
TypeConverterLoader arrives (#684)
    
    * fix(#625): Invalidate OsgiTypeConverter delegate when new 
TypeConverterLoader arrives
    
    The delegate was not rebuilt when a new TypeConverterLoader service was
    registered, causing HTTP TypeConverters to be missing when endpoints were
    eagerly created via to(). This worked with toD() because endpoints are
    lazily created after all bundles and their TypeConverters are loaded.
    
    Now the delegate is invalidated (set to null) on addingService, matching
    the existing pattern in removedService, so it is rebuilt with all
    available loaders on next access.
    
    * fix(#625): change camel-core-model dependency scope from test to provided
    
    KarafCamelContextProvider uses ModelCamelContext and RouteDefinition in
    main source code, so the dependency must be available at compile time.
    
    * fix(#625): open ServiceTracker early so TypeConverterLoaders are 
available during doInit
    
    The OsgiTypeConverter's ServiceTracker was only opened in doStart(),
    but endpoints created eagerly via to() during doInit() need type
    converters (e.g. String to Timeout for camel-http). The tracker was
    closed at that point, so createRegistry() found no OSGi-loaded
    TypeConverterLoaders.
    
    Open the tracker lazily via ensureTrackerOpen() in getDelegate() so
    that already-registered TypeConverterLoader services are discoverable
    when the delegate is first created. Also revert addingService() to
    load into the existing delegate instead of invalidating it, which
    preserves programmatically-added converters (e.g. Blueprint beans
    implementing TypeConverters).
    
    Add responseTimeout to the camel-http integration test to cover
    the exact scenario from #625.
---
 core/camel-core-osgi/pom.xml                       |  18 ++++
 .../apache/camel/karaf/core/OsgiTypeConverter.java |  20 +++-
 .../camel/karaf/core/OsgiTypeConverterTest.java    | 113 +++++++++++++++++++++
 .../karaf/camel/test/CamelHttpRouteSupplier.java   |   2 +-
 4 files changed, 150 insertions(+), 3 deletions(-)

diff --git a/core/camel-core-osgi/pom.xml b/core/camel-core-osgi/pom.xml
index 04218721f..4c066eb1f 100644
--- a/core/camel-core-osgi/pom.xml
+++ b/core/camel-core-osgi/pom.xml
@@ -67,12 +67,24 @@
             <version>${junit-jupiter-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${junit-jupiter-version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <version>${mockito-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>${mockito-version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-core</artifactId>
@@ -91,6 +103,12 @@
             <version>${camel-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-model</artifactId>
+            <version>${camel-version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java
index 258645467..2aa75037c 100644
--- 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java
@@ -58,6 +58,7 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
     private final Injector injector;
     private final ServiceTracker<TypeConverterLoader, Object> tracker;
     private volatile DefaultTypeConverter delegate;
+    private volatile boolean trackerOpened;
 
     public OsgiTypeConverter(BundleContext bundleContext, CamelContext 
camelContext, Injector injector) {
         this.bundleContext = bundleContext;
@@ -66,6 +67,13 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
         this.tracker = new ServiceTracker<>(bundleContext, 
TypeConverterLoader.class.getName(), this);
     }
 
+    private void ensureTrackerOpen() {
+        if (!trackerOpened) {
+            tracker.open();
+            trackerOpened = true;
+        }
+    }
+
     @Override
     public Object addingService(ServiceReference<TypeConverterLoader> 
serviceReference) {
         LOG.trace("AddingService: {}, Bundle: {}", serviceReference, 
serviceReference.getBundle());
@@ -74,7 +82,9 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
             try {
                 LOG.debug("loading type converter from bundle: {}", 
serviceReference.getBundle().getSymbolicName());
                 if (delegate != null) {
-                    ServiceHelper.startService(this.delegate);
+                    // load the converter directly into the existing delegate 
to preserve
+                    // any converters that were added programmatically (e.g. 
via Blueprint beans
+                    // implementing TypeConverters)
                     loader.load(delegate);
                 }
             } catch (Throwable t) {
@@ -104,12 +114,13 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
 
     @Override
     protected void doStart() throws Exception {
-        this.tracker.open();
+        ensureTrackerOpen();
     }
 
     @Override
     protected void doStop() throws Exception {
         this.tracker.close();
+        this.trackerOpened = false;
         ServiceHelper.stopService(this.delegate);
         this.delegate = null;
     }
@@ -232,6 +243,11 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
 
     public DefaultTypeConverter getDelegate() {
         if (delegate == null) {
+            // ensure the tracker is open so we can discover 
TypeConverterLoader services
+            // before creating the registry - this is important because 
getDelegate() may be
+            // called during doInit() (e.g. when to() eagerly creates 
endpoints) which happens
+            // before doStart() where the tracker is normally opened
+            ensureTrackerOpen();
             delegate = createRegistry();
         }
         return delegate;
diff --git 
a/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/OsgiTypeConverterTest.java
 
b/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/OsgiTypeConverterTest.java
new file mode 100644
index 000000000..838193400
--- /dev/null
+++ 
b/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/OsgiTypeConverterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.karaf.core;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Injector;
+import org.apache.camel.spi.TypeConverterLoader;
+import org.apache.camel.spi.TypeConverterRegistry;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class OsgiTypeConverterTest {
+
+    @Mock
+    private BundleContext bundleContext;
+    @Mock
+    private CamelContext camelContext;
+    @Mock
+    private Injector injector;
+    @Mock
+    private ServiceReference<TypeConverterLoader> serviceReference;
+    @Mock
+    private TypeConverterLoader loader;
+    @Mock
+    private Bundle bundle;
+
+    private OsgiTypeConverter osgiTypeConverter;
+
+    @BeforeEach
+    void setUp() {
+        
lenient().when(bundleContext.getService(serviceReference)).thenReturn(loader);
+        lenient().when(serviceReference.getBundle()).thenReturn(bundle);
+        lenient().when(bundle.getSymbolicName()).thenReturn("test-bundle");
+        osgiTypeConverter = new OsgiTypeConverter(bundleContext, camelContext, 
injector);
+    }
+
+    @Test
+    void addingServiceShouldLoadIntoExistingDelegate() throws Exception {
+        // trigger delegate creation
+        var delegate = osgiTypeConverter.getDelegate();
+        assertNotNull(delegate, "delegate should be created");
+
+        // simulate a new TypeConverterLoader service arriving
+        osgiTypeConverter.addingService(serviceReference);
+
+        // the loader should have been loaded into the existing delegate
+        verify(loader).load(delegate);
+
+        // the delegate should be the same instance (not invalidated)
+        assertSame(delegate, osgiTypeConverter.getDelegate(),
+            "delegate should be preserved when a new loader arrives");
+    }
+
+    @Test
+    void addingServiceShouldNotFailWhenDelegateIsNull() throws Exception {
+        // delegate is null initially, adding a service should not fail
+        // and should not attempt to load (no delegate to load into)
+        osgiTypeConverter.addingService(serviceReference);
+
+        verify(loader, never()).load(any());
+
+        // delegate should still be lazily created on next access
+        assertNotNull(osgiTypeConverter.getDelegate());
+    }
+
+    @Test
+    void newDelegateIncludesLateArrivingLoader() throws Exception {
+        // simulate a loader arriving before delegate is created
+        osgiTypeConverter.addingService(serviceReference);
+
+        // when delegate is created, it should pick up the loader
+        // via tracker.getServiceReferences() in createRegistry()
+        var delegate = osgiTypeConverter.getDelegate();
+        assertNotNull(delegate);
+    }
+
+    @Test
+    void removedServiceShouldInvalidateDelegate() throws Exception {
+        // trigger delegate creation
+        osgiTypeConverter.getDelegate();
+
+        // simulate service removal
+        osgiTypeConverter.removedService(serviceReference, loader);
+
+        // delegate should be rebuilt on next access
+        var delegateAfter = osgiTypeConverter.getDelegate();
+        assertNotNull(delegateAfter);
+    }
+}
diff --git 
a/tests/features/camel-http/src/main/java/org/apache/karaf/camel/test/CamelHttpRouteSupplier.java
 
b/tests/features/camel-http/src/main/java/org/apache/karaf/camel/test/CamelHttpRouteSupplier.java
index 28422e972..535323ab6 100644
--- 
a/tests/features/camel-http/src/main/java/org/apache/karaf/camel/test/CamelHttpRouteSupplier.java
+++ 
b/tests/features/camel-http/src/main/java/org/apache/karaf/camel/test/CamelHttpRouteSupplier.java
@@ -40,7 +40,7 @@ public class CamelHttpRouteSupplier extends 
AbstractCamelSingleFeatureResultMock
         configureConsumer(
             producerRoute.log("calling local http server")
                     .setHeader("CamelHttpMethod", constant("GET"))
-                    .toF("http://localhost:%s";, 
System.getProperty("http.port"))
+                    .toF("http://localhost:%s?responseTimeout=55000";, 
System.getProperty("http.port"))
                     .log("got ${body}"));
     }
 }

Reply via email to