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

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


The following commit(s) were added to refs/heads/main by this push:
     new 53219dce7deb CAMEL-26230: camel-influxdb2 - fail a Point without 
fields, call InfluxDB at startup only to create the organization or bucket, and 
do not change the Map body (#29072)
53219dce7deb is described below

commit 53219dce7deb1a06cc84386154578ce7ba5a07f1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:12:11 2026 +0100

    CAMEL-26230: camel-influxdb2 - fail a Point without fields, call InfluxDB 
at startup only to create the organization or bucket, and do not change the Map 
body (#29072)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/catalog/components/influxdb2.json |   2 +-
 .../camel/catalog/docs/influxdb2-component.adoc    |  17 ++
 .../camel/component/influxdb2/influxdb2.json       |   2 +-
 .../src/main/docs/influxdb2-component.adoc         |  17 ++
 .../component/influxdb2/InfluxDb2Endpoint.java     |  59 +++--
 .../component/influxdb2/InfluxDb2Producer.java     |  35 ++-
 .../converters/CamelInfluxDb2Converters.java       |  13 +-
 .../influxdb2/InfluxDb2EndpointStartupTest.java    | 247 +++++++++++++++++++++
 .../component/influxdb2/InfluxDb2ProducerTest.java |   4 +-
 .../InfluxDb2ProducerWriteFailureTest.java         |  94 ++++++++
 .../converters/CamelInfluxDbConverterTest.java     |  27 +++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  14 ++
 12 files changed, 496 insertions(+), 35 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb2.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb2.json
index b0b337fad97c..dcce3b9ef9df 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb2.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/influxdb2.json
@@ -34,7 +34,7 @@
     "CamelInfluxDB2WritePrecision": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "com.influxdb.client.domain.WritePrecision", "enum": [ "ms", "s", 
"us", "ns" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "InfluxDb Write precision.", "constantName": 
"org.apache.camel.component.influxdb2.InfluxDb2Constants#WRITE_PRECISION" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "any" ] } ], "description": "A 
com.influxdb.client.write.Point, or the Measurement, Record, Points, 
Measurements or Records wrappers of the component (package 
org.apache.camel.component.influxdb2.data), or a Map of the field names and 
their values with the measurement name under the CamelInfluxDB2MeasurementName 
key. Text, a List or an annotated class that is not wrapp [...]
+    "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "any" ] } ], "description": "A 
com.influxdb.client.write.Point, or the Measurement, Record, Points, 
Measurements or Records wrappers of the component (package 
org.apache.camel.component.influxdb2.data), or a Map of the field names and 
their values with the measurement name under the CamelInfluxDB2MeasurementName 
key. Text, a List or an annotated class that is not wrapp [...]
     "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "value" ], "javaType": 
"java.lang.Boolean" } ], "description": "An insert leaves the body unchanged; 
ping gives a Boolean, true when the server answered" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb2-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb2-component.adoc
index 9991a0c1b2f1..eb9be9f59978 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb2-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/influxdb2-component.adoc
@@ -16,6 +16,7 @@ This component allows you to interact with 
https://influxdata.com/time-series-pl
 a time series database.
 The native body type for this component is `Point` (the native InfluxDB class).
 However, it can also accept `Map<String, Object>` as message body, and it will 
get converted to `Point.class`, please note that the map must contain an 
element with `CamelInfluxDB2MeasurementName` as key.
+The other entries of the map are the fields of the point; the map itself is 
not changed, so it can be immutable.
 
 Additionally, you may register your own Converters to your data type to 
`Point`, or use the (un)marshalling tools provided by Camel.
 
