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

davsclaus pushed a commit to branch CAMEL-26270-openstack-thread-session
in repository https://gitbox.apache.org/repos/asf/camel.git

commit afc523fcb00ddb881dad2fef0f193e07bd2787d3
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 15:44:35 2026 +0100

    CAMEL-26270: camel-openstack - producers work from any thread, and the 
swift and glance payload converts the body
    
    The openstack4j session is thread local, and every producer authenticated
    its client on the thread that created it, so a call from another thread
    (a file, seda or timer consumer) failed with "Unable to retrieve current
    session". The producer now creates the session of the calling thread from
    the token of its client on each call (OSFactory.clientFromToken /
    clientFromAccess), and keeps the token that openstack4j renews when it
    expires. The producers implement doProcess instead of process.
    
    The payload of swift object create and glance create/upload now converts
    a body that is not a Payload, URL, File or InputStream to an InputStream,
    so text, bytes and the file of the file consumer work.
    
    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