This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.14.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.14.x by this push:
new d0d4b0565284 [backport camel-4.14.x] CAMEL-24342: camel-google-storage
- fix wrong blob metadata and missing object handling (#25583)
d0d4b0565284 is described below
commit d0d4b0565284b12e440ae9d5ec4d534b0c8fbef2
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Aug 24 08:01:48 2026 +0200
[backport camel-4.14.x] CAMEL-24342: camel-google-storage - fix wrong blob
metadata and missing object handling (#25583)
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).
(cherry picked from commit c3e964b0130f959fafc4efa618a99bb5ae3afa11)
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[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 83e98449baf4..58854716645e 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;
@@ -126,7 +131,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) {
@@ -134,7 +139,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
@@ -221,18 +226,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());
@@ -316,6 +315,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());
@@ -362,6 +365,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..bf1456fa232d
--- /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.junit5.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");
+ }
+}