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

jsinovassin pushed a commit to branch UNOMI-973-review-followups
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 6bd8077e7ab3354164e3e2714788ff0d34aa2e47
Author: jsinovassin <[email protected]>
AuthorDate: Tue Sep 1 18:38:43 2026 +0200

    UNOMI-973: Keep a failure to record the refusal from costing the batch its 
routes
    
    recordEndpointOutcome writes to the store from inside configure(), which
    builds the routes of every configuration of the batch. With the store
    unreachable -- Elasticsearch not up yet at start-up, say -- the exception
    leaves addRoutes and every other configuration loses its route, silently.
    That is the failure mode this validation exists to remove, reached through
    the report of a refusal rather than the refusal itself.
    
    The write is now logged and swallowed. The route is built or skipped exactly
    as decided; only the record of it can go missing.
---
 .../core/route/RouterAbstractRouteBuilder.java     | 24 ++++++++++++++++++++-
 .../core/route/RefusedConfigurationStatusTest.java | 25 ++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git 
a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
 
b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
index fa4998998..772e96014 100644
--- 
a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
+++ 
b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
@@ -26,6 +26,8 @@ import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.router.api.ImportExportConfiguration;
 import org.apache.unomi.router.api.RouterConstants;
 import org.apache.unomi.router.api.services.ImportExportConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Map;
 
@@ -34,6 +36,8 @@ import java.util.Map;
  */
 public abstract class RouterAbstractRouteBuilder extends RouteBuilder {
 
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RouterAbstractRouteBuilder.class);
+
     protected JacksonDataFormat jacksonDataFormat;
 
     protected String kafkaHost;
@@ -87,10 +91,28 @@ public abstract class RouterAbstractRouteBuilder extends 
RouteBuilder {
             T configuration, ImportExportConfigurationService<T> service, 
String refusal) {
         if (refusal != null) {
             
configuration.setStatus(RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT);
-            service.save(configuration, false);
+            saveQuietly(configuration, service);
         } else if 
(RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT.equals(configuration.getStatus()))
 {
             configuration.setStatus(null);
+            saveQuietly(configuration, service);
+        }
+    }
+
+    /**
+     * Saves the mark, and keeps a failure to itself.
+     *
+     * <p>This runs inside {@code configure()}, which builds the routes of 
every configuration of the
+     * batch. An exception thrown here would leave {@code addRoutes} and cost 
all of them their routes
+     * — the very failure this validation exists to prevent, over the report 
of a refusal rather than
+     * the refusal itself. The store may be unreachable at start-up; the mark 
is worth what it costs,
+     * and no more.
+     */
+    private <T extends ImportExportConfiguration> void saveQuietly(T 
configuration, ImportExportConfigurationService<T> service) {
+        try {
             service.save(configuration, false);
+        } catch (RuntimeException e) {
+            LOGGER.error("Could not record the endpoint outcome on 
configuration {}; its route is built "
+                    + "or skipped as decided, only the record of it is 
missing", configuration.getItemId(), e);
         }
     }
 
diff --git 
a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java
 
b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java
index d0daf23e4..41a19f966 100644
--- 
a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java
+++ 
b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java
@@ -41,6 +41,7 @@ import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -117,6 +118,18 @@ public class RefusedConfigurationStatusTest {
                 exportConfigurations.contains("out-of-bounds"));
     }
 
+    @Test
+    public void 
aStoreThatCannotRecordTheRefusalDoesNotCostTheBatchItsOtherRoutes() throws 
Exception {
+        importConfigurations.unwritable = true;
+
+        addImportRoutes(recurrentImport(fileUri(arbitraryDir, 
"?fileName=profiles.csv")),
+                inBoundsImport("in-bounds"));
+
+        assertNull("the refused configuration still gets no route", 
camelContext.getRouteDefinition("out-of-bounds"));
+        assertNotNull("failing to record the refusal must not cost the other 
configurations their routes",
+                camelContext.getRouteDefinition("in-bounds"));
+    }
+
     @Test
     public void 
recordingARefusedImportConfigurationDoesNotScheduleARouteRefresh() throws 
Exception {
         addImportRoutes(recurrentImport(fileUri(arbitraryDir, 
"?fileName=profiles.csv")));
@@ -200,6 +213,12 @@ public class RefusedConfigurationStatusTest {
         return "file://" + directory.getAbsolutePath() + suffix;
     }
 
+    private ImportConfiguration inBoundsImport(String itemId) {
+        ImportConfiguration configuration = 
recurrentImport(fileUri(permittedImportDir, "?fileName=profiles.csv"));
+        configuration.setItemId(itemId);
+        return configuration;
+    }
+
     private ImportConfiguration recurrentImport(String source) {
         ImportConfiguration configuration = new ImportConfiguration();
         configuration.setItemId("out-of-bounds");
@@ -265,6 +284,9 @@ public class RefusedConfigurationStatusTest {
 
         private boolean lastSaveAskedForARouteRefresh;
 
+        /** Stands in for a store that cannot be written to -- Elasticsearch 
unreachable at start-up. */
+        private boolean unwritable;
+
         boolean contains(String configId) {
             return stored.containsKey(configId);
         }
@@ -281,6 +303,9 @@ public class RefusedConfigurationStatusTest {
 
         @Override
         public T save(T configuration, boolean updateRunningRoute) {
+            if (unwritable) {
+                throw new IllegalStateException("the store is unreachable");
+            }
             lastSaveAskedForARouteRefresh = updateRunningRoute;
             stored.put(itemIdOf(configuration), configuration);
             return configuration;

Reply via email to