@@ -96,3 +97,19 @@ authorized), or a request that cannot be sent, fails the 
exchange with a `CamelI
 
 Each exchange is one write request. To write many points with one request, 
send a `Points`, `Measurements` or
 `Records` body, for example, from an xref:eips:aggregate-eip.adoc[Aggregate] 
EIP.
+
+InfluxDB needs at least one field in a point, and the client skips a point 
without a field that has a value (no field
+at all, or only fields whose value is `null`, NaN or infinite) instead of 
writing it. Such a `Point`, also one converted
+from a `Map` or one of the points of a `Points` body, fails the exchange with 
a `CamelInfluxDb2Exception`, and nothing
+is written.
+
+== Organization and bucket at startup
+
+When `autoCreateOrg` is `true` (the default), the endpoint looks the 
organization up when it starts and creates it when
+it does not exist. When `autoCreateBucket` is `true` (the default), it does 
the same for the bucket, in the organization
+of the endpoint. With `autoCreateOrg=false`, a missing bucket is created only 
when that organization exists, and
+otherwise the endpoint fails to start.
+
+A write names the organization and the bucket, so the endpoint does not need 
them to be looked up: with
+`autoCreateOrg=false` and `autoCreateBucket=false` it does not call InfluxDB 
when it starts, and the route starts while
+InfluxDB is down. An organization or a bucket that does not exist then fails 
the write, and so the exchange.
diff --git 
a/components/camel-influxdb2/src/generated/resources/META-INF/org/apache/camel/component/influxdb2/influxdb2.json
 
b/components/camel-influxdb2/src/generated/resources/META-INF/org/apache/camel/component/influxdb2/influxdb2.json
index b0b337fad97c..dcce3b9ef9df 100644
--- 
a/components/camel-influxdb2/src/generated/resources/META-INF/org/apache/camel/component/influxdb2/influxdb2.json
+++ 
b/components/camel-influxdb2/src/generated/resources/META-INF/org/apache/camel/component/influxdb2/influxdb2.json
@@ -34,7 +34,7 @@
     "CamelInfluxDB2WritePrecision": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "com.influxdb.client.domain.WritePrecision", "enum": [ "ms", "s", 
"us", "ns" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "InfluxDb Write precision.", "constantName": 
"org.apache.camel.component.influxdb2.InfluxDb2Constants#WRITE_PRECISION" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "any" ] } ], "description": "A 
com.influxdb.client.write.Point, or the Measurement, Record, Points, 
Measurements or Records wrappers of the component (package 
org.apache.camel.component.influxdb2.data), or a Map of the field names and 
their values with the measurement name under the CamelInfluxDB2MeasurementName 
key. Text, a List or an annotated class that is not wrapp [...]
+    "producerIn": { "carriers": [ "pojo", "map" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "any" ] } ], "description": "A 
com.influxdb.client.write.Point, or the Measurement, Record, Points, 
Measurements or Records wrappers of the component (package 
org.apache.camel.component.influxdb2.data), or a Map of the field names and 
their values with the measurement name under the CamelInfluxDB2MeasurementName 
key. Text, a List or an annotated class that is not wrapp [...]
     "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "PING", "carriers": [ "value" ], "javaType": 
"java.lang.Boolean" } ], "description": "An insert leaves the body unchanged; 
ping gives a Boolean, true when the server answered" }
   },
   "properties": {
diff --git a/components/camel-influxdb2/src/main/docs/influxdb2-component.adoc 
b/components/camel-influxdb2/src/main/docs/influxdb2-component.adoc
index 9991a0c1b2f1..eb9be9f59978 100644
--- a/components/camel-influxdb2/src/main/docs/influxdb2-component.adoc
+++ b/components/camel-influxdb2/src/main/docs/influxdb2-component.adoc
@@ -16,6 +16,7 @@ This component allows you to interact with 
https://influxdata.com/time-series-pl
 a time series database.
 The native body type for this component is `Point` (the native InfluxDB class).
 However, it can also accept `Map<String, Object>` as message body, and it will 
get converted to `Point.class`, please note that the map must contain an 
element with `CamelInfluxDB2MeasurementName` as key.
+The other entries of the map are the fields of the point; the map itself is 
not changed, so it can be immutable.
 
 Additionally, you may register your own Converters to your data type to 
`Point`, or use the (un)marshalling tools provided by Camel.
 
@@ -96,3 +97,19 @@ authorized), or a request that cannot be sent, fails the 
exchange with a `CamelI
 
 Each exchange is one write request. To write many points with one request, 
send a `Points`, `Measurements` or
 `Records` body, for example, from an xref:eips:aggregate-eip.adoc[Aggregate] 
EIP.
+
+InfluxDB needs at least one field in a point, and the client skips a point 
without a field that has a value (no field
+at all, or only fields whose value is `null`, NaN or infinite) instead of 
writing it. Such a `Point`, also one converted
+from a `Map` or one of the points of a `Points` body, fails the exchange with 
a `CamelInfluxDb2Exception`, and nothing
+is written.
+
+== Organization and bucket at startup
+
+When `autoCreateOrg` is `true` (the default), the endpoint looks the 
organization up when it starts and creates it when
+it does not exist. When `autoCreateBucket` is `true` (the default), it does 
the same for the bucket, in the organization
+of the endpoint. With `autoCreateOrg=false`, a missing bucket is created only 
when that organization exists, and
+otherwise the endpoint fails to start.
+
+A write names the organization and the bucket, so the endpoint does not need 
them to be looked up: with
+`autoCreateOrg=false` and `autoCreateBucket=false` it does not call InfluxDB 
when it starts, and the route starts while
+InfluxDB is down. An organization or a bucket that does not exist then fails 
the write, and so the exchange.
diff --git 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Endpoint.java
 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Endpoint.java
