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

oscerd 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 c3e964b0130f CAMEL-24342: camel-google-storage - fix wrong blob 
metadata and missing object handling (#25338)
c3e964b0130f is described below

commit c3e964b0130f959fafc4efa618a99bb5ae3afa11
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Aug 6 09:59:06 2026 +0200

    CAMEL-24342: camel-google-storage - fix wrong blob metadata and missing 
object handling (#25338)
    
    processFile assigned the content type to the Content-Encoding and 
Cache-Control
    fields of the blob, so an upload carrying those headers stored the content 
type
    in them (or null when no content type was sent). getObject dereferenced the 
blob
    returned by the storage client without a null check, so requesting an 
object not
    present in the bucket failed with a NullPointerException instead of the 
intended
    missing-object handling. Tests align with project conventions 
(package-private
    classes/methods and AssertJ assertions).
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../google/storage/GoogleCloudStorageConsumer.java |  8 +++
 .../google/storage/GoogleCloudStorageProducer.java | 27 +++++---
 ...oogleCloudStorageConsumerMissingObjectTest.java | 58 ++++++++++++++++
 .../storage/unit/ProducerBlobMetadataTest.java     | 79 ++++++++++++++++++++++
 4 files changed, 161 insertions(+), 11 deletions(-)

diff --git 
a/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumer.java
 
b/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumer.java
index 35b867c7391a..c6bd1b1f8c6d 100644
--- 
a/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumer.java
+++ 
b/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumer.java
@@ -94,6 +94,14 @@ public class GoogleCloudStorageConsumer extends 
ScheduledBatchPollingConsumer {
 
             Blob blob = getStorageClient().get(bucketName, fileName);
 
+            // okay we have some response from Google so lets mark the 
consumer as ready
+            forceConsumerAsReady();
+
+            if (blob == null) {
+                LOG.trace("No object found in bucket [{}] with file name 
[{}]", bucketName, fileName);
+                return 0;
+            }
+
             exchanges = createExchanges(blob, fileName);
         } else {
             LOG.trace("Queueing objects in bucket [{}]...", bucketName);
diff --git 
a/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageProducer.java
 
b/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageProducer.java
index 723b87050a53..e876cf7cb56b 100644
--- 
a/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageProducer.java
+++ 
b/components/camel-google/camel-google-storage/src/main/java/org/apache/camel/component/google/storage/GoogleCloudStorageProducer.java
@@ -23,7 +23,11 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.*;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import com.google.cloud.storage.Blob;
@@ -36,6 +40,7 @@ import com.google.cloud.storage.Storage.CopyRequest;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Message;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.IOHelper;
@@ -136,7 +141,7 @@ public class GoogleCloudStorageProducer extends 
DefaultProducer {
         }
         String ce = objectMetadata.remove("Content-Encoding");
         if (ce != null) {
-            builder.setContentEncoding(ct);
+            builder.setContentEncoding(ce);
         }
         String md5 = objectMetadata.remove("Content-Md5");
         if (md5 != null) {
@@ -144,7 +149,7 @@ public class GoogleCloudStorageProducer extends 
DefaultProducer {
         }
         String cc = objectMetadata.remove("Cache-Control");
         if (cc != null) {
-            builder.setCacheControl(ct);
+            builder.setCacheControl(cc);
         }
         BlobInfo blobInfo = builder.setMetadata(objectMetadata).build();
         // According to documentation, this internally uses a WriteChannel
@@ -231,18 +236,12 @@ public class GoogleCloudStorageProducer extends 
DefaultProducer {
     private void createDownloadLink(Storage storage, Exchange exchange) {
         final String bucketName = determineBucketName(exchange);
         final String objectName = determineObjectName(exchange);
-        Long expirationMillis
+        long expirationMillis
                 = 
exchange.getIn().getHeader(GoogleCloudStorageConstants.DOWNLOAD_LINK_EXPIRATION_TIME,
 300000L, Long.class);
-        long milliSeconds = 0;
-        if (expirationMillis != null) {
-            milliSeconds += expirationMillis;
-        } else {
-            milliSeconds += 1000 * 60 * 60;
-        }
 
         BlobId blobId = BlobId.of(bucketName, objectName);
         BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
-        URL url = storage.signUrl(blobInfo, milliSeconds, 
TimeUnit.MILLISECONDS);
+        URL url = storage.signUrl(blobInfo, expirationMillis, 
TimeUnit.MILLISECONDS);
 
         Message message = getMessageForResponse(exchange);
         message.setBody(url.toString());
@@ -326,6 +325,10 @@ public class GoogleCloudStorageProducer extends 
DefaultProducer {
         final String objectName = determineObjectName(exchange);
 
         Blob blob = storage.get(BlobId.of(bucketName, objectName));
+        if (blob == null) {
+            throw new RuntimeCamelException(
+                    "Object " + objectName + " does not exist in bucket " + 
bucketName);
+        }
         Message message = getMessageForResponse(exchange);
         
message.setBody(blob.getContent(Blob.BlobSourceOption.generationMatch()));
         message.setHeader(GoogleCloudStorageConstants.OBJECT_NAME, 
blob.getName());
@@ -372,6 +375,8 @@ public class GoogleCloudStorageProducer extends 
DefaultProducer {
     }
 
     private String determineObjectName(Exchange exchange) {
+        // the configured objectName deliberately wins over the header, see 
CAMEL-20998: a consumer in the
+        // same route sets the object name header, which would otherwise 
hijack the producer destination
         String key = getConfiguration().getObjectName();
         if (ObjectHelper.isEmpty(key)) {
             key = 
exchange.getIn().getHeader(GoogleCloudStorageConstants.OBJECT_NAME, 
String.class);
diff --git 
a/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumerMissingObjectTest.java
 
b/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumerMissingObjectTest.java
new file mode 100644
index 000000000000..2632efcf8002
--- /dev/null
+++ 
b/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/GoogleCloudStorageConsumerMissingObjectTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.google.storage;
+
+import org.apache.camel.CamelContext;
+import 
org.apache.camel.component.google.storage.localstorage.LocalStorageHelper;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+/**
+ * Verifies that polling for a configured object name that does not exist in 
the bucket is a no-op instead of failing.
+ */
+class GoogleCloudStorageConsumerMissingObjectTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        GoogleCloudStorageComponent component = 
context.getComponent("google-storage", GoogleCloudStorageComponent.class);
+        
component.getConfiguration().setStorageClient(LocalStorageHelper.getOptions().getService());
+        return context;
+    }
+
+    private GoogleCloudStorageConsumer createConsumer(String objectName) 
throws Exception {
+        GoogleCloudStorageEndpoint endpoint = context.getEndpoint(
+                
"google-storage://myCamelBucket?autoCreateBucket=true&objectName=" + objectName,
+                GoogleCloudStorageEndpoint.class);
+        GoogleCloudStorageConsumer consumer = (GoogleCloudStorageConsumer) 
endpoint.createConsumer(exchange -> {
+        });
+        endpoint.start();
+        return consumer;
+    }
+
+    @Test
+    void pollingAMissingObjectYieldsNoExchange() throws Exception {
+        GoogleCloudStorageConsumer consumer = 
createConsumer("there-is-no-such-object.txt");
+
+        // the object is not in the bucket, so the storage client hands back a 
null blob
+        assertThatNoException().isThrownBy(consumer::poll);
+        assertThat(consumer.poll()).isZero();
+    }
+}
diff --git 
a/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/unit/ProducerBlobMetadataTest.java
 
b/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/unit/ProducerBlobMetadataTest.java
new file mode 100644
index 000000000000..cf29f3a2b499
--- /dev/null
+++ 
b/components/camel-google/camel-google-storage/src/test/java/org/apache/camel/component/google/storage/unit/ProducerBlobMetadataTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.google.storage.unit;
+
+import java.io.ByteArrayInputStream;
+
+import com.google.cloud.storage.Blob;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.google.storage.GoogleCloudStorageConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ProducerBlobMetadataTest extends GoogleCloudStorageBaseTest {
+
+    private static final String FILE_NAME = "metadata.txt";
+
+    @EndpointInject
+    private ProducerTemplate template;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                
from("direct:store").to("google-storage://myCamelBucket?autoCreateBucket=true");
+                from("direct:getObject")
+                        
.to("google-storage://myCamelBucket?autoCreateBucket=true&operation=getObject");
+            }
+        };
+    }
+
+    @Test
+    void contentEncodingAndCacheControlAreStoredAsSent() {
+        Exchange exchange = template.request("direct:store", e -> {
+            e.getIn().setHeader(GoogleCloudStorageConstants.OBJECT_NAME, 
FILE_NAME);
+            e.getIn().setHeader(GoogleCloudStorageConstants.CONTENT_TYPE, 
"text/plain");
+            e.getIn().setHeader(GoogleCloudStorageConstants.CONTENT_ENCODING, 
"gzip");
+            e.getIn().setHeader(GoogleCloudStorageConstants.CACHE_CONTROL, 
"max-age=3600");
+            e.getIn().setBody(new ByteArrayInputStream("Hi, How are you 
?".getBytes()));
+        });
+
+        assertThat(exchange).isNotNull();
+        Blob blob = exchange.getMessage().getBody(Blob.class);
+        assertThat(blob).isNotNull();
+        // each field has to carry its own header value, they used to be 
overwritten with the content type
+        assertThat(blob.getContentType()).isEqualTo("text/plain");
+        assertThat(blob.getContentEncoding()).isEqualTo("gzip");
+        assertThat(blob.getCacheControl()).isEqualTo("max-age=3600");
+    }
+
+    @Test
+    void getObjectOnAMissingObjectReportsTheObjectName() {
+        Exchange exchange = template.request("direct:getObject",
+                e -> 
e.getIn().setHeader(GoogleCloudStorageConstants.OBJECT_NAME, 
"there-is-no-such-object.txt"));
+
+        assertThat(exchange).isNotNull();
+        Exception exception = exchange.getException();
+        assertThat(exception).as("a missing object must fail the 
exchange").isNotNull();
+        assertThat(exception.getMessage())
+                .isEqualTo("Object there-is-no-such-object.txt does not exist 
in bucket myCamelBucket");
+    }
+}

Reply via email to