This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch 22805-2 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5ab4655bdaeb66e3dbf1787f2f5187320660af3b Author: Andrea Cosentino <[email protected]> AuthorDate: Mon Jan 12 11:21:50 2026 +0100 CAMEL-22837 - Camel-AWS components: Avoid duplicated code and add pagination to producer operation where it makes sense - AWS ECS Signed-off-by: Andrea Cosentino <[email protected]> --- .../apache/camel/catalog/components/aws2-ecs.json | 5 +- .../apache/camel/component/aws2/ecs/aws2-ecs.json | 5 +- .../camel/component/aws2/ecs/ECS2Constants.java | 13 ++ .../camel/component/aws2/ecs/ECS2Producer.java | 255 +++++++++++---------- .../endpoint/dsl/ECS2EndpointBuilderFactory.java | 36 +++ 5 files changed, 196 insertions(+), 118 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-ecs.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-ecs.json index c5ccb02bd00f..0e9a053db958 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-ecs.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-ecs.json @@ -50,7 +50,10 @@ "headers": { "CamelAwsECSOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation we want to perform", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#OPERATION" }, "CamelAwsECSMaxResults": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The limit number of results while listing clusters", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#MAX_RESULTS" }, - "CamelAwsECSClusterName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The cluster name", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_NAME" } + "CamelAwsECSClusterName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The cluster name", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_NAME" }, + "CamelAwsECSNextToken": { "index": 3, "kind": "header", "displayName": "", "group": "listClusters", "label": "listClusters", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token for the next set of results.", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#NEXT_TOKEN" }, + "CamelAwsECSIsTruncated": { "index": 4, "kind": "header", "displayName": "", "group": "listClusters", "label": "listClusters", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the response has more results (is truncated).", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#IS_TRUNCATED" }, + "CamelAwsECSClusterArn": { "index": 5, "kind": "header", "displayName": "", "group": "createCluster describeCluster deleteCluster", "label": "createCluster describeCluster deleteCluster", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the cluster.", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_ARN" } }, "properties": { "label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.ecs.ECS2Configuration", "configurationField": "configuration", "description": "Logical name" }, diff --git a/components/camel-aws/camel-aws2-ecs/src/generated/resources/META-INF/org/apache/camel/component/aws2/ecs/aws2-ecs.json b/components/camel-aws/camel-aws2-ecs/src/generated/resources/META-INF/org/apache/camel/component/aws2/ecs/aws2-ecs.json index c5ccb02bd00f..0e9a053db958 100644 --- a/components/camel-aws/camel-aws2-ecs/src/generated/resources/META-INF/org/apache/camel/component/aws2/ecs/aws2-ecs.json +++ b/components/camel-aws/camel-aws2-ecs/src/generated/resources/META-INF/org/apache/camel/component/aws2/ecs/aws2-ecs.json @@ -50,7 +50,10 @@ "headers": { "CamelAwsECSOperation": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation we want to perform", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#OPERATION" }, "CamelAwsECSMaxResults": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The limit number of results while listing clusters", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#MAX_RESULTS" }, - "CamelAwsECSClusterName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The cluster name", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_NAME" } + "CamelAwsECSClusterName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The cluster name", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_NAME" }, + "CamelAwsECSNextToken": { "index": 3, "kind": "header", "displayName": "", "group": "listClusters", "label": "listClusters", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token for the next set of results.", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#NEXT_TOKEN" }, + "CamelAwsECSIsTruncated": { "index": 4, "kind": "header", "displayName": "", "group": "listClusters", "label": "listClusters", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the response has more results (is truncated).", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#IS_TRUNCATED" }, + "CamelAwsECSClusterArn": { "index": 5, "kind": "header", "displayName": "", "group": "createCluster describeCluster deleteCluster", "label": "createCluster describeCluster deleteCluster", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the cluster.", "constantName": "org.apache.camel.component.aws2.ecs.ECS2Constants#CLUSTER_ARN" } }, "properties": { "label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.ecs.ECS2Configuration", "configurationField": "configuration", "description": "Logical name" }, diff --git a/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Constants.java b/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Constants.java index bf228eb1d5fd..b3ba78c89ca8 100644 --- a/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Constants.java +++ b/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Constants.java @@ -28,4 +28,17 @@ public interface ECS2Constants { String MAX_RESULTS = "CamelAwsECSMaxResults"; @Metadata(description = "The cluster name", javaType = "String") String CLUSTER_NAME = "CamelAwsECSClusterName"; + + // Pagination constants + @Metadata(label = "listClusters", + description = "The token for the next set of results.", javaType = "String") + String NEXT_TOKEN = "CamelAwsECSNextToken"; + @Metadata(label = "listClusters", + description = "Whether the response has more results (is truncated).", javaType = "Boolean") + String IS_TRUNCATED = "CamelAwsECSIsTruncated"; + + // Response metadata + @Metadata(label = "createCluster describeCluster deleteCluster", + description = "The ARN of the cluster.", javaType = "String") + String CLUSTER_ARN = "CamelAwsECSClusterArn"; } diff --git a/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Producer.java b/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Producer.java index 4b32ae30c226..49a40337e00b 100644 --- a/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Producer.java +++ b/components/camel-aws/camel-aws2-ecs/src/main/java/org/apache/camel/component/aws2/ecs/ECS2Producer.java @@ -16,6 +16,10 @@ */ package org.apache.camel.component.aws2.ecs; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; @@ -37,7 +41,6 @@ import software.amazon.awssdk.services.ecs.model.DeleteClusterResponse; import software.amazon.awssdk.services.ecs.model.DescribeClustersRequest; import software.amazon.awssdk.services.ecs.model.DescribeClustersResponse; import software.amazon.awssdk.services.ecs.model.ListClustersRequest; -import software.amazon.awssdk.services.ecs.model.ListClustersRequest.Builder; import software.amazon.awssdk.services.ecs.model.ListClustersResponse; /** @@ -101,145 +104,165 @@ public class ECS2Producer extends DefaultProducer { } private void listClusters(EcsClient ecsClient, Exchange exchange) throws InvalidPayloadException { - if (getConfiguration().isPojoRequest()) { - Object payload = exchange.getIn().getMandatoryBody(); - if (payload instanceof ListClustersRequest) { - ListClustersResponse result; - try { - ListClustersRequest request = (ListClustersRequest) payload; - result = ecsClient.listClusters(request); - } catch (AwsServiceException ase) { - LOG.trace("List Clusters command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } - } else { - Builder builder = ListClustersRequest.builder(); - if (ObjectHelper.isNotEmpty(exchange.getIn().getHeader(ECS2Constants.MAX_RESULTS))) { - int maxRes = exchange.getIn().getHeader(ECS2Constants.MAX_RESULTS, Integer.class); - builder.maxResults(maxRes); - } - ListClustersResponse result; - try { - ListClustersRequest request = builder.build(); - result = ecsClient.listClusters(request); - } catch (AwsServiceException ase) { - LOG.trace("List Clusters command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } + executeOperation( + exchange, + ListClustersRequest.class, + ecsClient::listClusters, + () -> { + ListClustersRequest.Builder builder = ListClustersRequest.builder(); + Integer maxResults = getOptionalHeader(exchange, ECS2Constants.MAX_RESULTS, Integer.class); + if (maxResults != null) { + builder.maxResults(maxResults); + } + String nextToken = getOptionalHeader(exchange, ECS2Constants.NEXT_TOKEN, String.class); + if (nextToken != null) { + builder.nextToken(nextToken); + } + return ecsClient.listClusters(builder.build()); + }, + "List Clusters", + (ListClustersResponse response, Message message) -> { + message.setHeader(ECS2Constants.NEXT_TOKEN, response.nextToken()); + message.setHeader(ECS2Constants.IS_TRUNCATED, response.nextToken() != null); + }); } private void createCluster(EcsClient ecsClient, Exchange exchange) throws InvalidPayloadException { - if (getConfiguration().isPojoRequest()) { - Object payload = exchange.getIn().getMandatoryBody(); - if (payload instanceof CreateClusterRequest) { - CreateClusterResponse result; - try { - CreateClusterRequest request = (CreateClusterRequest) payload; - result = ecsClient.createCluster(request); - } catch (AwsServiceException ase) { - LOG.trace("Create Cluster command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } - } else { - CreateClusterRequest.Builder builder = CreateClusterRequest.builder(); - if (ObjectHelper.isNotEmpty(exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME))) { - String name = exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME, String.class); - builder.clusterName(name); - } - CreateClusterResponse result; - try { - CreateClusterRequest request = builder.build(); - result = ecsClient.createCluster(request); - } catch (AwsServiceException ase) { - LOG.trace("Create Cluster command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } + executeOperation( + exchange, + CreateClusterRequest.class, + ecsClient::createCluster, + () -> { + CreateClusterRequest.Builder builder = CreateClusterRequest.builder(); + String clusterName = getOptionalHeader(exchange, ECS2Constants.CLUSTER_NAME, String.class); + if (clusterName != null) { + builder.clusterName(clusterName); + } + return ecsClient.createCluster(builder.build()); + }, + "Create Cluster", + (CreateClusterResponse response, Message message) -> { + if (response.cluster() != null) { + message.setHeader(ECS2Constants.CLUSTER_ARN, response.cluster().clusterArn()); + } + }); } private void describeCluster(EcsClient ecsClient, Exchange exchange) throws InvalidPayloadException { - if (getConfiguration().isPojoRequest()) { - Object payload = exchange.getIn().getMandatoryBody(); - if (payload instanceof DescribeClustersRequest) { - DescribeClustersResponse result; - try { - DescribeClustersRequest request = (DescribeClustersRequest) payload; - result = ecsClient.describeClusters(request); - } catch (AwsServiceException ase) { - LOG.trace("Describe Clusters command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } - } else { - DescribeClustersRequest.Builder builder = DescribeClustersRequest.builder(); - if (ObjectHelper.isNotEmpty(exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME))) { - String clusterName = exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME, String.class); - builder.clusters(clusterName); - } - DescribeClustersResponse result; - try { - DescribeClustersRequest request = builder.build(); - result = ecsClient.describeClusters(request); - } catch (AwsServiceException ase) { - LOG.trace("Describe Clusters command returned the error code {}", ase.awsErrorDetails().errorCode()); - throw ase; - } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } + executeOperation( + exchange, + DescribeClustersRequest.class, + ecsClient::describeClusters, + () -> { + DescribeClustersRequest.Builder builder = DescribeClustersRequest.builder(); + String clusterName = getOptionalHeader(exchange, ECS2Constants.CLUSTER_NAME, String.class); + if (clusterName != null) { + builder.clusters(clusterName); + } + return ecsClient.describeClusters(builder.build()); + }, + "Describe Clusters", + (DescribeClustersResponse response, Message message) -> { + if (response.hasClusters() && !response.clusters().isEmpty()) { + message.setHeader(ECS2Constants.CLUSTER_ARN, response.clusters().get(0).clusterArn()); + } + }); } private void deleteCluster(EcsClient ecsClient, Exchange exchange) throws InvalidPayloadException { + executeOperation( + exchange, + DeleteClusterRequest.class, + ecsClient::deleteCluster, + () -> { + String clusterName = getRequiredHeader(exchange, ECS2Constants.CLUSTER_NAME, String.class, + "Cluster name must be specified"); + return ecsClient.deleteCluster(DeleteClusterRequest.builder().cluster(clusterName).build()); + }, + "Delete Cluster", + (DeleteClusterResponse response, Message message) -> { + if (response.cluster() != null) { + message.setHeader(ECS2Constants.CLUSTER_ARN, response.cluster().clusterArn()); + } + }); + } + + public static Message getMessageForResponse(final Exchange exchange) { + return exchange.getMessage(); + } + + /** + * Executes an ECS operation with POJO request support. + */ + private <REQ, RES> void executeOperation( + Exchange exchange, + Class<REQ> requestClass, + Function<REQ, RES> pojoExecutor, + Supplier<RES> headerExecutor, + String operationName) + throws InvalidPayloadException { + executeOperation(exchange, requestClass, pojoExecutor, headerExecutor, operationName, null); + } + + /** + * Executes an ECS operation with POJO request support and optional response post-processing. + */ + private <REQ, RES> void executeOperation( + Exchange exchange, + Class<REQ> requestClass, + Function<REQ, RES> pojoExecutor, + Supplier<RES> headerExecutor, + String operationName, + BiConsumer<RES, Message> responseProcessor) + throws InvalidPayloadException { + + RES result; if (getConfiguration().isPojoRequest()) { Object payload = exchange.getIn().getMandatoryBody(); - if (payload instanceof DeleteClusterRequest) { - DeleteClusterResponse result; + if (requestClass.isInstance(payload)) { try { - DeleteClusterRequest request = (DeleteClusterRequest) payload; - result = ecsClient.deleteCluster(request); + result = pojoExecutor.apply(requestClass.cast(payload)); } catch (AwsServiceException ase) { - LOG.trace("Delete Cluster command returned the error code {}", ase.awsErrorDetails().errorCode()); + LOG.trace("{} command returned the error code {}", operationName, ase.awsErrorDetails().errorCode()); throw ase; } - Message message = getMessageForResponse(exchange); - message.setBody(result); - } - } else { - DeleteClusterRequest.Builder builder = DeleteClusterRequest.builder(); - if (ObjectHelper.isNotEmpty(exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME))) { - String name = exchange.getIn().getHeader(ECS2Constants.CLUSTER_NAME, String.class); - builder.cluster(name); } else { - throw new IllegalArgumentException("Cluster name must be specified"); + throw new IllegalArgumentException( + String.format("Expected body of type %s but was %s", + requestClass.getName(), + payload != null ? payload.getClass().getName() : "null")); } - DeleteClusterResponse result; + } else { try { - DeleteClusterRequest request = builder.build(); - result = ecsClient.deleteCluster(request); + result = headerExecutor.get(); } catch (AwsServiceException ase) { - LOG.trace("Delete Cluster command returned the error code {}", ase.awsErrorDetails().errorCode()); + LOG.trace("{} command returned the error code {}", operationName, ase.awsErrorDetails().errorCode()); throw ase; } - Message message = getMessageForResponse(exchange); - message.setBody(result); + } + Message message = getMessageForResponse(exchange); + message.setBody(result); + if (responseProcessor != null) { + responseProcessor.accept(result, message); } } - public static Message getMessageForResponse(final Exchange exchange) { - return exchange.getMessage(); + /** + * Gets a required header value or throws an IllegalArgumentException. + */ + private <T> T getRequiredHeader(Exchange exchange, String headerName, Class<T> headerType, String errorMessage) { + T value = exchange.getIn().getHeader(headerName, headerType); + if (ObjectHelper.isEmpty(value)) { + throw new IllegalArgumentException(errorMessage); + } + return value; + } + + /** + * Gets an optional header value. + */ + private <T> T getOptionalHeader(Exchange exchange, String headerName, Class<T> headerType) { + return exchange.getIn().getHeader(headerName, headerType); } @Override diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ECS2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ECS2EndpointBuilderFactory.java index 89b2befe7871..c964a3c5de08 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ECS2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ECS2EndpointBuilderFactory.java @@ -627,6 +627,42 @@ public interface ECS2EndpointBuilderFactory { public String awsECSClusterName() { return "CamelAwsECSClusterName"; } + /** + * The token for the next set of results. + * + * The option is a: {@code String} type. + * + * Group: listClusters + * + * @return the name of the header {@code AwsECSNextToken}. + */ + public String awsECSNextToken() { + return "CamelAwsECSNextToken"; + } + /** + * Whether the response has more results (is truncated). + * + * The option is a: {@code Boolean} type. + * + * Group: listClusters + * + * @return the name of the header {@code AwsECSIsTruncated}. + */ + public String awsECSIsTruncated() { + return "CamelAwsECSIsTruncated"; + } + /** + * The ARN of the cluster. + * + * The option is a: {@code String} type. + * + * Group: createCluster describeCluster deleteCluster + * + * @return the name of the header {@code AwsECSClusterArn}. + */ + public String awsECSClusterArn() { + return "CamelAwsECSClusterArn"; + } } static ECS2EndpointBuilder endpointBuilder(String componentName, String path) { class ECS2EndpointBuilderImpl extends AbstractEndpointBuilder implements ECS2EndpointBuilder, AdvancedECS2EndpointBuilder {