index 24b205798d05..2763de435a2f 100644
--- 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Endpoint.java
+++ 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Endpoint.java
@@ -56,7 +56,9 @@ import org.slf4j.LoggerFactory;
                                                 + " and their values with the 
measurement name under the"
                                                 + " 
CamelInfluxDB2MeasurementName key. Text, a List or an annotated class"
                                                 + " that is not wrapped fail: 
wrap a line protocol String in a Record and"
-                                                + " an annotated object in a 
Measurement. Ping does not read the body"),
+                                                + " an annotated object in a 
Measurement. A Point without a field that has"
+                                                + " a value (also from a Map 
or in Points) fails the exchange, as the client"
+                                                + " would skip it. Ping does 
not read the body"),
            producerOut = @BodyType(value = Carrier.UNCHANGED,
                                    byOption = @BodyType.When(option = 
"operation", value = "PING", carriers = Carrier.VALUE,
                                                              javaType = 
"java.lang.Boolean"),
@@ -191,37 +193,44 @@ public class InfluxDb2Endpoint extends DefaultEndpoint {
     @Override
     protected void doInit() throws Exception {
         super.doInit();
-        ensureOrgExists();
-        ensureBucketExists();
+        // InfluxDB is called at startup only to create the organization or 
the bucket when it is missing. A write names
+        // the organization and the bucket, so an endpoint that creates 
neither does not need the server to start.
+        if (autoCreateOrg) {
+            ensureOrgExists();
+        }
+        if (autoCreateBucket) {
+            ensureBucketExists();
+        }
     }
 
     private void ensureOrgExists() {
-        Organization organization = null;
+        Organization organization = findOrganization();
+        if (organization == null) {
+            LOG.debug("Organization {} doesn't exist. Creating it...", org);
+            organization = 
getInfluxDBClient().getOrganizationsApi().createOrganization(org);
+        }
+        if (organization != null) {
+            setOrgID(organization.getId());
+        }
+    }
+
+    private Organization findOrganization() {
         OrganizationsQuery organizationsQuery = new OrganizationsQuery();
         organizationsQuery.setOrg(org);
-        boolean exists = false;
         try {
             List<Organization> organizations = 
getInfluxDBClient().getOrganizationsApi().findOrganizations(organizationsQuery);
-            if (organizations.stream().anyMatch(o -> o.getName().equals(org))) 
{
-                exists = true;
-                organization = organizations.stream().filter(o -> 
o.getName().equals(org)).findFirst().get();
+            for (Organization organization : organizations) {
+                if (organization.getName().equals(org)) {
+                    return organization;
+                }
             }
         } catch (NotFoundException ex) {
-            exists = false;
-        }
-        if (!exists && autoCreateOrg) {
-            LOG.debug("Organization {} doesn't exist. Creating it...", org);
-            organization = 
getInfluxDBClient().getOrganizationsApi().createOrganization(org);
-        }
-        if (organization != null) {
-            setOrgID(organization.getId());
+            // the organization does not exist
         }
+        return null;
     }
 
     private void ensureBucketExists() {
-
-        ensureOrgExists();
-
         boolean exists = false;
         BucketsQuery bucketsQuery = new BucketsQuery();
         bucketsQuery.setOrg(org);
@@ -234,7 +243,17 @@ public class InfluxDb2Endpoint extends DefaultEndpoint {
         } catch (NotFoundException ex) {
             exists = false;
         }
-        if (!exists && autoCreateBucket) {
+        if (!exists) {
+            if (!autoCreateOrg) {
+                // the organization was not looked up at startup: the bucket 
is created in it only when it exists
+                Organization organization = findOrganization();
+                if (organization == null) {
+                    throw new CamelInfluxDb2Exception(
+                            "Cannot create the bucket " + bucket + " because 
the organization " + org
+                                                      + " does not exist and 
autoCreateOrg is false");
+                }
+                setOrgID(organization.getId());
+            }
             LOG.debug("Bucket {} doesn't exist. Creating it...", bucket);
             getInfluxDBClient().getBucketsApi().createBucket(bucket, 
getOrgID());
         }
diff --git 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Producer.java
 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Producer.java
index ff525fef6a7d..146856209ff7 100644
--- 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Producer.java
+++ 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/InfluxDb2Producer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.component.influxdb2;
 
+import java.util.List;
+
 import com.influxdb.client.InfluxDBClient;
 import com.influxdb.client.WriteApiBlocking;
 import com.influxdb.client.domain.WritePrecision;
@@ -100,10 +102,11 @@ public class InfluxDb2Producer extends DefaultProducer {
             throws InvalidPayloadException {
         Point point = exchange.getIn().getMandatoryBody(Point.class);
         try {
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("Writing point {}", point.toLineProtocol());
-            }
+            String line = lineProtocolOf(point, "The point");
+            LOG.debug("Writing point {}", line);
             writeApi.writePoint(bucketName, orgName, point);
+        } catch (CamelInfluxDb2Exception ex) {
+            exchange.setException(ex);
         } catch (Exception ex) {
             exchange.setException(new CamelInfluxDb2Exception(ex));
         }
@@ -143,7 +146,16 @@ public class InfluxDb2Producer extends DefaultProducer {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Writing points {}", points);
             }
-            writeApi.writePoints(bucketName, orgName, 
points.getInfluxPoints());
+            List<Point> influxPoints = points.getInfluxPoints();
+            for (int i = 0; influxPoints != null && i < influxPoints.size(); 
i++) {
+                // a null element has nothing to write, and the client leaves 
it out as before
+                if (influxPoints.get(i) != null) {
+                    lineProtocolOf(influxPoints.get(i), "The point at index " 
+ i + " of the Points body");
+                }
+            }
+            writeApi.writePoints(bucketName, orgName, influxPoints);
+        } catch (CamelInfluxDb2Exception ex) {
+            exchange.setException(ex);
         } catch (Exception ex) {
             exchange.setException(new CamelInfluxDb2Exception(ex));
         }
@@ -176,6 +188,21 @@ public class InfluxDb2Producer extends DefaultProducer {
         }
     }
 
+    /**
+     * The line protocol of the point. InfluxDB needs at least one field, and 
the client skips a point that has no field
+     * with a value (no field, or only fields whose value is null, NaN or 
infinite) without an error, so such a point
+     * fails the exchange instead of completing it with nothing written.
+     */
+    private static String lineProtocolOf(Point point, String name) {
+        String line = point.toLineProtocol();
+        if (line.isEmpty()) {
+            throw new CamelInfluxDb2Exception(
+                    name + " has no field with a value: InfluxDB needs at 
least one field, and the client would skip"
+                                              + " the point without writing 
it");
+        }
+        return line;
+    }
+
     private void doPing(Exchange exchange) {
         Boolean result = connection.ping();
         MessageHelper.copyHeaders(exchange.getIn(), exchange.getOut(), true);
diff --git 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDb2Converters.java
 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDb2Converters.java
index 203ab5d10a30..d3bc1ca3a26e 100644
--- 
a/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDb2Converters.java
+++ 
b/components/camel-influxdb2/src/main/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDb2Converters.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.influxdb2.converters;
 
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import com.influxdb.client.write.Point;
@@ -40,12 +41,10 @@ public final class CamelInfluxDb2Converters {
             throw new CamelInfluxDb2Exception(format);
         }
 
-        String measurenmentNameString = measurementName.toString();
-        Point point = Point.measurement(measurenmentNameString);
-        map.remove(InfluxDb2Constants.MEASUREMENT);
-        point.addFields(map);
-        map.put(InfluxDb2Constants.MEASUREMENT, measurementName);
-
-        return point;
+        // the fields are a copy of the map without the measurement name, so 
the map of the caller is not changed (it can
+        // be immutable or shared with other code)
+        Map<String, Object> fields = new LinkedHashMap<>(map);
+        fields.remove(InfluxDb2Constants.MEASUREMENT);
+        return Point.measurement(measurementName.toString()).addFields(fields);
     }
 }
diff --git 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2EndpointStartupTest.java
 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2EndpointStartupTest.java
new file mode 100644
index 000000000000..f85e059de207
--- /dev/null
+++ 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2EndpointStartupTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.component.influxdb2;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.influxdb.client.InfluxDBClient;
+import com.influxdb.client.InfluxDBClientFactory;
+import com.influxdb.client.write.Point;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The endpoint calls InfluxDB when it starts only to create the organization 
or the bucket when it is missing. A local
+ * HTTP server plays the organizations, buckets and write APIs of InfluxDB 2.x 
and records the requests it receives.
+ */
+class InfluxDb2EndpointStartupTest extends CamelTestSupport {
+
+    private static final String ORG_ID = "00000000000000a1";
+    private static final String BUCKET_JSON
+            = "{\"id\":\"00000000000000b1\",\"orgID\":\"" + ORG_ID + 
"\",\"name\":\"myBucket\",\"retentionRules\":[]}";
+
+    private final AtomicBoolean orgExists = new AtomicBoolean(true);
+    private final AtomicBoolean bucketExists = new AtomicBoolean(true);
+    private final List<String> requests = new CopyOnWriteArrayList<>();
+    private final List<String> createdBuckets = new CopyOnWriteArrayList<>();
+    private final List<String> writeQueries = new CopyOnWriteArrayList<>();
+
+    private HttpServer server;
+    private InfluxDBClient client;
+    private InfluxDBClient clientOfAServerThatIsDown;
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        server = HttpServer.create(new 
InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+        server.createContext("/api/v2/orgs", exchange -> {
+            String method = record(exchange);
+            if ("POST".equals(method)) {
+                orgExists.set(true);
+                respond(exchange, 201, "{\"id\":\"" + ORG_ID + 
"\",\"name\":\"myOrg\"}");
+            } else if (orgExists.get()) {
+                respond(exchange, 200, "{\"orgs\":[{\"id\":\"" + ORG_ID + 
"\",\"name\":\"myOrg\"}]}");
+            } else {
+                respond(exchange, 404, "{\"code\":\"not 
found\",\"message\":\"organization name not found\"}");
+            }
+        });
+        server.createContext("/api/v2/buckets", exchange -> {
+            String body = new String(exchange.getRequestBody().readAllBytes(), 
StandardCharsets.UTF_8);
+            String method = record(exchange);
+            if ("POST".equals(method)) {
+                createdBuckets.add(body);
+                bucketExists.set(true);
+                respond(exchange, 201, BUCKET_JSON);
+            } else if (!orgExists.get()) {
+                respond(exchange, 404, "{\"code\":\"not 
found\",\"message\":\"organization name not found\"}");
+            } else {
+                respond(exchange, 200, "{\"buckets\":[" + (bucketExists.get() 
? BUCKET_JSON : "") + "]}");
+            }
+        });
+        server.createContext("/api/v2/write", exchange -> {
+            record(exchange);
+            
writeQueries.add(String.valueOf(exchange.getRequestURI().getQuery()));
+            respond(exchange, 204, null);
+        });
+        server.start();
+        client = InfluxDBClientFactory.create("http://127.0.0.1:"; + 
server.getAddress().getPort(), "token".toCharArray());
+
+        // nothing listens on the port of a socket that has been closed
+        int portOfAServerThatIsDown;
+        try (ServerSocket socket = new ServerSocket(0, 0, 
InetAddress.getLoopbackAddress())) {
+            portOfAServerThatIsDown = socket.getLocalPort();
+        }
+        clientOfAServerThatIsDown
+                = InfluxDBClientFactory.create("http://127.0.0.1:"; + 
portOfAServerThatIsDown, "token".toCharArray());
+
+        CamelContext context = super.createCamelContext();
+        context.getRegistry().bind("influxDbBean", client);
+        context.getRegistry().bind("influxDbThatIsDown", 
clientOfAServerThatIsDown);
+        return context;
+    }
+
+    @Override
+    protected void cleanupResources() {
+        if (server != null) {
+            server.stop(0);
+        }
+        if (client != null) {
+            client.close();
+        }
+        if (clientOfAServerThatIsDown != null) {
+            clientOfAServerThatIsDown.close();
+        }
+    }
+
+    @Test
+    void endpointThatCreatesNeitherDoesNotCallInfluxDbAtStartup() throws 
Exception {
+        
addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket&autoCreateOrg=false&autoCreateBucket=false");
+
+        assertEquals(List.of(), requests, "requests when the route has 
started");
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(point()));
+
+        assertNull(exchange.getException());
+        assertEquals(List.of("POST /api/v2/write"), requests, "requests when 
the exchange has completed");
+        // the write names the organization and the bucket
+        List<String> parameters = List.of(writeQueries.get(0).split("&"));
+        assertTrue(parameters.contains("org=myOrg"), writeQueries.get(0));
+        assertTrue(parameters.contains("bucket=myBucket"), 
writeQueries.get(0));
+    }
+
+    @Test
+    void routeStartsWhileInfluxDbIsDownWhenTheEndpointCreatesNeither() {
+        assertDoesNotThrow(() -> addRouteTo(
+                
"influxdb2:influxDbThatIsDown?org=myOrg&bucket=myBucket&autoCreateOrg=false&autoCreateBucket=false"));
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(point()));
+
+        assertInstanceOf(CamelInfluxDb2Exception.class, 
exchange.getException(),
+                "exception of an exchange whose write could not be sent");
+    }
+
+    @Test
+    void defaultsLookTheOrganizationAndTheBucketUpOnce() throws Exception {
+        addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket");
+
+        assertEquals(List.of("GET /api/v2/orgs", "GET /api/v2/buckets"), 
requests,
+                "requests when the route has started");
+    }
+
+    @Test
+    void defaultsCreateTheMissingOrganizationAndBucket() throws Exception {
+        orgExists.set(false);
+        bucketExists.set(false);
+
+        addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket");
+
+        assertEquals(List.of("GET /api/v2/orgs", "POST /api/v2/orgs", "GET 
/api/v2/buckets", "POST /api/v2/buckets"),
+                requests, "requests when the route has started");
+        assertTrue(createdBuckets.get(0).contains("\"orgID\":\"" + ORG_ID + 
"\""), createdBuckets.get(0));
+    }
+
+    @Test
+    void autoCreateBucketAloneLooksOnlyTheBucketUp() throws Exception {
+        
addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket&autoCreateOrg=false");
+
+        assertEquals(List.of("GET /api/v2/buckets"), requests, "requests when 
the route has started");
+    }
+
+    @Test
+    void autoCreateBucketAloneCreatesTheMissingBucketInTheOrganization() 
throws Exception {
+        bucketExists.set(false);
+
+        
addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket&autoCreateOrg=false");
+
+        assertEquals(List.of("GET /api/v2/buckets", "GET /api/v2/orgs", "POST 
/api/v2/buckets"), requests,
+                "requests when the route has started");
+        assertTrue(createdBuckets.get(0).contains("\"orgID\":\"" + ORG_ID + 
"\""), createdBuckets.get(0));
+    }
+
+    @Test
+    void autoCreateBucketAloneFailsToStartWhenTheOrganizationDoesNotExist() {
+        orgExists.set(false);
+        bucketExists.set(false);
+
+        Exception failure = assertThrows(Exception.class,
+                () -> 
addRouteTo("influxdb2:influxDbBean?org=myOrg&bucket=myBucket&autoCreateOrg=false"));
+
+        CamelInfluxDb2Exception cause = 
ObjectHelper.getException(CamelInfluxDb2Exception.class, failure);
+        assertNotNull(cause, "CamelInfluxDb2Exception in the causes of " + 
failure);
+        assertEquals("Cannot create the bucket myBucket because the 
organization myOrg does not exist and autoCreateOrg is"
+                     + " false",
+                cause.getMessage());
+        assertEquals(List.of("GET /api/v2/buckets", "GET /api/v2/orgs"), 
requests,
+                "requests when the route has failed to start");
+    }
+
+    private void addRouteTo(String uri) throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:write").to(uri);
+            }
+        });
+    }
+
+    private String record(HttpExchange exchange) {
+        String method = exchange.getRequestMethod();
+        requests.add(method + " " + exchange.getRequestURI().getPath());
+        return method;
+    }
+
+    private static Point point() {
+        return Point.measurement("temperature").addTag("location", 
"north").addField("value", 60.0);
+    }
+
+    private static void respond(HttpExchange exchange, int status, String 
json) throws IOException {
+        // read the request completely before answering
+        exchange.getRequestBody().readAllBytes();
+        if (json == null) {
+            exchange.sendResponseHeaders(status, -1);
+        } else {
+            byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
+            exchange.getResponseHeaders().add("Content-Type", 
"application/json");
+            exchange.sendResponseHeaders(status, bytes.length);
+            try (OutputStream out = exchange.getResponseBody()) {
+                out.write(bytes);
+            }
+        }
+        exchange.close();
+    }
+}
diff --git 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerTest.java
 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerTest.java
index dd1dbea6131c..624de1e6c8c5 100644
--- 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerTest.java
+++ 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerTest.java
@@ -78,7 +78,7 @@ public class InfluxDb2ProducerTest extends 
AbstractInfluxDbTest {
             }
         });
 
-        Point point = Point.measurement("temperature");
+        Point point = Point.measurement("temperature").addField("value", 60.0);
 
         TestSupport.sendBody(template, "direct:test", point);
 
@@ -174,7 +174,7 @@ public class InfluxDb2ProducerTest extends 
AbstractInfluxDbTest {
         });
         ArrayList<Point> points = new ArrayList<>() {
             {
-                add(Point.measurement("temperature"));
+                add(Point.measurement("temperature").addField("value", 60.0));
             }
         };
 
diff --git 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerWriteFailureTest.java
 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerWriteFailureTest.java
index 4242cc7e889a..9cb815809836 100644
--- 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerWriteFailureTest.java
+++ 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/InfluxDb2ProducerWriteFailureTest.java
@@ -21,9 +21,13 @@ import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
 
 import com.influxdb.client.InfluxDBClient;
 import com.influxdb.client.InfluxDBClientFactory;
@@ -34,12 +38,15 @@ import com.sun.net.httpserver.HttpServer;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.influxdb2.data.Points;
 import org.apache.camel.component.influxdb2.data.Record;
 import org.apache.camel.component.influxdb2.data.Records;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -191,6 +198,93 @@ class InfluxDb2ProducerWriteFailureTest extends 
CamelTestSupport {
         after.assertIsSatisfied();
     }
 
+    static Stream<Arguments> pointsWithoutAFieldToWrite() {
+        return Stream.of(
+                Arguments.of("no field", 
Point.measurement("temperature").addTag("location", "north")),
+                Arguments.of("a null field",
+                        Point.measurement("temperature").addTag("location", 
"north").addField("value", (Number) null)),
+                Arguments.of("a NaN field",
+                        Point.measurement("temperature").addTag("location", 
"north").addField("value", Double.NaN)),
+                Arguments.of("an infinite field",
+                        Point.measurement("temperature").addTag("location", 
"north")
+                                .addField("value", Double.POSITIVE_INFINITY)));
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("pointsWithoutAFieldToWrite")
+    void pointWithoutAFieldToWriteFailsTheExchange(String description, Point 
point) throws Exception {
+        MockEndpoint after = getMockEndpoint("mock:after");
+        after.expectedMessageCount(0);
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(point));
+
+        CamelInfluxDb2Exception failure = 
assertInstanceOf(CamelInfluxDb2Exception.class, exchange.getException(),
+                "exception of an exchange whose point has " + description);
+        assertEquals("The point has no field with a value: InfluxDB needs at 
least one field, and the client would skip"
+                     + " the point without writing it",
+                failure.getMessage());
+        assertEquals(List.of(), writes, "write requests at the server");
+        after.assertIsSatisfied();
+    }
+
+    @Test
+    void mapWithOnlyTheMeasurementNameFailsTheExchange() throws Exception {
+        MockEndpoint after = getMockEndpoint("mock:after");
+        after.expectedMessageCount(0);
+        Map<String, Object> body = new HashMap<>();
+        body.put(InfluxDb2Constants.MEASUREMENT, "temperature");
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(body));
+
+        assertInstanceOf(CamelInfluxDb2Exception.class, 
exchange.getException(),
+                "exception of an exchange whose map has no field");
+        assertEquals(List.of(), writes, "write requests at the server");
+        after.assertIsSatisfied();
+    }
+
+    @Test
+    void pointsWithAPointWithoutFieldsFailTheExchangeAndNothingIsWritten() 
throws Exception {
+        MockEndpoint after = getMockEndpoint("mock:after");
+        after.expectedMessageCount(0);
+        Points points = Points.create(List.of(point(), 
Point.measurement("temperature").addTag("location", "south")));
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(points));
+
+        CamelInfluxDb2Exception failure = 
assertInstanceOf(CamelInfluxDb2Exception.class, exchange.getException(),
+                "exception of an exchange whose second point has no field");
+        assertTrue(failure.getMessage().startsWith("The point at index 1 of 
the Points body has no field with a value"),
+                failure.getMessage());
+        assertEquals(List.of(), writes, "write requests at the server");
+        after.assertIsSatisfied();
+    }
+
+    @Test
+    void nullPointOfPointsIsLeftOutAsBefore() throws Exception {
+        MockEndpoint after = getMockEndpoint("mock:after");
+        after.expectedMessageCount(1);
+
+        Exchange exchange = template.send("direct:write", e -> 
e.getIn().setBody(Points.create(Arrays.asList(point(), null))));
+
+        assertNull(exchange.getException());
+        assertEquals(List.of("temperature,location=north value=60.0"), writes,
+                "write requests at the server when the exchange has 
completed");
+        after.assertIsSatisfied();
+    }
+
+    @Test
+    void immutableMapIsWritten() throws Exception {
+        MockEndpoint after = getMockEndpoint("mock:after");
+        after.expectedMessageCount(1);
+
+        Exchange exchange = template.send("direct:write",
+                e -> e.getIn().setBody(Map.of(InfluxDb2Constants.MEASUREMENT, 
"temperature", "value", 60.0)));
+
+        assertNull(exchange.getException());
+        assertEquals(List.of("temperature value=60.0"), writes,
+                "write requests at the server when the exchange has 
completed");
+        after.assertIsSatisfied();
+    }
+
     private static Point point() {
         return Point.measurement("temperature").addTag("location", 
"north").addField("value", 60.0);
     }
diff --git 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDbConverterTest.java
 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDbConverterTest.java
index 9eb33d60aa0c..3a68f06bfff9 100644
--- 
a/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDbConverterTest.java
+++ 
b/components/camel-influxdb2/src/test/java/org/apache/camel/component/influxdb2/converters/CamelInfluxDbConverterTest.java
@@ -17,6 +17,8 @@
 package org.apache.camel.component.influxdb2.converters;
 
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import com.influxdb.client.write.Point;
@@ -25,6 +27,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -87,6 +90,30 @@ public class CamelInfluxDbConverterTest {
 
     }
 
+    @Test
+    void convertsAnImmutableMap() {
+        Map<String, Object> pointInMapFormat = 
Map.of(InfluxDb2Constants.MEASUREMENT, "testCPU", "busy", 99);
+
+        Point p = CamelInfluxDb2Converters.fromMapToPoint(pointInMapFormat);
+
+        assertEquals("testCPU busy=99i", p.toLineProtocol());
+    }
+
+    @Test
+    void doesNotChangeTheMapOfTheCaller() {
+        Map<String, Object> pointInMapFormat = new LinkedHashMap<>();
+        pointInMapFormat.put(InfluxDb2Constants.MEASUREMENT, "testCPU");
+        pointInMapFormat.put("busy", 99);
+        Map<String, Object> before = new LinkedHashMap<>(pointInMapFormat);
+
+        Point p = CamelInfluxDb2Converters.fromMapToPoint(pointInMapFormat);
+
+        assertEquals("testCPU busy=99i", p.toLineProtocol());
+        assertEquals(before, pointInMapFormat);
+        // the entries are in the same order: the measurement name was not 
removed and put back at the end
+        assertEquals(List.copyOf(before.keySet()), 
List.copyOf(pointInMapFormat.keySet()));
+    }
+
     @Test
     public void canAddByte() {
         Map<String, Object> pointInMapFormat = new HashMap<>();
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index d13479088c32..eead8427aa02 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -4713,6 +4713,20 @@ by the time the next step read it. They now behave as 
follows:
 * `xmlSecurity`: the catalog now gives the defaults the data format uses: 
`secureTagContents` is `true`, and
   `keyCipherAlgorithm` has no default. The behaviour is unchanged.
 
+=== camel-influxdb2 - a point without fields fails the exchange, and InfluxDB 
is called at startup only to create the organization or bucket
+
+A `Point` without a field that has a value (no field at all, or only fields 
whose value is `null`, NaN or infinite) now
+fails the exchange with a `CamelInfluxDb2Exception`. This also applies to a 
`Point` converted from a `Map` body and to
+each point of a `Points` body. The InfluxDB client skipped such a point, so 
the exchange completed while nothing was
+written.
+
+The endpoint now looks the organization up when it starts only when 
`autoCreateOrg` is `true` (the default), and the
+bucket only when `autoCreateBucket` is `true` (the default). With both options 
set to `false` it no longer calls
+InfluxDB when it starts, so the route starts while InfluxDB is down, and an 
organization or a bucket that does not exist
+fails the first write instead of being looked up. With `autoCreateOrg=false` 
and `autoCreateBucket=true` the
+organization is looked up only when the bucket must be created, and the 
endpoint fails to start with a message that
+names them when that organization does not exist.
+
 === camel-mail - the text of multipart mails, the charset of sent text, and 
producers on imap or pop3
 
 A multipart mail without a text part, such as one with only a PDF attachment, 
now converts to a `String` or an

Reply via email to