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 6e6449e8ccdf CAMEL-26270: camel-openstack - producers work from any 
thread, and the swift and glance payload converts the body (#29109)
6e6449e8ccdf is described below

commit 6e6449e8ccdf57e6b28c487f16b9a7f9ac89e700
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Nov 5 07:16:47 2026 +0100

    CAMEL-26270: camel-openstack - producers work from any thread, and the 
swift and glance payload converts the body (#29109)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/components/openstack-glance.json |   2 +-
 .../camel/catalog/components/openstack-swift.json  |   2 +-
 .../catalog/docs/openstack-swift-component.adoc    |   4 +-
 .../openstack/glance/openstack-glance.json         |   2 +-
 .../component/openstack/swift/openstack-swift.json |   2 +-
 .../src/main/docs/openstack-swift-component.adoc   |   4 +-
 .../cinder/producer/SnapshotProducer.java          |   2 +-
 .../openstack/cinder/producer/VolumeProducer.java  |   2 +-
 .../common/AbstractOpenstackProducer.java          |  55 +++++++++--
 .../component/openstack/glance/GlanceEndpoint.java |  10 +-
 .../component/openstack/glance/GlanceProducer.java |   2 +-
 .../keystone/producer/DomainProducer.java          |   2 +-
 .../openstack/keystone/producer/GroupProducer.java |   2 +-
 .../keystone/producer/ProjectProducer.java         |   2 +-
 .../keystone/producer/RegionProducer.java          |   2 +-
 .../openstack/keystone/producer/UserProducer.java  |   2 +-
 .../neutron/producer/NetworkProducer.java          |   2 +-
 .../openstack/neutron/producer/PortProducer.java   |   2 +-
 .../openstack/neutron/producer/RouterProducer.java |   2 +-
 .../openstack/neutron/producer/SubnetProducer.java |   2 +-
 .../openstack/nova/producer/FlavorsProducer.java   |   2 +-
 .../openstack/nova/producer/KeypairProducer.java   |   2 +-
 .../openstack/nova/producer/ServerProducer.java    |   2 +-
 .../component/openstack/swift/SwiftEndpoint.java   |   6 +-
 .../swift/producer/ContainerProducer.java          |   2 +-
 .../openstack/swift/producer/ObjectProducer.java   |   2 +-
 .../openstack/it/OpenstackGlanceTest.java          |  30 ++++++
 .../openstack/it/OpenstackSessionTest.java         | 109 +++++++++++++++++++++
 .../openstack/it/OpenstackSwiftObjectTest.java     |  86 ++++++++++++++++
 .../openstack/it/OpenstackWiremockTestSupport.java |   4 +
 .../mappings/swift/objects/object_put.json         |  20 ++++
 31 files changed, 330 insertions(+), 40 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-glance.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-glance.json
index e3ec1c39a4d0..0415971ad6e4 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-glance.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-glance.json
@@ -42,7 +42,7 @@
     "CamelOpenstackProperties": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Map<String, String>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The image properties.", 
"constantName": 
"org.apache.camel.component.openstack.common.OpenstackConstants#PROPERTIES" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "create", "carriers": [ "stream", "file", "pojo" ] }, { 
"option": "operation", "value": "upload", "carriers": [ "stream", "file", 
"pojo" ] } ], "description": "For create and upload the image data: an 
InputStream, a File, a URL or a Payload of openstack4j; text, bytes or the file 
of the file consumer fail: convert it to a File or an InputStream first. 
reserve and update take an Image of openstack4j [...]
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "create", "carriers": [ "stream", "file", "text", 
"bytes", "pojo" ] }, { "option": "operation", "value": "upload", "carriers": [ 
"stream", "file", "text", "bytes", "pojo" ] } ], "description": "For create and 
upload the image data: an InputStream, a File, a URL or a Payload of 
openstack4j, or else a body that converts to an InputStream such as text, bytes 
or the file of the file consumer. reserve a [...]
     "producerOut": { "carriers": [ "pojo", "list", "null", "unchanged" ], 
"byOption": [ { "option": "operation", "value": "reserve", "carriers": [ "pojo" 
] }, { "option": "operation", "value": "create", "carriers": [ "pojo" ] }, { 
"option": "operation", "value": "upload", "carriers": [ "pojo" ] }, { "option": 
"operation", "value": "update", "carriers": [ "pojo" ] }, { "option": 
"operation", "value": "get", "carriers": [ "pojo", "null" ] }, { "option": 
"operation", "value": "getAll", "car [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-swift.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-swift.json
index f830823a94c3..8ac355c86ae4 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-swift.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openstack-swift.json
@@ -44,7 +44,7 @@
     "CamelOpenstackName": { "index": 13, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The name.", "constantName": 
"org.apache.camel.component.openstack.common.OpenstackConstants#NAME" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "description": "For create of an 
object the content: an InputStream, a File, a URL or a Payload of openstack4j; 
text, bytes or the file of the file consumer fail: convert it to a File or an 
InputStream first. createUpdateMetadata and deleteMetadata take the metadata as 
a Map. The containers take a CreateUpdateContainerOptions or 
ContainerListOptions of openstack4j, or else headers" },
+    "producerIn": { "carriers": [ "any" ], "description": "For create of an 
object the content: an InputStream, a File, a URL or a Payload of openstack4j, 
or else a body that converts to an InputStream such as text, bytes or the file 
of the file consumer. createUpdateMetadata and deleteMetadata take the metadata 
as a Map. The containers take a CreateUpdateContainerOptions or 
ContainerListOptions of openstack4j, or else headers" },
     "producerOut": { "carriers": [ "text", "pojo", "list", "map", "null", 
"unchanged" ], "byOption": [ { "option": "operation", "value": "getAll", 
"carriers": [ "list" ] }, { "option": "operation", "value": "getMetadata", 
"carriers": [ "map" ] }, { "option": "operation", "value": "delete", 
"carriers": [ "unchanged" ] }, { "option": "operation", "value": 
"createUpdateMetadata", "carriers": [ "unchanged" ] }, { "option": "operation", 
"value": "deleteMetadata", "carriers": [ "unchanged" ] } [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openstack-swift-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openstack-swift-component.adoc
index 9ec139f27562..451d479f8d85 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openstack-swift-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openstack-swift-component.adoc
@@ -103,7 +103,7 @@ or 
`org.openstack4j.model.storage.object.options.ContainerListOptions` for listi
 === Example
 
 The following route stores each file of the `outbox` folder as an object of 
the `reports` container.
-The body must be an `InputStream`, a `File`, a `URL` or a `Payload` of 
openstack4j, so the route converts the file of the file consumer to a 
`java.io.File` first.
+The body is the content of the object: an `InputStream`, a `File`, a `URL` or 
a `Payload` of openstack4j, or else a body that converts to an `InputStream`, 
such as text, bytes or the file of the file consumer.
 The endpoint is the address of the identity service (Keystone), and the 
credentials are property placeholders:
 
 [source,yaml]
@@ -122,8 +122,6 @@ The endpoint is the address of the identity service 
(Keystone), and the credenti
             expression:
               simple:
                 expression: "${header.CamelFileName}"
-        - convertBodyTo:
-            type: java.io.File
         - to:
             uri: openstack-swift:http://controller:5000/v3
             parameters:
diff --git 
a/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/glance/openstack-glance.json
 
b/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/glance/openstack-glance.json
index e3ec1c39a4d0..0415971ad6e4 100644
--- 
a/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/glance/openstack-glance.json
+++ 
b/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/glance/openstack-glance.json
@@ -42,7 +42,7 @@
     "CamelOpenstackProperties": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Map<String, String>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The image properties.", 
"constantName": 
"org.apache.camel.component.openstack.common.OpenstackConstants#PROPERTIES" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "create", "carriers": [ "stream", "file", "pojo" ] }, { 
"option": "operation", "value": "upload", "carriers": [ "stream", "file", 
"pojo" ] } ], "description": "For create and upload the image data: an 
InputStream, a File, a URL or a Payload of openstack4j; text, bytes or the file 
of the file consumer fail: convert it to a File or an InputStream first. 
reserve and update take an Image of openstack4j [...]
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "create", "carriers": [ "stream", "file", "text", 
"bytes", "pojo" ] }, { "option": "operation", "value": "upload", "carriers": [ 
"stream", "file", "text", "bytes", "pojo" ] } ], "description": "For create and 
upload the image data: an InputStream, a File, a URL or a Payload of 
openstack4j, or else a body that converts to an InputStream such as text, bytes 
or the file of the file consumer. reserve a [...]
     "producerOut": { "carriers": [ "pojo", "list", "null", "unchanged" ], 
"byOption": [ { "option": "operation", "value": "reserve", "carriers": [ "pojo" 
] }, { "option": "operation", "value": "create", "carriers": [ "pojo" ] }, { 
"option": "operation", "value": "upload", "carriers": [ "pojo" ] }, { "option": 
"operation", "value": "update", "carriers": [ "pojo" ] }, { "option": 
"operation", "value": "get", "carriers": [ "pojo", "null" ] }, { "option": 
"operation", "value": "getAll", "car [...]
   },
   "properties": {
diff --git 
a/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/swift/openstack-swift.json
 
b/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/swift/openstack-swift.json
index f830823a94c3..8ac355c86ae4 100644
--- 
a/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/swift/openstack-swift.json
+++ 
b/components/camel-openstack/src/generated/resources/META-INF/org/apache/camel/component/openstack/swift/openstack-swift.json
@@ -44,7 +44,7 @@
     "CamelOpenstackName": { "index": 13, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The name.", "constantName": 
"org.apache.camel.component.openstack.common.OpenstackConstants#NAME" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "description": "For create of an 
object the content: an InputStream, a File, a URL or a Payload of openstack4j; 
text, bytes or the file of the file consumer fail: convert it to a File or an 
InputStream first. createUpdateMetadata and deleteMetadata take the metadata as 
a Map. The containers take a CreateUpdateContainerOptions or 
ContainerListOptions of openstack4j, or else headers" },
+    "producerIn": { "carriers": [ "any" ], "description": "For create of an 
object the content: an InputStream, a File, a URL or a Payload of openstack4j, 
or else a body that converts to an InputStream such as text, bytes or the file 
of the file consumer. createUpdateMetadata and deleteMetadata take the metadata 
as a Map. The containers take a CreateUpdateContainerOptions or 
ContainerListOptions of openstack4j, or else headers" },
     "producerOut": { "carriers": [ "text", "pojo", "list", "map", "null", 
"unchanged" ], "byOption": [ { "option": "operation", "value": "getAll", 
"carriers": [ "list" ] }, { "option": "operation", "value": "getMetadata", 
"carriers": [ "map" ] }, { "option": "operation", "value": "delete", 
"carriers": [ "unchanged" ] }, { "option": "operation", "value": 
"createUpdateMetadata", "carriers": [ "unchanged" ] }, { "option": "operation", 
"value": "deleteMetadata", "carriers": [ "unchanged" ] } [...]
   },
   "properties": {
diff --git 
a/components/camel-openstack/src/main/docs/openstack-swift-component.adoc 
b/components/camel-openstack/src/main/docs/openstack-swift-component.adoc
index 9ec139f27562..451d479f8d85 100644
--- a/components/camel-openstack/src/main/docs/openstack-swift-component.adoc
+++ b/components/camel-openstack/src/main/docs/openstack-swift-component.adoc
@@ -103,7 +103,7 @@ or 
`org.openstack4j.model.storage.object.options.ContainerListOptions` for listi
 === Example
 
 The following route stores each file of the `outbox` folder as an object of 
the `reports` container.
-The body must be an `InputStream`, a `File`, a `URL` or a `Payload` of 
openstack4j, so the route converts the file of the file consumer to a 
`java.io.File` first.
+The body is the content of the object: an `InputStream`, a `File`, a `URL` or 
a `Payload` of openstack4j, or else a body that converts to an `InputStream`, 
such as text, bytes or the file of the file consumer.
 The endpoint is the address of the identity service (Keystone), and the 
credentials are property placeholders:
 
 [source,yaml]
@@ -122,8 +122,6 @@ The endpoint is the address of the identity service 
(Keystone), and the credenti
             expression:
               simple:
                 expression: "${header.CamelFileName}"
-        - convertBodyTo:
-            type: java.io.File
         - to:
             uri: openstack-swift:http://controller:5000/v3
             parameters:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/SnapshotProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/SnapshotProducer.java
index 59cc2f0158ce..181f23235b68 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/SnapshotProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/SnapshotProducer.java
@@ -39,7 +39,7 @@ public class SnapshotProducer extends 
AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
 
         switch (operation) {
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/VolumeProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/VolumeProducer.java
index 673cf3f03275..0126ea7ae6d7 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/VolumeProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/cinder/producer/VolumeProducer.java
@@ -40,7 +40,7 @@ public class VolumeProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
 
         switch (operation) {
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/common/AbstractOpenstackProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/common/AbstractOpenstackProducer.java
index 9eea683d1aab..0f5177b449c9 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/common/AbstractOpenstackProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/common/AbstractOpenstackProducer.java
@@ -26,20 +26,58 @@ import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.openstack4j.api.OSClient;
+import org.openstack4j.api.OSClient.OSClientV2;
+import org.openstack4j.api.OSClient.OSClientV3;
 import org.openstack4j.model.common.ActionResponse;
 import org.openstack4j.model.common.Payload;
 import org.openstack4j.model.common.Payloads;
+import org.openstack4j.openstack.OSFactory;
 
 public abstract class AbstractOpenstackProducer extends DefaultProducer {
 
     protected OSClient os;
 
-    private AbstractOpenstackEndpoint endpoint;
+    private final AbstractOpenstackEndpoint endpoint;
+
+    // the session with the newest token, as openstack4j re-authenticates an 
expired token into the session in use
+    private volatile OSClient session;
 
     protected AbstractOpenstackProducer(AbstractOpenstackEndpoint endpoint, 
OSClient client) {
         super(endpoint);
         this.os = client;
         this.endpoint = endpoint;
+        this.session = client;
+    }
+
+    @Override
+    public void process(Exchange exchange) throws Exception {
+        final OSClient current = useSession();
+        try {
+            doProcess(exchange);
+        } finally {
+            session = current;
+        }
+    }
+
+    /**
+     * Processes the exchange with the services of {@link #os}, which use the 
openstack4j session of the calling thread.
+     */
+    protected abstract void doProcess(Exchange exchange) throws Exception;
+
+    /**
+     * The services of openstack4j use the session of the calling thread 
(OSClientSession is thread local), and the
+     * client was authenticated on the thread that created the producer. 
Creates the session of the calling thread from
+     * the token of the client, as openstack4j documents for using a client 
from several threads.
+     */
+    private OSClient useSession() {
+        final OSClient client = session;
+        if (client instanceof OSClientV3 v3 && v3.getToken() != null) {
+            return OSFactory.clientFromToken(v3.getToken(), 
endpoint.getConfig());
+        }
+        if (client instanceof OSClientV2 v2 && v2.getAccess() != null) {
+            return OSFactory.clientFromAccess(v2.getAccess(), 
endpoint.getConfig());
+        }
+        return client;
     }
 
     protected Payload createPayload(Message msg) {
@@ -52,16 +90,21 @@ public abstract class AbstractOpenstackProducer extends 
DefaultProducer {
         Object messageBody = msg.getBody();
         if (messageBody instanceof URL) {
             payload = Payloads.create((URL) messageBody);
-        }
-        if (messageBody instanceof File) {
+        } else if (messageBody instanceof File) {
             payload = Payloads.create((File) messageBody);
-        }
-        if (messageBody instanceof InputStream) {
+        } else if (messageBody instanceof InputStream) {
             payload = Payloads.create((InputStream) messageBody);
+        } else if (messageBody != null) {
+            // such as text, bytes or the file of the file consumer
+            InputStream is = msg.getBody(InputStream.class);
+            if (is != null) {
+                payload = Payloads.create(is);
+            }
         }
 
         if (payload == null) {
-            throw new IllegalArgumentException("You have to set payload. It 
can be InputStream, File or URL class");
+            throw new IllegalArgumentException(
+                    "You have to set payload. It can be InputStream, File or 
URL class, or a body that converts to InputStream");
         }
 
         return payload;
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceEndpoint.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceEndpoint.java
index a0f0defb853d..82ddb285d238 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceEndpoint.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceEndpoint.java
@@ -36,11 +36,13 @@ import static 
org.apache.camel.component.openstack.common.OpenstackConstants.SCH
 @UriEndpoint(firstVersion = "2.19.0", scheme = SCHEME_GLANCE, title = 
"OpenStack Glance", syntax = "openstack-glance:host",
              category = { Category.CONTAINER }, producerOnly = true, 
headersClass = GlanceConstants.class)
 @BodyTypes(producerIn = @BodyType(value = Carrier.ANY, byOption = {
-        @BodyType.When(option = "operation", value = "create", carriers = { 
Carrier.STREAM, Carrier.FILE, Carrier.POJO }),
-        @BodyType.When(option = "operation", value = "upload", carriers = { 
Carrier.STREAM, Carrier.FILE, Carrier.POJO }) },
+        @BodyType.When(option = "operation", value = "create",
+                       carriers = { Carrier.STREAM, Carrier.FILE, 
Carrier.TEXT, Carrier.BYTES, Carrier.POJO }),
+        @BodyType.When(option = "operation", value = "upload",
+                       carriers = { Carrier.STREAM, Carrier.FILE, 
Carrier.TEXT, Carrier.BYTES, Carrier.POJO }) },
                                   description = "For create and upload the 
image data: an InputStream, a File, a URL or a Payload of "
-                                                + "openstack4j; text, bytes or 
the file of the file consumer fail: convert it to a File or "
-                                                + "an InputStream first. 
reserve and update take an Image of openstack4j, or else the fields from "
+                                                + "openstack4j, or else a body 
that converts to an InputStream such as text, bytes or "
+                                                + "the file of the file 
consumer. reserve and update take an Image of openstack4j, or else the fields 
from "
                                                 + "headers such as 
CamelOpenstackName"),
            producerOut = @BodyType(value = { Carrier.POJO, Carrier.LIST, 
Carrier.NULL, Carrier.UNCHANGED }, byOption = {
                    @BodyType.When(option = "operation", value = "reserve", 
carriers = Carrier.POJO),
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceProducer.java
index ec5ebb6db8a7..15237ba7dd2f 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/glance/GlanceProducer.java
@@ -41,7 +41,7 @@ public class GlanceProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
 
         switch (operation) {
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/DomainProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/DomainProducer.java
index 1bca892728e8..54bc267dc2f2 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/DomainProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/DomainProducer.java
@@ -38,7 +38,7 @@ public class DomainProducer extends AbstractKeystoneProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/GroupProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/GroupProducer.java
index e1413b4dcbc1..26098fea32b5 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/GroupProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/GroupProducer.java
@@ -38,7 +38,7 @@ public class GroupProducer extends AbstractKeystoneProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/ProjectProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/ProjectProducer.java
index 262ca8f2ffe1..858a6f7cdf92 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/ProjectProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/ProjectProducer.java
@@ -38,7 +38,7 @@ public class ProjectProducer extends AbstractKeystoneProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/RegionProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/RegionProducer.java
index d43f9bdf5de8..316d5d51983b 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/RegionProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/RegionProducer.java
@@ -38,7 +38,7 @@ public class RegionProducer extends AbstractKeystoneProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/UserProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/UserProducer.java
index 86420cf8dd1b..a58c8a23bf31 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/UserProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/keystone/producer/UserProducer.java
@@ -38,7 +38,7 @@ public class UserProducer extends AbstractKeystoneProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/NetworkProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/NetworkProducer.java
index 2e8cb66abd6b..317de9908944 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/NetworkProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/NetworkProducer.java
@@ -40,7 +40,7 @@ public class NetworkProducer extends 
AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/PortProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/PortProducer.java
index f22b7f440d02..e6d1fb9fc796 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/PortProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/PortProducer.java
@@ -39,7 +39,7 @@ public class PortProducer extends AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/RouterProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/RouterProducer.java
index 377ce99b0f73..77922ce219c0 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/RouterProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/RouterProducer.java
@@ -42,7 +42,7 @@ public class RouterProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/SubnetProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/SubnetProducer.java
index 7895218af641..8a4ddedd830b 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/SubnetProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/neutron/producer/SubnetProducer.java
@@ -42,7 +42,7 @@ public class SubnetProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/FlavorsProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/FlavorsProducer.java
index 7d60b31014c7..85fca63301ae 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/FlavorsProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/FlavorsProducer.java
@@ -39,7 +39,7 @@ public class FlavorsProducer extends 
AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/KeypairProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/KeypairProducer.java
index cccea3330296..ce5933981129 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/KeypairProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/KeypairProducer.java
@@ -35,7 +35,7 @@ public class KeypairProducer extends 
AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/ServerProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/ServerProducer.java
index ebe64ff5023f..f6fa642431c8 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/ServerProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/nova/producer/ServerProducer.java
@@ -42,7 +42,7 @@ public class ServerProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         final String operation = getOperation(exchange);
         switch (operation) {
             case OpenstackConstants.CREATE:
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/SwiftEndpoint.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/SwiftEndpoint.java
index c070f6d2a683..246c611061f4 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/SwiftEndpoint.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/SwiftEndpoint.java
@@ -38,9 +38,9 @@ import static 
org.apache.camel.component.openstack.common.OpenstackConstants.SCH
 @UriEndpoint(firstVersion = "2.19.0", scheme = SCHEME_SWIFT, title = 
"OpenStack Swift", syntax = "openstack-swift:host",
              category = { Category.CONTAINER }, producerOnly = true, 
headersClass = SwiftConstants.class)
 @BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
-                                  description = "For create of an object the 
content: an InputStream, a File, a URL or a Payload of openstack4j; "
-                                                + "text, bytes or the file of 
the file consumer fail: convert it to a File or an InputStream first. "
-                                                + "createUpdateMetadata and 
deleteMetadata take the metadata as a Map. The containers take a "
+                                  description = "For create of an object the 
content: an InputStream, a File, a URL or a Payload of openstack4j, "
+                                                + "or else a body that 
converts to an InputStream such as text, bytes or the file of the file "
+                                                + "consumer. 
createUpdateMetadata and deleteMetadata take the metadata as a Map. The 
containers take a "
                                                 + 
"CreateUpdateContainerOptions or ContainerListOptions of openstack4j, or else 
headers"),
            producerOut = @BodyType(value = {
                    Carrier.TEXT, Carrier.POJO, Carrier.LIST, Carrier.MAP, 
Carrier.NULL, Carrier.UNCHANGED },
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ContainerProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ContainerProducer.java
index 74ecb6f4e3fa..7626293d8e49 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ContainerProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ContainerProducer.java
@@ -40,7 +40,7 @@ public class ContainerProducer extends 
AbstractOpenstackProducer {
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
 
         switch (operation) {
diff --git 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ObjectProducer.java
 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ObjectProducer.java
index fcf8281e9c4c..b0b81f16e076 100644
--- 
a/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ObjectProducer.java
+++ 
b/components/camel-openstack/src/main/java/org/apache/camel/component/openstack/swift/producer/ObjectProducer.java
@@ -41,7 +41,7 @@ public class ObjectProducer extends AbstractOpenstackProducer 
{
     }
 
     @Override
-    public void process(Exchange exchange) throws Exception {
+    protected void doProcess(Exchange exchange) throws Exception {
         String operation = getOperation(exchange);
 
         switch (operation) {
diff --git 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackGlanceTest.java
 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackGlanceTest.java
index 93da9987d532..02346b7d2d49 100644
--- 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackGlanceTest.java
+++ 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackGlanceTest.java
@@ -21,6 +21,8 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.openstack.common.OpenstackConstants;
 import org.apache.camel.component.openstack.glance.GlanceConstants;
 import org.junit.jupiter.api.Test;
@@ -38,6 +40,18 @@ public class OpenstackGlanceTest extends 
OpenstackWiremockTestSupport {
 
     private static final String URI_FORMAT = 
"openstack-glance://%s?username=user&password=secret&project=project&operation=%s";
 
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // the producer is created when the route starts, and called 
from the seda consumer thread
+                from("seda:upload")
+                        .to(String.format(URI_FORMAT, url(), 
GlanceConstants.UPLOAD));
+            }
+        };
+    }
+
     @Test
     void createShouldSucceed() {
         Map<String, Object> headers = new HashMap<>();
@@ -60,6 +74,22 @@ public class OpenstackGlanceTest extends 
OpenstackWiremockTestSupport {
         assertEquals(0L, out.getMinRam());
     }
 
+    @Test
+    void uploadTextFromAnotherThreadShouldSucceed() {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(OpenstackConstants.NAME, "amphora-x64-haproxy");
+        headers.put(OpenstackConstants.ID, 
"4b434528-032b-4467-946c-b5880ce15c06");
+        headers.put(GlanceConstants.DISK_FORMAT, DiskFormat.QCOW2);
+        headers.put(GlanceConstants.CONTAINER_FORMAT, ContainerFormat.BARE);
+        headers.put(GlanceConstants.MIN_DISK, 0L);
+        headers.put(GlanceConstants.MIN_RAM, 0L);
+
+        Image out = template.requestBodyAndHeaders("seda:upload", "image 
data", headers, Image.class);
+
+        assertNotNull(out);
+        assertEquals("4b434528-032b-4467-946c-b5880ce15c06", out.getId());
+    }
+
     @Test
     void uploadShouldSucceed() {
         Map<String, Object> headers = new HashMap<>();
diff --git 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSessionTest.java
 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSessionTest.java
new file mode 100644
index 000000000000..ac10f789b4a4
--- /dev/null
+++ 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSessionTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.openstack.it;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.openstack.common.OpenstackConstants;
+import org.apache.camel.component.openstack.swift.SwiftConstants;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.openstack4j.model.storage.object.SwiftObject;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.head;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * The producer keeps using the token that openstack4j renewed when the first 
token expired, from any thread.
+ */
+public class OpenstackSessionTest extends OpenstackWiremockTestSupport {
+
+    private static final String URI_FORMAT
+            = 
"openstack-swift://%s?username=user&password=secret&project=project&operation=%s&subsystem="
+              + SwiftConstants.SWIFT_SUBSYSTEM_OBJECTS;
+
+    private static final String AUTH_URL = "/auth/tokens";
+    private static final String OBJECT_URL = 
"/12345321ae80457abc3728fa1e6feb78/test-container/test-file";
+
+    @BeforeAll
+    public static void stubExpiredToken() throws Exception {
+        String body;
+        try (InputStream is = 
OpenstackSessionTest.class.getResourceAsStream("/mappings/keystone/authv3_project.json"))
 {
+            JsonNode mapping = new ObjectMapper().readTree(is);
+            body = mapping.get("response").get("body").asText();
+        }
+
+        // the first authentication gives a token that has expired, the next 
one a valid token
+        server().stubFor(post(urlEqualTo(AUTH_URL)).atPriority(1)
+                
.inScenario("token").whenScenarioStateIs(STARTED).willSetStateTo("renewed")
+                .willReturn(tokenResponse(body, "expired")));
+        server().stubFor(post(urlEqualTo(AUTH_URL)).atPriority(1)
+                .inScenario("token").whenScenarioStateIs("renewed")
+                .willReturn(tokenResponse(body, "renewed")));
+        server().stubFor(head(urlEqualTo(OBJECT_URL)).atPriority(1)
+                .withHeader("X-Auth-Token", equalTo("expired"))
+                .willReturn(aResponse().withStatus(401)));
+    }
+
+    private static ResponseDefinitionBuilder tokenResponse(String body, String 
token) {
+        return aResponse().withStatus(201)
+                .withHeader("Content-Type", "application/json; charset=UTF-8")
+                .withHeader("X-Subject-Token", token)
+                .withBody(body)
+                .withTransformers("response-template");
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("seda:get?concurrentConsumers=2")
+                        .to(String.format(URI_FORMAT, url(), 
OpenstackConstants.GET));
+            }
+        };
+    }
+
+    @Test
+    void renewedTokenShouldBeKept() {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(SwiftConstants.CONTAINER_NAME, "test-container");
+        headers.put(SwiftConstants.OBJECT_NAME, "test-file");
+
+        for (int i = 0; i < 5; i++) {
+            SwiftObject swiftObject = 
template.requestBodyAndHeaders("seda:get", null, headers, SwiftObject.class);
+            assertEquals("test-file", swiftObject.getName());
+        }
+
+        // the authentication of the producer, and the one renewal of the 
expired token
+        server().verify(2, postRequestedFor(urlEqualTo(AUTH_URL)));
+    }
+
+}
diff --git 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSwiftObjectTest.java
 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSwiftObjectTest.java
index 5a442711cc9b..159023822ec0 100644
--- 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSwiftObjectTest.java
+++ 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackSwiftObjectTest.java
@@ -16,15 +16,25 @@
  */
 package org.apache.camel.component.openstack.it;
 
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.component.openstack.common.OpenstackConstants;
 import org.apache.camel.component.openstack.swift.SwiftConstants;
 import org.junit.jupiter.api.Test;
 import org.openstack4j.model.storage.object.SwiftObject;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class OpenstackSwiftObjectTest extends OpenstackWiremockTestSupport {
 
@@ -34,6 +44,27 @@ public class OpenstackSwiftObjectTest extends 
OpenstackWiremockTestSupport {
 
     private static final String OBJECT_CONTAINER_NAME = "test-container";
     private static final String OBJECT_NAME = "test-file";
+    // the stub only answers a PUT of this content; WireMock adds a --gzip 
suffix to the ETag of a gzip request
+    private static final String OBJECT_ETAG = 
"d8b3a3e8f1b7c9a2e4f6a8b0c2d4e6f8";
+    private static final String OBJECT_CONTENT = "Hello Swift";
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // the producers are created when the routes start, and called 
from the consumer threads
+                from("seda:get?concurrentConsumers=5")
+                        .to(String.format(URI_FORMAT, url(), 
OpenstackConstants.GET));
+
+                
from("file:target/openstack-swift-outbox?initialDelay=0&delay=10&delete=true")
+                        .setHeader(SwiftConstants.CONTAINER_NAME, 
constant(OBJECT_CONTAINER_NAME))
+                        .setHeader(SwiftConstants.OBJECT_NAME, 
header(Exchange.FILE_NAME))
+                        .to(String.format(URI_FORMAT, url(), 
OpenstackConstants.CREATE))
+                        .to("mock:stored");
+            }
+        };
+    }
 
     @Test
     void getShouldSucceed() {
@@ -51,4 +82,59 @@ public class OpenstackSwiftObjectTest extends 
OpenstackWiremockTestSupport {
         assertEquals("12345678901234567890", swiftObject.getETag());
     }
 
+    @Test
+    void getFromAnotherThreadShouldSucceed() {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(SwiftConstants.CONTAINER_NAME, OBJECT_CONTAINER_NAME);
+        headers.put(SwiftConstants.OBJECT_NAME, OBJECT_NAME);
+
+        List<CompletableFuture<SwiftObject>> futures = new ArrayList<>();
+        for (int i = 0; i < 20; i++) {
+            futures.add(template.asyncRequestBodyAndHeaders("seda:get", null, 
headers, SwiftObject.class));
+        }
+
+        for (CompletableFuture<SwiftObject> future : futures) {
+            SwiftObject swiftObject = future.join();
+            assertEquals(OBJECT_NAME, swiftObject.getName());
+            assertEquals("12345678901234567890", swiftObject.getETag());
+        }
+    }
+
+    @Test
+    void createFromTextShouldSucceed() {
+        String uri = String.format(URI_FORMAT, url(), 
OpenstackConstants.CREATE);
+        String etag = template.requestBodyAndHeaders(uri, OBJECT_CONTENT, 
objectHeaders(), String.class);
+
+        assertNotNull(etag);
+        assertTrue(etag.startsWith(OBJECT_ETAG), etag);
+    }
+
+    @Test
+    void createFromBytesShouldSucceed() {
+        String uri = String.format(URI_FORMAT, url(), 
OpenstackConstants.CREATE);
+        String etag = template.requestBodyAndHeaders(uri, 
OBJECT_CONTENT.getBytes(StandardCharsets.UTF_8), objectHeaders(),
+                String.class);
+
+        assertNotNull(etag);
+        assertTrue(etag.startsWith(OBJECT_ETAG), etag);
+    }
+
+    @Test
+    void createFromFileConsumerShouldSucceed() throws Exception {
+        MockEndpoint stored = getMockEndpoint("mock:stored");
+        stored.expectedMessageCount(1);
+        stored.message(0).body(String.class).startsWith(OBJECT_ETAG);
+
+        template.sendBodyAndHeader("file:target/openstack-swift-outbox", 
OBJECT_CONTENT, Exchange.FILE_NAME, OBJECT_NAME);
+
+        stored.assertIsSatisfied();
+    }
+
+    private static Map<String, Object> objectHeaders() {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(SwiftConstants.CONTAINER_NAME, OBJECT_CONTAINER_NAME);
+        headers.put(SwiftConstants.OBJECT_NAME, OBJECT_NAME);
+        return headers;
+    }
+
 }
diff --git 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackWiremockTestSupport.java
 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackWiremockTestSupport.java
index eb347e0e8129..2af14beacdd0 100644
--- 
a/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackWiremockTestSupport.java
+++ 
b/components/camel-openstack/src/test/java/org/apache/camel/component/openstack/it/OpenstackWiremockTestSupport.java
@@ -47,4 +47,8 @@ public class OpenstackWiremockTestSupport extends 
CamelTestSupport {
         return server.baseUrl();
     }
 
+    protected static WireMockServer server() {
+        return server;
+    }
+
 }
diff --git 
a/components/camel-openstack/src/test/resources/mappings/swift/objects/object_put.json
 
b/components/camel-openstack/src/test/resources/mappings/swift/objects/object_put.json
new file mode 100644
index 000000000000..bebe9a916573
--- /dev/null
+++ 
b/components/camel-openstack/src/test/resources/mappings/swift/objects/object_put.json
@@ -0,0 +1,20 @@
+{
+  "id" : "8d0c4c8e-5b8e-4a0f-9f3e-2f6d1c7a9b10",
+  "name" : "object_put.json",
+  "request" : {
+    "url" : "/12345321ae80457abc3728fa1e6feb78/test-container/test-file",
+    "method" : "PUT",
+    "bodyPatterns" : [ {
+      "equalTo" : "Hello Swift"
+    } ]
+  },
+  "response" : {
+    "headers" : {
+      "ETag" : "d8b3a3e8f1b7c9a2e4f6a8b0c2d4e6f8"
+    },
+    "status" : 201
+  },
+  "uuid" : "8d0c4c8e-5b8e-4a0f-9f3e-2f6d1c7a9b10",
+  "persistent" : true,
+  "insertionIndex" : 1
+}

Reply via email to