This is an automated email from the ASF dual-hosted git repository.
acosentino 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 082c4d29eeee CAMEL-23295: Fix resource leak and improve error handling
in camel-splunk-hec producer (#22483)
082c4d29eeee is described below
commit 082c4d29eeeeb44796067fccfff60b016af8a423
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Apr 8 13:46:56 2026 +0200
CAMEL-23295: Fix resource leak and improve error handling in
camel-splunk-hec producer (#22483)
* CAMEL-23295: Fix resource leak and improve error handling in
camel-splunk-hec producer
- Add warning log when skipTlsVerify is enabled to alert operators
- Replace RuntimeException with RuntimeCamelException for proper Camel
error handling
- Log error response body at DEBUG level for easier debugging
- Consume response entity on success path to prevent potential connection
leaks
- Add unit tests for error handling behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-23295: Address review feedback
- Add @AfterEach to close CamelContext and prevent resource leak in tests
- Remove redundant
testExceptionTypeIsRuntimeCamelExceptionNotRuntimeException test
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---------
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../component/splunkhec/SplunkHECProducer.java | 15 ++-
.../component/splunkhec/SplunkHECProducerTest.java | 129 +++++++++++++++++++++
2 files changed, 142 insertions(+), 2 deletions(-)
diff --git
a/components/camel-splunk-hec/src/main/java/org/apache/camel/component/splunkhec/SplunkHECProducer.java
b/components/camel-splunk-hec/src/main/java/org/apache/camel/component/splunkhec/SplunkHECProducer.java
index 09c6665ac9e0..0936d801f727 100644
---
a/components/camel-splunk-hec/src/main/java/org/apache/camel/component/splunkhec/SplunkHECProducer.java
+++
b/components/camel-splunk-hec/src/main/java/org/apache/camel/component/splunkhec/SplunkHECProducer.java
@@ -24,6 +24,7 @@ import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
+import org.apache.camel.RuntimeCamelException;
import org.apache.camel.support.DefaultProducer;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
@@ -36,13 +37,17 @@ import
org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityTemplate;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.ssl.SSLContextBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The Splunk HEC producer.
*/
public class SplunkHECProducer extends DefaultProducer {
+ private static final Logger LOG =
LoggerFactory.getLogger(SplunkHECProducer.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private final SplunkHECEndpoint endpoint;
private CloseableHttpClient httpClient;
@@ -59,6 +64,9 @@ public class SplunkHECProducer extends DefaultProducer {
.setUserAgent("Camel Splunk HEC/" +
getEndpoint().getCamelContext().getVersion());
PoolingHttpClientConnectionManager connManager;
if (endpoint.getConfiguration().isSkipTlsVerify()) {
+ LOG.warn("Splunk HEC endpoint is configured with
skipTlsVerify=true."
+ + " TLS certificate and hostname verification are
disabled."
+ + " This should not be used in production environments.");
SSLContextBuilder sslbuilder = new SSLContextBuilder();
sslbuilder.loadTrustMaterial(null, (chain, authType) -> true);
SSLConnectionSocketFactory sslsf
@@ -100,9 +108,12 @@ public class SplunkHECProducer extends DefaultProducer {
if (response.getCode() != 200) {
ByteArrayOutputStream output = new
ByteArrayOutputStream();
response.getEntity().writeTo(output);
-
- throw new RuntimeException(new StatusLine(response) +
"\n" + output.toString(StandardCharsets.UTF_8));
+ String responseBody =
output.toString(StandardCharsets.UTF_8);
+ LOG.debug("Splunk HEC error response (HTTP {}): {}",
response.getCode(), responseBody);
+ throw new RuntimeCamelException(
+ "Splunk HEC request failed: " + new
StatusLine(response) + "\n" + responseBody);
}
+ EntityUtils.consume(response.getEntity());
return null;
});
}
diff --git
a/components/camel-splunk-hec/src/test/java/org/apache/camel/component/splunkhec/SplunkHECProducerTest.java
b/components/camel-splunk-hec/src/test/java/org/apache/camel/component/splunkhec/SplunkHECProducerTest.java
new file mode 100644
index 000000000000..2e73ba8a9f28
--- /dev/null
+++
b/components/camel-splunk-hec/src/test/java/org/apache/camel/component/splunkhec/SplunkHECProducerTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.splunkhec;
+
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SplunkHECProducerTest {
+
+ private DefaultCamelContext camelContext;
+ private SplunkHECEndpoint endpoint;
+ private SplunkHECProducer producer;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ camelContext.start();
+
+ SplunkHECConfiguration config = new SplunkHECConfiguration();
+ config.setToken("11111111-1111-1111-1111-111111111111");
+ config.setHttps(false);
+ config.setSkipTlsVerify(false);
+
+ SplunkHECComponent component = new SplunkHECComponent();
+ component.setCamelContext(camelContext);
+
+ endpoint = new SplunkHECEndpoint("splunk-hec:localhost:8088",
component, config);
+ endpoint.setSplunkURL("localhost:8088");
+
+ producer = new SplunkHECProducer(endpoint);
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.close();
+ }
+
+ @Test
+ public void testProcessThrowsRuntimeCamelExceptionOnNon200Response()
throws Exception {
+ CloseableHttpClient mockClient = createMockClient(400, "Bad Request",
+ "{\"text\":\"Invalid data format\",\"code\":6}");
+ injectHttpClient(producer, mockClient);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setBody("test event");
+
+ RuntimeCamelException thrown =
assertThrows(RuntimeCamelException.class, () -> producer.process(exchange));
+ assertTrue(thrown.getMessage().contains("Splunk HEC request failed"));
+ assertTrue(thrown.getMessage().contains("Invalid data format"));
+ }
+
+ @Test
+ public void testProcessThrowsRuntimeCamelExceptionOnServerError() throws
Exception {
+ CloseableHttpClient mockClient = createMockClient(503, "Service
Unavailable",
+ "{\"text\":\"Server is busy\",\"code\":9}");
+ injectHttpClient(producer, mockClient);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setBody("test event");
+
+ RuntimeCamelException thrown =
assertThrows(RuntimeCamelException.class, () -> producer.process(exchange));
+ assertTrue(thrown.getMessage().contains("Splunk HEC request failed"));
+ assertTrue(thrown.getMessage().contains("Server is busy"));
+ }
+
+ @SuppressWarnings("unchecked")
+ private CloseableHttpClient createMockClient(int statusCode, String
reasonPhrase, String responseBody)
+ throws Exception {
+ CloseableHttpClient mockClient = mock(CloseableHttpClient.class);
+ when(mockClient.execute(any(HttpPost.class),
any(HttpClientResponseHandler.class)))
+ .thenAnswer(invocation -> {
+ HttpClientResponseHandler<Object> handler =
invocation.getArgument(1);
+ ClassicHttpResponse mockResponse =
mock(ClassicHttpResponse.class);
+ when(mockResponse.getCode()).thenReturn(statusCode);
+
when(mockResponse.getReasonPhrase()).thenReturn(reasonPhrase);
+
when(mockResponse.getVersion()).thenReturn(org.apache.hc.core5.http.HttpVersion.HTTP_1_1);
+ HttpEntity entity = mock(HttpEntity.class);
+ doAnswer(inv -> {
+ OutputStream os = inv.getArgument(0);
+
os.write(responseBody.getBytes(StandardCharsets.UTF_8));
+ return null;
+ }).when(entity).writeTo(any(OutputStream.class));
+ when(mockResponse.getEntity()).thenReturn(entity);
+ return handler.handleResponse(mockResponse);
+ });
+ return mockClient;
+ }
+
+ private void injectHttpClient(SplunkHECProducer producer,
CloseableHttpClient client) throws Exception {
+ Field httpClientField =
SplunkHECProducer.class.getDeclaredField("httpClient");
+ httpClientField.setAccessible(true);
+ httpClientField.set(producer, client);
+ }
+}