From eb27cd7aced63604d39908764311df9f9f088f26 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Fri, 23 Oct 2026 23:18:23 +0200 Subject: [PATCH] CAMEL-26072: camel-aws - bug fixes for polly, translate, textract, timestream, s3-vectors, security-hub and cloudtrail CAMEL-26072 aws2-polly: the textType and outputFormat headers are read; next token header; lexicon names trimmed CAMEL-26073 aws2-translate: each language from its option or header; Croatian is hr CAMEL-26074 aws2-textract: feature types as Strings; the start operations set the job id header CAMEL-26077 aws2-timestream: describeScheduledQuery sends the ARN; clear errors for the operation and client type CAMEL-26078 aws-cloudtrail: application/json for the CloudEvent data type; events without data or time CAMEL-26079 aws2-polly, textract, sns, s3: trace logs and checks of an AWS error without details CAMEL-26092 aws2-s3-vectors: no output bucket/index headers, keys from the body, data/metadata/distance, metadata filter CAMEL-26094 aws-security-hub: headers of another type fail, findings as any collection, failed imports fail Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../catalog/components/aws-security-hub.json | 14 +- .../camel/catalog/components/aws2-polly.json | 11 +- .../catalog/components/aws2-s3-vectors.json | 41 +-- .../catalog/components/aws2-textract.json | 4 +- .../catalog/components/aws2-timestream.json | 10 +- .../catalog/components/aws2-translate.json | 12 +- .../docs/aws-cloudtrail-component.adoc | 2 - .../docs/aws-security-hub-component.adoc | 21 ++ .../catalog/docs/aws2-polly-component.adoc | 77 ++++- .../docs/aws2-s3-vectors-component.adoc | 97 +++++- .../catalog/docs/aws2-textract-component.adoc | 56 +++- .../docs/aws2-timestream-component.adoc | 11 + .../docs/aws2-translate-component.adoc | 15 +- .../main/docs/aws-cloudtrail-component.adoc | 2 - .../aws/cloudtrail/CloudtrailConsumer.java | 29 +- ...oudtrailCloudEventDataTypeTransformer.java | 3 +- .../cloudtrail/CloudtrailConsumerTest.java | 40 +++ ...railCloudEventDataTypeTransformerTest.java | 54 +++ .../aws/securityhub/aws-security-hub.json | 14 +- .../main/docs/aws-security-hub-component.adoc | 21 ++ .../aws/securityhub/SecurityHubConstants.java | 21 +- .../aws/securityhub/SecurityHubProducer.java | 74 ++++- .../securityhub/SecurityHubProducerTest.java | 214 ++++++++++++ .../component/aws2/polly/aws2-polly.json | 11 +- .../src/main/docs/aws2-polly-component.adoc | 77 ++++- .../aws2/polly/Polly2Configuration.java | 16 +- .../component/aws2/polly/Polly2Constants.java | 6 + .../component/aws2/polly/Polly2Producer.java | 131 +++++--- .../aws2/polly/AmazonPollyClientMock.java | 16 + .../aws2/polly/Polly2ProducerTest.java | 119 ++++++- .../aws2/s3vectors/aws2-s3-vectors.json | 41 +-- .../main/docs/aws2-s3-vectors-component.adoc | 97 +++++- .../s3vectors/AWS2S3VectorsConfiguration.java | 24 +- .../s3vectors/AWS2S3VectorsConstants.java | 55 +++- .../aws2/s3vectors/AWS2S3VectorsConsumer.java | 26 +- .../aws2/s3vectors/AWS2S3VectorsHelper.java | 205 ++++++++++++ .../aws2/s3vectors/AWS2S3VectorsProducer.java | 107 +++--- .../s3vectors/AWS2S3VectorsConsumerTest.java | 67 ++++ .../AWS2S3VectorsOptionEnumsTest.java | 63 ++++ .../AWS2S3VectorsProducerRequestTest.java | 308 ++++++++++++++++++ .../component/aws2/s3/AWS2S3Consumer.java | 2 +- .../component/aws2/s3/AWS2S3Endpoint.java | 2 +- .../aws2/s3/AWS2S3MissingBucketTest.java | 97 ++++++ .../component/aws2/sns/Sns2Endpoint.java | 3 +- .../aws2/textract/aws2-textract.json | 4 +- .../main/docs/aws2-textract-component.adoc | 56 +++- .../aws2/textract/Textract2Constants.java | 9 +- .../aws2/textract/Textract2Producer.java | 75 ++++- .../aws2/textract/AmazonAWSTextractMock.java | 57 ++++ .../aws2/textract/Textract2ProducerTest.java | 95 ++++++ .../aws2/timestream/aws2-timestream.json | 10 +- .../main/docs/aws2-timestream-component.adoc | 11 + .../aws2/timestream/Timestream2Component.java | 13 +- .../aws2/timestream/Timestream2Constants.java | 11 +- .../timestream/Timestream2Operations.java | 69 ++-- .../query/Timestream2QueryProducer.java | 17 + .../write/Timestream2WriteProducer.java | 14 +- .../Timestream2OperationValidationTest.java | 115 +++++++ .../AmazonTimestreamQueryClientMock.java | 9 + .../query/Timestream2QueryProducerTest.java | 9 + .../aws2/translate/aws2-translate.json | 12 +- .../main/docs/aws2-translate-component.adoc | 15 +- .../translate/Translate2Configuration.java | 8 +- .../translate/Translate2LanguageEnum.java | 2 +- .../aws2/translate/Translate2Producer.java | 51 ++- .../translate/AmazonAWSTranslateMock.java | 4 + .../translate/Translate2ProducerTest.java | 82 +++++ .../pages/camel-4x-upgrade-guide-4_24.adoc | 81 +++++ .../dsl/Aws2PollyComponentBuilderFactory.java | 10 +- .../Aws2S3VectorsComponentBuilderFactory.java | 20 +- .../Aws2TranslateComponentBuilderFactory.java | 11 +- .../AWS2S3VectorsEndpointBuilderFactory.java | 133 ++++++-- .../dsl/Polly2EndpointBuilderFactory.java | 33 +- .../SecurityHubEndpointBuilderFactory.java | 26 +- .../dsl/Textract2EndpointBuilderFactory.java | 14 +- .../Timestream2EndpointBuilderFactory.java | 15 +- .../dsl/Translate2EndpointBuilderFactory.java | 15 +- 77 files changed, 2995 insertions(+), 427 deletions(-) create mode 100644 components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformerTest.java create mode 100644 components/camel-aws/camel-aws-security-hub/src/test/java/org/apache/camel/component/aws/securityhub/SecurityHubProducerTest.java create mode 100644 components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsHelper.java create mode 100644 components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsOptionEnumsTest.java create mode 100644 components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducerRequestTest.java create mode 100644 components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3MissingBucketTest.java create mode 100644 components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/Timestream2OperationValidationTest.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-security-hub.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-security-hub.json index c550f5a645995..a198c540f0cfd 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-security-hub.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-security-hub.json @@ -49,21 +49,21 @@ }, "headers": { "CamelAwsSecurityHubOperation": { "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.aws.securityhub.SecurityHubConstants#OPERATION" }, - "CamelAwsSecurityHubFindings": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings to import", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDINGS" }, + "CamelAwsSecurityHubFindings": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The findings to import, as a List of AwsSecurityFinding. When not set, the findings are taken from the body: a Collection or an array of AwsSecurityFinding, or one AwsSecurityFinding.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDINGS" }, "CamelAwsSecurityHubFindingIdentifiers": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The finding identifiers for batch update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDING_IDENTIFIERS" }, - "CamelAwsSecurityHubFilters": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "AwsSecurityFindingFilters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The filters to apply when retrieving findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FILTERS" }, - "CamelAwsSecurityHubNote": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "NoteUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The note to add to findings during update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NOTE" }, - "CamelAwsSecurityHubSeverity": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "SeverityUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The severity to set on findings during update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SEVERITY" }, - "CamelAwsSecurityHubWorkflow": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "WorkflowUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The workflow status to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#WORKFLOW" }, + "CamelAwsSecurityHubFilters": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "AwsSecurityFindingFilters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The filters to apply when retrieving findings. It must be an AwsSecurityFindingFilters object; a value of another type, such as JSON text, fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FILTERS" }, + "CamelAwsSecurityHubNote": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "NoteUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The note to add to findings during update. It must be a NoteUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NOTE" }, + "CamelAwsSecurityHubSeverity": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "SeverityUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The severity to set on findings during update. It must be a SeverityUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SEVERITY" }, + "CamelAwsSecurityHubWorkflow": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "WorkflowUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The workflow status to set on findings. It must be a WorkflowUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#WORKFLOW" }, "CamelAwsSecurityHubVerificationState": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The verification state to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#VERIFICATION_STATE" }, "CamelAwsSecurityHubConfidence": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The confidence level to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#CONFIDENCE" }, "CamelAwsSecurityHubCriticality": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The criticality level to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#CRITICALITY" }, "CamelAwsSecurityHubUserDefinedFields": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "User-defined fields to add to findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#USER_DEFINED_FIELDS" }, "CamelAwsSecurityHubRelatedFindings": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Related findings to associate", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#RELATED_FINDINGS" }, "CamelAwsSecurityHubTypes": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The types to assign to findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#TYPES" }, - "CamelAwsSecurityHubNextToken": { "index": 13, "kind": "header", "displayName": "", "group": "getFindings listEnabledProductsForImport getFindingHistory", "label": "getFindings listEnabledProductsForImport getFindingHistory", "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.aws.securityhub.SecurityHubConstants#NEXT_TOKEN" }, + "CamelAwsSecurityHubNextToken": { "index": 13, "kind": "header", "displayName": "", "group": "getFindings listEnabledProductsForImport getFindingHistory", "label": "getFindings listEnabledProductsForImport getFindingHistory", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token for the next set of results. It is both an input and an output: the operation sends it and then sets it to the token of the response, so remove it before calling a different list operation in the same route.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NEXT_TOKEN" }, "CamelAwsSecurityHubMaxResults": { "index": 14, "kind": "header", "displayName": "", "group": "getFindings", "label": "getFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results to return", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#MAX_RESULTS" }, - "CamelAwsSecurityHubFailedCount": { "index": 15, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that failed to import", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FAILED_COUNT" }, + "CamelAwsSecurityHubFailedCount": { "index": 15, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that failed to import. When it is more than zero, the exchange fails with an exception naming the failed findings.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FAILED_COUNT" }, "CamelAwsSecurityHubSuccessCount": { "index": 16, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that were successfully imported", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SUCCESS_COUNT" }, "CamelAwsSecurityHubUnprocessedFindings": { "index": 17, "kind": "header", "displayName": "", "group": "batchUpdateFindings", "label": "batchUpdateFindings", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings that were not updated", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#UNPROCESSED_FINDINGS" }, "CamelAwsSecurityHubProcessedFindings": { "index": 18, "kind": "header", "displayName": "", "group": "batchUpdateFindings", "label": "batchUpdateFindings", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings that were updated successfully", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#PROCESSED_FINDINGS" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-polly.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-polly.json index d2290ba44fc61..88a58e3eb51c0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-polly.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-polly.json @@ -32,7 +32,7 @@ "lexiconName": { "index": 5, "kind": "property", "displayName": "Lexicon Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The name of the lexicon to use for getLexicon, putLexicon, or deleteLexicon operations" }, "lexiconNames": { "index": 6, "kind": "property", "displayName": "Lexicon Names", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Lexicon names to apply during synthesis" }, "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.polly.Polly2Operations", "enum": [ "synthesizeSpeech", "describeVoices", "listLexicons", "getLexicon", "putLexicon", "deleteLexicon", "startSpeechSynthesisTask", "getSpeechSynthesisTask", "listSpeechSynthesisTasks" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "synthesizeSpeech", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, - "outputFormat": { "index": 8, "kind": "property", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MP3", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format" }, + "outputFormat": { "index": 8, "kind": "property", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format. When neither this option nor the CamelAwsPollyOutputFormat header is set, MP3 is used." }, "overrideEndpoint": { "index": 9, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 10, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 11, "kind": "property", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The region in which the Polly client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, @@ -41,7 +41,7 @@ "sampleRate": { "index": 14, "kind": "property", "displayName": "Sample Rate", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The sample rate in Hz for the audio output" }, "snsTopicArn": { "index": 15, "kind": "property", "displayName": "Sns Topic Arn", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The SNS topic ARN for startSpeechSynthesisTask notifications" }, "taskId": { "index": 16, "kind": "property", "displayName": "Task Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The task ID for getSpeechSynthesisTask operation" }, - "textType": { "index": 17, "kind": "property", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TEXT", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml)" }, + "textType": { "index": 17, "kind": "property", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml). When neither this option nor the CamelAwsPollyTextType header is set, TEXT is used." }, "uriEndpointOverride": { "index": 18, "kind": "property", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "voiceId": { "index": 19, "kind": "property", "displayName": "Voice Id", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.VoiceId", "enum": [ "Aditi", "Amy", "Astrid", "Bianca", "Brian", "Camila", "Carla", "Carmen", "Celine", "Chantal", "Conchita", "Cristiano", "Dora", "Emma", "Enrique", "Ewa", "Filiz", "Gabrielle", "Geraint", "Giorgio", "Gwyneth", "Hans", "Ines", "Ivy", "Jacek", "Jan", "Joanna", "Joey", "Justin", "Karl", "Kendra", "Kevin", "Kimberly", "Lea", "Liv", "Lotte", "Lucia", "Lupe", "Mads", "Maja", "Marlene", "Mathieu", "Matthew", "Maxim", "Mia", "Miguel", "Mizuki", "Naja", "Nicole", "Olivia", "Penelope", "Raveena", "Ricardo", "Ruben", "Russell", "Salli", "Seoyeon", "Takumi", "Tatyana", "Vicki", "Vitoria", "Zeina", "Zhiyu", "Aria", "Ayanda", "Arlet", "Hannah", "Arthur", "Daniel", "Liam", "Pedro", "Kajal", "Hiujin", "Laura", "Elin", "Ida", "Suvi", "Ola", "Hala", "Andres", "Sergio", "Remi", "Adriano", "Thiago", "Ruth", "Stephen", "Kazuha", "Tomoko", "Niamh", "Sofie", "Lisa", "Isabelle", "Zayd", "Danielle", "Gregory", "Burcu", "Jitka", "Sabrina", "Jasmine", "Jihye", "Ambre", "Beatrice", "Florian", "Lennart", "Lorenzo", "Tiffany", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The voice ID to use for synthesis" }, "autowiredEnabled": { "index": 20, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, @@ -76,7 +76,8 @@ "CamelAwsPollyS3KeyPrefix": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 key prefix for output", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#S3_KEY_PREFIX" }, "CamelAwsPollySnsTopicArn": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The SNS topic ARN for notifications", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#SNS_TOPIC_ARN" }, "CamelAwsPollyContentType": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type of the audio stream", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#CONTENT_TYPE" }, - "CamelAwsPollyRequestCharacters": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of characters synthesized", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#REQUEST_CHARACTERS" } + "CamelAwsPollyRequestCharacters": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of characters synthesized", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#REQUEST_CHARACTERS" }, + "CamelAwsPollyNextToken": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token of the next page for the describeVoices, listLexicons and listSpeechSynthesisTasks operations. It is in and out: when set, it is sent as the next token of the request, and after the call it holds the next token of the response, or it is removed when the response has none, which is the last page. Remove it before calling a different list operation.", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#NEXT_TOKEN" } }, "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.polly.Polly2Configuration", "configurationField": "configuration", "description": "Logical name" }, @@ -86,7 +87,7 @@ "lexiconName": { "index": 4, "kind": "parameter", "displayName": "Lexicon Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The name of the lexicon to use for getLexicon, putLexicon, or deleteLexicon operations" }, "lexiconNames": { "index": 5, "kind": "parameter", "displayName": "Lexicon Names", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Lexicon names to apply during synthesis" }, "operation": { "index": 6, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.polly.Polly2Operations", "enum": [ "synthesizeSpeech", "describeVoices", "listLexicons", "getLexicon", "putLexicon", "deleteLexicon", "startSpeechSynthesisTask", "getSpeechSynthesisTask", "listSpeechSynthesisTasks" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "synthesizeSpeech", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, - "outputFormat": { "index": 7, "kind": "parameter", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MP3", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format" }, + "outputFormat": { "index": 7, "kind": "parameter", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format. When neither this option nor the CamelAwsPollyOutputFormat header is set, MP3 is used." }, "overrideEndpoint": { "index": 8, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 9, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 10, "kind": "parameter", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The region in which the Polly client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, @@ -95,7 +96,7 @@ "sampleRate": { "index": 13, "kind": "parameter", "displayName": "Sample Rate", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The sample rate in Hz for the audio output" }, "snsTopicArn": { "index": 14, "kind": "parameter", "displayName": "Sns Topic Arn", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The SNS topic ARN for startSpeechSynthesisTask notifications" }, "taskId": { "index": 15, "kind": "parameter", "displayName": "Task Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The task ID for getSpeechSynthesisTask operation" }, - "textType": { "index": 16, "kind": "parameter", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TEXT", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml)" }, + "textType": { "index": 16, "kind": "parameter", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml). When neither this option nor the CamelAwsPollyTextType header is set, TEXT is used." }, "uriEndpointOverride": { "index": 17, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "voiceId": { "index": 18, "kind": "parameter", "displayName": "Voice Id", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.VoiceId", "enum": [ "Aditi", "Amy", "Astrid", "Bianca", "Brian", "Camila", "Carla", "Carmen", "Celine", "Chantal", "Conchita", "Cristiano", "Dora", "Emma", "Enrique", "Ewa", "Filiz", "Gabrielle", "Geraint", "Giorgio", "Gwyneth", "Hans", "Ines", "Ivy", "Jacek", "Jan", "Joanna", "Joey", "Justin", "Karl", "Kendra", "Kevin", "Kimberly", "Lea", "Liv", "Lotte", "Lucia", "Lupe", "Mads", "Maja", "Marlene", "Mathieu", "Matthew", "Maxim", "Mia", "Miguel", "Mizuki", "Naja", "Nicole", "Olivia", "Penelope", "Raveena", "Ricardo", "Ruben", "Russell", "Salli", "Seoyeon", "Takumi", "Tatyana", "Vicki", "Vitoria", "Zeina", "Zhiyu", "Aria", "Ayanda", "Arlet", "Hannah", "Arthur", "Daniel", "Liam", "Pedro", "Kajal", "Hiujin", "Laura", "Elin", "Ida", "Suvi", "Ola", "Hala", "Andres", "Sergio", "Remi", "Adriano", "Thiago", "Ruth", "Stephen", "Kazuha", "Tomoko", "Niamh", "Sofie", "Lisa", "Isabelle", "Zayd", "Danielle", "Gregory", "Burcu", "Jitka", "Sabrina", "Jasmine", "Jihye", "Ambre", "Beatrice", "Florian", "Lennart", "Lorenzo", "Tiffany", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The voice ID to use for synthesis" }, "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3-vectors.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3-vectors.json index 58da8dc70451f..8e356e2d531ec 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3-vectors.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-s3-vectors.json @@ -25,17 +25,17 @@ }, "componentProperties": { "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "common", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, - "dataType": { "index": 1, "kind": "property", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32", "float16" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vector. Options: float32, float16" }, - "distanceMetric": { "index": 2, "kind": "property", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean", "dot-product" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric to use for similarity search. Options: cosine, euclidean, dot-product" }, + "dataType": { "index": 1, "kind": "property", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vectors of the index to create. S3 Vectors supports float32 only." }, + "distanceMetric": { "index": 2, "kind": "property", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric of the index to create, which the similarity search uses. Options: cosine, euclidean" }, "overrideEndpoint": { "index": 3, "kind": "property", "displayName": "Override Endpoint", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with uriEndpointOverride option" }, "region": { "index": 4, "kind": "property", "displayName": "Region", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The region in which S3 Vectors client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example ap-east-1)" }, - "similarityThreshold": { "index": 5, "kind": "property", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The minimum similarity threshold for results" }, + "similarityThreshold": { "index": 5, "kind": "property", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. The consumer sets the CamelAwsS3VectorsDistance header to the distance of each vector from the query vector. Deprecation note: S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead." }, "topK": { "index": 6, "kind": "property", "displayName": "Top K", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The number of top similar vectors to return in a query" }, "uriEndpointOverride": { "index": 7, "kind": "property", "displayName": "Uri Endpoint Override", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "vectorDimensions": { "index": 8, "kind": "property", "displayName": "Vector Dimensions", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1536, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The dimensions of the vector embeddings (default: 1536, which is the dimension for OpenAI text-embedding-3-small)" }, "vectorIndexName": { "index": 9, "kind": "property", "displayName": "Vector Index Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The name of the vector index" }, "bridgeErrorHandler": { "index": 10, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, - "consumerMetadataFilter": { "index": 11, "kind": "property", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Optional metadata filter for the consumer to filter vectors during polling" }, + "consumerMetadataFilter": { "index": 11, "kind": "property", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The metadata filter of the similarity search of the consumer, as a JSON object with the filter operators of S3 Vectors, such as {genre: {$eq: drama}}. Only the vectors whose metadata match the filter are consumed." }, "consumerQueryVector": { "index": 12, "kind": "property", "displayName": "Consumer Query Vector", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The query vector to use for the consumer to poll for similar vectors. Specified as comma-separated float values (e.g., 0.1,0.2,0.3). If not specified, the consumer will not poll." }, "delay": { "index": 13, "kind": "property", "displayName": "Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Milliseconds before the next poll for the consumer" }, "deleteAfterRead": { "index": 14, "kind": "property", "displayName": "Delete After Read", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Delete vectors after they have been consumed" }, @@ -60,35 +60,36 @@ }, "headers": { "CamelAwsS3VectorsOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#OPERATION" }, - "CamelAwsS3VectorsVectorBucketName": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector bucket which will be used for the current operation", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_NAME" }, - "CamelAwsS3VectorsVectorIndexName": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector index which will be used for the current operation", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_INDEX_NAME" }, - "CamelAwsS3VectorsVectorId": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The unique identifier for a vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_ID" }, + "CamelAwsS3VectorsVectorBucketName": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector bucket to use for the current operation instead of the vector bucket of the endpoint. An input only: neither the producer nor the consumer sets it.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_NAME" }, + "CamelAwsS3VectorsVectorIndexName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector index to use for the current operation instead of the vectorIndexName option of the endpoint. An input only: neither the producer nor the consumer sets it.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_INDEX_NAME" }, + "CamelAwsS3VectorsVectorId": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key of a vector. putVectors puts the vector under this key (the exchange id when it is not set) and sets it to the key it used. getVectors and deleteVectors take their key, or a list of keys, from this header only when the message body holds no keys. The consumer sets it to the key of the consumed vector.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_ID" }, "CamelAwsS3VectorsVectorData": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List or float[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The vector embedding data as a list of floats or float array", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_DATA" }, "CamelAwsS3VectorsVectorDimensions": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The dimensions of the vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_DIMENSIONS" }, - "CamelAwsS3VectorsDataType": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The data type of the vector (float32 or float16)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DATA_TYPE" }, - "CamelAwsS3VectorsVectorMetadata": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Additional metadata for the vector as a map", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_METADATA" }, + "CamelAwsS3VectorsDataType": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The data type of the vectors of the index to create (float32)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DATA_TYPE" }, + "CamelAwsS3VectorsVectorMetadata": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Additional metadata for the vector as a map. The values may be Strings, numbers, booleans or lists of these.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_METADATA" }, "CamelAwsS3VectorsQueryVector": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List or float[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The query vector for similarity search as a list of floats or float array", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#QUERY_VECTOR" }, "CamelAwsS3VectorsTopK": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of top similar vectors to return", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#TOP_K" }, - "CamelAwsS3VectorsDistanceMetric": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance metric to use for similarity search (cosine, euclidean, dot-product)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE_METRIC" }, - "CamelAwsS3VectorsSimilarityThreshold": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The minimum similarity threshold for results", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_THRESHOLD" }, - "CamelAwsS3VectorsMetadataFilter": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional filter expression for metadata filtering during vector search", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#METADATA_FILTER" }, - "CamelAwsS3VectorsSimilarityScore": { "index": 13, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The similarity score of the returned vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_SCORE" }, - "CamelAwsS3VectorsResultCount": { "index": 14, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of vectors returned in the result", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#RESULT_COUNT" }, - "CamelAwsS3VectorsIndexStatus": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The status of the vector index", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#INDEX_STATUS" }, - "CamelAwsS3VectorsVectorBucketArn": { "index": 16, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the vector bucket", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_ARN" } + "CamelAwsS3VectorsDistanceMetric": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance metric of the index to create (cosine or euclidean)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE_METRIC" }, + "CamelAwsS3VectorsSimilarityThreshold": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this header. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. Deprecation note: S3 Vectors has no similarity threshold, the component never used this header. Filter the results on their CamelAwsS3VectorsDistance header instead.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_THRESHOLD" }, + "CamelAwsS3VectorsMetadataFilter": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The metadata filter of queryVectors, as a JSON object with the filter operators of S3 Vectors, such as $eq, $in or $and. Only the vectors whose metadata match the filter are returned.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#METADATA_FILTER" }, + "CamelAwsS3VectorsSimilarityScore": { "index": 13, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": true, "deprecationNote": "S3 Vectors returns a distance, not a similarity score, and the component never set this header. Use the CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "description": "(deprecated) Never set: S3 Vectors returns a distance, see CamelAwsS3VectorsDistance. Deprecation note: S3 Vectors returns a distance, not a similarity score, and the component never set this header. Use the CamelAwsS3VectorsDistance header instead.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_SCORE" }, + "CamelAwsS3VectorsDistance": { "index": 14, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance of the consumed vector from the query vector, computed with the distance metric of the index: the lower, the closer.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE" }, + "CamelAwsS3VectorsResultCount": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of vectors returned in the result", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#RESULT_COUNT" }, + "CamelAwsS3VectorsIndexStatus": { "index": 16, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The status of the vector index", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#INDEX_STATUS" }, + "CamelAwsS3VectorsVectorBucketArn": { "index": 17, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the vector bucket", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_ARN" } }, "properties": { "vectorBucketName": { "index": 0, "kind": "path", "displayName": "Vector Bucket Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector bucket name or ARN" }, - "dataType": { "index": 1, "kind": "parameter", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32", "float16" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vector. Options: float32, float16" }, - "distanceMetric": { "index": 2, "kind": "parameter", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean", "dot-product" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric to use for similarity search. Options: cosine, euclidean, dot-product" }, + "dataType": { "index": 1, "kind": "parameter", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vectors of the index to create. S3 Vectors supports float32 only." }, + "distanceMetric": { "index": 2, "kind": "parameter", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric of the index to create, which the similarity search uses. Options: cosine, euclidean" }, "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with uriEndpointOverride option" }, "region": { "index": 4, "kind": "parameter", "displayName": "Region", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The region in which S3 Vectors client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example ap-east-1)" }, - "similarityThreshold": { "index": 5, "kind": "parameter", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The minimum similarity threshold for results" }, + "similarityThreshold": { "index": 5, "kind": "parameter", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. The consumer sets the CamelAwsS3VectorsDistance header to the distance of each vector from the query vector. Deprecation note: S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead." }, "topK": { "index": 6, "kind": "parameter", "displayName": "Top K", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The number of top similar vectors to return in a query" }, "uriEndpointOverride": { "index": 7, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "vectorDimensions": { "index": 8, "kind": "parameter", "displayName": "Vector Dimensions", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1536, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The dimensions of the vector embeddings (default: 1536, which is the dimension for OpenAI text-embedding-3-small)" }, "vectorIndexName": { "index": 9, "kind": "parameter", "displayName": "Vector Index Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The name of the vector index" }, - "consumerMetadataFilter": { "index": 10, "kind": "parameter", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Optional metadata filter for the consumer to filter vectors during polling" }, + "consumerMetadataFilter": { "index": 10, "kind": "parameter", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The metadata filter of the similarity search of the consumer, as a JSON object with the filter operators of S3 Vectors, such as {genre: {$eq: drama}}. Only the vectors whose metadata match the filter are consumed." }, "consumerQueryVector": { "index": 11, "kind": "parameter", "displayName": "Consumer Query Vector", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The query vector to use for the consumer to poll for similar vectors. Specified as comma-separated float values (e.g., 0.1,0.2,0.3). If not specified, the consumer will not poll." }, "delay": { "index": 12, "kind": "parameter", "displayName": "Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Milliseconds before the next poll for the consumer" }, "deleteAfterRead": { "index": 13, "kind": "parameter", "displayName": "Delete After Read", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Delete vectors after they have been consumed" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-textract.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-textract.json index c3ac336ad3f2a..cab6f0e5cdd9c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-textract.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-textract.json @@ -55,10 +55,10 @@ "CamelAwsTextractS3Bucket": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 bucket name containing the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_BUCKET" }, "CamelAwsTextractS3Object": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 object name (key) of the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_OBJECT" }, "CamelAwsTextractS3ObjectVersion": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 object version of the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_OBJECT_VERSION" }, - "CamelAwsTextractJobId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The job ID for async operations (StartDocumentTextDetection, StartDocumentAnalysis)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#JOB_ID" }, + "CamelAwsTextractJobId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The job ID of an async operation. It is set from the response of the startDocumentTextDetection, startDocumentAnalysis and startExpenseAnalysis operations, and it is the job ID of the getDocumentTextDetection, getDocumentAnalysis and getExpenseAnalysis operations (else the body is used as the job ID)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#JOB_ID" }, "CamelAwsTextractMaxResults": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results to return in paginated operations", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#MAX_RESULTS" }, "CamelAwsTextractNextToken": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The next token for pagination in operations that return multiple pages", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#NEXT_TOKEN" }, - "CamelAwsTextractFeatureTypes": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The feature types for document analysis (TABLES, FORMS, SIGNATURES, etc.)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#FEATURE_TYPES" } + "CamelAwsTextractFeatureTypes": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The feature types of the analyzeDocument and startDocumentAnalysis operations (TABLES, FORMS, QUERIES, SIGNATURES, LAYOUT): a list of FeatureType or String values, or a String with comma separated values. The String values are case-insensitive. Without this header TABLES and FORMS are used.", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#FEATURE_TYPES" } }, "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.textract.Textract2Configuration", "configurationField": "configuration", "description": "Logical name" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-timestream.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-timestream.json index 37ecd9c677a55..596611c84c520 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-timestream.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-timestream.json @@ -50,21 +50,21 @@ }, "headers": { "CamelAwsTimestreamOperation": { "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.timestream.Timestream2Constants#OPERATION" }, - "CamelAwsTimestreamRecord": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Record", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Represents a time-series data point being written into Timestream", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD" }, - "CamelAwsTimestreamRecordList": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "List of Records", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_LIST" }, + "CamelAwsTimestreamRecord": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Record", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The common attributes of the writeRecords operation: the dimensions, measure and time values that all the records of the CamelAwsTimestreamRecordList header share. It is not written as a record of its own.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD" }, + "CamelAwsTimestreamRecordList": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The records of the writeRecords operation", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_LIST" }, "CamelAwsTimestreamTaskStatus": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Status of Batch Load Task", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TASK_STATUS" }, "CamelAwsTimestreamTaskId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ID of the batch load task to resume.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TASK_ID" }, "CamelAwsTimestreamDatabaseName": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Database", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATABASE_NAME" }, "CamelAwsTimestreamTableName": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Table", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TABLE_NAME" }, "CamelAwsTimestreamTargetDatabaseName": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Target Database", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TARGET_DATABASE_NAME" }, "CamelAwsTimestreamTargetTableName": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Target Table", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TARGET_TABLE_NAME" }, - "CamelAwsTimestreamRecordVersion": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Record version", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_VERSION" }, + "CamelAwsTimestreamRecordVersion": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The record version of the createBatchLoadTask operation", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_VERSION" }, "CamelAwsTimestreamDataModelConfiguration": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.DataModelConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Configuration of Data Model", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATA_MODEL_CONFIGURATION" }, "CamelAwsTimestreamDataSourceConfiguration": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.DataSourceConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Configuration of Data Source", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATA_SOURCE_CONFIGURATION" }, "CamelAwsTimestreamReportConfiguration": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.ReportConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reporting Configuration", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#REPORT_CONFIGURATION" }, "CamelAwsTimestreamTableSchema": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Schema", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Schema", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEMA" }, "CamelAwsTimestreamRetentionProperties": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.RetentionProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Retention Properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RETENTION_PROPERTIES" }, - "CamelAwsTimestreamMagneticStoreWriteProperties": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.MagneticStoreWriteProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Magentic Store Write properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MAGNETIC_STORE_WRITE_PROPERTIES" }, + "CamelAwsTimestreamMagneticStoreWriteProperties": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.MagneticStoreWriteProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Magnetic Store Write properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MAGNETIC_STORE_WRITE_PROPERTIES" }, "CamelAwsTimestreamTimeColumn": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Time column", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TIME_COLUMN" }, "CamelAwsTimestreamMeasureColumnName": { "index": 17, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of the measure column.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MEASURE_NAME_COLUMN" }, "CamelAwsTimestreamDimensionMappingList": { "index": 18, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This is to allow mapping column(s) from the query result to the dimension in the destination table.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DIMENSION_MAPPING_LIST" }, @@ -84,7 +84,7 @@ "CamelAwsTimestreamErrorReportS3BucketName": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 Bucket name for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_BUCKET_NAME" }, "CamelAwsTimestreamErrorReportS3ObjectKeyPrefix": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 object key prefix for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_OBJECT_KEY_PREFIX" }, "CamelAwsTimestreamErrorReportS3EncryptionOption": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 encryption option for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_ENCRYPTION_OPTION" }, - "CamelAwsTimestreamScheduledQueryExecutionRoleArn": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "he ARN for the IAM role that Timestream will assume when running the scheduled query.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEDULED_QUERY_EXECUTION_ROLE_ARN" }, + "CamelAwsTimestreamScheduledQueryExecutionRoleArn": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN for the IAM role that Timestream will assume when running the scheduled query.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEDULED_QUERY_EXECUTION_ROLE_ARN" }, "CamelAwsTimestreamClientToken": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Using a ClientToken makes the call to CreateScheduledQuery idempotent", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#CLIENT_TOKEN" }, "CamelAwsTimestreamKmsKeyId": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Amazon KMS key used to encrypt the scheduled query resource, at-rest.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#KMS_KEY_ID" } }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-translate.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-translate.json index 88dadd2d7a35c..d96925fb29d59 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-translate.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws2-translate.json @@ -24,15 +24,15 @@ "remote": true }, "componentProperties": { - "autodetectSourceLanguage": { "index": 0, "kind": "property", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language" }, + "autodetectSourceLanguage": { "index": 0, "kind": "property", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language. When enabled, the source language is auto, and the sourceLanguage option and the CamelAwsTranslateSourceLanguage header are not used." }, "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.translate.Translate2Configuration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" }, "lazyStartProducer": { "index": 2, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.translate.Translate2Operations", "enum": [ "translateText" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "translateText", "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, "overrideEndpoint": { "index": 4, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 5, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 6, "kind": "property", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The region in which the Translate client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, - "sourceLanguage": { "index": 7, "kind": "property", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use" }, - "targetLanguage": { "index": 8, "kind": "property", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use" }, + "sourceLanguage": { "index": 7, "kind": "property", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use. When not set, the CamelAwsTranslateSourceLanguage header is used. It is not used when autodetectSourceLanguage is enabled." }, + "targetLanguage": { "index": 8, "kind": "property", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use. When not set, the CamelAwsTranslateTargetLanguage header is used." }, "uriEndpointOverride": { "index": 9, "kind": "property", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "autowiredEnabled": { "index": 10, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, "translateClient": { "index": 11, "kind": "property", "displayName": "Translate Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "software.amazon.awssdk.services.translate.TranslateClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "To use an existing configured AWS Translate client" }, @@ -58,13 +58,13 @@ }, "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.translate.Translate2Configuration", "configurationField": "configuration", "description": "Logical name" }, - "autodetectSourceLanguage": { "index": 1, "kind": "parameter", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language" }, + "autodetectSourceLanguage": { "index": 1, "kind": "parameter", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language. When enabled, the source language is auto, and the sourceLanguage option and the CamelAwsTranslateSourceLanguage header are not used." }, "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.translate.Translate2Operations", "enum": [ "translateText" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "translateText", "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 4, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 5, "kind": "parameter", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The region in which the Translate client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, - "sourceLanguage": { "index": 6, "kind": "parameter", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use" }, - "targetLanguage": { "index": 7, "kind": "parameter", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use" }, + "sourceLanguage": { "index": 6, "kind": "parameter", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use. When not set, the CamelAwsTranslateSourceLanguage header is used. It is not used when autodetectSourceLanguage is enabled." }, + "targetLanguage": { "index": 7, "kind": "parameter", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use. When not set, the CamelAwsTranslateTargetLanguage header is used." }, "uriEndpointOverride": { "index": 8, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "translateClient": { "index": 10, "kind": "parameter", "displayName": "Translate Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "software.amazon.awssdk.services.translate.TranslateClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "To use an existing configured AWS Translate client" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-cloudtrail-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-cloudtrail-component.adoc index 11feb141feb7c..8bcedf1855e9b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-cloudtrail-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-cloudtrail-component.adoc @@ -29,8 +29,6 @@ at https://aws.amazon.com/cloudtrail/[AWS CloudTrail] aws-cloudtrail://label[?options] ----------------------------------- -The stream needs to be created prior to it being used. - You can append query options to the URI in the following format: `?options=value&option2=value&...` diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-security-hub-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-security-hub-component.adoc index 1aef282d4f838..615e5fd3c1344 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-security-hub-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws-security-hub-component.adoc @@ -73,11 +73,32 @@ Camel-AWS Security Hub component provides the following operation on the produce - getFindingHistory - describeHub - listEnabledProductsForImport +- getFindingAggregator + +The SDK model headers (`CamelAwsSecurityHubFilters`, `CamelAwsSecurityHubNote`, `CamelAwsSecurityHubSeverity` +and `CamelAwsSecurityHubWorkflow`) must hold the AWS SDK object of the type the header lists, such as +`AwsSecurityFindingFilters`. A value of another type, such as JSON text, fails the exchange with an +`IllegalArgumentException`, as there is no type converter for these classes. To send a request built +elsewhere, set `pojoRequest=true` and put the request object, such as `GetFindingsRequest`, in the body. + +The `CamelAwsSecurityHubNextToken` header is both an input and an output of `getFindings`, `getFindingHistory` +and `listEnabledProductsForImport`: the operation sends the token in the header, and then sets the header to the +token of the next page. A route that calls a different list operation afterwards must remove the header first, +for example with `removeHeader("CamelAwsSecurityHubNextToken")`, or that operation sends a token it does not know. ==== BatchImportFindings Import security findings into Security Hub. This is the primary way to send findings from custom integrations. +The findings are taken from the `CamelAwsSecurityHubFindings` header, or else from the body, which can be a +`Collection` or an array of `AwsSecurityFinding`, or one `AwsSecurityFinding`. An element of another type fails +the exchange with an `IllegalArgumentException`. + +When Security Hub does not import some of the findings, the exchange fails with an `IllegalStateException` +whose message names the failed finding ids with their error codes and messages. The +`CamelAwsSecurityHubFailedCount` and `CamelAwsSecurityHubSuccessCount` headers, and the response in the body, +are set before the exchange fails. + ._Java-only: uses inline Processor with AWS SDK builder API_ [source,java] ---- diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-polly-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-polly-component.adoc index 930cf682cf30c..dd6371f615248 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-polly-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-polly-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Polly component options -You have to provide the amazonPollyClient in the +You have to provide the pollyClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/polly/[Amazon Polly] service. @@ -82,6 +82,81 @@ Camel-AWS Polly component provides the following operations on the producer side - getSpeechSynthesisTask - listSpeechSynthesisTasks +=== Text type and output format + +The `textType` option, or else the `CamelAwsPollyTextType` header, says whether the text is plain text (`TEXT`) or SSML (`SSML`). +The `outputFormat` option, or else the `CamelAwsPollyOutputFormat` header, sets the audio format. +When neither the option nor the header is set, `TEXT` and `MP3` are used. +This applies to the synthesizeSpeech and startSpeechSynthesisTask operations. + +The `lexiconNames` option, or else the `CamelAwsPollyLexiconNames` header, is a comma-separated list of lexicon names. +The spaces around each name are removed, so `a, b` is the same as `a,b`. + +=== Paging the list operations + +The describeVoices, listLexicons and listSpeechSynthesisTasks operations return one page of results in the body. +The `CamelAwsPollyNextToken` header is both an input and an output: + +- when it is set, it is sent as the next token of the request, so the call returns the next page; +- after the call, it holds the next token of the response, or it is removed when the response has none, which means this was the last page. + +A route can therefore call the operation again while the header is set. +Because the header stays on the message, remove it before calling a different list operation, or that call starts from a token that is not its own. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("aws2-polly://test?operation=listLexicons") + .log("Lexicons: ${body}") + .loopDoWhile(header(Polly2Constants.NEXT_TOKEN).isNotNull()) + .to("aws2-polly://test?operation=listLexicons") + .log("Lexicons: ${body}") + .end() + .removeHeader(Polly2Constants.NEXT_TOKEN) + .to("aws2-polly://test?operation=describeVoices"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - to: + uri: aws2-polly:test + parameters: + operation: listLexicons + - log: + message: "Lexicons: ${body}" + - loop: + doWhile: true + expression: + simple: + expression: "${header.CamelAwsPollyNextToken} != null" + steps: + - to: + uri: aws2-polly:test + parameters: + operation: listLexicons + - log: + message: "Lexicons: ${body}" + - removeHeader: + name: CamelAwsPollyNextToken + - to: + uri: aws2-polly:test + parameters: + operation: describeVoices +---- +==== + +With `pojoRequest=true`, the next token is taken from the request in the body, and the `CamelAwsPollyNextToken` header is set from the response in the same way. + == Examples === Synthesize Speech example diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-s3-vectors-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-s3-vectors-component.adoc index c083b360a7727..516ba4cd0ffe1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-s3-vectors-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-s3-vectors-component.adoc @@ -48,17 +48,72 @@ include::partial$component-endpoint-headers.adoc[] * `getVectors` - Retrieve vectors by ID * `createVectorBucket` - Create bucket * `deleteVectorBucket` - Delete bucket -* `listVectorBuckets` - List buckets +* `listVectorBuckets` - List buckets (the first page of the result only) * `describeVectorBucket` - Get bucket info * `createVectorIndex` - Create index * `deleteVectorIndex` - Delete index -* `listVectorIndexes` - List indexes +* `listVectorIndexes` - List indexes (the first page of the result only) * `describeVectorIndex` - Get index info +`listVectorBuckets` and `listVectorIndexes` send a single request, so they return the first page of the buckets or +indexes that S3 Vectors returns, not all of them. + +=== Bucket and index of an operation + +Each operation uses the vector bucket of the endpoint URI and the `vectorIndexName` option. The +`CamelAwsS3VectorsVectorBucketName` and `CamelAwsS3VectorsVectorIndexName` headers override them for a single +message. These headers are inputs only: neither the producer nor the consumer sets them, so a route that reads from one +bucket and writes to another one does not write back into the bucket it reads from. + +=== Vector keys + +`putVectors` puts the vector under the key of the `CamelAwsS3VectorsVectorId` header, or under the exchange id when the +header is not set, and sets the header to the key it used. + +`getVectors` and `deleteVectors` take their keys from the message body when the body holds keys: a `String`, or a +`Collection` or an array whose elements are all Strings. Otherwise they take the keys from the +`CamelAwsS3VectorsVectorId` header (a `String` or a list of Strings). Keys that are not Strings, such as a list of +numbers, fail with an `IllegalArgumentException`. As the body comes first, a `getVectors` after a `putVectors` in the +same route gets the keys of its body, not the key that the `putVectors` left in the header. + +`getVectors` returns the vectors with their data and their metadata. + +=== Vector data and metadata + +The vector of `putVectors` and `queryVectors` is a `List` of numbers, a `float[]` or a `double[]`. A `null` element +fails with an `IllegalArgumentException` that names its position. + +The `CamelAwsS3VectorsVectorMetadata` header of `putVectors` is a `Map` whose values may be Strings, numbers, booleans, +or lists of these. Any other value fails with an `IllegalArgumentException` that names its key. + +=== Metadata filter and distance + +The `CamelAwsS3VectorsMetadataFilter` header of `queryVectors` and the `consumerMetadataFilter` option of the consumer +are JSON objects with the +https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-metadata-filtering.html[filter operators of S3 Vectors], +such as `{"genre": {"$eq": "drama"}}` or `{"$and": [{"genre": "drama"}, {"year": {"$gte": 2020}}]}`. Only the vectors +whose metadata match the filter are returned. A filter that is not a valid JSON object fails with an +`IllegalArgumentException`: the producer fails the exchange, and the consumer does not start. + +`queryVectors` and the consumer return the vectors with their metadata and their distance from the query vector. The +distance is computed with the distance metric of the index (`cosine` or `euclidean`): the lower, the closer. S3 Vectors +has no similarity threshold, so the `similarityThreshold` option and the `CamelAwsS3VectorsSimilarityThreshold` and +`CamelAwsS3VectorsSimilarityScore` headers are deprecated and do nothing. To keep only the close vectors, filter on the +distance, such as `.filter(simple("${header.CamelAwsS3VectorsDistance} < 0.3"))` after the consumer. + +=== Index data type and distance metric + +The `dataType` option of `createVectorIndex` is `float32`, the only data type of S3 Vectors. The `distanceMetric` +option is `cosine` (the default) or `euclidean`. + === Consumer The consumer polls for vectors using similarity search. It tracks processed vector IDs to avoid duplicates. +Each consumed vector is a `QueryOutputVector` in the message body, with its metadata. The consumer sets the +`CamelAwsS3VectorsVectorId` header to the key of the vector and the `CamelAwsS3VectorsDistance` header to its distance +from the `consumerQueryVector`. + == Examples The vector in the message body is a `List` of numbers, a `float[]` or a `double[]`. @@ -175,6 +230,44 @@ YAML:: ---- ==== +=== Query with a Metadata Filter + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:search") + .setBody(constant(Arrays.asList(0.15f, 0.25f, 0.35f))) + .setHeader(AWS2S3VectorsConstants.METADATA_FILTER, constant("{\"genre\": {\"$eq\": \"drama\"}}")) + .to("aws2-s3-vectors://my-bucket?operation=queryVectors&vectorIndexName=my-index") + .log("Found ${body.size} similar drama vectors"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:search + steps: + - setBody: + groovy: "[0.15, 0.25, 0.35]" + - setHeader: + name: CamelAwsS3VectorsMetadataFilter + constant: '{"genre": {"$eq": "drama"}}' + - to: + uri: aws2-s3-vectors://my-bucket + parameters: + operation: queryVectors + vectorIndexName: my-index + - log: + message: "Found ${body.size} similar drama vectors" +---- +==== + === Create Index [tabs] diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-textract-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-textract-component.adoc index 9e2bf3f6534b6..989d89272dc89 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-textract-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-textract-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Textract component options -You have to provide the amazonTextractClient in the +You have to provide the textractClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/textract/[Amazon Textract] service. @@ -68,6 +68,60 @@ Only one of static, default and profile credentials could be used at the same ti For more information about this you can look at https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html[AWS credentials documentation] +=== The document + +The synchronous operations (`detectDocumentText`, `analyzeDocument` and `analyzeExpense`) read the document from S3 or from the message body; +the asynchronous `start` operations read it from S3 only: + +* The bucket is the `s3Bucket` option, or else the `CamelAwsTextractS3Bucket` header. +* The object is the `s3Object` option, or else the `CamelAwsTextractS3Object` header. +* The version is the `s3ObjectVersion` option, or else the `CamelAwsTextractS3ObjectVersion` header. + +The options are used first: a header is read only when its option is not set. +When both the bucket and the object are known, the document is that S3 object. +Otherwise a synchronous operation uses the message body (as bytes) as the document, and an asynchronous operation fails. + +=== Feature types + +The `analyzeDocument` and `startDocumentAnalysis` operations analyze the feature types of the `CamelAwsTextractFeatureTypes` header, +or else `TABLES` and `FORMS`. +The header can be a list (or array) of `FeatureType` or `String` values, or one `String` with comma separated values, such as `TABLES,SIGNATURES`. +The `String` values are case-insensitive, and an unknown value fails with an `IllegalArgumentException`. + +=== Asynchronous operations + +The `startDocumentTextDetection`, `startDocumentAnalysis` and `startExpenseAnalysis` operations return the start response as the body, +and set the `CamelAwsTextractJobId` header to the job ID of the response. +The `getDocumentTextDetection`, `getDocumentAnalysis` and `getExpenseAnalysis` operations read the job ID from the `CamelAwsTextractJobId` header, +or else from the message body, so a `get` step can follow a `start` step (the job must have finished, see the `JobStatus` of the `get` response): + +[source,yaml] +---- +- route: + from: + uri: direct:analyze + steps: + - setHeader: + name: CamelAwsTextractFeatureTypes + constant: + expression: "TABLES,SIGNATURES" + - to: + uri: aws2-textract:textract + parameters: + operation: startDocumentAnalysis + s3Bucket: my-documents + s3Object: contracts/contract-1.pdf + - delay: + constant: + expression: "10000" + - to: + uri: aws2-textract:textract + parameters: + operation: getDocumentAnalysis + - log: + message: "${body.jobStatus}" +---- + === Example The following route detects the text of an image in S3. Without `s3Bucket` and `s3Object`, the document is the message body: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-timestream-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-timestream-component.adoc index 37a71156ad224..7510b6ae2fd80 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-timestream-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-timestream-component.adoc @@ -120,6 +120,17 @@ Camel-AWS Timestream component provides the following operation on the producer ** query ** cancelQuery +The operation is the `CamelAwsTimestreamOperation` header, or else the `operation` option; without either, the producer fails with an `IllegalArgumentException`. +The write operations need `clientType` `write` and the query operations need `clientType` `query` (`describeEndpoints` is an operation of both): +an operation of the other client type fails with an `IllegalArgumentException`, already when the producer starts if it is the `operation` option. + +=== Writing records + +The `writeRecords` operation writes the records of the `CamelAwsTimestreamRecordList` header (a `List` of `Record`) into the table of the +`CamelAwsTimestreamDatabaseName` and `CamelAwsTimestreamTableName` headers. +The `CamelAwsTimestreamRecord` header is the common attributes of the request: the dimensions, measure and time values that all the records share. +It is not written as a record of its own, so a request with only this header writes no record. + == Examples === Producer Examples diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-translate-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-translate-component.adoc index 3e9991ec828d6..1fc2aa34a5740 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-translate-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aws2-translate-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Translate component options -You have to provide the amazonTranslateClient in the +You have to provide the translateClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/translate/[Amazon Translate] service. @@ -74,6 +74,19 @@ Camel-AWS Translate component provides the following operation on the producer s - translateText +=== Source and target language + +Each language is taken from its option, or else from its header: + +- the source language from the `sourceLanguage` option, or else the `CamelAwsTranslateSourceLanguage` header; +- the target language from the `targetLanguage` option, or else the `CamelAwsTranslateTargetLanguage` header. + +So one language can be set as an option and the other as a header. +With `autodetectSourceLanguage=true`, the source language is `auto`, and Amazon Translate detects it. + +When the target language is missing, or the source language is missing and `autodetectSourceLanguage` is not enabled, +the producer fails with an `IllegalArgumentException` that names the option and the header to set. + == Examples === Translate Text example diff --git a/components/camel-aws/camel-aws-cloudtrail/src/main/docs/aws-cloudtrail-component.adoc b/components/camel-aws/camel-aws-cloudtrail/src/main/docs/aws-cloudtrail-component.adoc index 11feb141feb7c..8bcedf1855e9b 100644 --- a/components/camel-aws/camel-aws-cloudtrail/src/main/docs/aws-cloudtrail-component.adoc +++ b/components/camel-aws/camel-aws-cloudtrail/src/main/docs/aws-cloudtrail-component.adoc @@ -29,8 +29,6 @@ at https://aws.amazon.com/cloudtrail/[AWS CloudTrail] aws-cloudtrail://label[?options] ----------------------------------- -The stream needs to be created prior to it being used. - You can append query options to the URI in the following format: `?options=value&option2=value&...` diff --git a/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumer.java b/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumer.java index da4b3ef2203b4..3c99b06b830fb 100644 --- a/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumer.java +++ b/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumer.java @@ -48,6 +48,9 @@ public class CloudtrailConsumer extends ScheduledBatchPollingConsumer { // Event ids already delivered at exactly lastTime. startTime is inclusive, so these events are // returned again on the next poll and must be filtered to avoid duplicate delivery. private final Set lastProcessedEventIds = new HashSet<>(); + // Ids of the delivered events that have no event time. They cannot move the cursor, so they are skipped by id + // while the lookup keeps returning them. + private final Set undatedEventIds = new HashSet<>(); // Late-event lookup (lookBackWindow > 0): every lookBackInterval the events of [now - lookBackWindow, lastTime] // are looked up again, one page per poll, and the ones not routed yet are routed. CloudTrail can make an event @@ -125,15 +128,22 @@ protected int poll() throws Exception { Instant newest = lastTime; Set newestEventIds = new HashSet<>(); Queue exchanges = new ArrayDeque<>(); + Set seenUndatedEventIds = new HashSet<>(); for (Event event : events) { - if (lastProcessedEventIds.contains(event.eventId()) || routedEventIds.containsKey(event.eventId())) { + Instant eventTime = event.eventTime(); + if (eventTime == null) { + seenUndatedEventIds.add(event.eventId()); + } + if (lastProcessedEventIds.contains(event.eventId()) || routedEventIds.containsKey(event.eventId()) + || undatedEventIds.contains(event.eventId())) { continue; } exchanges.add(createExchange(event)); rememberRouted(event); - Instant eventTime = event.eventTime(); - if (newest == null || eventTime.isAfter(newest)) { + if (eventTime == null) { + undatedEventIds.add(event.eventId()); + } else if (newest == null || eventTime.isAfter(newest)) { newest = eventTime; newestEventIds.clear(); newestEventIds.add(event.eventId()); @@ -149,6 +159,8 @@ protected int poll() throws Exception { lastTime = newest; lastProcessedEventIds.clear(); lastProcessedEventIds.addAll(newestEventIds); + // an event without a time that the lookup no longer returns does not need to be skipped anymore + undatedEventIds.retainAll(seenUndatedEventIds); if (getEndpoint().getConfiguration().getLookBackWindow() > 0) { lookBackPage(attributes, exchanges); @@ -197,10 +209,11 @@ private void lookBackPage(List attributes, Queue exch return; } for (Event event : response.events()) { - if (!lastProcessedEventIds.contains(event.eventId()) && !routedEventIds.containsKey(event.eventId())) { + if (!lastProcessedEventIds.contains(event.eventId()) && !routedEventIds.containsKey(event.eventId()) + && !undatedEventIds.contains(event.eventId())) { exchanges.add(createExchange(event)); rememberRouted(event); - if (event.eventTime().equals(lastTime)) { + if (lastTime.equals(event.eventTime())) { // the normal lookup starts at lastTime (inclusive) and returns it again lastProcessedEventIds.add(event.eventId()); } @@ -223,7 +236,8 @@ private void lookBackPage(List attributes, Queue exch private void rememberRouted(Event event) { if (getEndpoint().getConfiguration().getLookBackWindow() > 0) { - routedEventIds.put(event.eventId(), event.eventTime()); + // an event without a time is kept as long as an event at the cursor + routedEventIds.put(event.eventId(), event.eventTime() != null ? event.eventTime() : lastTime); } } @@ -266,7 +280,8 @@ public CloudtrailEndpoint getEndpoint() { protected Exchange createExchange(Event event) { Exchange exchange = createExchange(true); - exchange.getMessage().setBody(event.cloudTrailEvent().getBytes(StandardCharsets.UTF_8)); + String record = event.cloudTrailEvent(); + exchange.getMessage().setBody(record != null ? record.getBytes(StandardCharsets.UTF_8) : null); exchange.getMessage().setHeader(CloudtrailConstants.EVENT_ID, event.eventId()); exchange.getMessage().setHeader(CloudtrailConstants.EVENT_NAME, event.eventName()); exchange.getMessage().setHeader(CloudtrailConstants.EVENT_SOURCE, event.eventSource()); diff --git a/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformer.java b/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformer.java index 3092db62bed94..cf2934c1b8717 100644 --- a/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformer.java +++ b/components/camel-aws/camel-aws-cloudtrail/src/main/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformer.java @@ -51,6 +51,7 @@ public void transform(Message message, DataType fromType, DataType toType) { headers.put(CloudEvent.CAMEL_CLOUD_EVENT_SUBJECT, message.getHeader(CloudtrailConstants.EVENT_ID, String.class)); headers.put(CloudEvent.CAMEL_CLOUD_EVENT_TIME, cloudEvent.getEventTime(message.getExchange())); - headers.put(CloudEvent.CAMEL_CLOUD_EVENT_CONTENT_TYPE, CloudEvent.TEXT_PLAIN_MIME_TYPE); + // the body is the JSON of the CloudTrail record + headers.put(CloudEvent.CAMEL_CLOUD_EVENT_CONTENT_TYPE, "application/json"); } } diff --git a/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumerTest.java b/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumerTest.java index 414573afe44fc..bdc304491c1ea 100644 --- a/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumerTest.java +++ b/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/CloudtrailConsumerTest.java @@ -20,11 +20,13 @@ import org.apache.camel.AsyncProcessor; import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; import org.apache.camel.impl.DefaultCamelContext; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -103,4 +105,42 @@ public void pollDoesNotRedeliverBoundaryEventsAcrossPolls() throws Exception { Assertions.assertEquals(2, underTest.poll(), "first poll delivers both new events"); Assertions.assertEquals(1, underTest.poll(), "second poll must skip the already-delivered boundary event e2"); } + + @Test + public void eventWithoutCloudTrailEventGivesNullBody() throws Exception { + Event noRecord = Event.builder().eventId("e1").eventName("name").eventSource("src").username("user") + .eventTime(Instant.parse("2099-01-01T00:00:00Z")).build(); + + Exchange exchange = underTest.createExchange(noRecord); + + Assertions.assertNull(exchange.getMessage().getBody()); + Assertions.assertEquals("e1", exchange.getMessage().getHeader(CloudtrailConstants.EVENT_ID)); + } + + @Test + public void eventWithoutEventTimeIsDeliveredOnceAndDoesNotStopTheCursor() throws Exception { + Instant t = Instant.parse("2099-01-01T00:00:00Z"); + Event noTime = Event.builder().eventId("e0").eventName("name").eventSource("src").username("user") + .cloudTrailEvent("{}").build(); + var firstPoll = LookupEventsResponse.builder() + .events(event("e1", t.plusSeconds(1)), noTime) + .build(); + var secondPoll = LookupEventsResponse.builder() + .events(event("e2", t.plusSeconds(2)), event("e1", t.plusSeconds(1)), noTime) + .build(); + var thirdPoll = LookupEventsResponse.builder() + .events(event("e2", t.plusSeconds(2)), noTime) + .build(); + when(cloudTrailClient.lookupEvents(any(LookupEventsRequest.class))).thenReturn(firstPoll, secondPoll, thirdPoll); + + Assertions.assertEquals(2, underTest.poll(), "first poll delivers both events"); + Assertions.assertEquals(1, underTest.poll(), "second poll delivers only e2"); + Assertions.assertEquals(0, underTest.poll(), "third poll delivers nothing new"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(LookupEventsRequest.class); + verify(cloudTrailClient, times(3)).lookupEvents(captor.capture()); + // the cursor moves to the newest event time, even with an event without a time in the same poll + Assertions.assertEquals(t.plusSeconds(1), captor.getAllValues().get(1).startTime()); + Assertions.assertEquals(t.plusSeconds(2), captor.getAllValues().get(2).startTime()); + } } diff --git a/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformerTest.java b/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformerTest.java new file mode 100644 index 0000000000000..b48ad400036ae --- /dev/null +++ b/components/camel-aws/camel-aws-cloudtrail/src/test/java/org/apache/camel/component/aws/cloudtrail/transform/CloudtrailCloudEventDataTypeTransformerTest.java @@ -0,0 +1,54 @@ +/* + * 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.aws.cloudtrail.transform; + +import java.nio.charset.StandardCharsets; + +import org.apache.camel.Exchange; +import org.apache.camel.cloudevents.CloudEvent; +import org.apache.camel.component.aws.cloudtrail.CloudtrailConstants; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.spi.DataType; +import org.apache.camel.support.DefaultExchange; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CloudtrailCloudEventDataTypeTransformerTest { + + private final DefaultCamelContext camelContext = new DefaultCamelContext(); + + private final CloudtrailCloudEventDataTypeTransformer transformer = new CloudtrailCloudEventDataTypeTransformer(); + + @Test + void shouldMapToCloudEventWithJsonContentType() { + Exchange exchange = new DefaultExchange(camelContext); + exchange.getMessage().setHeader(CloudtrailConstants.EVENT_ID, "1234"); + exchange.getMessage().setHeader(CloudtrailConstants.EVENT_SOURCE, "s3.amazonaws.com"); + exchange.getMessage().setBody("{\"eventVersion\":\"1.08\"}".getBytes(StandardCharsets.UTF_8)); + + transformer.transform(exchange.getMessage(), DataType.ANY, DataType.ANY); + + Assertions.assertEquals("org.apache.camel.event.aws.cloudtrail.lookupEvents", + exchange.getMessage().getHeader(CloudEvent.CAMEL_CLOUD_EVENT_TYPE)); + Assertions.assertEquals("1234", exchange.getMessage().getHeader(CloudEvent.CAMEL_CLOUD_EVENT_SUBJECT)); + Assertions.assertEquals("aws.cloudtrail.event.source.s3.amazonaws.com", + exchange.getMessage().getHeader(CloudEvent.CAMEL_CLOUD_EVENT_SOURCE)); + // the body is the JSON of the CloudTrail record + Assertions.assertEquals("application/json", + exchange.getMessage().getHeader(CloudEvent.CAMEL_CLOUD_EVENT_CONTENT_TYPE)); + } +} diff --git a/components/camel-aws/camel-aws-security-hub/src/generated/resources/META-INF/org/apache/camel/component/aws/securityhub/aws-security-hub.json b/components/camel-aws/camel-aws-security-hub/src/generated/resources/META-INF/org/apache/camel/component/aws/securityhub/aws-security-hub.json index c550f5a645995..a198c540f0cfd 100644 --- a/components/camel-aws/camel-aws-security-hub/src/generated/resources/META-INF/org/apache/camel/component/aws/securityhub/aws-security-hub.json +++ b/components/camel-aws/camel-aws-security-hub/src/generated/resources/META-INF/org/apache/camel/component/aws/securityhub/aws-security-hub.json @@ -49,21 +49,21 @@ }, "headers": { "CamelAwsSecurityHubOperation": { "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.aws.securityhub.SecurityHubConstants#OPERATION" }, - "CamelAwsSecurityHubFindings": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings to import", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDINGS" }, + "CamelAwsSecurityHubFindings": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The findings to import, as a List of AwsSecurityFinding. When not set, the findings are taken from the body: a Collection or an array of AwsSecurityFinding, or one AwsSecurityFinding.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDINGS" }, "CamelAwsSecurityHubFindingIdentifiers": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The finding identifiers for batch update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FINDING_IDENTIFIERS" }, - "CamelAwsSecurityHubFilters": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "AwsSecurityFindingFilters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The filters to apply when retrieving findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FILTERS" }, - "CamelAwsSecurityHubNote": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "NoteUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The note to add to findings during update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NOTE" }, - "CamelAwsSecurityHubSeverity": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "SeverityUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The severity to set on findings during update", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SEVERITY" }, - "CamelAwsSecurityHubWorkflow": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "WorkflowUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The workflow status to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#WORKFLOW" }, + "CamelAwsSecurityHubFilters": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "AwsSecurityFindingFilters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The filters to apply when retrieving findings. It must be an AwsSecurityFindingFilters object; a value of another type, such as JSON text, fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FILTERS" }, + "CamelAwsSecurityHubNote": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "NoteUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The note to add to findings during update. It must be a NoteUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NOTE" }, + "CamelAwsSecurityHubSeverity": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "SeverityUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The severity to set on findings during update. It must be a SeverityUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SEVERITY" }, + "CamelAwsSecurityHubWorkflow": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "WorkflowUpdate", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The workflow status to set on findings. It must be a WorkflowUpdate object; a value of another type fails the exchange.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#WORKFLOW" }, "CamelAwsSecurityHubVerificationState": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The verification state to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#VERIFICATION_STATE" }, "CamelAwsSecurityHubConfidence": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The confidence level to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#CONFIDENCE" }, "CamelAwsSecurityHubCriticality": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The criticality level to set on findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#CRITICALITY" }, "CamelAwsSecurityHubUserDefinedFields": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "User-defined fields to add to findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#USER_DEFINED_FIELDS" }, "CamelAwsSecurityHubRelatedFindings": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Related findings to associate", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#RELATED_FINDINGS" }, "CamelAwsSecurityHubTypes": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The types to assign to findings", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#TYPES" }, - "CamelAwsSecurityHubNextToken": { "index": 13, "kind": "header", "displayName": "", "group": "getFindings listEnabledProductsForImport getFindingHistory", "label": "getFindings listEnabledProductsForImport getFindingHistory", "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.aws.securityhub.SecurityHubConstants#NEXT_TOKEN" }, + "CamelAwsSecurityHubNextToken": { "index": 13, "kind": "header", "displayName": "", "group": "getFindings listEnabledProductsForImport getFindingHistory", "label": "getFindings listEnabledProductsForImport getFindingHistory", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token for the next set of results. It is both an input and an output: the operation sends it and then sets it to the token of the response, so remove it before calling a different list operation in the same route.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#NEXT_TOKEN" }, "CamelAwsSecurityHubMaxResults": { "index": 14, "kind": "header", "displayName": "", "group": "getFindings", "label": "getFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results to return", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#MAX_RESULTS" }, - "CamelAwsSecurityHubFailedCount": { "index": 15, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that failed to import", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FAILED_COUNT" }, + "CamelAwsSecurityHubFailedCount": { "index": 15, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that failed to import. When it is more than zero, the exchange fails with an exception naming the failed findings.", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#FAILED_COUNT" }, "CamelAwsSecurityHubSuccessCount": { "index": 16, "kind": "header", "displayName": "", "group": "batchImportFindings", "label": "batchImportFindings", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The count of findings that were successfully imported", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#SUCCESS_COUNT" }, "CamelAwsSecurityHubUnprocessedFindings": { "index": 17, "kind": "header", "displayName": "", "group": "batchUpdateFindings", "label": "batchUpdateFindings", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings that were not updated", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#UNPROCESSED_FINDINGS" }, "CamelAwsSecurityHubProcessedFindings": { "index": 18, "kind": "header", "displayName": "", "group": "batchUpdateFindings", "label": "batchUpdateFindings", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of findings that were updated successfully", "constantName": "org.apache.camel.component.aws.securityhub.SecurityHubConstants#PROCESSED_FINDINGS" }, diff --git a/components/camel-aws/camel-aws-security-hub/src/main/docs/aws-security-hub-component.adoc b/components/camel-aws/camel-aws-security-hub/src/main/docs/aws-security-hub-component.adoc index 1aef282d4f838..615e5fd3c1344 100644 --- a/components/camel-aws/camel-aws-security-hub/src/main/docs/aws-security-hub-component.adoc +++ b/components/camel-aws/camel-aws-security-hub/src/main/docs/aws-security-hub-component.adoc @@ -73,11 +73,32 @@ Camel-AWS Security Hub component provides the following operation on the produce - getFindingHistory - describeHub - listEnabledProductsForImport +- getFindingAggregator + +The SDK model headers (`CamelAwsSecurityHubFilters`, `CamelAwsSecurityHubNote`, `CamelAwsSecurityHubSeverity` +and `CamelAwsSecurityHubWorkflow`) must hold the AWS SDK object of the type the header lists, such as +`AwsSecurityFindingFilters`. A value of another type, such as JSON text, fails the exchange with an +`IllegalArgumentException`, as there is no type converter for these classes. To send a request built +elsewhere, set `pojoRequest=true` and put the request object, such as `GetFindingsRequest`, in the body. + +The `CamelAwsSecurityHubNextToken` header is both an input and an output of `getFindings`, `getFindingHistory` +and `listEnabledProductsForImport`: the operation sends the token in the header, and then sets the header to the +token of the next page. A route that calls a different list operation afterwards must remove the header first, +for example with `removeHeader("CamelAwsSecurityHubNextToken")`, or that operation sends a token it does not know. ==== BatchImportFindings Import security findings into Security Hub. This is the primary way to send findings from custom integrations. +The findings are taken from the `CamelAwsSecurityHubFindings` header, or else from the body, which can be a +`Collection` or an array of `AwsSecurityFinding`, or one `AwsSecurityFinding`. An element of another type fails +the exchange with an `IllegalArgumentException`. + +When Security Hub does not import some of the findings, the exchange fails with an `IllegalStateException` +whose message names the failed finding ids with their error codes and messages. The +`CamelAwsSecurityHubFailedCount` and `CamelAwsSecurityHubSuccessCount` headers, and the response in the body, +are set before the exchange fails. + ._Java-only: uses inline Processor with AWS SDK builder API_ [source,java] ---- diff --git a/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubConstants.java b/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubConstants.java index 1cefa249b9e76..e44eb5e98ba8e 100644 --- a/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubConstants.java +++ b/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubConstants.java @@ -26,22 +26,27 @@ public interface SecurityHubConstants { @Metadata(description = "The operation we want to perform", javaType = "String") String OPERATION = "CamelAwsSecurityHubOperation"; - @Metadata(description = "The list of findings to import", javaType = "List") + @Metadata(description = "The findings to import, as a List of AwsSecurityFinding. When not set, the findings are taken from the body: a Collection or an array of AwsSecurityFinding, or one AwsSecurityFinding.", + javaType = "List") String FINDINGS = "CamelAwsSecurityHubFindings"; @Metadata(description = "The finding identifiers for batch update", javaType = "List") String FINDING_IDENTIFIERS = "CamelAwsSecurityHubFindingIdentifiers"; - @Metadata(description = "The filters to apply when retrieving findings", javaType = "AwsSecurityFindingFilters") + @Metadata(description = "The filters to apply when retrieving findings. It must be an AwsSecurityFindingFilters object; a value of another type, such as JSON text, fails the exchange.", + javaType = "AwsSecurityFindingFilters") String FILTERS = "CamelAwsSecurityHubFilters"; - @Metadata(description = "The note to add to findings during update", javaType = "NoteUpdate") + @Metadata(description = "The note to add to findings during update. It must be a NoteUpdate object; a value of another type fails the exchange.", + javaType = "NoteUpdate") String NOTE = "CamelAwsSecurityHubNote"; - @Metadata(description = "The severity to set on findings during update", javaType = "SeverityUpdate") + @Metadata(description = "The severity to set on findings during update. It must be a SeverityUpdate object; a value of another type fails the exchange.", + javaType = "SeverityUpdate") String SEVERITY = "CamelAwsSecurityHubSeverity"; - @Metadata(description = "The workflow status to set on findings", javaType = "WorkflowUpdate") + @Metadata(description = "The workflow status to set on findings. It must be a WorkflowUpdate object; a value of another type fails the exchange.", + javaType = "WorkflowUpdate") String WORKFLOW = "CamelAwsSecurityHubWorkflow"; @Metadata(description = "The verification state to set on findings", javaType = "String") @@ -64,7 +69,8 @@ public interface SecurityHubConstants { // Pagination constants @Metadata(label = "getFindings listEnabledProductsForImport getFindingHistory", - description = "The token for the next set of results", javaType = "String") + description = "The token for the next set of results. It is both an input and an output: the operation sends it and then sets it to the token of the response, so remove it before calling a different list operation in the same route.", + javaType = "String") String NEXT_TOKEN = "CamelAwsSecurityHubNextToken"; @Metadata(label = "getFindings", @@ -73,7 +79,8 @@ public interface SecurityHubConstants { // Response metadata @Metadata(label = "batchImportFindings", - description = "The count of findings that failed to import", javaType = "Integer") + description = "The count of findings that failed to import. When it is more than zero, the exchange fails with an exception naming the failed findings.", + javaType = "Integer") String FAILED_COUNT = "CamelAwsSecurityHubFailedCount"; @Metadata(label = "batchImportFindings", diff --git a/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubProducer.java b/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubProducer.java index 0caacbcbdca0e..8e712a5a4bd9f 100644 --- a/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubProducer.java +++ b/components/camel-aws/camel-aws-security-hub/src/main/java/org/apache/camel/component/aws/securityhub/SecurityHubProducer.java @@ -16,11 +16,15 @@ */ package org.apache.camel.component.aws.securityhub; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; @@ -51,6 +55,7 @@ import software.amazon.awssdk.services.securityhub.model.GetFindingHistoryResponse; import software.amazon.awssdk.services.securityhub.model.GetFindingsRequest; import software.amazon.awssdk.services.securityhub.model.GetFindingsResponse; +import software.amazon.awssdk.services.securityhub.model.ImportFindingsError; import software.amazon.awssdk.services.securityhub.model.ListEnabledProductsForImportRequest; import software.amazon.awssdk.services.securityhub.model.ListEnabledProductsForImportResponse; import software.amazon.awssdk.services.securityhub.model.NoteUpdate; @@ -137,12 +142,9 @@ private void batchImportFindings(SecurityHubClient securityHubClient, Exchange e = exchange.getIn().getHeader(SecurityHubConstants.FINDINGS, List.class); if (findings == null || findings.isEmpty()) { // Try to get findings from body - Object body = exchange.getIn().getBody(); - if (body instanceof List list) { - findings = (List) list; - } else if (body instanceof AwsSecurityFinding asf) { - findings = List.of(asf); - } + findings = toFindings(exchange.getIn().getBody()); + } else { + findings = toFindings(findings); } if (findings == null || findings.isEmpty()) { throw new IllegalArgumentException("At least one finding must be specified"); @@ -154,9 +156,61 @@ private void batchImportFindings(SecurityHubClient securityHubClient, Exchange e (BatchImportFindingsResponse response, Message message) -> { message.setHeader(SecurityHubConstants.FAILED_COUNT, response.failedCount()); message.setHeader(SecurityHubConstants.SUCCESS_COUNT, response.successCount()); + if (response.failedCount() != null && response.failedCount() > 0) { + throw new IllegalStateException( + "Security Hub did not import " + response.failedCount() + " finding(s): " + + describeFailedFindings(response.failedFindings())); + } }); } + /** + * The findings to import: a Collection or an array of AwsSecurityFinding, or one AwsSecurityFinding. + */ + private static List toFindings(Object value) { + Collection elements; + if (value instanceof AwsSecurityFinding finding) { + return List.of(finding); + } else if (value instanceof Collection collection) { + elements = collection; + } else if (value instanceof Object[] array) { + elements = Arrays.asList(array); + } else { + return null; + } + List findings = new ArrayList<>(elements.size()); + for (Object element : elements) { + if (element instanceof AwsSecurityFinding finding) { + findings.add(finding); + } else { + throw new IllegalArgumentException( + "The findings to import must be of type " + AwsSecurityFinding.class.getName() + " but one was " + + (element != null ? element.getClass().getName() : "null")); + } + } + return findings; + } + + private static String describeFailedFindings(List errors) { + return errors.stream() + .map(e -> e.id() + " (" + e.errorCode() + ": " + e.errorMessage() + ")") + .collect(Collectors.joining(", ")); + } + + /** + * Gets a header that must be of the given type, as there is no type converter for the AWS SDK model classes. + */ + private static T getTypedHeader(Exchange exchange, String name, Class type) { + Object value = exchange.getIn().getHeader(name); + if (value == null || type.isInstance(value)) { + return type.cast(value); + } + throw new IllegalArgumentException( + "The header " + name + " must be of type " + type.getName() + " but was " + value.getClass().getName() + + ". Set it to a " + type.getSimpleName() + + ", or use pojoRequest=true with the request object in the body."); + } + private void getFindings(SecurityHubClient securityHubClient, Exchange exchange) throws InvalidPayloadException { executeOperation( exchange, @@ -165,7 +219,7 @@ private void getFindings(SecurityHubClient securityHubClient, Exchange exchange) () -> { GetFindingsRequest.Builder builder = GetFindingsRequest.builder(); AwsSecurityFindingFilters filters - = exchange.getIn().getHeader(SecurityHubConstants.FILTERS, AwsSecurityFindingFilters.class); + = getTypedHeader(exchange, SecurityHubConstants.FILTERS, AwsSecurityFindingFilters.class); if (filters != null) { builder.filters(filters); } @@ -201,17 +255,17 @@ private void batchUpdateFindings(SecurityHubClient securityHubClient, Exchange e } builder.findingIdentifiers(findingIdentifiers); - NoteUpdate note = exchange.getIn().getHeader(SecurityHubConstants.NOTE, NoteUpdate.class); + NoteUpdate note = getTypedHeader(exchange, SecurityHubConstants.NOTE, NoteUpdate.class); if (note != null) { builder.note(note); } - SeverityUpdate severity = exchange.getIn().getHeader(SecurityHubConstants.SEVERITY, SeverityUpdate.class); + SeverityUpdate severity = getTypedHeader(exchange, SecurityHubConstants.SEVERITY, SeverityUpdate.class); if (severity != null) { builder.severity(severity); } - WorkflowUpdate workflow = exchange.getIn().getHeader(SecurityHubConstants.WORKFLOW, WorkflowUpdate.class); + WorkflowUpdate workflow = getTypedHeader(exchange, SecurityHubConstants.WORKFLOW, WorkflowUpdate.class); if (workflow != null) { builder.workflow(workflow); } diff --git a/components/camel-aws/camel-aws-security-hub/src/test/java/org/apache/camel/component/aws/securityhub/SecurityHubProducerTest.java b/components/camel-aws/camel-aws-security-hub/src/test/java/org/apache/camel/component/aws/securityhub/SecurityHubProducerTest.java new file mode 100644 index 0000000000000..eb61feeb3718e --- /dev/null +++ b/components/camel-aws/camel-aws-security-hub/src/test/java/org/apache/camel/component/aws/securityhub/SecurityHubProducerTest.java @@ -0,0 +1,214 @@ +/* + * 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.aws.securityhub; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import software.amazon.awssdk.services.securityhub.SecurityHubClient; +import software.amazon.awssdk.services.securityhub.model.AwsSecurityFinding; +import software.amazon.awssdk.services.securityhub.model.AwsSecurityFindingFilters; +import software.amazon.awssdk.services.securityhub.model.AwsSecurityFindingIdentifier; +import software.amazon.awssdk.services.securityhub.model.BatchImportFindingsRequest; +import software.amazon.awssdk.services.securityhub.model.BatchImportFindingsResponse; +import software.amazon.awssdk.services.securityhub.model.BatchUpdateFindingsRequest; +import software.amazon.awssdk.services.securityhub.model.BatchUpdateFindingsResponse; +import software.amazon.awssdk.services.securityhub.model.GetFindingsRequest; +import software.amazon.awssdk.services.securityhub.model.GetFindingsResponse; +import software.amazon.awssdk.services.securityhub.model.ImportFindingsError; +import software.amazon.awssdk.services.securityhub.model.StringFilter; +import software.amazon.awssdk.services.securityhub.model.StringFilterComparison; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SecurityHubProducerTest extends CamelTestSupport { + + @BindToRegistry("securityHubClient") + private final SecurityHubClient securityHubClient = mock(SecurityHubClient.class); + + @Test + public void getFindingsUsesFiltersOfTheRightType() { + when(securityHubClient.getFindings(any(GetFindingsRequest.class))) + .thenReturn(GetFindingsResponse.builder().build()); + AwsSecurityFindingFilters filters = AwsSecurityFindingFilters.builder() + .severityLabel(StringFilter.builder().comparison(StringFilterComparison.EQUALS).value("HIGH").build()) + .build(); + + Exchange exchange = template.send("direct:getFindings", + e -> e.getIn().setHeader(SecurityHubConstants.FILTERS, filters)); + + assertNull(exchange.getException()); + ArgumentCaptor captor = ArgumentCaptor.forClass(GetFindingsRequest.class); + verify(securityHubClient).getFindings(captor.capture()); + assertEquals(filters, captor.getValue().filters()); + } + + @Test + public void getFindingsFailsOnFiltersAsJsonText() { + Exchange exchange = template.send("direct:getFindings", + e -> e.getIn().setHeader(SecurityHubConstants.FILTERS, "{\"SeverityLabel\": [{\"Value\": \"HIGH\"}]}")); + + IllegalArgumentException iae = assertInstanceOf(IllegalArgumentException.class, exchange.getException()); + assertTrue(iae.getMessage().contains(SecurityHubConstants.FILTERS), iae.getMessage()); + assertTrue(iae.getMessage().contains(AwsSecurityFindingFilters.class.getName()), iae.getMessage()); + assertTrue(iae.getMessage().contains("pojoRequest"), iae.getMessage()); + verify(securityHubClient, never()).getFindings(any(GetFindingsRequest.class)); + } + + @ParameterizedTest + @ValueSource(strings = { SecurityHubConstants.NOTE, SecurityHubConstants.SEVERITY, SecurityHubConstants.WORKFLOW }) + public void batchUpdateFindingsFailsOnUpdateHeaderOfAnotherType(String header) { + Exchange exchange = template.send("direct:batchUpdateFindings", e -> { + e.getIn().setHeader(SecurityHubConstants.FINDING_IDENTIFIERS, + List.of(AwsSecurityFindingIdentifier.builder().id("f-1").productArn("arn:p").build())); + e.getIn().setHeader(header, "{\"Text\": \"some text\"}"); + }); + + IllegalArgumentException iae = assertInstanceOf(IllegalArgumentException.class, exchange.getException()); + assertTrue(iae.getMessage().contains(header), iae.getMessage()); + verify(securityHubClient, never()).batchUpdateFindings(any(BatchUpdateFindingsRequest.class)); + } + + @Test + public void batchUpdateFindingsWithoutUpdateHeaders() { + when(securityHubClient.batchUpdateFindings(any(BatchUpdateFindingsRequest.class))) + .thenReturn(BatchUpdateFindingsResponse.builder().build()); + + Exchange exchange = template.send("direct:batchUpdateFindings", + e -> e.getIn().setHeader(SecurityHubConstants.FINDING_IDENTIFIERS, + List.of(AwsSecurityFindingIdentifier.builder().id("f-1").productArn("arn:p").build()))); + + assertNull(exchange.getException()); + verify(securityHubClient).batchUpdateFindings(any(BatchUpdateFindingsRequest.class)); + } + + @Test + public void batchImportFindingsTakesAnArrayInTheBody() { + mockImportSuccess(); + AwsSecurityFinding f1 = finding("f-1"); + AwsSecurityFinding f2 = finding("f-2"); + + Exchange exchange = template.send("direct:batchImportFindings", + e -> e.getIn().setBody(new AwsSecurityFinding[] { f1, f2 })); + + assertNull(exchange.getException()); + assertEquals(List.of(f1, f2), capturedImport().findings()); + } + + @Test + public void batchImportFindingsTakesASetInTheBody() { + mockImportSuccess(); + AwsSecurityFinding f1 = finding("f-1"); + AwsSecurityFinding f2 = finding("f-2"); + Set findings = new LinkedHashSet<>(List.of(f1, f2)); + + Exchange exchange = template.send("direct:batchImportFindings", e -> e.getIn().setBody(findings)); + + assertNull(exchange.getException()); + assertEquals(List.of(f1, f2), capturedImport().findings()); + } + + @Test + public void batchImportFindingsFailsOnAnElementOfAnotherType() { + Exchange exchange = template.send("direct:batchImportFindings", + e -> e.getIn().setBody(List.of(finding("f-1"), "not a finding"))); + + IllegalArgumentException iae = assertInstanceOf(IllegalArgumentException.class, exchange.getException()); + assertTrue(iae.getMessage().contains(AwsSecurityFinding.class.getName()), iae.getMessage()); + assertTrue(iae.getMessage().contains(String.class.getName()), iae.getMessage()); + verify(securityHubClient, never()).batchImportFindings(any(BatchImportFindingsRequest.class)); + } + + @Test + public void batchImportFindingsFailsWhenSecurityHubReportsFailedFindings() { + when(securityHubClient.batchImportFindings(any(BatchImportFindingsRequest.class))) + .thenReturn(BatchImportFindingsResponse.builder() + .successCount(1) + .failedCount(1) + .failedFindings(ImportFindingsError.builder() + .id("f-2").errorCode("InvalidInput").errorMessage("Missing Title").build()) + .build()); + + Exchange exchange = template.send("direct:batchImportFindings", + e -> e.getIn().setBody(List.of(finding("f-1"), finding("f-2")))); + + IllegalStateException ise = assertInstanceOf(IllegalStateException.class, exchange.getException()); + assertTrue(ise.getMessage().contains("f-2"), ise.getMessage()); + assertTrue(ise.getMessage().contains("InvalidInput"), ise.getMessage()); + assertTrue(ise.getMessage().contains("Missing Title"), ise.getMessage()); + assertEquals(1, exchange.getMessage().getHeader(SecurityHubConstants.FAILED_COUNT)); + assertEquals(1, exchange.getMessage().getHeader(SecurityHubConstants.SUCCESS_COUNT)); + } + + @Test + public void batchImportFindingsWithoutFailedFindings() { + mockImportSuccess(); + + Exchange exchange = template.send("direct:batchImportFindings", e -> e.getIn().setBody(finding("f-1"))); + + assertNull(exchange.getException()); + assertEquals(0, exchange.getMessage().getHeader(SecurityHubConstants.FAILED_COUNT)); + assertEquals(1, exchange.getMessage().getHeader(SecurityHubConstants.SUCCESS_COUNT)); + } + + private void mockImportSuccess() { + when(securityHubClient.batchImportFindings(any(BatchImportFindingsRequest.class))) + .thenReturn(BatchImportFindingsResponse.builder().successCount(1).failedCount(0).build()); + } + + private BatchImportFindingsRequest capturedImport() { + ArgumentCaptor captor = ArgumentCaptor.forClass(BatchImportFindingsRequest.class); + verify(securityHubClient).batchImportFindings(captor.capture()); + return captor.getValue(); + } + + private static AwsSecurityFinding finding(String id) { + return AwsSecurityFinding.builder().id(id).title("Title " + id).build(); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:getFindings") + .to("aws-security-hub://test?securityHubClient=#securityHubClient&operation=getFindings"); + from("direct:batchUpdateFindings") + .to("aws-security-hub://test?securityHubClient=#securityHubClient&operation=batchUpdateFindings"); + from("direct:batchImportFindings") + .to("aws-security-hub://test?securityHubClient=#securityHubClient&operation=batchImportFindings"); + } + }; + } +} diff --git a/components/camel-aws/camel-aws2-polly/src/generated/resources/META-INF/org/apache/camel/component/aws2/polly/aws2-polly.json b/components/camel-aws/camel-aws2-polly/src/generated/resources/META-INF/org/apache/camel/component/aws2/polly/aws2-polly.json index d2290ba44fc61..88a58e3eb51c0 100644 --- a/components/camel-aws/camel-aws2-polly/src/generated/resources/META-INF/org/apache/camel/component/aws2/polly/aws2-polly.json +++ b/components/camel-aws/camel-aws2-polly/src/generated/resources/META-INF/org/apache/camel/component/aws2/polly/aws2-polly.json @@ -32,7 +32,7 @@ "lexiconName": { "index": 5, "kind": "property", "displayName": "Lexicon Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The name of the lexicon to use for getLexicon, putLexicon, or deleteLexicon operations" }, "lexiconNames": { "index": 6, "kind": "property", "displayName": "Lexicon Names", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Lexicon names to apply during synthesis" }, "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.polly.Polly2Operations", "enum": [ "synthesizeSpeech", "describeVoices", "listLexicons", "getLexicon", "putLexicon", "deleteLexicon", "startSpeechSynthesisTask", "getSpeechSynthesisTask", "listSpeechSynthesisTasks" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "synthesizeSpeech", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, - "outputFormat": { "index": 8, "kind": "property", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MP3", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format" }, + "outputFormat": { "index": 8, "kind": "property", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format. When neither this option nor the CamelAwsPollyOutputFormat header is set, MP3 is used." }, "overrideEndpoint": { "index": 9, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 10, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 11, "kind": "property", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The region in which the Polly client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, @@ -41,7 +41,7 @@ "sampleRate": { "index": 14, "kind": "property", "displayName": "Sample Rate", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The sample rate in Hz for the audio output" }, "snsTopicArn": { "index": 15, "kind": "property", "displayName": "Sns Topic Arn", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The SNS topic ARN for startSpeechSynthesisTask notifications" }, "taskId": { "index": 16, "kind": "property", "displayName": "Task Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The task ID for getSpeechSynthesisTask operation" }, - "textType": { "index": 17, "kind": "property", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TEXT", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml)" }, + "textType": { "index": 17, "kind": "property", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml). When neither this option nor the CamelAwsPollyTextType header is set, TEXT is used." }, "uriEndpointOverride": { "index": 18, "kind": "property", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "voiceId": { "index": 19, "kind": "property", "displayName": "Voice Id", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.VoiceId", "enum": [ "Aditi", "Amy", "Astrid", "Bianca", "Brian", "Camila", "Carla", "Carmen", "Celine", "Chantal", "Conchita", "Cristiano", "Dora", "Emma", "Enrique", "Ewa", "Filiz", "Gabrielle", "Geraint", "Giorgio", "Gwyneth", "Hans", "Ines", "Ivy", "Jacek", "Jan", "Joanna", "Joey", "Justin", "Karl", "Kendra", "Kevin", "Kimberly", "Lea", "Liv", "Lotte", "Lucia", "Lupe", "Mads", "Maja", "Marlene", "Mathieu", "Matthew", "Maxim", "Mia", "Miguel", "Mizuki", "Naja", "Nicole", "Olivia", "Penelope", "Raveena", "Ricardo", "Ruben", "Russell", "Salli", "Seoyeon", "Takumi", "Tatyana", "Vicki", "Vitoria", "Zeina", "Zhiyu", "Aria", "Ayanda", "Arlet", "Hannah", "Arthur", "Daniel", "Liam", "Pedro", "Kajal", "Hiujin", "Laura", "Elin", "Ida", "Suvi", "Ola", "Hala", "Andres", "Sergio", "Remi", "Adriano", "Thiago", "Ruth", "Stephen", "Kazuha", "Tomoko", "Niamh", "Sofie", "Lisa", "Isabelle", "Zayd", "Danielle", "Gregory", "Burcu", "Jitka", "Sabrina", "Jasmine", "Jihye", "Ambre", "Beatrice", "Florian", "Lennart", "Lorenzo", "Tiffany", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The voice ID to use for synthesis" }, "autowiredEnabled": { "index": 20, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, @@ -76,7 +76,8 @@ "CamelAwsPollyS3KeyPrefix": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 key prefix for output", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#S3_KEY_PREFIX" }, "CamelAwsPollySnsTopicArn": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The SNS topic ARN for notifications", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#SNS_TOPIC_ARN" }, "CamelAwsPollyContentType": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type of the audio stream", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#CONTENT_TYPE" }, - "CamelAwsPollyRequestCharacters": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of characters synthesized", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#REQUEST_CHARACTERS" } + "CamelAwsPollyRequestCharacters": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Number of characters synthesized", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#REQUEST_CHARACTERS" }, + "CamelAwsPollyNextToken": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The token of the next page for the describeVoices, listLexicons and listSpeechSynthesisTasks operations. It is in and out: when set, it is sent as the next token of the request, and after the call it holds the next token of the response, or it is removed when the response has none, which is the last page. Remove it before calling a different list operation.", "constantName": "org.apache.camel.component.aws2.polly.Polly2Constants#NEXT_TOKEN" } }, "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.polly.Polly2Configuration", "configurationField": "configuration", "description": "Logical name" }, @@ -86,7 +87,7 @@ "lexiconName": { "index": 4, "kind": "parameter", "displayName": "Lexicon Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The name of the lexicon to use for getLexicon, putLexicon, or deleteLexicon operations" }, "lexiconNames": { "index": 5, "kind": "parameter", "displayName": "Lexicon Names", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Lexicon names to apply during synthesis" }, "operation": { "index": 6, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.polly.Polly2Operations", "enum": [ "synthesizeSpeech", "describeVoices", "listLexicons", "getLexicon", "putLexicon", "deleteLexicon", "startSpeechSynthesisTask", "getSpeechSynthesisTask", "listSpeechSynthesisTasks" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "synthesizeSpeech", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, - "outputFormat": { "index": 7, "kind": "parameter", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MP3", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format" }, + "outputFormat": { "index": 7, "kind": "parameter", "displayName": "Output Format", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.OutputFormat", "enum": [ "json", "mp3", "ogg_opus", "ogg_vorbis", "pcm", "mulaw", "alaw", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The audio output format. When neither this option nor the CamelAwsPollyOutputFormat header is set, MP3 is used." }, "overrideEndpoint": { "index": 8, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 9, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 10, "kind": "parameter", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The region in which the Polly client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, @@ -95,7 +96,7 @@ "sampleRate": { "index": 13, "kind": "parameter", "displayName": "Sample Rate", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The sample rate in Hz for the audio output" }, "snsTopicArn": { "index": 14, "kind": "parameter", "displayName": "Sns Topic Arn", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The SNS topic ARN for startSpeechSynthesisTask notifications" }, "taskId": { "index": 15, "kind": "parameter", "displayName": "Task Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The task ID for getSpeechSynthesisTask operation" }, - "textType": { "index": 16, "kind": "parameter", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TEXT", "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml)" }, + "textType": { "index": 16, "kind": "parameter", "displayName": "Text Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.TextType", "enum": [ "ssml", "text", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The type of text input (text or ssml). When neither this option nor the CamelAwsPollyTextType header is set, TEXT is used." }, "uriEndpointOverride": { "index": 17, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "voiceId": { "index": 18, "kind": "parameter", "displayName": "Voice Id", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "software.amazon.awssdk.services.polly.model.VoiceId", "enum": [ "Aditi", "Amy", "Astrid", "Bianca", "Brian", "Camila", "Carla", "Carmen", "Celine", "Chantal", "Conchita", "Cristiano", "Dora", "Emma", "Enrique", "Ewa", "Filiz", "Gabrielle", "Geraint", "Giorgio", "Gwyneth", "Hans", "Ines", "Ivy", "Jacek", "Jan", "Joanna", "Joey", "Justin", "Karl", "Kendra", "Kevin", "Kimberly", "Lea", "Liv", "Lotte", "Lucia", "Lupe", "Mads", "Maja", "Marlene", "Mathieu", "Matthew", "Maxim", "Mia", "Miguel", "Mizuki", "Naja", "Nicole", "Olivia", "Penelope", "Raveena", "Ricardo", "Ruben", "Russell", "Salli", "Seoyeon", "Takumi", "Tatyana", "Vicki", "Vitoria", "Zeina", "Zhiyu", "Aria", "Ayanda", "Arlet", "Hannah", "Arthur", "Daniel", "Liam", "Pedro", "Kajal", "Hiujin", "Laura", "Elin", "Ida", "Suvi", "Ola", "Hala", "Andres", "Sergio", "Remi", "Adriano", "Thiago", "Ruth", "Stephen", "Kazuha", "Tomoko", "Niamh", "Sofie", "Lisa", "Isabelle", "Zayd", "Danielle", "Gregory", "Burcu", "Jitka", "Sabrina", "Jasmine", "Jihye", "Ambre", "Beatrice", "Florian", "Lennart", "Lorenzo", "Tiffany", "null" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.polly.Polly2Configuration", "configurationField": "configuration", "description": "The voice ID to use for synthesis" }, "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, diff --git a/components/camel-aws/camel-aws2-polly/src/main/docs/aws2-polly-component.adoc b/components/camel-aws/camel-aws2-polly/src/main/docs/aws2-polly-component.adoc index 930cf682cf30c..dd6371f615248 100644 --- a/components/camel-aws/camel-aws2-polly/src/main/docs/aws2-polly-component.adoc +++ b/components/camel-aws/camel-aws2-polly/src/main/docs/aws2-polly-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Polly component options -You have to provide the amazonPollyClient in the +You have to provide the pollyClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/polly/[Amazon Polly] service. @@ -82,6 +82,81 @@ Camel-AWS Polly component provides the following operations on the producer side - getSpeechSynthesisTask - listSpeechSynthesisTasks +=== Text type and output format + +The `textType` option, or else the `CamelAwsPollyTextType` header, says whether the text is plain text (`TEXT`) or SSML (`SSML`). +The `outputFormat` option, or else the `CamelAwsPollyOutputFormat` header, sets the audio format. +When neither the option nor the header is set, `TEXT` and `MP3` are used. +This applies to the synthesizeSpeech and startSpeechSynthesisTask operations. + +The `lexiconNames` option, or else the `CamelAwsPollyLexiconNames` header, is a comma-separated list of lexicon names. +The spaces around each name are removed, so `a, b` is the same as `a,b`. + +=== Paging the list operations + +The describeVoices, listLexicons and listSpeechSynthesisTasks operations return one page of results in the body. +The `CamelAwsPollyNextToken` header is both an input and an output: + +- when it is set, it is sent as the next token of the request, so the call returns the next page; +- after the call, it holds the next token of the response, or it is removed when the response has none, which means this was the last page. + +A route can therefore call the operation again while the header is set. +Because the header stays on the message, remove it before calling a different list operation, or that call starts from a token that is not its own. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("aws2-polly://test?operation=listLexicons") + .log("Lexicons: ${body}") + .loopDoWhile(header(Polly2Constants.NEXT_TOKEN).isNotNull()) + .to("aws2-polly://test?operation=listLexicons") + .log("Lexicons: ${body}") + .end() + .removeHeader(Polly2Constants.NEXT_TOKEN) + .to("aws2-polly://test?operation=describeVoices"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - to: + uri: aws2-polly:test + parameters: + operation: listLexicons + - log: + message: "Lexicons: ${body}" + - loop: + doWhile: true + expression: + simple: + expression: "${header.CamelAwsPollyNextToken} != null" + steps: + - to: + uri: aws2-polly:test + parameters: + operation: listLexicons + - log: + message: "Lexicons: ${body}" + - removeHeader: + name: CamelAwsPollyNextToken + - to: + uri: aws2-polly:test + parameters: + operation: describeVoices +---- +==== + +With `pojoRequest=true`, the next token is taken from the request in the body, and the `CamelAwsPollyNextToken` header is set from the response in the same way. + == Examples === Synthesize Speech example diff --git a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Configuration.java b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Configuration.java index 71bf41d66f1ba..a0e8c42f26023 100644 --- a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Configuration.java +++ b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Configuration.java @@ -57,10 +57,12 @@ public class Polly2Configuration implements Cloneable, AwsCommonConfiguration { private String region; @UriParam(description = "The voice ID to use for synthesis") private VoiceId voiceId; - @UriParam(description = "The audio output format", defaultValue = "MP3") - private OutputFormat outputFormat = OutputFormat.MP3; - @UriParam(description = "The type of text input (text or ssml)", defaultValue = "TEXT") - private TextType textType = TextType.TEXT; + @UriParam(description = "The audio output format. When neither this option nor the CamelAwsPollyOutputFormat header" + + " is set, MP3 is used.") + private OutputFormat outputFormat; + @UriParam(description = "The type of text input (text or ssml). When neither this option nor the CamelAwsPollyTextType" + + " header is set, TEXT is used.") + private TextType textType; @UriParam(description = "The engine to use for synthesis (standard, neural, long-form, generative)") private Engine engine; @UriParam(description = "The sample rate in Hz for the audio output") @@ -214,7 +216,8 @@ public OutputFormat getOutputFormat() { } /** - * The audio format in which the resulting stream will be encoded + * The audio format in which the resulting stream will be encoded. When neither this option nor the + * CamelAwsPollyOutputFormat header is set, MP3 is used. */ public void setOutputFormat(OutputFormat outputFormat) { this.outputFormat = outputFormat; @@ -225,7 +228,8 @@ public TextType getTextType() { } /** - * Specifies whether the input text is plain text or SSML + * Specifies whether the input text is plain text or SSML. When neither this option nor the CamelAwsPollyTextType + * header is set, TEXT is used. */ public void setTextType(TextType textType) { this.textType = textType; diff --git a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Constants.java b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Constants.java index 41e3a66632a5a..c65d27e5b10e5 100644 --- a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Constants.java +++ b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Constants.java @@ -58,4 +58,10 @@ public interface Polly2Constants { String CONTENT_TYPE = "CamelAwsPollyContentType"; @Metadata(description = "Number of characters synthesized", javaType = "Integer") String REQUEST_CHARACTERS = "CamelAwsPollyRequestCharacters"; + @Metadata(description = "The token of the next page for the describeVoices, listLexicons and listSpeechSynthesisTasks operations. " + + "It is in and out: when set, it is sent as the next token of the request, and after the call it holds the " + + "next token of the response, or it is removed when the response has none, which is the last page. " + + "Remove it before calling a different list operation.", + javaType = "String") + String NEXT_TOKEN = "CamelAwsPollyNextToken"; } diff --git a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Producer.java b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Producer.java index 226c8a4f4be8b..1d3f954fdb956 100644 --- a/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Producer.java +++ b/components/camel-aws/camel-aws2-polly/src/main/java/org/apache/camel/component/aws2/polly/Polly2Producer.java @@ -17,6 +17,7 @@ package org.apache.camel.component.aws2.polly; import java.util.Arrays; +import java.util.List; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; @@ -166,23 +167,11 @@ private void synthesizeSpeech(PollyClient pollyClient, Exchange exchange) throws } request.voiceId(voiceId); - // Set output format - endpoint option first, then header - OutputFormat outputFormat = getConfiguration().getOutputFormat(); - if (ObjectHelper.isEmpty(outputFormat)) { - outputFormat = exchange.getIn().getHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.class); - } - if (ObjectHelper.isNotEmpty(outputFormat)) { - request.outputFormat(outputFormat); - } + // Set output format - endpoint option first, then header, else MP3 + request.outputFormat(determineOutputFormat(exchange)); - // Set text type - endpoint option first, then header - TextType textType = getConfiguration().getTextType(); - if (ObjectHelper.isEmpty(textType)) { - textType = exchange.getIn().getHeader(Polly2Constants.TEXT_TYPE, TextType.class); - } - if (ObjectHelper.isNotEmpty(textType)) { - request.textType(textType); - } + // Set text type - endpoint option first, then header, else TEXT + request.textType(determineTextType(exchange)); // Set engine - endpoint option first, then header Engine engine = getConfiguration().getEngine(); @@ -216,8 +205,9 @@ private void synthesizeSpeech(PollyClient pollyClient, Exchange exchange) throws if (ObjectHelper.isEmpty(lexiconNames)) { lexiconNames = exchange.getIn().getHeader(Polly2Constants.LEXICON_NAMES, String.class); } - if (ObjectHelper.isNotEmpty(lexiconNames)) { - request.lexiconNames(Arrays.asList(lexiconNames.split(","))); + List lexiconNameList = splitLexiconNames(lexiconNames); + if (!lexiconNameList.isEmpty()) { + request.lexiconNames(lexiconNameList); } // Set the text to synthesize @@ -252,6 +242,7 @@ private void describeVoices(PollyClient pollyClient, Exchange exchange) throws I } Message message = getMessageForResponse(exchange); message.setBody(result.voices()); + setNextToken(message, result.nextToken()); } else { throw new IllegalArgumentException( "describeVoices operation requires DescribeVoicesRequest in POJO mode"); @@ -277,6 +268,12 @@ private void describeVoices(PollyClient pollyClient, Exchange exchange) throws I request.languageCode(languageCode); } + // Set next token of a previous page - header + String nextToken = exchange.getIn().getHeader(Polly2Constants.NEXT_TOKEN, String.class); + if (ObjectHelper.isNotEmpty(nextToken)) { + request.nextToken(nextToken); + } + DescribeVoicesResponse result; try { result = pollyClient.describeVoices(request.build()); @@ -286,6 +283,7 @@ private void describeVoices(PollyClient pollyClient, Exchange exchange) throws I } Message message = getMessageForResponse(exchange); message.setBody(result.voices()); + setNextToken(message, result.nextToken()); } } @@ -302,21 +300,28 @@ private void listLexicons(PollyClient pollyClient, Exchange exchange) throws Inv } Message message = getMessageForResponse(exchange); message.setBody(result.lexicons()); + setNextToken(message, result.nextToken()); } else { throw new IllegalArgumentException( "listLexicons operation requires ListLexiconsRequest in POJO mode"); } } else { - ListLexiconsRequest request = ListLexiconsRequest.builder().build(); + ListLexiconsRequest.Builder request = ListLexiconsRequest.builder(); + // Set next token of a previous page - header + String nextToken = exchange.getIn().getHeader(Polly2Constants.NEXT_TOKEN, String.class); + if (ObjectHelper.isNotEmpty(nextToken)) { + request.nextToken(nextToken); + } ListLexiconsResponse result; try { - result = pollyClient.listLexicons(request); + result = pollyClient.listLexicons(request.build()); } catch (AwsServiceException ase) { LOG.trace("List Lexicons command returned the error code {}", AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); message.setBody(result.lexicons()); + setNextToken(message, result.nextToken()); } } @@ -456,7 +461,7 @@ private void startSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange result = pollyClient.startSpeechSynthesisTask(req); } catch (AwsServiceException ase) { LOG.trace("Start Speech Synthesis Task command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); @@ -479,14 +484,8 @@ private void startSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange } request.voiceId(voiceId); - // Set output format - endpoint option first, then header - OutputFormat outputFormat = getConfiguration().getOutputFormat(); - if (ObjectHelper.isEmpty(outputFormat)) { - outputFormat = exchange.getIn().getHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.class); - } - if (ObjectHelper.isNotEmpty(outputFormat)) { - request.outputFormat(outputFormat); - } + // Set output format - endpoint option first, then header, else MP3 + request.outputFormat(determineOutputFormat(exchange)); // Set S3 bucket - endpoint option first, then header String s3Bucket = getConfiguration().getS3Bucket(); @@ -516,14 +515,8 @@ private void startSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange request.snsTopicArn(snsTopicArn); } - // Set text type - endpoint option first, then header - TextType textType = getConfiguration().getTextType(); - if (ObjectHelper.isEmpty(textType)) { - textType = exchange.getIn().getHeader(Polly2Constants.TEXT_TYPE, TextType.class); - } - if (ObjectHelper.isNotEmpty(textType)) { - request.textType(textType); - } + // Set text type - endpoint option first, then header, else TEXT + request.textType(determineTextType(exchange)); // Set engine - endpoint option first, then header Engine engine = getConfiguration().getEngine(); @@ -557,8 +550,9 @@ private void startSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange if (ObjectHelper.isEmpty(lexiconNames)) { lexiconNames = exchange.getIn().getHeader(Polly2Constants.LEXICON_NAMES, String.class); } - if (ObjectHelper.isNotEmpty(lexiconNames)) { - request.lexiconNames(Arrays.asList(lexiconNames.split(","))); + List lexiconNameList = splitLexiconNames(lexiconNames); + if (!lexiconNameList.isEmpty()) { + request.lexiconNames(lexiconNameList); } // Set the text to synthesize @@ -569,7 +563,7 @@ private void startSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange result = pollyClient.startSpeechSynthesisTask(request.build()); } catch (AwsServiceException ase) { LOG.trace("Start Speech Synthesis Task command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); @@ -587,7 +581,7 @@ private void getSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange) result = pollyClient.getSpeechSynthesisTask(req); } catch (AwsServiceException ase) { LOG.trace("Get Speech Synthesis Task command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); @@ -612,7 +606,7 @@ private void getSpeechSynthesisTask(PollyClient pollyClient, Exchange exchange) result = pollyClient.getSpeechSynthesisTask(request); } catch (AwsServiceException ase) { LOG.trace("Get Speech Synthesis Task command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); @@ -629,27 +623,72 @@ private void listSpeechSynthesisTasks(PollyClient pollyClient, Exchange exchange result = pollyClient.listSpeechSynthesisTasks(req); } catch (AwsServiceException ase) { LOG.trace("List Speech Synthesis Tasks command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); message.setBody(result.synthesisTasks()); + setNextToken(message, result.nextToken()); } else { throw new IllegalArgumentException( "listSpeechSynthesisTasks operation requires ListSpeechSynthesisTasksRequest in POJO mode"); } } else { - ListSpeechSynthesisTasksRequest request = ListSpeechSynthesisTasksRequest.builder().build(); + ListSpeechSynthesisTasksRequest.Builder request = ListSpeechSynthesisTasksRequest.builder(); + // Set next token of a previous page - header + String nextToken = exchange.getIn().getHeader(Polly2Constants.NEXT_TOKEN, String.class); + if (ObjectHelper.isNotEmpty(nextToken)) { + request.nextToken(nextToken); + } ListSpeechSynthesisTasksResponse result; try { - result = pollyClient.listSpeechSynthesisTasks(request); + result = pollyClient.listSpeechSynthesisTasks(request.build()); } catch (AwsServiceException ase) { LOG.trace("List Speech Synthesis Tasks command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); message.setBody(result.synthesisTasks()); + setNextToken(message, result.nextToken()); + } + } + + private OutputFormat determineOutputFormat(Exchange exchange) { + OutputFormat outputFormat = getConfiguration().getOutputFormat(); + if (ObjectHelper.isEmpty(outputFormat)) { + outputFormat = exchange.getIn().getHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.class); + } + return ObjectHelper.isNotEmpty(outputFormat) ? outputFormat : OutputFormat.MP3; + } + + private TextType determineTextType(Exchange exchange) { + TextType textType = getConfiguration().getTextType(); + if (ObjectHelper.isEmpty(textType)) { + textType = exchange.getIn().getHeader(Polly2Constants.TEXT_TYPE, TextType.class); + } + return ObjectHelper.isNotEmpty(textType) ? textType : TextType.TEXT; + } + + private static List splitLexiconNames(String lexiconNames) { + if (ObjectHelper.isEmpty(lexiconNames)) { + return List.of(); + } + return Arrays.stream(lexiconNames.split(",")) + .map(String::trim) + .filter(name -> !name.isEmpty()) + .toList(); + } + + /** + * Sets the next token of a list response, or removes the header when this is the last page, so a route looping over + * the pages ends. + */ + private static void setNextToken(Message message, String nextToken) { + if (ObjectHelper.isNotEmpty(nextToken)) { + message.setHeader(Polly2Constants.NEXT_TOKEN, nextToken); + } else { + message.removeHeader(Polly2Constants.NEXT_TOKEN); } } diff --git a/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/AmazonPollyClientMock.java b/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/AmazonPollyClientMock.java index 36967ff75da44..2a2b9fd796a12 100644 --- a/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/AmazonPollyClientMock.java +++ b/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/AmazonPollyClientMock.java @@ -55,8 +55,17 @@ public class AmazonPollyClientMock implements PollyClient { + // the last requests received, and the next token the list operations return + SynthesizeSpeechRequest lastSynthesizeSpeechRequest; + StartSpeechSynthesisTaskRequest lastStartSpeechSynthesisTaskRequest; + DescribeVoicesRequest lastDescribeVoicesRequest; + ListLexiconsRequest lastListLexiconsRequest; + ListSpeechSynthesisTasksRequest lastListSpeechSynthesisTasksRequest; + String nextToken; + @Override public ResponseInputStream synthesizeSpeech(SynthesizeSpeechRequest request) { + lastSynthesizeSpeechRequest = request; SynthesizeSpeechResponse response = SynthesizeSpeechResponse.builder() .contentType("audio/mpeg") .requestCharacters(request.text().length()) @@ -71,6 +80,7 @@ public ResponseInputStream synthesizeSpeech(Synthesize @Override public DescribeVoicesResponse describeVoices(DescribeVoicesRequest request) { + lastDescribeVoicesRequest = request; Voice voice1 = Voice.builder() .id(VoiceId.JOANNA) .name("Joanna") @@ -91,17 +101,20 @@ public DescribeVoicesResponse describeVoices(DescribeVoicesRequest request) { return DescribeVoicesResponse.builder() .voices(Arrays.asList(voice1, voice2)) + .nextToken(nextToken) .build(); } @Override public ListLexiconsResponse listLexicons(ListLexiconsRequest request) { + lastListLexiconsRequest = request; LexiconDescription lexicon = LexiconDescription.builder() .name("TestLexicon") .build(); return ListLexiconsResponse.builder() .lexicons(Arrays.asList(lexicon)) + .nextToken(nextToken) .build(); } @@ -129,6 +142,7 @@ public DeleteLexiconResponse deleteLexicon(DeleteLexiconRequest request) { @Override public StartSpeechSynthesisTaskResponse startSpeechSynthesisTask(StartSpeechSynthesisTaskRequest request) { + lastStartSpeechSynthesisTaskRequest = request; SynthesisTask task = SynthesisTask.builder() .taskId("test-task-id-123") .taskStatus(TaskStatus.IN_PROGRESS) @@ -159,6 +173,7 @@ public GetSpeechSynthesisTaskResponse getSpeechSynthesisTask(GetSpeechSynthesisT @Override public ListSpeechSynthesisTasksResponse listSpeechSynthesisTasks(ListSpeechSynthesisTasksRequest request) { + lastListSpeechSynthesisTasksRequest = request; SynthesisTask task = SynthesisTask.builder() .taskId("test-task-id-123") .taskStatus(TaskStatus.COMPLETED) @@ -168,6 +183,7 @@ public ListSpeechSynthesisTasksResponse listSpeechSynthesisTasks(ListSpeechSynth return ListSpeechSynthesisTasksResponse.builder() .synthesisTasks(Arrays.asList(task)) + .nextToken(nextToken) .build(); } diff --git a/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/Polly2ProducerTest.java b/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/Polly2ProducerTest.java index 8654a852e2f1c..c306c223440b8 100644 --- a/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/Polly2ProducerTest.java +++ b/components/camel-aws/camel-aws2-polly/src/test/java/org/apache/camel/component/aws2/polly/Polly2ProducerTest.java @@ -29,17 +29,22 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import software.amazon.awssdk.services.polly.model.Lexicon; import software.amazon.awssdk.services.polly.model.LexiconDescription; +import software.amazon.awssdk.services.polly.model.ListLexiconsRequest; import software.amazon.awssdk.services.polly.model.OutputFormat; +import software.amazon.awssdk.services.polly.model.StartSpeechSynthesisTaskRequest; import software.amazon.awssdk.services.polly.model.SynthesisTask; import software.amazon.awssdk.services.polly.model.SynthesizeSpeechRequest; +import software.amazon.awssdk.services.polly.model.TextType; import software.amazon.awssdk.services.polly.model.Voice; import software.amazon.awssdk.services.polly.model.VoiceId; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; public class Polly2ProducerTest extends CamelTestSupport { @@ -231,6 +236,118 @@ public void process(Exchange exchange) { assertEquals(1, tasks.size()); } + @Test + public void synthesizeSpeechTextTypeAndOutputFormatFromHeaders() { + template.send("direct:synthesizeSpeech", exchange -> { + exchange.getIn().setHeader(Polly2Constants.VOICE_ID, VoiceId.JOANNA); + exchange.getIn().setHeader(Polly2Constants.TEXT_TYPE, TextType.SSML); + exchange.getIn().setHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.OGG_VORBIS); + exchange.getIn().setBody("Hello"); + }); + + assertEquals(TextType.SSML, clientMock.lastSynthesizeSpeechRequest.textType()); + assertEquals(OutputFormat.OGG_VORBIS, clientMock.lastSynthesizeSpeechRequest.outputFormat()); + } + + @Test + public void synthesizeSpeechTextTypeAndOutputFormatDefaults() { + template.send("direct:synthesizeSpeech", exchange -> { + exchange.getIn().setHeader(Polly2Constants.VOICE_ID, VoiceId.JOANNA); + exchange.getIn().setBody("Hello"); + }); + + assertEquals(TextType.TEXT, clientMock.lastSynthesizeSpeechRequest.textType()); + assertEquals(OutputFormat.MP3, clientMock.lastSynthesizeSpeechRequest.outputFormat()); + } + + @Test + public void synthesizeSpeechOptionWinsOverHeader() { + template.send("direct:synthesizeSpeechWithOptions", exchange -> { + exchange.getIn().setHeader(Polly2Constants.TEXT_TYPE, TextType.TEXT); + exchange.getIn().setHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.OGG_VORBIS); + exchange.getIn().setBody("Hello"); + }); + + assertEquals(TextType.SSML, clientMock.lastSynthesizeSpeechRequest.textType()); + assertEquals(OutputFormat.MP3, clientMock.lastSynthesizeSpeechRequest.outputFormat()); + } + + @Test + public void synthesizeSpeechLexiconNamesAreTrimmed() { + template.send("direct:synthesizeSpeech", exchange -> { + exchange.getIn().setHeader(Polly2Constants.VOICE_ID, VoiceId.JOANNA); + exchange.getIn().setHeader(Polly2Constants.LEXICON_NAMES, "a, b, ,c "); + exchange.getIn().setBody("Hello"); + }); + + assertEquals(List.of("a", "b", "c"), clientMock.lastSynthesizeSpeechRequest.lexiconNames()); + } + + @Test + public void startSpeechSynthesisTaskTextTypeOutputFormatAndLexiconNamesFromHeaders() { + template.send("direct:startSpeechSynthesisTask", exchange -> { + exchange.getIn().setHeader(Polly2Constants.VOICE_ID, VoiceId.JOANNA); + exchange.getIn().setHeader(Polly2Constants.S3_BUCKET, "my-bucket"); + exchange.getIn().setHeader(Polly2Constants.TEXT_TYPE, TextType.SSML); + exchange.getIn().setHeader(Polly2Constants.OUTPUT_FORMAT, OutputFormat.OGG_VORBIS); + exchange.getIn().setHeader(Polly2Constants.LEXICON_NAMES, "a, b"); + exchange.getIn().setBody("Hello"); + }); + + StartSpeechSynthesisTaskRequest request = clientMock.lastStartSpeechSynthesisTaskRequest; + assertEquals(TextType.SSML, request.textType()); + assertEquals(OutputFormat.OGG_VORBIS, request.outputFormat()); + assertEquals(List.of("a", "b"), request.lexiconNames()); + } + + @Test + public void startSpeechSynthesisTaskTextTypeAndOutputFormatDefaults() { + template.send("direct:startSpeechSynthesisTask", exchange -> { + exchange.getIn().setHeader(Polly2Constants.VOICE_ID, VoiceId.JOANNA); + exchange.getIn().setHeader(Polly2Constants.S3_BUCKET, "my-bucket"); + exchange.getIn().setBody("Hello"); + }); + + assertEquals(TextType.TEXT, clientMock.lastStartSpeechSynthesisTaskRequest.textType()); + assertEquals(OutputFormat.MP3, clientMock.lastStartSpeechSynthesisTaskRequest.outputFormat()); + } + + @ParameterizedTest + @ValueSource(strings = { "direct:describeVoices", "direct:listLexicons", "direct:listSpeechSynthesisTasks" }) + void listOperationNextToken(String route) { + // a page that has a next page: the token in the header is sent, and the next one is returned + clientMock.nextToken = "token-2"; + Exchange exchange = template.send(route, e -> e.getIn().setHeader(Polly2Constants.NEXT_TOKEN, "token-1")); + + assertEquals("token-1", requestNextToken(route)); + assertEquals("token-2", exchange.getMessage().getHeader(Polly2Constants.NEXT_TOKEN)); + + // the last page: the header is removed so a loop ends + clientMock.nextToken = null; + exchange = template.send(route, e -> e.getIn().setHeader(Polly2Constants.NEXT_TOKEN, "token-2")); + + assertEquals("token-2", requestNextToken(route)); + assertNull(exchange.getMessage().getHeader(Polly2Constants.NEXT_TOKEN)); + } + + @Test + void listLexiconsPojoNextToken() { + clientMock.nextToken = "token-2"; + Exchange exchange = template.send("direct:listLexiconsPojo", + e -> e.getIn().setBody(ListLexiconsRequest.builder().nextToken("token-1").build())); + + assertEquals("token-1", clientMock.lastListLexiconsRequest.nextToken()); + assertEquals("token-2", exchange.getMessage().getHeader(Polly2Constants.NEXT_TOKEN)); + } + + private String requestNextToken(String route) { + return switch (route) { + case "direct:describeVoices" -> clientMock.lastDescribeVoicesRequest.nextToken(); + case "direct:listLexicons" -> clientMock.lastListLexiconsRequest.nextToken(); + default -> clientMock.lastListSpeechSynthesisTasksRequest.nextToken(); + }; + } + @ParameterizedTest @CsvSource({ "direct:synthesizeSpeechPojo,synthesizeSpeech operation requires SynthesizeSpeechRequest in POJO mode", @@ -259,7 +376,7 @@ public void configure() { .to("mock:result"); from("direct:synthesizeSpeechWithOptions") - .to("aws2-polly://test?pollyClient=#amazonPollyClient&operation=synthesizeSpeech&voiceId=JOANNA&engine=NEURAL&outputFormat=MP3") + .to("aws2-polly://test?pollyClient=#amazonPollyClient&operation=synthesizeSpeech&voiceId=JOANNA&engine=NEURAL&outputFormat=MP3&textType=SSML") .to("mock:result"); from("direct:synthesizeSpeechPojo") diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3vectors/aws2-s3-vectors.json b/components/camel-aws/camel-aws2-s3-vectors/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3vectors/aws2-s3-vectors.json index 58da8dc70451f..8e356e2d531ec 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3vectors/aws2-s3-vectors.json +++ b/components/camel-aws/camel-aws2-s3-vectors/src/generated/resources/META-INF/org/apache/camel/component/aws2/s3vectors/aws2-s3-vectors.json @@ -25,17 +25,17 @@ }, "componentProperties": { "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "common", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, - "dataType": { "index": 1, "kind": "property", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32", "float16" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vector. Options: float32, float16" }, - "distanceMetric": { "index": 2, "kind": "property", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean", "dot-product" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric to use for similarity search. Options: cosine, euclidean, dot-product" }, + "dataType": { "index": 1, "kind": "property", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vectors of the index to create. S3 Vectors supports float32 only." }, + "distanceMetric": { "index": 2, "kind": "property", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric of the index to create, which the similarity search uses. Options: cosine, euclidean" }, "overrideEndpoint": { "index": 3, "kind": "property", "displayName": "Override Endpoint", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with uriEndpointOverride option" }, "region": { "index": 4, "kind": "property", "displayName": "Region", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The region in which S3 Vectors client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example ap-east-1)" }, - "similarityThreshold": { "index": 5, "kind": "property", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The minimum similarity threshold for results" }, + "similarityThreshold": { "index": 5, "kind": "property", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. The consumer sets the CamelAwsS3VectorsDistance header to the distance of each vector from the query vector. Deprecation note: S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead." }, "topK": { "index": 6, "kind": "property", "displayName": "Top K", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The number of top similar vectors to return in a query" }, "uriEndpointOverride": { "index": 7, "kind": "property", "displayName": "Uri Endpoint Override", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "vectorDimensions": { "index": 8, "kind": "property", "displayName": "Vector Dimensions", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1536, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The dimensions of the vector embeddings (default: 1536, which is the dimension for OpenAI text-embedding-3-small)" }, "vectorIndexName": { "index": 9, "kind": "property", "displayName": "Vector Index Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The name of the vector index" }, "bridgeErrorHandler": { "index": 10, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, - "consumerMetadataFilter": { "index": 11, "kind": "property", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Optional metadata filter for the consumer to filter vectors during polling" }, + "consumerMetadataFilter": { "index": 11, "kind": "property", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The metadata filter of the similarity search of the consumer, as a JSON object with the filter operators of S3 Vectors, such as {genre: {$eq: drama}}. Only the vectors whose metadata match the filter are consumed." }, "consumerQueryVector": { "index": 12, "kind": "property", "displayName": "Consumer Query Vector", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The query vector to use for the consumer to poll for similar vectors. Specified as comma-separated float values (e.g., 0.1,0.2,0.3). If not specified, the consumer will not poll." }, "delay": { "index": 13, "kind": "property", "displayName": "Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Milliseconds before the next poll for the consumer" }, "deleteAfterRead": { "index": 14, "kind": "property", "displayName": "Delete After Read", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Delete vectors after they have been consumed" }, @@ -60,35 +60,36 @@ }, "headers": { "CamelAwsS3VectorsOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#OPERATION" }, - "CamelAwsS3VectorsVectorBucketName": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector bucket which will be used for the current operation", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_NAME" }, - "CamelAwsS3VectorsVectorIndexName": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector index which will be used for the current operation", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_INDEX_NAME" }, - "CamelAwsS3VectorsVectorId": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The unique identifier for a vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_ID" }, + "CamelAwsS3VectorsVectorBucketName": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector bucket to use for the current operation instead of the vector bucket of the endpoint. An input only: neither the producer nor the consumer sets it.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_NAME" }, + "CamelAwsS3VectorsVectorIndexName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the vector index to use for the current operation instead of the vectorIndexName option of the endpoint. An input only: neither the producer nor the consumer sets it.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_INDEX_NAME" }, + "CamelAwsS3VectorsVectorId": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String or List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The key of a vector. putVectors puts the vector under this key (the exchange id when it is not set) and sets it to the key it used. getVectors and deleteVectors take their key, or a list of keys, from this header only when the message body holds no keys. The consumer sets it to the key of the consumed vector.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_ID" }, "CamelAwsS3VectorsVectorData": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List or float[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The vector embedding data as a list of floats or float array", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_DATA" }, "CamelAwsS3VectorsVectorDimensions": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The dimensions of the vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_DIMENSIONS" }, - "CamelAwsS3VectorsDataType": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The data type of the vector (float32 or float16)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DATA_TYPE" }, - "CamelAwsS3VectorsVectorMetadata": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Additional metadata for the vector as a map", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_METADATA" }, + "CamelAwsS3VectorsDataType": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The data type of the vectors of the index to create (float32)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DATA_TYPE" }, + "CamelAwsS3VectorsVectorMetadata": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Additional metadata for the vector as a map. The values may be Strings, numbers, booleans or lists of these.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_METADATA" }, "CamelAwsS3VectorsQueryVector": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List or float[]", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The query vector for similarity search as a list of floats or float array", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#QUERY_VECTOR" }, "CamelAwsS3VectorsTopK": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of top similar vectors to return", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#TOP_K" }, - "CamelAwsS3VectorsDistanceMetric": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance metric to use for similarity search (cosine, euclidean, dot-product)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE_METRIC" }, - "CamelAwsS3VectorsSimilarityThreshold": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The minimum similarity threshold for results", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_THRESHOLD" }, - "CamelAwsS3VectorsMetadataFilter": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional filter expression for metadata filtering during vector search", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#METADATA_FILTER" }, - "CamelAwsS3VectorsSimilarityScore": { "index": 13, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The similarity score of the returned vector", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_SCORE" }, - "CamelAwsS3VectorsResultCount": { "index": 14, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of vectors returned in the result", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#RESULT_COUNT" }, - "CamelAwsS3VectorsIndexStatus": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The status of the vector index", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#INDEX_STATUS" }, - "CamelAwsS3VectorsVectorBucketArn": { "index": 16, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the vector bucket", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_ARN" } + "CamelAwsS3VectorsDistanceMetric": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance metric of the index to create (cosine or euclidean)", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE_METRIC" }, + "CamelAwsS3VectorsSimilarityThreshold": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this header. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. Deprecation note: S3 Vectors has no similarity threshold, the component never used this header. Filter the results on their CamelAwsS3VectorsDistance header instead.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_THRESHOLD" }, + "CamelAwsS3VectorsMetadataFilter": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The metadata filter of queryVectors, as a JSON object with the filter operators of S3 Vectors, such as $eq, $in or $and. Only the vectors whose metadata match the filter are returned.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#METADATA_FILTER" }, + "CamelAwsS3VectorsSimilarityScore": { "index": 13, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": true, "deprecationNote": "S3 Vectors returns a distance, not a similarity score, and the component never set this header. Use the CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "description": "(deprecated) Never set: S3 Vectors returns a distance, see CamelAwsS3VectorsDistance. Deprecation note: S3 Vectors returns a distance, not a similarity score, and the component never set this header. Use the CamelAwsS3VectorsDistance header instead.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#SIMILARITY_SCORE" }, + "CamelAwsS3VectorsDistance": { "index": 14, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Float", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The distance of the consumed vector from the query vector, computed with the distance metric of the index: the lower, the closer.", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#DISTANCE" }, + "CamelAwsS3VectorsResultCount": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of vectors returned in the result", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#RESULT_COUNT" }, + "CamelAwsS3VectorsIndexStatus": { "index": 16, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The status of the vector index", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#INDEX_STATUS" }, + "CamelAwsS3VectorsVectorBucketArn": { "index": 17, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN of the vector bucket", "constantName": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants#VECTOR_BUCKET_ARN" } }, "properties": { "vectorBucketName": { "index": 0, "kind": "path", "displayName": "Vector Bucket Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Vector bucket name or ARN" }, - "dataType": { "index": 1, "kind": "parameter", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32", "float16" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vector. Options: float32, float16" }, - "distanceMetric": { "index": 2, "kind": "parameter", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean", "dot-product" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric to use for similarity search. Options: cosine, euclidean, dot-product" }, + "dataType": { "index": 1, "kind": "parameter", "displayName": "Data Type", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "float32" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "float32", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The data type of the vectors of the index to create. S3 Vectors supports float32 only." }, + "distanceMetric": { "index": 2, "kind": "parameter", "displayName": "Distance Metric", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "cosine", "euclidean" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "cosine", "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The distance metric of the index to create, which the similarity search uses. Options: cosine, euclidean" }, "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with uriEndpointOverride option" }, "region": { "index": 4, "kind": "parameter", "displayName": "Region", "group": "common", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The region in which S3 Vectors client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example ap-east-1)" }, - "similarityThreshold": { "index": 5, "kind": "parameter", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The minimum similarity threshold for results" }, + "similarityThreshold": { "index": 5, "kind": "parameter", "displayName": "Similarity Threshold", "group": "common", "label": "", "required": false, "type": "number", "javaType": "java.lang.Float", "deprecated": true, "deprecationNote": "S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead.", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "(deprecated) Never used: S3 Vectors has no similarity threshold. The consumer sets the CamelAwsS3VectorsDistance header to the distance of each vector from the query vector. Deprecation note: S3 Vectors has no similarity threshold, the component never used this option. Filter the results on their CamelAwsS3VectorsDistance header instead." }, "topK": { "index": 6, "kind": "parameter", "displayName": "Top K", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The number of top similar vectors to return in a query" }, "uriEndpointOverride": { "index": 7, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "vectorDimensions": { "index": 8, "kind": "parameter", "displayName": "Vector Dimensions", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1536, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The dimensions of the vector embeddings (default: 1536, which is the dimension for OpenAI text-embedding-3-small)" }, "vectorIndexName": { "index": 9, "kind": "parameter", "displayName": "Vector Index Name", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The name of the vector index" }, - "consumerMetadataFilter": { "index": 10, "kind": "parameter", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Optional metadata filter for the consumer to filter vectors during polling" }, + "consumerMetadataFilter": { "index": 10, "kind": "parameter", "displayName": "Consumer Metadata Filter", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The metadata filter of the similarity search of the consumer, as a JSON object with the filter operators of S3 Vectors, such as {genre: {$eq: drama}}. Only the vectors whose metadata match the filter are consumed." }, "consumerQueryVector": { "index": 11, "kind": "parameter", "displayName": "Consumer Query Vector", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "The query vector to use for the consumer to poll for similar vectors. Specified as comma-separated float values (e.g., 0.1,0.2,0.3). If not specified, the consumer will not poll." }, "delay": { "index": 12, "kind": "parameter", "displayName": "Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Milliseconds before the next poll for the consumer" }, "deleteAfterRead": { "index": 13, "kind": "parameter", "displayName": "Delete After Read", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConfiguration", "configurationField": "configuration", "description": "Delete vectors after they have been consumed" }, diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/docs/aws2-s3-vectors-component.adoc b/components/camel-aws/camel-aws2-s3-vectors/src/main/docs/aws2-s3-vectors-component.adoc index c083b360a7727..516ba4cd0ffe1 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/main/docs/aws2-s3-vectors-component.adoc +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/docs/aws2-s3-vectors-component.adoc @@ -48,17 +48,72 @@ include::partial$component-endpoint-headers.adoc[] * `getVectors` - Retrieve vectors by ID * `createVectorBucket` - Create bucket * `deleteVectorBucket` - Delete bucket -* `listVectorBuckets` - List buckets +* `listVectorBuckets` - List buckets (the first page of the result only) * `describeVectorBucket` - Get bucket info * `createVectorIndex` - Create index * `deleteVectorIndex` - Delete index -* `listVectorIndexes` - List indexes +* `listVectorIndexes` - List indexes (the first page of the result only) * `describeVectorIndex` - Get index info +`listVectorBuckets` and `listVectorIndexes` send a single request, so they return the first page of the buckets or +indexes that S3 Vectors returns, not all of them. + +=== Bucket and index of an operation + +Each operation uses the vector bucket of the endpoint URI and the `vectorIndexName` option. The +`CamelAwsS3VectorsVectorBucketName` and `CamelAwsS3VectorsVectorIndexName` headers override them for a single +message. These headers are inputs only: neither the producer nor the consumer sets them, so a route that reads from one +bucket and writes to another one does not write back into the bucket it reads from. + +=== Vector keys + +`putVectors` puts the vector under the key of the `CamelAwsS3VectorsVectorId` header, or under the exchange id when the +header is not set, and sets the header to the key it used. + +`getVectors` and `deleteVectors` take their keys from the message body when the body holds keys: a `String`, or a +`Collection` or an array whose elements are all Strings. Otherwise they take the keys from the +`CamelAwsS3VectorsVectorId` header (a `String` or a list of Strings). Keys that are not Strings, such as a list of +numbers, fail with an `IllegalArgumentException`. As the body comes first, a `getVectors` after a `putVectors` in the +same route gets the keys of its body, not the key that the `putVectors` left in the header. + +`getVectors` returns the vectors with their data and their metadata. + +=== Vector data and metadata + +The vector of `putVectors` and `queryVectors` is a `List` of numbers, a `float[]` or a `double[]`. A `null` element +fails with an `IllegalArgumentException` that names its position. + +The `CamelAwsS3VectorsVectorMetadata` header of `putVectors` is a `Map` whose values may be Strings, numbers, booleans, +or lists of these. Any other value fails with an `IllegalArgumentException` that names its key. + +=== Metadata filter and distance + +The `CamelAwsS3VectorsMetadataFilter` header of `queryVectors` and the `consumerMetadataFilter` option of the consumer +are JSON objects with the +https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-metadata-filtering.html[filter operators of S3 Vectors], +such as `{"genre": {"$eq": "drama"}}` or `{"$and": [{"genre": "drama"}, {"year": {"$gte": 2020}}]}`. Only the vectors +whose metadata match the filter are returned. A filter that is not a valid JSON object fails with an +`IllegalArgumentException`: the producer fails the exchange, and the consumer does not start. + +`queryVectors` and the consumer return the vectors with their metadata and their distance from the query vector. The +distance is computed with the distance metric of the index (`cosine` or `euclidean`): the lower, the closer. S3 Vectors +has no similarity threshold, so the `similarityThreshold` option and the `CamelAwsS3VectorsSimilarityThreshold` and +`CamelAwsS3VectorsSimilarityScore` headers are deprecated and do nothing. To keep only the close vectors, filter on the +distance, such as `.filter(simple("${header.CamelAwsS3VectorsDistance} < 0.3"))` after the consumer. + +=== Index data type and distance metric + +The `dataType` option of `createVectorIndex` is `float32`, the only data type of S3 Vectors. The `distanceMetric` +option is `cosine` (the default) or `euclidean`. + === Consumer The consumer polls for vectors using similarity search. It tracks processed vector IDs to avoid duplicates. +Each consumed vector is a `QueryOutputVector` in the message body, with its metadata. The consumer sets the +`CamelAwsS3VectorsVectorId` header to the key of the vector and the `CamelAwsS3VectorsDistance` header to its distance +from the `consumerQueryVector`. + == Examples The vector in the message body is a `List` of numbers, a `float[]` or a `double[]`. @@ -175,6 +230,44 @@ YAML:: ---- ==== +=== Query with a Metadata Filter + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:search") + .setBody(constant(Arrays.asList(0.15f, 0.25f, 0.35f))) + .setHeader(AWS2S3VectorsConstants.METADATA_FILTER, constant("{\"genre\": {\"$eq\": \"drama\"}}")) + .to("aws2-s3-vectors://my-bucket?operation=queryVectors&vectorIndexName=my-index") + .log("Found ${body.size} similar drama vectors"); +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:search + steps: + - setBody: + groovy: "[0.15, 0.25, 0.35]" + - setHeader: + name: CamelAwsS3VectorsMetadataFilter + constant: '{"genre": {"$eq": "drama"}}' + - to: + uri: aws2-s3-vectors://my-bucket + parameters: + operation: queryVectors + vectorIndexName: my-index + - log: + message: "Found ${body.size} similar drama vectors" +---- +==== + === Create Index [tabs] diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConfiguration.java b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConfiguration.java index 32074a12a380d..2ffe39535b688 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConfiguration.java +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConfiguration.java @@ -67,13 +67,16 @@ public class AWS2S3VectorsConfiguration implements Cloneable, AwsCommonConfigura private String vectorIndexName; @UriParam(defaultValue = "1536") private Integer vectorDimensions = 1536; - @UriParam(enums = "float32,float16", defaultValue = "float32") + @UriParam(enums = "float32", defaultValue = "float32") private String dataType = "float32"; - @UriParam(enums = "cosine,euclidean,dot-product", defaultValue = "cosine") + @UriParam(enums = "cosine,euclidean", defaultValue = "cosine") private String distanceMetric = "cosine"; @UriParam(defaultValue = "10") private Integer topK = 10; @UriParam + @Metadata(deprecationNote = "S3 Vectors has no similarity threshold, the component never used this option." + + " Filter the results on their CamelAwsS3VectorsDistance header instead.") + @Deprecated private Float similarityThreshold; // Consumer configuration @@ -305,7 +308,7 @@ public String getDataType() { } /** - * The data type of the vector. Options: float32, float16 + * The data type of the vectors of the index to create. S3 Vectors supports float32 only. */ public void setDataType(String dataType) { this.dataType = dataType; @@ -316,7 +319,7 @@ public String getDistanceMetric() { } /** - * The distance metric to use for similarity search. Options: cosine, euclidean, dot-product + * The distance metric of the index to create, which the similarity search uses. Options: cosine, euclidean */ public void setDistanceMetric(String distanceMetric) { this.distanceMetric = distanceMetric; @@ -333,13 +336,21 @@ public void setTopK(Integer topK) { this.topK = topK; } + /** + * @deprecated S3 Vectors has no similarity threshold, the component never used this option. + */ + @Deprecated public Float getSimilarityThreshold() { return similarityThreshold; } /** - * The minimum similarity threshold for results + * (deprecated) Never used: S3 Vectors has no similarity threshold. The consumer sets the CamelAwsS3VectorsDistance + * header to the distance of each vector from the query vector. + * + * @deprecated S3 Vectors has no similarity threshold, the component never used this option. */ + @Deprecated public void setSimilarityThreshold(Float similarityThreshold) { this.similarityThreshold = similarityThreshold; } @@ -394,7 +405,8 @@ public String getConsumerMetadataFilter() { } /** - * Optional metadata filter for the consumer to filter vectors during polling + * The metadata filter of the similarity search of the consumer, as a JSON object with the filter operators of S3 + * Vectors, such as {"genre": {"$eq": "drama"}}. Only the vectors whose metadata match the filter are consumed. */ public void setConsumerMetadataFilter(String consumerMetadataFilter) { this.consumerMetadataFilter = consumerMetadataFilter; diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConstants.java b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConstants.java index 8cbda99857a4c..fa616a42786ee 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConstants.java +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConstants.java @@ -27,15 +27,24 @@ public interface AWS2S3VectorsConstants { String OPERATION = "CamelAwsS3VectorsOperation"; // Vector Bucket and Index - @Metadata(description = "The name of the vector bucket which will be used for the current operation", + @Metadata(label = "producer", + description = "The name of the vector bucket to use for the current operation instead of the vector bucket" + + " of the endpoint. An input only: neither the producer nor the consumer sets it.", javaType = "String") String VECTOR_BUCKET_NAME = "CamelAwsS3VectorsVectorBucketName"; - @Metadata(description = "The name of the vector index which will be used for the current operation", + @Metadata(label = "producer", + description = "The name of the vector index to use for the current operation instead of the" + + " vectorIndexName option of the endpoint. An input only: neither the producer nor the" + + " consumer sets it.", javaType = "String") String VECTOR_INDEX_NAME = "CamelAwsS3VectorsVectorIndexName"; // Vector Data - @Metadata(label = "producer", description = "The unique identifier for a vector", javaType = "String") + @Metadata(description = "The key of a vector. putVectors puts the vector under this key (the exchange id when it is" + + " not set) and sets it to the key it used. getVectors and deleteVectors take their key, or" + + " a list of keys, from this header only when the message body holds no keys. The" + + " consumer sets it to the key of the consumed vector.", + javaType = "String or List") String VECTOR_ID = "CamelAwsS3VectorsVectorId"; @Metadata(label = "producer", description = "The vector embedding data as a list of floats or float array", @@ -44,12 +53,13 @@ public interface AWS2S3VectorsConstants { @Metadata(label = "producer", description = "The dimensions of the vector", javaType = "Integer") String VECTOR_DIMENSIONS = "CamelAwsS3VectorsVectorDimensions"; @Metadata(label = "producer", - description = "The data type of the vector (float32 or float16)", + description = "The data type of the vectors of the index to create (float32)", javaType = "String") String DATA_TYPE = "CamelAwsS3VectorsDataType"; @Metadata(label = "producer", - description = "Additional metadata for the vector as a map", - javaType = "Map") + description = "Additional metadata for the vector as a map. The values may be Strings, numbers, booleans" + + " or lists of these.", + javaType = "Map") String VECTOR_METADATA = "CamelAwsS3VectorsVectorMetadata"; // Query Parameters @@ -62,23 +72,44 @@ public interface AWS2S3VectorsConstants { javaType = "Integer") String TOP_K = "CamelAwsS3VectorsTopK"; @Metadata(label = "producer", - description = "The distance metric to use for similarity search (cosine, euclidean, dot-product)", + description = "The distance metric of the index to create (cosine or euclidean)", javaType = "String") String DISTANCE_METRIC = "CamelAwsS3VectorsDistanceMetric"; + /** + * @deprecated S3 Vectors has no similarity threshold, the component never used this header. Use the + * {@link #DISTANCE} header of the results instead. + */ @Metadata(label = "producer", - description = "The minimum similarity threshold for results", - javaType = "Float") + description = "(deprecated) Never used: S3 Vectors has no similarity threshold.", + javaType = "Float", + deprecationNote = "S3 Vectors has no similarity threshold, the component never used this header. Filter" + + " the results on their CamelAwsS3VectorsDistance header instead.") + @Deprecated String SIMILARITY_THRESHOLD = "CamelAwsS3VectorsSimilarityThreshold"; @Metadata(label = "producer", - description = "Optional filter expression for metadata filtering during vector search", + description = "The metadata filter of queryVectors, as a JSON object with the filter operators of S3" + + " Vectors, such as $eq, $in or $and. Only the vectors whose metadata match the filter are" + + " returned.", javaType = "String") String METADATA_FILTER = "CamelAwsS3VectorsMetadataFilter"; // Query Results + /** + * @deprecated S3 Vectors returns a distance, not a similarity score, and the component never set this header. Use + * {@link #DISTANCE} instead. + */ @Metadata(label = "consumer", - description = "The similarity score of the returned vector", - javaType = "Float") + description = "(deprecated) Never set: S3 Vectors returns a distance, see CamelAwsS3VectorsDistance.", + javaType = "Float", + deprecationNote = "S3 Vectors returns a distance, not a similarity score, and the component never set this" + + " header. Use the CamelAwsS3VectorsDistance header instead.") + @Deprecated String SIMILARITY_SCORE = "CamelAwsS3VectorsSimilarityScore"; + @Metadata(label = "consumer", + description = "The distance of the consumed vector from the query vector, computed with the distance" + + " metric of the index: the lower, the closer.", + javaType = "Float") + String DISTANCE = "CamelAwsS3VectorsDistance"; @Metadata(label = "consumer", description = "The number of vectors returned in the result", javaType = "Integer") diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumer.java b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumer.java index 134aa102e3c30..98d694099e1c6 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumer.java +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumer.java @@ -33,6 +33,7 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.document.Document; import software.amazon.awssdk.services.s3vectors.S3VectorsClient; import software.amazon.awssdk.services.s3vectors.model.DeleteVectorsRequest; import software.amazon.awssdk.services.s3vectors.model.QueryOutputVector; @@ -51,6 +52,8 @@ public class AWS2S3VectorsConsumer extends ScheduledBatchPollingConsumer { // deleteAfterRead), which a later poll must not consume again. It is updated from the threads that complete the // exchanges, so it must be thread-safe. private final Set processedVectorIds = ConcurrentHashMap.newKeySet(); + // the consumerMetadataFilter option parsed when the consumer starts + private Document metadataFilter; public AWS2S3VectorsConsumer(AWS2S3VectorsEndpoint endpoint, Processor processor) { super(endpoint, processor); @@ -86,13 +89,11 @@ protected int poll() throws Exception { .vectorBucketName(vectorBucketName) .indexName(vectorIndexName) .queryVector(VectorData.builder().float32(queryVector).build()) - .topK(resolveTopK()); - - // Add metadata filter if configured - String metadataFilter = getConfiguration().getConsumerMetadataFilter(); - if (ObjectHelper.isNotEmpty(metadataFilter)) { - // Note: Metadata filter implementation depends on AWS SDK support - LOG.trace("Using metadata filter: {}", metadataFilter); + .topK(resolveTopK()) + .returnMetadata(true) + .returnDistance(true); + if (metadataFilter != null) { + requestBuilder.filter(metadataFilter); } // Execute query @@ -119,10 +120,10 @@ protected int poll() throws Exception { // Set vector data as body message.setBody(vector); - // Set headers + // the bucket and the index are not set as headers: they are inputs of the producer, and would + // redirect a producer later in the route to the bucket and index of this consumer message.setHeader(AWS2S3VectorsConstants.VECTOR_ID, vectorId); - message.setHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME, vectorBucketName); - message.setHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME, vectorIndexName); + message.setHeader(AWS2S3VectorsConstants.DISTANCE, vector.distance()); // Add delete callback if deleteAfterRead is enabled if (getConfiguration().isDeleteAfterRead()) { @@ -185,6 +186,11 @@ public int processBatch(Queue exchanges) throws Exception { @Override protected void doStart() throws Exception { + // parsed before the scheduler starts polling + String filter = getConfiguration().getConsumerMetadataFilter(); + metadataFilter = ObjectHelper.isNotEmpty(filter) + ? AWS2S3VectorsHelper.parseMetadataFilter(filter, "consumerMetadataFilter option") : null; + super.doStart(); LOG.debug("Starting S3 Vectors consumer for bucket [{}]", getConfiguration().getVectorBucketName()); diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsHelper.java b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsHelper.java new file mode 100644 index 0000000000000..11a249d93ed19 --- /dev/null +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsHelper.java @@ -0,0 +1,205 @@ +/* + * 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.aws2.s3vectors; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.camel.util.json.DeserializationException; +import org.apache.camel.util.json.Jsoner; +import software.amazon.awssdk.core.document.Document; + +/** + * Converts the values that the routes give to the component (keys, metadata, metadata filters) into the types of the S3 + * Vectors requests. + */ +final class AWS2S3VectorsHelper { + + private AWS2S3VectorsHelper() { + } + + /** + * Parses a metadata filter given as JSON text into the filter document of a query. + * + * @param json the JSON object of the filter + * @param source the header or the option that holds the filter, for the error message + * @return the filter document + */ + static Document parseMetadataFilter(String json, String source) { + Object parsed; + try { + parsed = Jsoner.deserialize(json); + } catch (DeserializationException e) { + throw new IllegalArgumentException( + "The " + source + " must be a JSON object such as {\"genre\": {\"$eq\": \"drama\"}}, but it is not" + + " valid JSON: " + e.getMessage(), + e); + } + if (!(parsed instanceof Map)) { + throw new IllegalArgumentException( + "The " + source + " must be a JSON object such as {\"genre\": {\"$eq\": \"drama\"}}, but it is a " + + (parsed == null ? "null" : "JSON " + parsed.getClass().getSimpleName())); + } + return jsonToDocument(parsed); + } + + private static Document jsonToDocument(Object value) { + if (value == null) { + return Document.fromNull(); + } else if (value instanceof String str) { + return Document.fromString(str); + } else if (value instanceof Boolean bool) { + return Document.fromBoolean(bool); + } else if (value instanceof Number number) { + return numberToDocument(number); + } else if (value instanceof Map map) { + Map result = new LinkedHashMap<>(); + map.forEach((k, v) -> result.put(String.valueOf(k), jsonToDocument(v))); + return Document.fromMap(result); + } else if (value instanceof Collection list) { + return Document.fromList(list.stream().map(AWS2S3VectorsHelper::jsonToDocument).toList()); + } + // the JSON parser gives only the types above + throw new IllegalArgumentException("Unexpected JSON value of type " + value.getClass().getName()); + } + + /** + * Converts the metadata of a vector into a document. The values may be Strings, numbers, booleans and lists of + * these. + */ + static Document metadataToDocument(Map metadata) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : metadata.entrySet()) { + String key = String.valueOf(entry.getKey()); + Object value = entry.getValue(); + Document document = scalarToDocument(value); + if (document == null) { + List list = asList(value); + if (list == null) { + throw new IllegalArgumentException( + "The metadata value of key " + key + " is " + describe(value) + + ": use a String, a number, a boolean or a list of these"); + } + List elements = new ArrayList<>(list.size()); + for (Object element : list) { + Document elementDocument = scalarToDocument(element); + if (elementDocument == null) { + throw new IllegalArgumentException( + "The metadata value of key " + key + " is a list with an element that is " + + describe(element) + + ": the elements must be Strings, numbers or booleans"); + } + elements.add(elementDocument); + } + document = Document.fromList(elements); + } + result.put(key, document); + } + return Document.fromMap(result); + } + + private static Document scalarToDocument(Object value) { + if (value instanceof CharSequence str) { + return Document.fromString(str.toString()); + } else if (value instanceof Boolean bool) { + return Document.fromBoolean(bool); + } else if (value instanceof Number number) { + return numberToDocument(number); + } + return null; + } + + private static Document numberToDocument(Number number) { + if (number instanceof Integer || number instanceof Short || number instanceof Byte) { + return Document.fromNumber(number.intValue()); + } else if (number instanceof Long l) { + return Document.fromNumber(l); + } else if (number instanceof Float f) { + return Document.fromNumber(f); + } else if (number instanceof Double d) { + return Document.fromNumber(d); + } else if (number instanceof BigDecimal bd) { + return Document.fromNumber(bd); + } else if (number instanceof BigInteger bi) { + return Document.fromNumber(bi); + } + return Document.fromNumber(number.toString()); + } + + /** + * The vector keys that the value holds: a String, or a non-empty Collection or array whose elements are all + * Strings. + * + * @return the keys, or null when the value holds no keys + */ + static List asKeys(Object value) { + if (value instanceof CharSequence str) { + return str.isEmpty() ? null : List.of(str.toString()); + } + List list = asList(value); + if (list == null || list.isEmpty()) { + return null; + } + List keys = new ArrayList<>(list.size()); + for (Object element : list) { + if (!(element instanceof CharSequence str)) { + return null; + } + keys.add(str.toString()); + } + return keys; + } + + /** + * Describes a value that holds no keys or no valid metadata, for an error message. + */ + static String describe(Object value) { + if (value == null) { + return "null"; + } + List list = asList(value); + if (list != null) { + if (list.isEmpty()) { + return "an empty " + value.getClass().getName(); + } + for (Object element : list) { + if (!(element instanceof CharSequence)) { + return "a " + value.getClass().getName() + " with an element of type " + + (element == null ? "null" : element.getClass().getName()); + } + } + } + return "of type " + value.getClass().getName(); + } + + private static List asList(Object value) { + if (value instanceof List list) { + return list; + } else if (value instanceof Collection collection) { + return new ArrayList<>(collection); + } else if (value instanceof Object[] array) { + return Arrays.asList(array); + } + return null; + } +} diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducer.java b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducer.java index cb8c7ea98f862..66fef5fb5fbea 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducer.java +++ b/components/camel-aws/camel-aws2-s3-vectors/src/main/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducer.java @@ -27,7 +27,6 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import software.amazon.awssdk.core.document.Document; import software.amazon.awssdk.services.s3vectors.S3VectorsClient; import software.amazon.awssdk.services.s3vectors.model.CreateIndexRequest; import software.amazon.awssdk.services.s3vectors.model.CreateIndexResponse; @@ -143,8 +142,7 @@ private void putVectors(S3VectorsClient client, Exchange exchange) { } // Get metadata if provided - @SuppressWarnings("unchecked") - Map metadata = message.getHeader(AWS2S3VectorsConstants.VECTOR_METADATA, Map.class); + Map metadata = message.getHeader(AWS2S3VectorsConstants.VECTOR_METADATA, Map.class); // Convert vector data to List List vectorData = convertToFloatList(vectorDataObj); @@ -155,9 +153,7 @@ private void putVectors(S3VectorsClient client, Exchange exchange) { .data(VectorData.builder().float32(vectorData).build()); if (metadata != null && !metadata.isEmpty()) { - // Convert Map to Document - Document metadataDoc = convertMapToDocument(metadata); - vectorBuilder.metadata(metadataDoc); + vectorBuilder.metadata(AWS2S3VectorsHelper.metadataToDocument(metadata)); } // Build and execute request @@ -171,9 +167,8 @@ private void putVectors(S3VectorsClient client, Exchange exchange) { PutVectorsResponse response = client.putVectors(request); LOG.trace("Put vector response: {}", response); - // Set response headers - message.setHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME, vectorBucketName); - message.setHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME, vectorIndexName); + // the key the vector was put under (the exchange id when no key was given); the bucket and the index are not + // set as headers, as they are inputs that would redirect a later step of the route message.setHeader(AWS2S3VectorsConstants.VECTOR_ID, vectorId); message.setBody(response); } @@ -197,12 +192,19 @@ private void queryVectors(S3VectorsClient client, Exchange exchange) { topK = getConfiguration().getTopK(); } - // Build and execute request (metadata filter can be complex, skip for now) QueryVectorsRequest.Builder requestBuilder = QueryVectorsRequest.builder() .vectorBucketName(vectorBucketName) .indexName(vectorIndexName) .queryVector(VectorData.builder().float32(queryVector).build()) - .topK(topK); + .topK(topK) + .returnMetadata(true) + .returnDistance(true); + + String metadataFilter = message.getHeader(AWS2S3VectorsConstants.METADATA_FILTER, String.class); + if (ObjectHelper.isNotEmpty(metadataFilter)) { + requestBuilder.filter(AWS2S3VectorsHelper.parseMetadataFilter(metadataFilter, + AWS2S3VectorsConstants.METADATA_FILTER + " header")); + } LOG.trace("Querying vectors in bucket [{}] index [{}] with topK [{}]", vectorBucketName, vectorIndexName, topK); QueryVectorsResponse response = client.queryVectors(requestBuilder.build()); @@ -218,15 +220,8 @@ private void deleteVectors(S3VectorsClient client, Exchange exchange) { String vectorBucketName = determineVectorBucketName(exchange); String vectorIndexName = determineVectorIndexName(exchange); - // Get vector IDs from header or body - Object vectorIdsObj = message.getHeader(AWS2S3VectorsConstants.VECTOR_ID); - if (ObjectHelper.isEmpty(vectorIdsObj)) { - vectorIdsObj = message.getBody(); - } + List vectorIds = determineVectorKeys(message, "deleteVectors"); - List vectorIds = convertToStringList(vectorIdsObj); - - // Build and execute request DeleteVectorsRequest request = DeleteVectorsRequest.builder() .vectorBucketName(vectorBucketName) .indexName(vectorIndexName) @@ -245,19 +240,14 @@ private void getVectors(S3VectorsClient client, Exchange exchange) { String vectorBucketName = determineVectorBucketName(exchange); String vectorIndexName = determineVectorIndexName(exchange); - // Get vector IDs from header or body - Object vectorIdsObj = message.getHeader(AWS2S3VectorsConstants.VECTOR_ID); - if (ObjectHelper.isEmpty(vectorIdsObj)) { - vectorIdsObj = message.getBody(); - } - - List vectorIds = convertToStringList(vectorIdsObj); + List vectorIds = determineVectorKeys(message, "getVectors"); - // Build and execute request GetVectorsRequest request = GetVectorsRequest.builder() .vectorBucketName(vectorBucketName) .indexName(vectorIndexName) .keys(vectorIds) + .returnData(true) + .returnMetadata(true) .build(); LOG.trace("Getting vectors [{}] from bucket [{}] index [{}]", vectorIds, vectorBucketName, vectorIndexName); @@ -279,7 +269,6 @@ private void createVectorBucket(S3VectorsClient client, Exchange exchange) { CreateVectorBucketResponse response = client.createVectorBucket(request); LOG.trace("Create vector bucket response: {}", response); - message.setHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME, vectorBucketName); message.setHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_ARN, response.vectorBucketArn()); message.setBody(response); } @@ -360,7 +349,6 @@ private void createVectorIndex(S3VectorsClient client, Exchange exchange) { CreateIndexResponse response = client.createIndex(request); LOG.trace("Create vector index response: {}", response); - message.setHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME, vectorIndexName); // Status may not be available in response message.setBody(response); } @@ -445,13 +433,19 @@ private List convertToFloatList(Object obj) { if (obj instanceof List list) { List result = new ArrayList<>(list.size()); - for (Object item : list) { + for (int i = 0; i < list.size(); i++) { + Object item = list.get(i); if (item instanceof Float floatItem) { result.add(floatItem); } else if (item instanceof Number number) { result.add(number.floatValue()); + } else if (item == null) { + throw new IllegalArgumentException( + "The vector has a null element at position " + i + ": every element must be a number"); } else { - throw new IllegalArgumentException("Invalid vector data type: " + item.getClass()); + throw new IllegalArgumentException( + "The vector has an element of type " + item.getClass().getName() + " at position " + i + + ": every element must be a number"); } } return result; @@ -472,38 +466,31 @@ private List convertToFloatList(Object obj) { } } - @SuppressWarnings("unchecked") - private List convertToStringList(Object obj) { - if (ObjectHelper.isEmpty(obj)) { - throw new IllegalArgumentException("Vector IDs cannot be null"); + /** + * The keys of getVectors and deleteVectors: from the body when it holds keys, else from the vector id header, so + * the key that an earlier putVectors left in the header does not win over the body. + */ + private List determineVectorKeys(Message message, String operation) { + Object body = message.getBody(); + List keys = AWS2S3VectorsHelper.asKeys(body); + if (keys != null) { + return keys; } - - if (obj instanceof List) { - return (List) obj; - } else if (obj instanceof String str) { - List result = new ArrayList<>(); - result.add(str); - return result; - } else if (obj instanceof String[] array) { - List result = new ArrayList<>(array.length); - for (String s : array) { - result.add(s); + Object header = message.getHeader(AWS2S3VectorsConstants.VECTOR_ID); + if (ObjectHelper.isNotEmpty(header)) { + keys = AWS2S3VectorsHelper.asKeys(header); + if (keys == null) { + throw new IllegalArgumentException( + "The " + AWS2S3VectorsConstants.VECTOR_ID + " header must hold the vector key as a String, or" + + " the vector keys as a list of Strings, but it is " + + AWS2S3VectorsHelper.describe(header)); } - return result; - } else { - throw new IllegalArgumentException("Unsupported vector ID type: " + obj.getClass()); - } - } - - private Document convertMapToDocument(Map map) { - if (map == null || map.isEmpty()) { - return null; - } - Document.MapBuilder builder = Document.mapBuilder(); - for (Map.Entry entry : map.entrySet()) { - builder.putString(entry.getKey(), entry.getValue()); + return keys; } - return builder.build(); + throw new IllegalArgumentException( + operation + " needs the vector keys in the body (a String, or a list of Strings) or in the " + + AWS2S3VectorsConstants.VECTOR_ID + " header, but the body is " + + AWS2S3VectorsHelper.describe(body)); } @Override diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumerTest.java b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumerTest.java index 285bc62dc5bc7..a689da0349515 100644 --- a/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumerTest.java +++ b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsConsumerTest.java @@ -16,17 +16,21 @@ */ package org.apache.camel.component.aws2.s3vectors; +import java.util.ArrayList; import java.util.List; +import org.apache.camel.Exchange; import org.apache.camel.impl.DefaultCamelContext; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import software.amazon.awssdk.services.s3vectors.S3VectorsClient; +import software.amazon.awssdk.services.s3vectors.model.QueryOutputVector; import software.amazon.awssdk.services.s3vectors.model.QueryVectorsRequest; import software.amazon.awssdk.services.s3vectors.model.QueryVectorsResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; class AWS2S3VectorsConsumerTest { @@ -72,4 +76,67 @@ void delayOptionDrivesTheConsumerPollInterval() throws Exception { assertThat(consumer.getDelay()).isEqualTo(1234L); } } + + @Test + void consumerAsksForMetadataAndDistanceAndSendsTheFilter() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + S3VectorsClient client = Mockito.mock(S3VectorsClient.class); + ArgumentCaptor captor = ArgumentCaptor.forClass(QueryVectorsRequest.class); + when(client.queryVectors(captor.capture())).thenReturn(QueryVectorsResponse.builder() + .vectors(QueryOutputVector.builder().key("v1").distance(0.25f).build()) + .build()); + context.getRegistry().bind("client", client); + context.start(); + + AWS2S3VectorsEndpoint endpoint = context.getEndpoint( + "aws2-s3-vectors://test-bucket?vectorIndexName=test-index&s3VectorsClient=#client" + + "&consumerQueryVector=0.1,0.2&startScheduler=false" + + "&consumerMetadataFilter={\"genre\": {\"$eq\": \"drama\"}}", + AWS2S3VectorsEndpoint.class); + List received = new ArrayList<>(); + AWS2S3VectorsConsumer consumer = (AWS2S3VectorsConsumer) endpoint.createConsumer(received::add); + consumer.start(); + try { + consumer.poll(); + } finally { + consumer.stop(); + } + + QueryVectorsRequest request = captor.getValue(); + assertThat(request.returnMetadata()).isTrue(); + assertThat(request.returnDistance()).isTrue(); + assertThat(request.filter()).isNotNull(); + assertThat(request.filter().asMap().get("genre").asMap().get("$eq").asString()).isEqualTo("drama"); + + assertThat(received).hasSize(1); + Exchange exchange = received.get(0); + assertThat(exchange.getIn().getHeader(AWS2S3VectorsConstants.VECTOR_ID)).isEqualTo("v1"); + assertThat(exchange.getIn().getHeader(AWS2S3VectorsConstants.DISTANCE)).isEqualTo(0.25f); + // the bucket and the index are inputs of the producer: the consumer must not set them, or a producer + // later in the route would write to the bucket and index of the consumer + assertThat(exchange.getIn().getHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME)).isNull(); + assertThat(exchange.getIn().getHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME)).isNull(); + } + } + + @Test + void consumerRejectsAMetadataFilterThatIsNotJson() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + S3VectorsClient client = Mockito.mock(S3VectorsClient.class); + context.getRegistry().bind("client", client); + context.start(); + + AWS2S3VectorsEndpoint endpoint = context.getEndpoint( + "aws2-s3-vectors://test-bucket?vectorIndexName=test-index&s3VectorsClient=#client" + + "&consumerQueryVector=0.1,0.2&startScheduler=false" + + "&consumerMetadataFilter=genre = drama", + AWS2S3VectorsEndpoint.class); + AWS2S3VectorsConsumer consumer = (AWS2S3VectorsConsumer) endpoint.createConsumer(exchange -> { + }); + + assertThatThrownBy(consumer::start) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("consumerMetadataFilter"); + } + } } diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsOptionEnumsTest.java b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsOptionEnumsTest.java new file mode 100644 index 0000000000000..ccda5859b7706 --- /dev/null +++ b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsOptionEnumsTest.java @@ -0,0 +1,63 @@ +/* + * 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.aws2.s3vectors; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.s3vectors.model.DataType; +import software.amazon.awssdk.services.s3vectors.model.DistanceMetric; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The dataType and distanceMetric options offer only the values that the AWS SDK knows. + */ +class AWS2S3VectorsOptionEnumsTest { + + @Test + void dataTypeOffersOnlyTheValuesOfTheSdk() throws Exception { + assertThat(enumsOf("dataType")) + .containsExactlyInAnyOrderElementsOf(DataType.knownValues().stream().map(DataType::toString).toList()); + } + + @Test + void distanceMetricOffersOnlyTheValuesOfTheSdk() throws Exception { + assertThat(enumsOf("distanceMetric")) + .containsExactlyInAnyOrderElementsOf( + DistanceMetric.knownValues().stream().map(DistanceMetric::toString).toList()); + } + + private static List enumsOf(String option) throws Exception { + try (InputStream is = Objects.requireNonNull(AWS2S3VectorsOptionEnumsTest.class.getResourceAsStream( + "/META-INF/org/apache/camel/component/aws2/s3vectors/aws2-s3-vectors.json")); + Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + JsonObject json = (JsonObject) Jsoner.deserialize(reader); + JsonObject properties = json.getMap("properties"); + JsonObject property = properties.getMap(option); + List values = property.getCollection("enum"); + return values.stream().map(String.class::cast).toList(); + } + } +} diff --git a/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducerRequestTest.java b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducerRequestTest.java new file mode 100644 index 0000000000000..54390337e7bac --- /dev/null +++ b/components/camel-aws/camel-aws2-s3-vectors/src/test/java/org/apache/camel/component/aws2/s3vectors/AWS2S3VectorsProducerRequestTest.java @@ -0,0 +1,308 @@ +/* + * 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.aws2.s3vectors; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.Producer; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.support.DefaultExchange; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import software.amazon.awssdk.core.document.Document; +import software.amazon.awssdk.services.s3vectors.S3VectorsClient; +import software.amazon.awssdk.services.s3vectors.model.CreateIndexRequest; +import software.amazon.awssdk.services.s3vectors.model.CreateIndexResponse; +import software.amazon.awssdk.services.s3vectors.model.CreateVectorBucketRequest; +import software.amazon.awssdk.services.s3vectors.model.CreateVectorBucketResponse; +import software.amazon.awssdk.services.s3vectors.model.DeleteVectorsRequest; +import software.amazon.awssdk.services.s3vectors.model.DeleteVectorsResponse; +import software.amazon.awssdk.services.s3vectors.model.GetVectorsRequest; +import software.amazon.awssdk.services.s3vectors.model.GetVectorsResponse; +import software.amazon.awssdk.services.s3vectors.model.PutVectorsRequest; +import software.amazon.awssdk.services.s3vectors.model.PutVectorsResponse; +import software.amazon.awssdk.services.s3vectors.model.QueryVectorsRequest; +import software.amazon.awssdk.services.s3vectors.model.QueryVectorsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * The requests the producer sends to S3 Vectors, and the headers it leaves for the next steps. + */ +class AWS2S3VectorsProducerRequestTest { + + private DefaultCamelContext context; + private S3VectorsClient client; + + @BeforeEach + void setup() { + client = Mockito.mock(S3VectorsClient.class); + when(client.putVectors(any(PutVectorsRequest.class))).thenReturn(PutVectorsResponse.builder().build()); + when(client.getVectors(any(GetVectorsRequest.class))) + .thenReturn(GetVectorsResponse.builder().vectors(List.of()).build()); + when(client.deleteVectors(any(DeleteVectorsRequest.class))).thenReturn(DeleteVectorsResponse.builder().build()); + when(client.queryVectors(any(QueryVectorsRequest.class))) + .thenReturn(QueryVectorsResponse.builder().vectors(List.of()).build()); + when(client.createVectorBucket(any(CreateVectorBucketRequest.class))) + .thenReturn(CreateVectorBucketResponse.builder().build()); + when(client.createIndex(any(CreateIndexRequest.class))).thenReturn(CreateIndexResponse.builder().build()); + context = new DefaultCamelContext(); + context.getRegistry().bind("client", client); + context.start(); + } + + @AfterEach + void tearDown() { + context.stop(); + } + + @Test + void putVectorsDoesNotSetBucketAndIndexHeaders() throws Exception { + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_ID, "doc-1"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.putVectors, exchange); + + assertThat(exchange.getMessage().getHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME)).isNull(); + assertThat(exchange.getMessage().getHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME)).isNull(); + assertThat(exchange.getMessage().getHeader(AWS2S3VectorsConstants.VECTOR_ID)).isEqualTo("doc-1"); + } + + @Test + void createVectorBucketDoesNotRedirectTheNextStep() throws Exception { + Exchange exchange = exchange(null); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.createVectorBucket, exchange); + send("bucket-b", "index-b", AWS2S3VectorsOperations.createVectorBucket, exchange); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CreateVectorBucketRequest.class); + Mockito.verify(client, Mockito.times(2)).createVectorBucket(captor.capture()); + assertThat(captor.getAllValues()).extracting(CreateVectorBucketRequest::vectorBucketName) + .containsExactly("bucket-a", "bucket-b"); + assertThat(exchange.getMessage().getHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME)).isNull(); + } + + @Test + void createVectorIndexDoesNotSetIndexHeader() throws Exception { + Exchange exchange = exchange(null); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.createVectorIndex, exchange); + + assertThat(exchange.getMessage().getHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME)).isNull(); + } + + @Test + void bucketAndIndexHeadersOverrideTheEndpoint() throws Exception { + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_BUCKET_NAME, "header-bucket"); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_INDEX_NAME, "header-index"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.putVectors, exchange); + + PutVectorsRequest request = captured(PutVectorsRequest.class); + assertThat(request.vectorBucketName()).isEqualTo("header-bucket"); + assertThat(request.indexName()).isEqualTo("header-index"); + } + + @Test + void getVectorsTakesTheKeysFromTheBodyBeforeTheHeader() throws Exception { + Exchange exchange = exchange("doc-list"); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_ID, "key-of-earlier-put"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.getVectors, exchange); + + assertThat(captured(GetVectorsRequest.class).keys()).containsExactly("doc-list"); + } + + @Test + void deleteVectorsTakesTheKeysFromAListBody() throws Exception { + Exchange exchange = exchange(List.of("k1", "k2")); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_ID, "key-of-earlier-put"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.deleteVectors, exchange); + + assertThat(captured(DeleteVectorsRequest.class).keys()).containsExactly("k1", "k2"); + } + + @Test + void getVectorsTakesTheKeysFromTheHeaderWhenTheBodyHoldsNone() throws Exception { + Exchange exchange = exchange(PutVectorsResponse.builder().build()); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_ID, "doc-1"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.getVectors, exchange); + + assertThat(captured(GetVectorsRequest.class).keys()).containsExactly("doc-1"); + } + + @Test + void getVectorsRejectsKeysThatAreNotStrings() throws Exception { + Exchange exchange = exchange(List.of(1, 2)); + + assertThatThrownBy(() -> send("bucket-a", "index-a", AWS2S3VectorsOperations.getVectors, exchange)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("java.lang.Integer"); + Mockito.verify(client, Mockito.never()).getVectors(any(GetVectorsRequest.class)); + } + + @Test + void deleteVectorsRejectsAHeaderOfKeysThatAreNotStrings() throws Exception { + Exchange exchange = exchange(null); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_ID, List.of(1, 2)); + + assertThatThrownBy(() -> send("bucket-a", "index-a", AWS2S3VectorsOperations.deleteVectors, exchange)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(AWS2S3VectorsConstants.VECTOR_ID) + .hasMessageContaining("java.lang.Integer"); + Mockito.verify(client, Mockito.never()).deleteVectors(any(DeleteVectorsRequest.class)); + } + + @Test + void getVectorsAsksForDataAndMetadata() throws Exception { + send("bucket-a", "index-a", AWS2S3VectorsOperations.getVectors, exchange("doc-1")); + + GetVectorsRequest request = captured(GetVectorsRequest.class); + assertThat(request.returnData()).isTrue(); + assertThat(request.returnMetadata()).isTrue(); + } + + @Test + void queryVectorsAsksForMetadataAndDistanceAndSendsTheFilter() throws Exception { + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.METADATA_FILTER, + "{\"genre\": {\"$eq\": \"drama\"}, \"year\": {\"$gte\": 2020}, \"$or\": [{\"draft\": false}]}"); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.queryVectors, exchange); + + QueryVectorsRequest request = captured(QueryVectorsRequest.class); + assertThat(request.returnMetadata()).isTrue(); + assertThat(request.returnDistance()).isTrue(); + Document filter = request.filter(); + assertThat(filter).isNotNull(); + assertThat(filter.asMap().get("genre").asMap().get("$eq").asString()).isEqualTo("drama"); + assertThat(filter.asMap().get("year").asMap().get("$gte").asNumber().intValue()).isEqualTo(2020); + assertThat(filter.asMap().get("$or").asList().get(0).asMap().get("draft").asBoolean()).isFalse(); + } + + @Test + void queryVectorsWithoutFilterSendsNoFilter() throws Exception { + send("bucket-a", "index-a", AWS2S3VectorsOperations.queryVectors, exchange(List.of(0.1f, 0.2f))); + + assertThat(captured(QueryVectorsRequest.class).filter()).isNull(); + } + + @Test + void queryVectorsRejectsAFilterThatIsNotJson() throws Exception { + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.METADATA_FILTER, "genre = drama"); + + assertThatThrownBy(() -> send("bucket-a", "index-a", AWS2S3VectorsOperations.queryVectors, exchange)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(AWS2S3VectorsConstants.METADATA_FILTER); + Mockito.verify(client, Mockito.never()).queryVectors(any(QueryVectorsRequest.class)); + } + + @Test + void putVectorsRejectsANullElementNamingItsPosition() throws Exception { + Exchange exchange = exchange(Arrays.asList(0.1f, null, 0.3f)); + + assertThatThrownBy(() -> send("bucket-a", "index-a", AWS2S3VectorsOperations.putVectors, exchange)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("position 1"); + } + + @Test + void putVectorsConvertsMetadataValuesThatAreNotStrings() throws Exception { + Map metadata = new LinkedHashMap<>(); + metadata.put("title", "Camel"); + metadata.put("year", 2026); + metadata.put("rating", 4.5d); + metadata.put("published", true); + metadata.put("tags", List.of("integration", 1, false)); + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_METADATA, metadata); + + send("bucket-a", "index-a", AWS2S3VectorsOperations.putVectors, exchange); + + Map sent = captured(PutVectorsRequest.class).vectors().get(0).metadata().asMap(); + assertThat(sent.get("title").asString()).isEqualTo("Camel"); + assertThat(sent.get("year").asNumber().intValue()).isEqualTo(2026); + assertThat(sent.get("rating").asNumber().doubleValue()).isEqualTo(4.5d); + assertThat(sent.get("published").asBoolean()).isTrue(); + List tags = sent.get("tags").asList(); + assertThat(tags.get(0).asString()).isEqualTo("integration"); + assertThat(tags.get(1).asNumber().intValue()).isEqualTo(1); + assertThat(tags.get(2).asBoolean()).isFalse(); + } + + @Test + void putVectorsRejectsAnUnsupportedMetadataValueNamingTheKey() throws Exception { + Exchange exchange = exchange(List.of(0.1f, 0.2f)); + exchange.getIn().setHeader(AWS2S3VectorsConstants.VECTOR_METADATA, Map.of("author", Map.of("name", "x"))); + + assertThatThrownBy(() -> send("bucket-a", "index-a", AWS2S3VectorsOperations.putVectors, exchange)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("author"); + Mockito.verify(client, Mockito.never()).putVectors(any(PutVectorsRequest.class)); + } + + private Exchange exchange(Object body) { + Exchange exchange = new DefaultExchange(context); + exchange.getIn().setBody(body); + return exchange; + } + + private void send(String bucket, String index, AWS2S3VectorsOperations operation, Exchange exchange) + throws Exception { + AWS2S3VectorsEndpoint endpoint = context.getEndpoint( + "aws2-s3-vectors://" + bucket + "?vectorIndexName=" + index + "&operation=" + operation + + "&s3VectorsClient=#client", + AWS2S3VectorsEndpoint.class); + Producer producer = endpoint.createProducer(); + producer.start(); + try { + producer.process(exchange); + } finally { + producer.stop(); + } + } + + private T captured(Class requestType) { + ArgumentCaptor captor = ArgumentCaptor.forClass(requestType); + if (requestType == PutVectorsRequest.class) { + Mockito.verify(client).putVectors((PutVectorsRequest) captor.capture()); + } else if (requestType == GetVectorsRequest.class) { + Mockito.verify(client).getVectors((GetVectorsRequest) captor.capture()); + } else if (requestType == DeleteVectorsRequest.class) { + Mockito.verify(client).deleteVectors((DeleteVectorsRequest) captor.capture()); + } else if (requestType == QueryVectorsRequest.class) { + Mockito.verify(client).queryVectors((QueryVectorsRequest) captor.capture()); + } else { + throw new IllegalArgumentException("Not captured: " + requestType); + } + return captor.getValue(); + } +} diff --git a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Consumer.java b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Consumer.java index d6dd300ee5201..6221c7d2f273f 100644 --- a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Consumer.java +++ b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Consumer.java @@ -91,7 +91,7 @@ protected void doStart() throws Exception { return; } catch (AwsServiceException ase) { /* 404 means the bucket doesn't exist */ - if (ase.awsErrorDetails().sdkHttpResponse().statusCode() != 404) { + if (ase.statusCode() != 404) { throw ase; } } diff --git a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Endpoint.java b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Endpoint.java index 7350a7bebd8f2..26743a85cd7c0 100644 --- a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Endpoint.java +++ b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Endpoint.java @@ -160,7 +160,7 @@ public void doStart() throws Exception { } } /* 404 means the bucket doesn't exist */ - if (!(ase.awsErrorDetails().sdkHttpResponse().statusCode() == 404)) { + if (ase.statusCode() != 404) { throw ase; } } diff --git a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3MissingBucketTest.java b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3MissingBucketTest.java new file mode 100644 index 0000000000000..88d077d94cf98 --- /dev/null +++ b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3MissingBucketTest.java @@ -0,0 +1,97 @@ +/* + * 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.aws2.s3; + +import org.apache.camel.Consumer; +import org.apache.camel.Endpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.CreateBucketResponse; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * A missing bucket is detected from the status code of the exception, also when the exception has no error details. + */ +public class AWS2S3MissingBucketTest { + + private final S3Client s3Client = mock(S3Client.class); + private DefaultCamelContext context; + + @BeforeEach + public void setUp() { + // only the bucket "mybucket" exists; a missing bucket fails with a 404 without error details, as an + // AwsServiceException can be built + when(s3Client.headBucket(any(HeadBucketRequest.class))).thenAnswer(inv -> { + HeadBucketRequest request = inv.getArgument(0); + if ("mybucket".equals(request.bucket())) { + return HeadBucketResponse.builder().build(); + } + throw AwsServiceException.builder().statusCode(404).message("Not Found").build(); + }); + when(s3Client.createBucket(any(CreateBucketRequest.class))).thenReturn(CreateBucketResponse.builder().build()); + context = new DefaultCamelContext(); + context.getRegistry().bind("s3", s3Client); + context.start(); + } + + @AfterEach + public void tearDown() { + context.stop(); + } + + @Test + public void endpointCreatesTheMissingBucket() throws Exception { + Endpoint endpoint = context.getEndpoint("aws2-s3://newbucket?amazonS3Client=#s3&autoCreateBucket=true"); + + endpoint.start(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CreateBucketRequest.class); + verify(s3Client).createBucket(captor.capture()); + assertEquals("newbucket", captor.getValue().bucket()); + endpoint.stop(); + } + + @Test + public void consumerCreatesTheMissingDestinationBucket() throws Exception { + Endpoint endpoint = context.getEndpoint("aws2-s3://mybucket?amazonS3Client=#s3&autoCreateBucket=true" + + "&moveAfterRead=true&destinationBucket=dest&initialDelay=3600000"); + endpoint.start(); + Consumer consumer = endpoint.createConsumer(exchange -> { + }); + + consumer.start(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CreateBucketRequest.class); + verify(s3Client).createBucket(captor.capture()); + assertEquals("dest", captor.getValue().bucket()); + consumer.stop(); + endpoint.stop(); + } +} diff --git a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Endpoint.java b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Endpoint.java index e5cdb788e7d50..16c9dae79d9f7 100644 --- a/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Endpoint.java +++ b/components/camel-aws/camel-aws2-sns/src/main/java/org/apache/camel/component/aws2/sns/Sns2Endpoint.java @@ -26,6 +26,7 @@ import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.component.aws.common.AwsExceptionUtil; import org.apache.camel.component.aws2.sns.client.Sns2ClientFactory; import org.apache.camel.spi.*; import org.apache.camel.support.DefaultEndpoint; @@ -126,7 +127,7 @@ public void doInit() throws Exception { } } while (ObjectHelper.isNotEmpty(nextToken)); } catch (final AwsServiceException ase) { - LOG.trace("The list topics operation return the following error code {}", ase.awsErrorDetails().errorCode()); + LOG.trace("The list topics operation return the following error code {}", AwsExceptionUtil.errorCode(ase)); throw ase; } } diff --git a/components/camel-aws/camel-aws2-textract/src/generated/resources/META-INF/org/apache/camel/component/aws2/textract/aws2-textract.json b/components/camel-aws/camel-aws2-textract/src/generated/resources/META-INF/org/apache/camel/component/aws2/textract/aws2-textract.json index c3ac336ad3f2a..cab6f0e5cdd9c 100644 --- a/components/camel-aws/camel-aws2-textract/src/generated/resources/META-INF/org/apache/camel/component/aws2/textract/aws2-textract.json +++ b/components/camel-aws/camel-aws2-textract/src/generated/resources/META-INF/org/apache/camel/component/aws2/textract/aws2-textract.json @@ -55,10 +55,10 @@ "CamelAwsTextractS3Bucket": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 bucket name containing the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_BUCKET" }, "CamelAwsTextractS3Object": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 object name (key) of the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_OBJECT" }, "CamelAwsTextractS3ObjectVersion": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The S3 object version of the document to process", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#S3_OBJECT_VERSION" }, - "CamelAwsTextractJobId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The job ID for async operations (StartDocumentTextDetection, StartDocumentAnalysis)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#JOB_ID" }, + "CamelAwsTextractJobId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The job ID of an async operation. It is set from the response of the startDocumentTextDetection, startDocumentAnalysis and startExpenseAnalysis operations, and it is the job ID of the getDocumentTextDetection, getDocumentAnalysis and getExpenseAnalysis operations (else the body is used as the job ID)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#JOB_ID" }, "CamelAwsTextractMaxResults": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results to return in paginated operations", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#MAX_RESULTS" }, "CamelAwsTextractNextToken": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The next token for pagination in operations that return multiple pages", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#NEXT_TOKEN" }, - "CamelAwsTextractFeatureTypes": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The feature types for document analysis (TABLES, FORMS, SIGNATURES, etc.)", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#FEATURE_TYPES" } + "CamelAwsTextractFeatureTypes": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The feature types of the analyzeDocument and startDocumentAnalysis operations (TABLES, FORMS, QUERIES, SIGNATURES, LAYOUT): a list of FeatureType or String values, or a String with comma separated values. The String values are case-insensitive. Without this header TABLES and FORMS are used.", "constantName": "org.apache.camel.component.aws2.textract.Textract2Constants#FEATURE_TYPES" } }, "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.textract.Textract2Configuration", "configurationField": "configuration", "description": "Logical name" }, diff --git a/components/camel-aws/camel-aws2-textract/src/main/docs/aws2-textract-component.adoc b/components/camel-aws/camel-aws2-textract/src/main/docs/aws2-textract-component.adoc index 9e2bf3f6534b6..989d89272dc89 100644 --- a/components/camel-aws/camel-aws2-textract/src/main/docs/aws2-textract-component.adoc +++ b/components/camel-aws/camel-aws2-textract/src/main/docs/aws2-textract-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Textract component options -You have to provide the amazonTextractClient in the +You have to provide the textractClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/textract/[Amazon Textract] service. @@ -68,6 +68,60 @@ Only one of static, default and profile credentials could be used at the same ti For more information about this you can look at https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html[AWS credentials documentation] +=== The document + +The synchronous operations (`detectDocumentText`, `analyzeDocument` and `analyzeExpense`) read the document from S3 or from the message body; +the asynchronous `start` operations read it from S3 only: + +* The bucket is the `s3Bucket` option, or else the `CamelAwsTextractS3Bucket` header. +* The object is the `s3Object` option, or else the `CamelAwsTextractS3Object` header. +* The version is the `s3ObjectVersion` option, or else the `CamelAwsTextractS3ObjectVersion` header. + +The options are used first: a header is read only when its option is not set. +When both the bucket and the object are known, the document is that S3 object. +Otherwise a synchronous operation uses the message body (as bytes) as the document, and an asynchronous operation fails. + +=== Feature types + +The `analyzeDocument` and `startDocumentAnalysis` operations analyze the feature types of the `CamelAwsTextractFeatureTypes` header, +or else `TABLES` and `FORMS`. +The header can be a list (or array) of `FeatureType` or `String` values, or one `String` with comma separated values, such as `TABLES,SIGNATURES`. +The `String` values are case-insensitive, and an unknown value fails with an `IllegalArgumentException`. + +=== Asynchronous operations + +The `startDocumentTextDetection`, `startDocumentAnalysis` and `startExpenseAnalysis` operations return the start response as the body, +and set the `CamelAwsTextractJobId` header to the job ID of the response. +The `getDocumentTextDetection`, `getDocumentAnalysis` and `getExpenseAnalysis` operations read the job ID from the `CamelAwsTextractJobId` header, +or else from the message body, so a `get` step can follow a `start` step (the job must have finished, see the `JobStatus` of the `get` response): + +[source,yaml] +---- +- route: + from: + uri: direct:analyze + steps: + - setHeader: + name: CamelAwsTextractFeatureTypes + constant: + expression: "TABLES,SIGNATURES" + - to: + uri: aws2-textract:textract + parameters: + operation: startDocumentAnalysis + s3Bucket: my-documents + s3Object: contracts/contract-1.pdf + - delay: + constant: + expression: "10000" + - to: + uri: aws2-textract:textract + parameters: + operation: getDocumentAnalysis + - log: + message: "${body.jobStatus}" +---- + === Example The following route detects the text of an image in S3. Without `s3Bucket` and `s3Object`, the document is the message body: diff --git a/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Constants.java b/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Constants.java index 67114911c2d34..be5cae2e8a129 100644 --- a/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Constants.java +++ b/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Constants.java @@ -31,7 +31,9 @@ public interface Textract2Constants { @Metadata(label = "producer", description = "The S3 object version of the document to process", javaType = "String") String S3_OBJECT_VERSION = "CamelAwsTextractS3ObjectVersion"; @Metadata(label = "producer", - description = "The job ID for async operations (StartDocumentTextDetection, StartDocumentAnalysis)", + description = "The job ID of an async operation. It is set from the response of the startDocumentTextDetection, " + + "startDocumentAnalysis and startExpenseAnalysis operations, and it is the job ID of the " + + "getDocumentTextDetection, getDocumentAnalysis and getExpenseAnalysis operations (else the body is used as the job ID)", javaType = "String") String JOB_ID = "CamelAwsTextractJobId"; @Metadata(label = "producer", description = "The maximum number of results to return in paginated operations", @@ -40,7 +42,10 @@ public interface Textract2Constants { @Metadata(label = "producer", description = "The next token for pagination in operations that return multiple pages", javaType = "String") String NEXT_TOKEN = "CamelAwsTextractNextToken"; - @Metadata(label = "producer", description = "The feature types for document analysis (TABLES, FORMS, SIGNATURES, etc.)", + @Metadata(label = "producer", + description = "The feature types of the analyzeDocument and startDocumentAnalysis operations (TABLES, FORMS, QUERIES, " + + "SIGNATURES, LAYOUT): a list of FeatureType or String values, or a String with comma separated values. " + + "The String values are case-insensitive. Without this header TABLES and FORMS are used.", javaType = "List") String FEATURE_TYPES = "CamelAwsTextractFeatureTypes"; } diff --git a/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Producer.java b/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Producer.java index 89e76478d2801..520c7ee478286 100644 --- a/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Producer.java +++ b/components/camel-aws/camel-aws2-textract/src/main/java/org/apache/camel/component/aws2/textract/Textract2Producer.java @@ -16,7 +16,10 @@ */ package org.apache.camel.component.aws2.textract; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Locale; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; @@ -168,9 +171,8 @@ private void analyzeDocument(TextractClient textractClient, Exchange exchange) t request.document(document); // Get feature types from headers or default to TABLES and FORMS - @SuppressWarnings("unchecked") - List featureTypes = exchange.getIn().getHeader(Textract2Constants.FEATURE_TYPES, List.class); - if (featureTypes != null && !featureTypes.isEmpty()) { + List featureTypes = getFeatureTypes(exchange); + if (!featureTypes.isEmpty()) { request.featureTypes(featureTypes); } else { request.featureTypes(FeatureType.TABLES, FeatureType.FORMS); @@ -231,11 +233,12 @@ private void startDocumentTextDetection(TextractClient textractClient, Exchange result = textractClient.startDocumentTextDetection(req); } catch (AwsServiceException ase) { LOG.trace("Start Document Text Detection command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } else { throw new IllegalArgumentException( "startDocumentTextDetection operation requires StartDocumentTextDetectionRequest in POJO mode"); @@ -250,11 +253,12 @@ private void startDocumentTextDetection(TextractClient textractClient, Exchange result = textractClient.startDocumentTextDetection(request.build()); } catch (AwsServiceException ase) { LOG.trace("Start Document Text Detection command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } } @@ -271,6 +275,7 @@ private void startDocumentAnalysis(TextractClient textractClient, Exchange excha } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } else { throw new IllegalArgumentException( "startDocumentAnalysis operation requires StartDocumentAnalysisRequest in POJO mode"); @@ -280,9 +285,8 @@ private void startDocumentAnalysis(TextractClient textractClient, Exchange excha DocumentLocation documentLocation = createDocumentLocationFromExchange(exchange); request.documentLocation(documentLocation); - @SuppressWarnings("unchecked") - List featureTypes = exchange.getIn().getHeader(Textract2Constants.FEATURE_TYPES, List.class); - if (featureTypes != null && !featureTypes.isEmpty()) { + List featureTypes = getFeatureTypes(exchange); + if (!featureTypes.isEmpty()) { request.featureTypes(featureTypes); } else { request.featureTypes(FeatureType.TABLES, FeatureType.FORMS); @@ -297,6 +301,7 @@ private void startDocumentAnalysis(TextractClient textractClient, Exchange excha } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } } @@ -313,6 +318,7 @@ private void startExpenseAnalysis(TextractClient textractClient, Exchange exchan } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } else { throw new IllegalArgumentException( "startExpenseAnalysis operation requires StartExpenseAnalysisRequest in POJO mode"); @@ -331,6 +337,7 @@ private void startExpenseAnalysis(TextractClient textractClient, Exchange exchan } Message message = getMessageForResponse(exchange); message.setBody(result); + message.setHeader(Textract2Constants.JOB_ID, result.jobId()); } } @@ -343,7 +350,7 @@ private void getDocumentTextDetection(TextractClient textractClient, Exchange ex result = textractClient.getDocumentTextDetection(req); } catch (AwsServiceException ase) { LOG.trace("Get Document Text Detection command returned the error code {}", - ase.awsErrorDetails().errorCode()); + AwsExceptionUtil.errorCode(ase)); throw ase; } Message message = getMessageForResponse(exchange); @@ -488,6 +495,56 @@ private void getExpenseAnalysis(TextractClient textractClient, Exchange exchange } } + /** + * The feature types of the {@link Textract2Constants#FEATURE_TYPES} header: a FeatureType, a String with comma + * separated values, or an array or collection of them. The String values are case-insensitive. + */ + private static List getFeatureTypes(Exchange exchange) { + Object header = exchange.getIn().getHeader(Textract2Constants.FEATURE_TYPES); + List answer = new ArrayList<>(); + if (header == null) { + return answer; + } + Iterable values; + if (header instanceof Iterable it) { + values = it; + } else if (header instanceof Object[] array) { + values = Arrays.asList(array); + } else { + values = List.of(header); + } + for (Object value : values) { + if (value instanceof FeatureType featureType) { + answer.add(featureType); + } else if (value instanceof String text) { + for (String part : text.split(",")) { + String name = part.trim(); + if (!name.isEmpty()) { + answer.add(toFeatureType(name)); + } + } + } else { + throw new IllegalArgumentException( + "The " + Textract2Constants.FEATURE_TYPES + " header must hold FeatureType or String values, but has a " + + (value == null ? "null" : value.getClass().getName()) + " value"); + } + } + return answer; + } + + private static FeatureType toFeatureType(String name) { + FeatureType answer = FeatureType.fromValue(name); + if (answer == FeatureType.UNKNOWN_TO_SDK_VERSION) { + answer = FeatureType.fromValue(name.toUpperCase(Locale.ROOT)); + } + if (answer == FeatureType.UNKNOWN_TO_SDK_VERSION) { + throw new IllegalArgumentException( + "Unknown feature type " + name + " in the " + Textract2Constants.FEATURE_TYPES + " header: use one of " + + FeatureType.knownValues()); + } + return answer; + } + private Document createDocumentFromExchange(Exchange exchange) throws InvalidPayloadException { // Check if we have S3 location in configuration or headers String s3Bucket = getConfiguration().getS3Bucket(); diff --git a/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/AmazonAWSTextractMock.java b/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/AmazonAWSTextractMock.java index 16adc175bc596..41f6519fef834 100644 --- a/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/AmazonAWSTextractMock.java +++ b/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/AmazonAWSTextractMock.java @@ -20,13 +20,70 @@ import software.amazon.awssdk.services.textract.TextractClient; import software.amazon.awssdk.services.textract.TextractServiceClientConfiguration; +import software.amazon.awssdk.services.textract.model.AnalyzeDocumentRequest; +import software.amazon.awssdk.services.textract.model.AnalyzeDocumentResponse; import software.amazon.awssdk.services.textract.model.Block; import software.amazon.awssdk.services.textract.model.BlockType; import software.amazon.awssdk.services.textract.model.DetectDocumentTextRequest; import software.amazon.awssdk.services.textract.model.DetectDocumentTextResponse; +import software.amazon.awssdk.services.textract.model.GetDocumentTextDetectionRequest; +import software.amazon.awssdk.services.textract.model.GetDocumentTextDetectionResponse; +import software.amazon.awssdk.services.textract.model.JobStatus; +import software.amazon.awssdk.services.textract.model.StartDocumentAnalysisRequest; +import software.amazon.awssdk.services.textract.model.StartDocumentAnalysisResponse; +import software.amazon.awssdk.services.textract.model.StartDocumentTextDetectionRequest; +import software.amazon.awssdk.services.textract.model.StartDocumentTextDetectionResponse; +import software.amazon.awssdk.services.textract.model.StartExpenseAnalysisRequest; +import software.amazon.awssdk.services.textract.model.StartExpenseAnalysisResponse; public class AmazonAWSTextractMock implements TextractClient { + public static final String JOB_ID = "mock-job-id"; + + private volatile AnalyzeDocumentRequest lastAnalyzeDocumentRequest; + private volatile StartDocumentAnalysisRequest lastStartDocumentAnalysisRequest; + private volatile GetDocumentTextDetectionRequest lastGetDocumentTextDetectionRequest; + + public AnalyzeDocumentRequest getLastAnalyzeDocumentRequest() { + return lastAnalyzeDocumentRequest; + } + + public StartDocumentAnalysisRequest getLastStartDocumentAnalysisRequest() { + return lastStartDocumentAnalysisRequest; + } + + public GetDocumentTextDetectionRequest getLastGetDocumentTextDetectionRequest() { + return lastGetDocumentTextDetectionRequest; + } + + @Override + public AnalyzeDocumentResponse analyzeDocument(AnalyzeDocumentRequest request) { + lastAnalyzeDocumentRequest = request; + return AnalyzeDocumentResponse.builder().build(); + } + + @Override + public StartDocumentTextDetectionResponse startDocumentTextDetection(StartDocumentTextDetectionRequest request) { + return StartDocumentTextDetectionResponse.builder().jobId(JOB_ID).build(); + } + + @Override + public StartDocumentAnalysisResponse startDocumentAnalysis(StartDocumentAnalysisRequest request) { + lastStartDocumentAnalysisRequest = request; + return StartDocumentAnalysisResponse.builder().jobId(JOB_ID).build(); + } + + @Override + public StartExpenseAnalysisResponse startExpenseAnalysis(StartExpenseAnalysisRequest request) { + return StartExpenseAnalysisResponse.builder().jobId(JOB_ID).build(); + } + + @Override + public GetDocumentTextDetectionResponse getDocumentTextDetection(GetDocumentTextDetectionRequest request) { + lastGetDocumentTextDetectionRequest = request; + return GetDocumentTextDetectionResponse.builder().jobStatus(JobStatus.SUCCEEDED).build(); + } + @Override public DetectDocumentTextResponse detectDocumentText(DetectDocumentTextRequest request) { Block block = Block.builder() diff --git a/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/Textract2ProducerTest.java b/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/Textract2ProducerTest.java index dba02346a12f8..98ff5fd586b36 100644 --- a/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/Textract2ProducerTest.java +++ b/components/camel-aws/camel-aws2-textract/src/test/java/org/apache/camel/component/aws2/textract/Textract2ProducerTest.java @@ -16,6 +16,8 @@ */ package org.apache.camel.component.aws2.textract; +import java.util.List; + import org.apache.camel.BindToRegistry; import org.apache.camel.EndpointInject; import org.apache.camel.Exchange; @@ -26,10 +28,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.textract.model.DetectDocumentTextRequest; import software.amazon.awssdk.services.textract.model.DetectDocumentTextResponse; import software.amazon.awssdk.services.textract.model.Document; +import software.amazon.awssdk.services.textract.model.FeatureType; +import software.amazon.awssdk.services.textract.model.StartDocumentTextDetectionRequest; +import software.amazon.awssdk.services.textract.model.StartDocumentTextDetectionResponse; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -133,6 +139,84 @@ void pojoRequestWithWrongBodyTypeThrows(String route, String expectedMessage) { .hasRootCauseMessage(expectedMessage); } + @Test + public void analyzeDocumentFeatureTypesFromListOfStrings() { + template.sendBodyAndHeader("direct:analyzeDocument", null, Textract2Constants.FEATURE_TYPES, + List.of("tables", "Signatures")); + + assertEquals(List.of(FeatureType.TABLES, FeatureType.SIGNATURES), + clientMock.getLastAnalyzeDocumentRequest().featureTypes()); + } + + @Test + public void analyzeDocumentFeatureTypesFromCommaSeparatedString() { + template.sendBodyAndHeader("direct:analyzeDocument", null, Textract2Constants.FEATURE_TYPES, "LAYOUT, signatures"); + + assertEquals(List.of(FeatureType.LAYOUT, FeatureType.SIGNATURES), + clientMock.getLastAnalyzeDocumentRequest().featureTypes()); + } + + @Test + public void analyzeDocumentFeatureTypesFromFeatureTypes() { + template.sendBodyAndHeader("direct:analyzeDocument", null, Textract2Constants.FEATURE_TYPES, + List.of(FeatureType.LAYOUT)); + + assertEquals(List.of(FeatureType.LAYOUT), clientMock.getLastAnalyzeDocumentRequest().featureTypes()); + } + + @Test + public void startDocumentAnalysisFeatureTypesFromStringArray() { + template.sendBodyAndHeader("direct:startDocumentAnalysis", null, Textract2Constants.FEATURE_TYPES, + new String[] { "layout", "TABLES" }); + + assertEquals(List.of(FeatureType.LAYOUT, FeatureType.TABLES), + clientMock.getLastStartDocumentAnalysisRequest().featureTypes()); + } + + @Test + public void analyzeDocumentUnknownFeatureTypeFails() { + assertThatThrownBy(() -> template.sendBodyAndHeader("direct:analyzeDocument", null, + Textract2Constants.FEATURE_TYPES, List.of("TABLE"))) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .rootCause().hasMessageContaining(Textract2Constants.FEATURE_TYPES).hasMessageContaining("TABLE"); + } + + @Test + public void startDocumentAnalysisFeatureTypeOfOtherTypeFails() { + assertThatThrownBy(() -> template.sendBodyAndHeader("direct:startDocumentAnalysis", null, + Textract2Constants.FEATURE_TYPES, List.of(1))) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .rootCause().hasMessageContaining(Textract2Constants.FEATURE_TYPES).hasMessageContaining("Integer"); + } + + @ParameterizedTest + @ValueSource(strings = { + "direct:startDocumentTextDetection", "direct:startDocumentAnalysis", + "direct:startExpenseAnalysis" }) + void startOperationsSetJobIdHeader(String route) { + Exchange exchange = template.request(route, e -> { + }); + + assertEquals(AmazonAWSTextractMock.JOB_ID, exchange.getMessage().getHeader(Textract2Constants.JOB_ID)); + assertNotNull(exchange.getMessage().getBody()); + } + + @Test + public void startDocumentTextDetectionPojoSetsJobIdHeader() { + Exchange exchange = template.request("direct:startDocumentTextDetectionPojo", + e -> e.getIn().setBody(StartDocumentTextDetectionRequest.builder().build())); + + assertEquals(AmazonAWSTextractMock.JOB_ID, exchange.getMessage().getHeader(Textract2Constants.JOB_ID)); + assertNotNull(exchange.getMessage().getBody(StartDocumentTextDetectionResponse.class)); + } + + @Test + public void startThenGetDocumentTextDetectionUsesJobId() { + template.sendBody("direct:startThenGetDocumentTextDetection", null); + + assertEquals(AmazonAWSTextractMock.JOB_ID, clientMock.getLastGetDocumentTextDetectionRequest().jobId()); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -163,6 +247,17 @@ public void configure() { .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=getDocumentAnalysis&pojoRequest=true"); from("direct:getExpenseAnalysisPojo") .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=getExpenseAnalysis&pojoRequest=true"); + from("direct:analyzeDocument") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=analyzeDocument&s3Bucket=testbucket&s3Object=testobject.pdf"); + from("direct:startDocumentTextDetection") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=startDocumentTextDetection&s3Bucket=testbucket&s3Object=testobject.pdf"); + from("direct:startDocumentAnalysis") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=startDocumentAnalysis&s3Bucket=testbucket&s3Object=testobject.pdf"); + from("direct:startExpenseAnalysis") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=startExpenseAnalysis&s3Bucket=testbucket&s3Object=testobject.pdf"); + from("direct:startThenGetDocumentTextDetection") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=startDocumentTextDetection&s3Bucket=testbucket&s3Object=testobject.pdf") + .to("aws2-textract://test?textractClient=#amazonTextractClient&operation=getDocumentTextDetection"); } }; } diff --git a/components/camel-aws/camel-aws2-timestream/src/generated/resources/META-INF/org/apache/camel/component/aws2/timestream/aws2-timestream.json b/components/camel-aws/camel-aws2-timestream/src/generated/resources/META-INF/org/apache/camel/component/aws2/timestream/aws2-timestream.json index 37ecd9c677a55..596611c84c520 100644 --- a/components/camel-aws/camel-aws2-timestream/src/generated/resources/META-INF/org/apache/camel/component/aws2/timestream/aws2-timestream.json +++ b/components/camel-aws/camel-aws2-timestream/src/generated/resources/META-INF/org/apache/camel/component/aws2/timestream/aws2-timestream.json @@ -50,21 +50,21 @@ }, "headers": { "CamelAwsTimestreamOperation": { "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.timestream.Timestream2Constants#OPERATION" }, - "CamelAwsTimestreamRecord": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Record", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Represents a time-series data point being written into Timestream", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD" }, - "CamelAwsTimestreamRecordList": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "List of Records", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_LIST" }, + "CamelAwsTimestreamRecord": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Record", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The common attributes of the writeRecords operation: the dimensions, measure and time values that all the records of the CamelAwsTimestreamRecordList header share. It is not written as a record of its own.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD" }, + "CamelAwsTimestreamRecordList": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The records of the writeRecords operation", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_LIST" }, "CamelAwsTimestreamTaskStatus": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Status of Batch Load Task", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TASK_STATUS" }, "CamelAwsTimestreamTaskId": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ID of the batch load task to resume.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TASK_ID" }, "CamelAwsTimestreamDatabaseName": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Database", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATABASE_NAME" }, "CamelAwsTimestreamTableName": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Table", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TABLE_NAME" }, "CamelAwsTimestreamTargetDatabaseName": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Target Database", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TARGET_DATABASE_NAME" }, "CamelAwsTimestreamTargetTableName": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Target Table", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TARGET_TABLE_NAME" }, - "CamelAwsTimestreamRecordVersion": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Record version", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_VERSION" }, + "CamelAwsTimestreamRecordVersion": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The record version of the createBatchLoadTask operation", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RECORD_VERSION" }, "CamelAwsTimestreamDataModelConfiguration": { "index": 10, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.DataModelConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Configuration of Data Model", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATA_MODEL_CONFIGURATION" }, "CamelAwsTimestreamDataSourceConfiguration": { "index": 11, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.DataSourceConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Configuration of Data Source", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DATA_SOURCE_CONFIGURATION" }, "CamelAwsTimestreamReportConfiguration": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.ReportConfiguration", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Reporting Configuration", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#REPORT_CONFIGURATION" }, "CamelAwsTimestreamTableSchema": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.Schema", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Schema", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEMA" }, "CamelAwsTimestreamRetentionProperties": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.RetentionProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Retention Properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#RETENTION_PROPERTIES" }, - "CamelAwsTimestreamMagneticStoreWriteProperties": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.MagneticStoreWriteProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Magentic Store Write properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MAGNETIC_STORE_WRITE_PROPERTIES" }, + "CamelAwsTimestreamMagneticStoreWriteProperties": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "software.amazon.awssdk.services.timestreamwrite.model.MagneticStoreWriteProperties", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Timestream Table Magnetic Store Write properties", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MAGNETIC_STORE_WRITE_PROPERTIES" }, "CamelAwsTimestreamTimeColumn": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of Time column", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#TIME_COLUMN" }, "CamelAwsTimestreamMeasureColumnName": { "index": 17, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of the measure column.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#MEASURE_NAME_COLUMN" }, "CamelAwsTimestreamDimensionMappingList": { "index": 18, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This is to allow mapping column(s) from the query result to the dimension in the destination table.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#DIMENSION_MAPPING_LIST" }, @@ -84,7 +84,7 @@ "CamelAwsTimestreamErrorReportS3BucketName": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 Bucket name for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_BUCKET_NAME" }, "CamelAwsTimestreamErrorReportS3ObjectKeyPrefix": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 object key prefix for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_OBJECT_KEY_PREFIX" }, "CamelAwsTimestreamErrorReportS3EncryptionOption": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "S3 encryption option for error reporting.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#ERROR_REPORT_S3_ENCRYPTION_OPTION" }, - "CamelAwsTimestreamScheduledQueryExecutionRoleArn": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "he ARN for the IAM role that Timestream will assume when running the scheduled query.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEDULED_QUERY_EXECUTION_ROLE_ARN" }, + "CamelAwsTimestreamScheduledQueryExecutionRoleArn": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The ARN for the IAM role that Timestream will assume when running the scheduled query.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#SCHEDULED_QUERY_EXECUTION_ROLE_ARN" }, "CamelAwsTimestreamClientToken": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Using a ClientToken makes the call to CreateScheduledQuery idempotent", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#CLIENT_TOKEN" }, "CamelAwsTimestreamKmsKeyId": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Amazon KMS key used to encrypt the scheduled query resource, at-rest.", "constantName": "org.apache.camel.component.aws2.timestream.Timestream2Constants#KMS_KEY_ID" } }, diff --git a/components/camel-aws/camel-aws2-timestream/src/main/docs/aws2-timestream-component.adoc b/components/camel-aws/camel-aws2-timestream/src/main/docs/aws2-timestream-component.adoc index 37a71156ad224..7510b6ae2fd80 100644 --- a/components/camel-aws/camel-aws2-timestream/src/main/docs/aws2-timestream-component.adoc +++ b/components/camel-aws/camel-aws2-timestream/src/main/docs/aws2-timestream-component.adoc @@ -120,6 +120,17 @@ Camel-AWS Timestream component provides the following operation on the producer ** query ** cancelQuery +The operation is the `CamelAwsTimestreamOperation` header, or else the `operation` option; without either, the producer fails with an `IllegalArgumentException`. +The write operations need `clientType` `write` and the query operations need `clientType` `query` (`describeEndpoints` is an operation of both): +an operation of the other client type fails with an `IllegalArgumentException`, already when the producer starts if it is the `operation` option. + +=== Writing records + +The `writeRecords` operation writes the records of the `CamelAwsTimestreamRecordList` header (a `List` of `Record`) into the table of the +`CamelAwsTimestreamDatabaseName` and `CamelAwsTimestreamTableName` headers. +The `CamelAwsTimestreamRecord` header is the common attributes of the request: the dimensions, measure and time values that all the records share. +It is not written as a record of its own, so a request with only this header writes no record. + == Examples === Producer Examples diff --git a/components/camel-aws/camel-aws2-timestream/src/main/java/org/apache/camel/component/aws2/timestream/Timestream2Component.java b/components/camel-aws/camel-aws2-timestream/src/main/java/org/apache/camel/component/aws2/timestream/Timestream2Component.java index 0bc1ed7b0447f..fee12d89884da 100644 --- a/components/camel-aws/camel-aws2-timestream/src/main/java/org/apache/camel/component/aws2/timestream/Timestream2Component.java +++ b/components/camel-aws/camel-aws2-timestream/src/main/java/org/apache/camel/component/aws2/timestream/Timestream2Component.java @@ -48,7 +48,7 @@ protected Endpoint createEndpoint(String uri, String remaining, Map template.sendBody("direct:writeNoOperation", null)) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage(NO_OPERATION); + } + + @Test + public void queryWithoutOperationFails() { + assertThatThrownBy(() -> template.sendBody("direct:queryNoOperation", null)) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage(NO_OPERATION); + } + + @Test + public void writeWithQueryOperationHeaderFails() { + assertThatThrownBy(() -> template.sendBodyAndHeader("direct:writeNoOperation", null, Timestream2Constants.OPERATION, + Timestream2Operations.query)) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("The operation query is not an operation of the write client: use clientType=query"); + } + + @Test + public void queryWithWriteOperationHeaderFails() { + assertThatThrownBy(() -> template.sendBodyAndHeader("direct:queryNoOperation", null, Timestream2Constants.OPERATION, + "describeDatabase")) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage( + "The operation describeDatabase is not an operation of the query client: use clientType=write"); + } + + @Test + public void queryWithWriteOperationOptionFailsOnStart() throws Exception { + Producer producer = context.getEndpoint( + "aws2-timestream://query:test?awsTimestreamQueryClient=#awsTimestreamQueryClient&operation=describeDatabase") + .createProducer(); + + assertThatThrownBy(producer::start) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The operation describeDatabase is not an operation of the query client: use clientType=write"); + } + + @Test + public void writeWithQueryOperationOptionFailsOnStart() throws Exception { + Producer producer = context.getEndpoint( + "aws2-timestream://write:test?awsTimestreamWriteClient=#awsTimestreamWriteClient&operation=createScheduledQuery") + .createProducer(); + + assertThatThrownBy(producer::start) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The operation createScheduledQuery is not an operation of the write client: use clientType=query"); + } + + @Test + public void unknownClientTypeFails() { + assertThatThrownBy(() -> context.getEndpoint( + "aws2-timestream://read:test?awsTimestreamQueryClient=#awsTimestreamQueryClient&operation=query")) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("Unknown clientType read: use write or query"); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:writeNoOperation") + .to("aws2-timestream://write:test?awsTimestreamWriteClient=#awsTimestreamWriteClient"); + from("direct:queryNoOperation") + .to("aws2-timestream://query:test?awsTimestreamQueryClient=#awsTimestreamQueryClient"); + } + }; + } +} diff --git a/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/AmazonTimestreamQueryClientMock.java b/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/AmazonTimestreamQueryClientMock.java index f3f39e9ebaa35..78b655af3379d 100644 --- a/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/AmazonTimestreamQueryClientMock.java +++ b/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/AmazonTimestreamQueryClientMock.java @@ -25,6 +25,7 @@ public class AmazonTimestreamQueryClientMock implements TimestreamQueryClient { private CreateScheduledQueryRequest lastCreateScheduledQueryRequest; + private DescribeScheduledQueryRequest lastDescribeScheduledQueryRequest; public AmazonTimestreamQueryClientMock() { } @@ -36,6 +37,13 @@ public CreateScheduledQueryRequest getLastCreateScheduledQueryRequest() { return lastCreateScheduledQueryRequest; } + /** + * The last request passed to {@link #describeScheduledQuery}, so tests can assert how it was built. + */ + public DescribeScheduledQueryRequest getLastDescribeScheduledQueryRequest() { + return lastDescribeScheduledQueryRequest; + } + @Override public DescribeEndpointsResponse describeEndpoints(DescribeEndpointsRequest describeEndpointsRequest) { DescribeEndpointsResponse.Builder result = DescribeEndpointsResponse.builder(); @@ -70,6 +78,7 @@ public DeleteScheduledQueryResponse deleteScheduledQuery(DeleteScheduledQueryReq @Override public DescribeScheduledQueryResponse describeScheduledQuery(DescribeScheduledQueryRequest describeScheduledQueryRequest) { + this.lastDescribeScheduledQueryRequest = describeScheduledQueryRequest; DescribeScheduledQueryResponse.Builder result = DescribeScheduledQueryResponse.builder(); ScheduledQueryDescription.Builder description = ScheduledQueryDescription.builder(); description.arn("aws-timestream:test:scheduled-query:arn"); diff --git a/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/Timestream2QueryProducerTest.java b/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/Timestream2QueryProducerTest.java index 0b69ef0c11803..b46d6c331da30 100644 --- a/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/Timestream2QueryProducerTest.java +++ b/components/camel-aws/camel-aws2-timestream/src/test/java/org/apache/camel/component/aws2/timestream/query/Timestream2QueryProducerTest.java @@ -170,6 +170,15 @@ public void process(Exchange exchange) { assertNotNull(resultGet); } + @Test + public void timestreamDescribeScheduledQuerySendsArnFromHeader() { + template.sendBodyAndHeader("direct:describeScheduledQuery", null, Timestream2Constants.SCHEDULED_QUERY_ARN, + "arn:aws:timestream:eu-west-1:123456789012:scheduled-query/my-query"); + + assertEquals("arn:aws:timestream:eu-west-1:123456789012:scheduled-query/my-query", + clientMock.getLastDescribeScheduledQueryRequest().scheduledQueryArn()); + } + @Test public void timestreamDescribeScheduledQueryTest() throws Exception { diff --git a/components/camel-aws/camel-aws2-translate/src/generated/resources/META-INF/org/apache/camel/component/aws2/translate/aws2-translate.json b/components/camel-aws/camel-aws2-translate/src/generated/resources/META-INF/org/apache/camel/component/aws2/translate/aws2-translate.json index 88dadd2d7a35c..d96925fb29d59 100644 --- a/components/camel-aws/camel-aws2-translate/src/generated/resources/META-INF/org/apache/camel/component/aws2/translate/aws2-translate.json +++ b/components/camel-aws/camel-aws2-translate/src/generated/resources/META-INF/org/apache/camel/component/aws2/translate/aws2-translate.json @@ -24,15 +24,15 @@ "remote": true }, "componentProperties": { - "autodetectSourceLanguage": { "index": 0, "kind": "property", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language" }, + "autodetectSourceLanguage": { "index": 0, "kind": "property", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language. When enabled, the source language is auto, and the sourceLanguage option and the CamelAwsTranslateSourceLanguage header are not used." }, "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.translate.Translate2Configuration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" }, "lazyStartProducer": { "index": 2, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.translate.Translate2Operations", "enum": [ "translateText" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "translateText", "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, "overrideEndpoint": { "index": 4, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 5, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 6, "kind": "property", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The region in which the Translate client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, - "sourceLanguage": { "index": 7, "kind": "property", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use" }, - "targetLanguage": { "index": 8, "kind": "property", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use" }, + "sourceLanguage": { "index": 7, "kind": "property", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use. When not set, the CamelAwsTranslateSourceLanguage header is used. It is not used when autodetectSourceLanguage is enabled." }, + "targetLanguage": { "index": 8, "kind": "property", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use. When not set, the CamelAwsTranslateTargetLanguage header is used." }, "uriEndpointOverride": { "index": 9, "kind": "property", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "autowiredEnabled": { "index": 10, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, "translateClient": { "index": 11, "kind": "property", "displayName": "Translate Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "software.amazon.awssdk.services.translate.TranslateClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "To use an existing configured AWS Translate client" }, @@ -58,13 +58,13 @@ }, "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.translate.Translate2Configuration", "configurationField": "configuration", "description": "Logical name" }, - "autodetectSourceLanguage": { "index": 1, "kind": "parameter", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language" }, + "autodetectSourceLanguage": { "index": 1, "kind": "parameter", "displayName": "Autodetect Source Language", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Being able to autodetect the source language. When enabled, the source language is auto, and the sourceLanguage option and the CamelAwsTranslateSourceLanguage header are not used." }, "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.aws2.translate.Translate2Operations", "enum": [ "translateText" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "translateText", "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The operation to perform" }, "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combination with the uriEndpointOverride option" }, "pojoRequest": { "index": 4, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "region": { "index": 5, "kind": "parameter", "displayName": "Region", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ap-south-2", "ap-south-1", "eu-south-1", "eu-south-2", "us-gov-east-1", "me-central-1", "il-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "eu-central-2", "eu-isoe-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "ca-west-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "ap-southeast-4", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "aws-cn-global", "aws-us-gov-global", "aws-iso-global", "aws-iso-b-global" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "The region in which the Translate client needs to work. When using this parameter, the configuration will expect the lowercase name of the region (for example, ap-east-1) You'll need to use the name Region.EU_WEST_1.id()" }, - "sourceLanguage": { "index": 6, "kind": "parameter", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use" }, - "targetLanguage": { "index": 7, "kind": "parameter", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use" }, + "sourceLanguage": { "index": 6, "kind": "parameter", "displayName": "Source Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Source language to use. When not set, the CamelAwsTranslateSourceLanguage header is used. It is not used when autodetectSourceLanguage is enabled." }, + "targetLanguage": { "index": 7, "kind": "parameter", "displayName": "Target Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Target language to use. When not set, the CamelAwsTranslateTargetLanguage header is used." }, "uriEndpointOverride": { "index": 8, "kind": "parameter", "displayName": "Uri Endpoint Override", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "Set the overriding uri endpoint. This option needs to be used in combination with overrideEndpoint option" }, "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "translateClient": { "index": 10, "kind": "parameter", "displayName": "Translate Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "software.amazon.awssdk.services.translate.TranslateClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.aws2.translate.Translate2Configuration", "configurationField": "configuration", "description": "To use an existing configured AWS Translate client" }, diff --git a/components/camel-aws/camel-aws2-translate/src/main/docs/aws2-translate-component.adoc b/components/camel-aws/camel-aws2-translate/src/main/docs/aws2-translate-component.adoc index 3e9991ec828d6..1fc2aa34a5740 100644 --- a/components/camel-aws/camel-aws2-translate/src/main/docs/aws2-translate-component.adoc +++ b/components/camel-aws/camel-aws2-translate/src/main/docs/aws2-translate-component.adoc @@ -43,7 +43,7 @@ include::partial$component-endpoint-headers.adoc[] Required Translate component options -You have to provide the amazonTranslateClient in the +You have to provide the translateClient in the Registry or your accessKey and secretKey to access the https://aws.amazon.com/translate/[Amazon Translate] service. @@ -74,6 +74,19 @@ Camel-AWS Translate component provides the following operation on the producer s - translateText +=== Source and target language + +Each language is taken from its option, or else from its header: + +- the source language from the `sourceLanguage` option, or else the `CamelAwsTranslateSourceLanguage` header; +- the target language from the `targetLanguage` option, or else the `CamelAwsTranslateTargetLanguage` header. + +So one language can be set as an option and the other as a header. +With `autodetectSourceLanguage=true`, the source language is `auto`, and Amazon Translate detects it. + +When the target language is missing, or the source language is missing and `autodetectSourceLanguage` is not enabled, +the producer fails with an `IllegalArgumentException` that names the option and the header to set. + == Examples === Translate Text example diff --git a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Configuration.java b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Configuration.java index 9fbec24d064cf..66e2e31672e0b 100644 --- a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Configuration.java +++ b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Configuration.java @@ -179,7 +179,8 @@ public boolean isAutodetectSourceLanguage() { } /** - * Being able to autodetect the source language + * Being able to autodetect the source language. When enabled, the source language is auto, and the sourceLanguage + * option and the CamelAwsTranslateSourceLanguage header are not used. */ public void setAutodetectSourceLanguage(boolean autodetectSourceLanguage) { this.autodetectSourceLanguage = autodetectSourceLanguage; @@ -190,7 +191,8 @@ public String getSourceLanguage() { } /** - * Source language to use + * Source language to use. When not set, the CamelAwsTranslateSourceLanguage header is used. It is not used when + * autodetectSourceLanguage is enabled. */ public void setSourceLanguage(String sourceLanguage) { this.sourceLanguage = sourceLanguage; @@ -201,7 +203,7 @@ public String getTargetLanguage() { } /** - * Target language to use + * Target language to use. When not set, the CamelAwsTranslateTargetLanguage header is used. */ public void setTargetLanguage(String targetLanguage) { this.targetLanguage = targetLanguage; diff --git a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2LanguageEnum.java b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2LanguageEnum.java index dd509e7ff77c2..f631220e43c77 100644 --- a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2LanguageEnum.java +++ b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2LanguageEnum.java @@ -30,7 +30,7 @@ public enum Translate2LanguageEnum { CATALAN("ca"), CHINESE_SIMPLIFIED("zh"), CHINESE_TRADITIONAL("zh-TW"), - CROATIAN("cr"), + CROATIAN("hr"), CZECH("cs"), DANISH("da"), DARI("fa-AF"), diff --git a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Producer.java b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Producer.java index beb5dbe4efcde..6e7fb01bcb2eb 100644 --- a/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Producer.java +++ b/components/camel-aws/camel-aws2-translate/src/main/java/org/apache/camel/component/aws2/translate/Translate2Producer.java @@ -107,36 +107,33 @@ private void translateText(TranslateClient translateClient, Exchange exchange) t } } else { Builder request = TranslateTextRequest.builder(); - if (!getConfiguration().isAutodetectSourceLanguage()) { - if (ObjectHelper.isEmpty(getConfiguration().getSourceLanguage()) - && ObjectHelper.isEmpty(getConfiguration().getTargetLanguage())) { - String source = exchange.getIn().getHeader(Translate2Constants.SOURCE_LANGUAGE, String.class); - String target = exchange.getIn().getHeader(Translate2Constants.TARGET_LANGUAGE, String.class); - if (ObjectHelper.isEmpty(source) || ObjectHelper.isEmpty(target)) { - throw new IllegalArgumentException( - "Source and target language must be specified as headers or endpoint options"); - } - request.sourceLanguageCode(source); - request.targetLanguageCode(target); - } else { - request.sourceLanguageCode(getConfiguration().getSourceLanguage()); - request.targetLanguageCode(getConfiguration().getTargetLanguage()); - } + // Each language - endpoint option first, then header + String source; + if (getConfiguration().isAutodetectSourceLanguage()) { + source = "auto"; } else { - String source = "auto"; - if (ObjectHelper.isEmpty(getConfiguration().getTargetLanguage())) { - String target = exchange.getIn().getHeader(Translate2Constants.TARGET_LANGUAGE, String.class); - if (ObjectHelper.isEmpty(source) || ObjectHelper.isEmpty(target)) { - throw new IllegalArgumentException( - "Target language must be specified when autodetection of source language is enabled"); - } - request.sourceLanguageCode(source); - request.targetLanguageCode(target); - } else { - request.sourceLanguageCode(source); - request.targetLanguageCode(getConfiguration().getTargetLanguage()); + source = getConfiguration().getSourceLanguage(); + if (ObjectHelper.isEmpty(source)) { + source = exchange.getIn().getHeader(Translate2Constants.SOURCE_LANGUAGE, String.class); + } + if (ObjectHelper.isEmpty(source)) { + throw new IllegalArgumentException( + "The source language is missing: set the sourceLanguage option or the " + + Translate2Constants.SOURCE_LANGUAGE + + " header, or enable the autodetectSourceLanguage option"); } } + String target = getConfiguration().getTargetLanguage(); + if (ObjectHelper.isEmpty(target)) { + target = exchange.getIn().getHeader(Translate2Constants.TARGET_LANGUAGE, String.class); + } + if (ObjectHelper.isEmpty(target)) { + throw new IllegalArgumentException( + "The target language is missing: set the targetLanguage option or the " + + Translate2Constants.TARGET_LANGUAGE + " header"); + } + request.sourceLanguageCode(source); + request.targetLanguageCode(target); if (!ObjectHelper.isEmpty(exchange.getIn().getHeader(Translate2Constants.TERMINOLOGY_NAMES, Collection.class))) { Collection terminologies = exchange.getIn().getHeader(Translate2Constants.TERMINOLOGY_NAMES, Collection.class); diff --git a/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/AmazonAWSTranslateMock.java b/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/AmazonAWSTranslateMock.java index 805912bdf863a..8711740aecedf 100644 --- a/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/AmazonAWSTranslateMock.java +++ b/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/AmazonAWSTranslateMock.java @@ -23,8 +23,12 @@ public class AmazonAWSTranslateMock implements TranslateClient { + // the last request received + TranslateTextRequest lastRequest; + @Override public TranslateTextResponse translateText(TranslateTextRequest request) { + lastRequest = request; TranslateTextResponse result = TranslateTextResponse.builder().translatedText("Hello").build(); return result; } diff --git a/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/Translate2ProducerTest.java b/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/Translate2ProducerTest.java index dad42153da4e9..9c66904a1be46 100644 --- a/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/Translate2ProducerTest.java +++ b/components/camel-aws/camel-aws2-translate/src/test/java/org/apache/camel/component/aws2/translate/Translate2ProducerTest.java @@ -102,6 +102,82 @@ public void process(Exchange exchange) { } + @Test + public void sourceLanguageOptionAndTargetLanguageHeader() { + template.send("direct:translateTextSourceOption", exchange -> { + exchange.getIn().setHeader(Translate2Constants.TARGET_LANGUAGE, Translate2LanguageEnum.ENGLISH); + exchange.getIn().setBody("ciao"); + }); + + assertEquals("it", clientMock.lastRequest.sourceLanguageCode()); + assertEquals("en", clientMock.lastRequest.targetLanguageCode()); + } + + @Test + public void sourceLanguageHeaderAndTargetLanguageOption() { + template.send("direct:translateTextTargetOption", exchange -> { + exchange.getIn().setHeader(Translate2Constants.SOURCE_LANGUAGE, Translate2LanguageEnum.ITALIAN); + exchange.getIn().setBody("ciao"); + }); + + assertEquals("it", clientMock.lastRequest.sourceLanguageCode()); + assertEquals("en", clientMock.lastRequest.targetLanguageCode()); + } + + @Test + public void optionWinsOverHeader() { + template.send("direct:translateTextOptions", exchange -> { + exchange.getIn().setHeader(Translate2Constants.SOURCE_LANGUAGE, Translate2LanguageEnum.FRENCH); + exchange.getIn().setHeader(Translate2Constants.TARGET_LANGUAGE, Translate2LanguageEnum.GERMAN); + exchange.getIn().setBody("ciao"); + }); + + assertEquals("it", clientMock.lastRequest.sourceLanguageCode()); + assertEquals("en", clientMock.lastRequest.targetLanguageCode()); + } + + @Test + public void autodetectSourceLanguageWithTargetLanguageHeader() { + template.send("direct:translateTextAutodetect", exchange -> { + exchange.getIn().setHeader(Translate2Constants.SOURCE_LANGUAGE, Translate2LanguageEnum.FRENCH); + exchange.getIn().setHeader(Translate2Constants.TARGET_LANGUAGE, Translate2LanguageEnum.ENGLISH); + exchange.getIn().setBody("ciao"); + }); + + assertEquals("auto", clientMock.lastRequest.sourceLanguageCode()); + assertEquals("en", clientMock.lastRequest.targetLanguageCode()); + } + + @Test + void missingTargetLanguageThrows() { + assertThatThrownBy(() -> template.requestBody("direct:translateTextSourceOption", "ciao")) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage( + "The target language is missing: set the targetLanguage option or the CamelAwsTranslateTargetLanguage header"); + } + + @Test + void missingTargetLanguageWithAutodetectThrows() { + assertThatThrownBy(() -> template.requestBody("direct:translateTextAutodetect", "ciao")) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage( + "The target language is missing: set the targetLanguage option or the CamelAwsTranslateTargetLanguage header"); + } + + @Test + void missingSourceLanguageThrows() { + assertThatThrownBy(() -> template.requestBody("direct:translateTextTargetOption", "ciao")) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage( + "The source language is missing: set the sourceLanguage option or the CamelAwsTranslateSourceLanguage header," + + " or enable the autodetectSourceLanguage option"); + } + + @Test + void croatianLanguageCode() { + assertEquals("hr", Translate2LanguageEnum.CROATIAN.toString()); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -116,6 +192,12 @@ public void configure() { from("direct:translateTextOptions").to( "aws2-translate://test?translateClient=#amazonTranslateClient&operation=translateText&sourceLanguage=it&targetLanguage=en") .to("mock:result"); + from("direct:translateTextSourceOption").to( + "aws2-translate://test?translateClient=#amazonTranslateClient&operation=translateText&sourceLanguage=it"); + from("direct:translateTextTargetOption").to( + "aws2-translate://test?translateClient=#amazonTranslateClient&operation=translateText&targetLanguage=en"); + from("direct:translateTextAutodetect").to( + "aws2-translate://test?translateClient=#amazonTranslateClient&operation=translateText&autodetectSourceLanguage=true"); } }; } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index ceb34e9b59065..1cbd6f4a839f5 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -2636,3 +2636,84 @@ Node producer creates it, from the options of the Edge Node endpoint (`deviceIds Device route that starts before the Edge Node route, or while it is stopped, therefore no longer fails with a `NullPointerException` when the Device URI has no `deviceIds`. Sending to a Device endpoint while no producer of its Edge Node endpoint is started now fails the exchange with an `IllegalStateException`; before, the data was dropped. + +=== camel-aws2-polly - textType, outputFormat and the next token + +* The `textType` and `outputFormat` options no longer have a default value, so the `CamelAwsPollyTextType` and + `CamelAwsPollyOutputFormat` headers are now read when the option is not set. Previously the defaults `TEXT` and `MP3` + were always set, and the headers were ignored: SSML sent with `CamelAwsPollyTextType=ssml` was spoken as plain text. + When neither the option nor the header is set, `TEXT` and `MP3` are still sent. +* describeVoices, listLexicons and listSpeechSynthesisTasks now read and set the new `CamelAwsPollyNextToken` header: + it is sent as the token of the next page, and set to the token of the response, or removed on the last page. A route + that calls a different list operation on the same message must remove the header first. +* The lexicon names are trimmed, and empty names are dropped. + +=== camel-aws2-translate - the languages and the code of Croatian + +* Each language now comes from its option, or else from its header. Previously, when one of the `sourceLanguage` and + `targetLanguage` options was set, both were taken from the options, and the request had no language for the other. +* A missing target language, or a missing source language without `autodetectSourceLanguage`, now fails with an + `IllegalArgumentException` that names the option and the header. +* `Translate2LanguageEnum.CROATIAN` is now `hr`, the code Amazon Translate uses. It was `cr`, which is Cree. + +=== camel-aws2-textract - feature types and the job id + +* The `CamelAwsTextractFeatureTypes` header now also takes Strings (in any case), as a list, an array or one String + with comma separated values. An unknown value, or an element of another type, fails with an + `IllegalArgumentException` instead of a `ClassCastException` in the AWS SDK. A single String, which was ignored + before, is now used. +* startDocumentTextDetection, startDocumentAnalysis and startExpenseAnalysis now set the `CamelAwsTextractJobId` + header to the job id of the response, so a get step that follows uses it. The body is still the response. + +=== camel-aws2-timestream - describeScheduledQuery and the operation + +* describeScheduledQuery now sends the ARN of the `CamelAwsTimestreamScheduledQueryArn` header. Previously it worked + with `pojoRequest=true` only. +* Without an operation option and an operation header, the producer fails with an `IllegalArgumentException` instead of + a `NullPointerException`. An operation of the other client type fails with a message that names the client type to + use; when it is the `operation` option, the route already fails to start. +* An unknown client type in the URI fails with `Unknown clientType ...: use write or query`. +* The catalog now describes `CamelAwsTimestreamRecord` as what writeRecords sends it as: the common attributes of the + records of `CamelAwsTimestreamRecordList`. `CamelAwsTimestreamRecordVersion` is listed as a `Long`, the type it is + read as. + +=== camel-aws2-s3-vectors - headers, returned data and metadata filter + +* The consumer and the producer no longer set the `CamelAwsS3VectorsVectorBucketName` and + `CamelAwsS3VectorsVectorIndexName` headers. They only echoed the endpoint, and the next aws2-s3-vectors step of the + route then used that bucket and index instead of its own: a route from the consumer to putVectors on another index + overwrote the consumed vectors. The headers still override the endpoint when the route sets them. +* getVectors and deleteVectors take their keys from the message body when it holds keys (a String, or a collection or + an array of Strings), else from the `CamelAwsS3VectorsVectorId` header. Previously the header won, so a getVectors + after a putVectors got the key of the putVectors. Keys that are not Strings now fail with an + `IllegalArgumentException`. +* getVectors now returns the data and the metadata of the vectors, and queryVectors and the consumer return the + metadata and the distance. Previously they returned the keys only. The consumer sets the new + `CamelAwsS3VectorsDistance` header. +* The `CamelAwsS3VectorsMetadataFilter` header and the `consumerMetadataFilter` option are now applied. They are JSON + objects in the filter syntax of S3 Vectors. Invalid JSON fails the exchange, or the consumer does not start. + Previously both were ignored. +* The `similarityThreshold` option and the `CamelAwsS3VectorsSimilarityThreshold` and + `CamelAwsS3VectorsSimilarityScore` headers are deprecated. They never did anything: S3 Vectors returns a distance and + has no threshold. +* The values of `CamelAwsS3VectorsVectorMetadata` may be numbers, booleans and lists, which failed with a + `ClassCastException` before. A null in a vector fails with an `IllegalArgumentException` that names its position. +* The `dataType` option no longer offers `float16`, and the `distanceMetric` option no longer offers `dot-product`: + S3 Vectors has neither. + +=== camel-aws-security-hub - headers of another type and failed imports + +* A `CamelAwsSecurityHubFilters`, `CamelAwsSecurityHubNote`, `CamelAwsSecurityHubSeverity` or + `CamelAwsSecurityHubWorkflow` header that is not of the AWS SDK type (`AwsSecurityFindingFilters`, `NoteUpdate`, + `SeverityUpdate`, `WorkflowUpdate`) now fails with an `IllegalArgumentException`. Previously it was ignored: getFindings + with the filters as JSON text returned every finding. +* batchImportFindings now fails the exchange with an `IllegalStateException` when Security Hub does not import some of + the findings. The message names their ids and errors. The `CamelAwsSecurityHubFailedCount` and + `CamelAwsSecurityHubSuccessCount` headers and the body are still set. +* batchImportFindings also takes an array or any collection of `AwsSecurityFinding` in the body. An element of another + type fails with an `IllegalArgumentException` instead of a `ClassCastException`. + +=== camel-aws-cloudtrail - the content type of the CloudEvent data type + +The `aws-cloudtrail:application-cloudevents` data type now sets the content type `application/json`, as the body is +the JSON of the event. It was `text/plain`. diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2PollyComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2PollyComponentBuilderFactory.java index 431ba661b6c82..2d8bb876c0fe7 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2PollyComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2PollyComponentBuilderFactory.java @@ -187,14 +187,13 @@ default Aws2PollyComponentBuilder operation(org.apache.camel.component.aws2.poll return this; } - /** - * The audio output format. + * The audio output format. When neither this option nor the + * CamelAwsPollyOutputFormat header is set, MP3 is used. * * The option is a: * <code>software.amazon.awssdk.services.polly.model.OutputFormat</code> type. * - * Default: MP3 * Group: producer * * @param outputFormat the value to set @@ -333,14 +332,13 @@ default Aws2PollyComponentBuilder taskId(java.lang.String taskId) { return this; } - /** - * The type of text input (text or ssml). + * The type of text input (text or ssml). When neither this option nor + * the CamelAwsPollyTextType header is set, TEXT is used. * * The option is a: * <code>software.amazon.awssdk.services.polly.model.TextType</code> type. * - * Default: TEXT * Group: producer * * @param textType the value to set diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2S3VectorsComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2S3VectorsComponentBuilderFactory.java index 0dca70e7487ab..a14e9b3804540 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2S3VectorsComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2S3VectorsComponentBuilderFactory.java @@ -70,7 +70,8 @@ default Aws2S3VectorsComponentBuilder configuration(org.apache.camel.component.a /** - * The data type of the vector. Options: float32, float16. + * The data type of the vectors of the index to create. S3 Vectors + * supports float32 only. * * The option is a: <code>java.lang.String</code> type. * @@ -87,8 +88,8 @@ default Aws2S3VectorsComponentBuilder dataType(java.lang.String dataType) { /** - * The distance metric to use for similarity search. Options: cosine, - * euclidean, dot-product. + * The distance metric of the index to create, which the similarity + * search uses. Options: cosine, euclidean. * * The option is a: <code>java.lang.String</code> type. * @@ -139,7 +140,11 @@ default Aws2S3VectorsComponentBuilder region(java.lang.String region) { } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option is a: <code>java.lang.Float</code> type. * @@ -148,6 +153,7 @@ default Aws2S3VectorsComponentBuilder region(java.lang.String region) { * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default Aws2S3VectorsComponentBuilder similarityThreshold(java.lang.Float similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; @@ -248,8 +254,10 @@ default Aws2S3VectorsComponentBuilder bridgeErrorHandler(boolean bridgeErrorHand } /** - * Optional metadata filter for the consumer to filter vectors during - * polling. + * The metadata filter of the similarity search of the consumer, as a + * JSON object with the filter operators of S3 Vectors, such as {genre: + * {$eq: drama}}. Only the vectors whose metadata match the filter are + * consumed. * * The option is a: <code>java.lang.String</code> type. * diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2TranslateComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2TranslateComponentBuilderFactory.java index e7775f512fdd2..e038047119f48 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2TranslateComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/Aws2TranslateComponentBuilderFactory.java @@ -52,7 +52,9 @@ interface Aws2TranslateComponentBuilder extends ComponentBuilderjava.lang.String type. * @@ -60,8 +61,8 @@ default AWS2S3VectorsEndpointConsumerBuilder dataType(String dataType) { return this; } /** - * The distance metric to use for similarity search. Options: cosine, - * euclidean, dot-product. + * The distance metric of the index to create, which the similarity + * search uses. Options: cosine, euclidean. * * The option is a: java.lang.String type. * @@ -124,7 +125,11 @@ default AWS2S3VectorsEndpointConsumerBuilder region(String region) { return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option is a: java.lang.Float type. * @@ -133,12 +138,17 @@ default AWS2S3VectorsEndpointConsumerBuilder region(String region) { * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointConsumerBuilder similarityThreshold(Float similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option will be converted to a java.lang.Float type. * @@ -147,6 +157,7 @@ default AWS2S3VectorsEndpointConsumerBuilder similarityThreshold(Float similarit * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointConsumerBuilder similarityThreshold(String similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; @@ -245,8 +256,10 @@ default AWS2S3VectorsEndpointConsumerBuilder vectorIndexName(String vectorIndexN return this; } /** - * Optional metadata filter for the consumer to filter vectors during - * polling. + * The metadata filter of the similarity search of the consumer, as a + * JSON object with the filter operators of S3 Vectors, such as {genre: + * {$eq: drama}}. Only the vectors whose metadata match the filter are + * consumed. * * The option is a: java.lang.String type. * @@ -1298,7 +1311,8 @@ default AdvancedAWS2S3VectorsEndpointProducerBuilder advanced() { } /** - * The data type of the vector. Options: float32, float16. + * The data type of the vectors of the index to create. S3 Vectors + * supports float32 only. * * The option is a: java.lang.String type. * @@ -1313,8 +1327,8 @@ default AWS2S3VectorsEndpointProducerBuilder dataType(String dataType) { return this; } /** - * The distance metric to use for similarity search. Options: cosine, - * euclidean, dot-product. + * The distance metric of the index to create, which the similarity + * search uses. Options: cosine, euclidean. * * The option is a: java.lang.String type. * @@ -1377,7 +1391,11 @@ default AWS2S3VectorsEndpointProducerBuilder region(String region) { return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option is a: java.lang.Float type. * @@ -1386,12 +1404,17 @@ default AWS2S3VectorsEndpointProducerBuilder region(String region) { * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointProducerBuilder similarityThreshold(Float similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option will be converted to a java.lang.Float type. * @@ -1400,6 +1423,7 @@ default AWS2S3VectorsEndpointProducerBuilder similarityThreshold(Float similarit * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointProducerBuilder similarityThreshold(String similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; @@ -1894,7 +1918,8 @@ default AdvancedAWS2S3VectorsEndpointBuilder advanced() { } /** - * The data type of the vector. Options: float32, float16. + * The data type of the vectors of the index to create. S3 Vectors + * supports float32 only. * * The option is a: java.lang.String type. * @@ -1909,8 +1934,8 @@ default AWS2S3VectorsEndpointBuilder dataType(String dataType) { return this; } /** - * The distance metric to use for similarity search. Options: cosine, - * euclidean, dot-product. + * The distance metric of the index to create, which the similarity + * search uses. Options: cosine, euclidean. * * The option is a: java.lang.String type. * @@ -1973,7 +1998,11 @@ default AWS2S3VectorsEndpointBuilder region(String region) { return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option is a: java.lang.Float type. * @@ -1982,12 +2011,17 @@ default AWS2S3VectorsEndpointBuilder region(String region) { * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointBuilder similarityThreshold(Float similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. The + * consumer sets the CamelAwsS3VectorsDistance header to the distance of + * each vector from the query vector. Deprecation note: S3 Vectors has + * no similarity threshold, the component never used this option. Filter + * the results on their CamelAwsS3VectorsDistance header instead. * * The option will be converted to a java.lang.Float type. * @@ -1996,6 +2030,7 @@ default AWS2S3VectorsEndpointBuilder similarityThreshold(Float similarityThresho * @param similarityThreshold the value to set * @return the dsl builder */ + @Deprecated default AWS2S3VectorsEndpointBuilder similarityThreshold(String similarityThreshold) { doSetProperty("similarityThreshold", similarityThreshold); return this; @@ -2487,12 +2522,13 @@ public String awsS3VectorsOperation() { return "CamelAwsS3VectorsOperation"; } /** - * The name of the vector bucket which will be used for the current - * operation. + * The name of the vector bucket to use for the current operation + * instead of the vector bucket of the endpoint. An input only: neither + * the producer nor the consumer sets it. * * The option is a: {@code String} type. * - * Group: common + * Group: producer * * @return the name of the header {@code AwsS3VectorsVectorBucketName}. */ @@ -2500,12 +2536,13 @@ public String awsS3VectorsVectorBucketName() { return "CamelAwsS3VectorsVectorBucketName"; } /** - * The name of the vector index which will be used for the current - * operation. + * The name of the vector index to use for the current operation instead + * of the vectorIndexName option of the endpoint. An input only: neither + * the producer nor the consumer sets it. * * The option is a: {@code String} type. * - * Group: common + * Group: producer * * @return the name of the header {@code AwsS3VectorsVectorIndexName}. */ @@ -2513,11 +2550,15 @@ public String awsS3VectorsVectorIndexName() { return "CamelAwsS3VectorsVectorIndexName"; } /** - * The unique identifier for a vector. + * The key of a vector. putVectors puts the vector under this key (the + * exchange id when it is not set) and sets it to the key it used. + * getVectors and deleteVectors take their key, or a list of keys, from + * this header only when the message body holds no keys. The consumer + * sets it to the key of the consumed vector. * - * The option is a: {@code String} type. + * The option is a: {@code String or List} type. * - * Group: producer + * Group: common * * @return the name of the header {@code AwsS3VectorsVectorId}. */ @@ -2549,7 +2590,7 @@ public String awsS3VectorsVectorDimensions() { return "CamelAwsS3VectorsVectorDimensions"; } /** - * The data type of the vector (float32 or float16). + * The data type of the vectors of the index to create (float32). * * The option is a: {@code String} type. * @@ -2561,9 +2602,10 @@ public String awsS3VectorsDataType() { return "CamelAwsS3VectorsDataType"; } /** - * Additional metadata for the vector as a map. + * Additional metadata for the vector as a map. The values may be + * Strings, numbers, booleans or lists of these. * - * The option is a: {@code Map} type. + * The option is a: {@code Map} type. * * Group: producer * @@ -2598,8 +2640,7 @@ public String awsS3VectorsTopK() { return "CamelAwsS3VectorsTopK"; } /** - * The distance metric to use for similarity search (cosine, euclidean, - * dot-product). + * The distance metric of the index to create (cosine or euclidean). * * The option is a: {@code String} type. * @@ -2611,7 +2652,10 @@ public String awsS3VectorsDistanceMetric() { return "CamelAwsS3VectorsDistanceMetric"; } /** - * The minimum similarity threshold for results. + * (deprecated) Never used: S3 Vectors has no similarity threshold. + * Deprecation note: S3 Vectors has no similarity threshold, the + * component never used this header. Filter the results on their + * CamelAwsS3VectorsDistance header instead. * * The option is a: {@code Float} type. * @@ -2620,12 +2664,14 @@ public String awsS3VectorsDistanceMetric() { * @return the name of the header {@code * AwsS3VectorsSimilarityThreshold}. */ + @Deprecated public String awsS3VectorsSimilarityThreshold() { return "CamelAwsS3VectorsSimilarityThreshold"; } /** - * Optional filter expression for metadata filtering during vector - * search. + * The metadata filter of queryVectors, as a JSON object with the filter + * operators of S3 Vectors, such as $eq, $in or $and. Only the vectors + * whose metadata match the filter are returned. * * The option is a: {@code String} type. * @@ -2637,7 +2683,10 @@ public String awsS3VectorsMetadataFilter() { return "CamelAwsS3VectorsMetadataFilter"; } /** - * The similarity score of the returned vector. + * (deprecated) Never set: S3 Vectors returns a distance, see + * CamelAwsS3VectorsDistance. Deprecation note: S3 Vectors returns a + * distance, not a similarity score, and the component never set this + * header. Use the CamelAwsS3VectorsDistance header instead. * * The option is a: {@code Float} type. * @@ -2645,9 +2694,23 @@ public String awsS3VectorsMetadataFilter() { * * @return the name of the header {@code AwsS3VectorsSimilarityScore}. */ + @Deprecated public String awsS3VectorsSimilarityScore() { return "CamelAwsS3VectorsSimilarityScore"; } + /** + * The distance of the consumed vector from the query vector, computed + * with the distance metric of the index: the lower, the closer. + * + * The option is a: {@code Float} type. + * + * Group: consumer + * + * @return the name of the header {@code AwsS3VectorsDistance}. + */ + public String awsS3VectorsDistance() { + return "CamelAwsS3VectorsDistance"; + } /** * The number of vectors returned in the result. * diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Polly2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Polly2EndpointBuilderFactory.java index bee7a654ee408..c1c8c0c0c1de5 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Polly2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Polly2EndpointBuilderFactory.java @@ -170,13 +170,13 @@ default Polly2EndpointBuilder operation(String operation) { return this; } /** - * The audio output format. + * The audio output format. When neither this option nor the + * CamelAwsPollyOutputFormat header is set, MP3 is used. * * The option is a: * software.amazon.awssdk.services.polly.model.OutputFormat * type. * - * Default: MP3 * Group: producer * * @param outputFormat the value to set @@ -187,13 +187,13 @@ default Polly2EndpointBuilder outputFormat(software.amazon.awssdk.services.polly return this; } /** - * The audio output format. + * The audio output format. When neither this option nor the + * CamelAwsPollyOutputFormat header is set, MP3 is used. * * The option will be converted to a * software.amazon.awssdk.services.polly.model.OutputFormat * type. * - * Default: MP3 * Group: producer * * @param outputFormat the value to set @@ -353,13 +353,13 @@ default Polly2EndpointBuilder taskId(String taskId) { return this; } /** - * The type of text input (text or ssml). + * The type of text input (text or ssml). When neither this option nor + * the CamelAwsPollyTextType header is set, TEXT is used. * * The option is a: * software.amazon.awssdk.services.polly.model.TextType * type. * - * Default: TEXT * Group: producer * * @param textType the value to set @@ -370,13 +370,13 @@ default Polly2EndpointBuilder textType(software.amazon.awssdk.services.polly.mod return this; } /** - * The type of text input (text or ssml). + * The type of text input (text or ssml). When neither this option nor + * the CamelAwsPollyTextType header is set, TEXT is used. * * The option will be converted to a * software.amazon.awssdk.services.polly.model.TextType * type. * - * Default: TEXT * Group: producer * * @param textType the value to set @@ -1050,6 +1050,23 @@ public String awsPollyContentType() { public String awsPollyRequestCharacters() { return "CamelAwsPollyRequestCharacters"; } + /** + * The token of the next page for the describeVoices, listLexicons and + * listSpeechSynthesisTasks operations. It is in and out: when set, it + * is sent as the next token of the request, and after the call it holds + * the next token of the response, or it is removed when the response + * has none, which is the last page. Remove it before calling a + * different list operation. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code AwsPollyNextToken}. + */ + public String awsPollyNextToken() { + return "CamelAwsPollyNextToken"; + } } static Polly2EndpointBuilder endpointBuilder(String componentName, String path) { class Polly2EndpointBuilderImpl extends AbstractEndpointBuilder implements Polly2EndpointBuilder, AdvancedPolly2EndpointBuilder { diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SecurityHubEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SecurityHubEndpointBuilderFactory.java index a8251ef413498..ef6d14fe1d55d 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SecurityHubEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SecurityHubEndpointBuilderFactory.java @@ -608,7 +608,9 @@ public String awsSecurityHubOperation() { return "CamelAwsSecurityHubOperation"; } /** - * The list of findings to import. + * The findings to import, as a List of AwsSecurityFinding. When not + * set, the findings are taken from the body: a Collection or an array + * of AwsSecurityFinding, or one AwsSecurityFinding. * * The option is a: {@code List} type. * @@ -633,7 +635,9 @@ public String awsSecurityHubFindingIdentifiers() { return "CamelAwsSecurityHubFindingIdentifiers"; } /** - * The filters to apply when retrieving findings. + * The filters to apply when retrieving findings. It must be an + * AwsSecurityFindingFilters object; a value of another type, such as + * JSON text, fails the exchange. * * The option is a: {@code AwsSecurityFindingFilters} type. * @@ -645,7 +649,8 @@ public String awsSecurityHubFilters() { return "CamelAwsSecurityHubFilters"; } /** - * The note to add to findings during update. + * The note to add to findings during update. It must be a NoteUpdate + * object; a value of another type fails the exchange. * * The option is a: {@code NoteUpdate} type. * @@ -657,7 +662,8 @@ public String awsSecurityHubNote() { return "CamelAwsSecurityHubNote"; } /** - * The severity to set on findings during update. + * The severity to set on findings during update. It must be a + * SeverityUpdate object; a value of another type fails the exchange. * * The option is a: {@code SeverityUpdate} type. * @@ -669,7 +675,8 @@ public String awsSecurityHubSeverity() { return "CamelAwsSecurityHubSeverity"; } /** - * The workflow status to set on findings. + * The workflow status to set on findings. It must be a WorkflowUpdate + * object; a value of another type fails the exchange. * * The option is a: {@code WorkflowUpdate} type. * @@ -755,7 +762,10 @@ public String awsSecurityHubTypes() { return "CamelAwsSecurityHubTypes"; } /** - * The token for the next set of results. + * The token for the next set of results. It is both an input and an + * output: the operation sends it and then sets it to the token of the + * response, so remove it before calling a different list operation in + * the same route. * * The option is a: {@code String} type. * @@ -779,7 +789,9 @@ public String awsSecurityHubMaxResults() { return "CamelAwsSecurityHubMaxResults"; } /** - * The count of findings that failed to import. + * The count of findings that failed to import. When it is more than + * zero, the exchange fails with an exception naming the failed + * findings. * * The option is a: {@code Integer} type. * diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Textract2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Textract2EndpointBuilderFactory.java index 4832ae84ead20..2a459681fe880 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Textract2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Textract2EndpointBuilderFactory.java @@ -689,8 +689,11 @@ public String awsTextractS3ObjectVersion() { return "CamelAwsTextractS3ObjectVersion"; } /** - * The job ID for async operations (StartDocumentTextDetection, - * StartDocumentAnalysis). + * The job ID of an async operation. It is set from the response of the + * startDocumentTextDetection, startDocumentAnalysis and + * startExpenseAnalysis operations, and it is the job ID of the + * getDocumentTextDetection, getDocumentAnalysis and getExpenseAnalysis + * operations (else the body is used as the job ID). * * The option is a: {@code String} type. * @@ -727,8 +730,11 @@ public String awsTextractNextToken() { return "CamelAwsTextractNextToken"; } /** - * The feature types for document analysis (TABLES, FORMS, SIGNATURES, - * etc.). + * The feature types of the analyzeDocument and startDocumentAnalysis + * operations (TABLES, FORMS, QUERIES, SIGNATURES, LAYOUT): a list of + * FeatureType or String values, or a String with comma separated + * values. The String values are case-insensitive. Without this header + * TABLES and FORMS are used. * * The option is a: {@code List} type. * diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Timestream2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Timestream2EndpointBuilderFactory.java index fc28d881311e5..c1dc830be3893 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Timestream2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Timestream2EndpointBuilderFactory.java @@ -646,7 +646,10 @@ public String awsTimestreamOperation() { return "CamelAwsTimestreamOperation"; } /** - * Represents a time-series data point being written into Timestream. + * The common attributes of the writeRecords operation: the dimensions, + * measure and time values that all the records of the + * CamelAwsTimestreamRecordList header share. It is not written as a + * record of its own. * * The option is a: {@code * software.amazon.awssdk.services.timestreamwrite.model.Record} type. @@ -659,7 +662,7 @@ public String awsTimestreamRecord() { return "CamelAwsTimestreamRecord"; } /** - * List of Records. + * The records of the writeRecords operation. * * The option is a: {@code List} type. * @@ -744,9 +747,9 @@ public String awsTimestreamTargetTableName() { return "CamelAwsTimestreamTargetTableName"; } /** - * Record version. + * The record version of the createBatchLoadTask operation. * - * The option is a: {@code String} type. + * The option is a: {@code Long} type. * * Group: producer * @@ -825,7 +828,7 @@ public String awsTimestreamRetentionProperties() { return "CamelAwsTimestreamRetentionProperties"; } /** - * Timestream Table Magentic Store Write properties. + * Timestream Table Magnetic Store Write properties. * * The option is a: {@code * software.amazon.awssdk.services.timestreamwrite.model.MagneticStoreWriteProperties} type. @@ -1083,7 +1086,7 @@ public String awsTimestreamErrorReportS3EncryptionOption() { return "CamelAwsTimestreamErrorReportS3EncryptionOption"; } /** - * he ARN for the IAM role that Timestream will assume when running the + * The ARN for the IAM role that Timestream will assume when running the * scheduled query. * * The option is a: {@code String} type. diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Translate2EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Translate2EndpointBuilderFactory.java index 6207ef5909085..cc542c57df454 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Translate2EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/Translate2EndpointBuilderFactory.java @@ -45,7 +45,9 @@ default AdvancedTranslate2EndpointBuilder advanced() { } /** - * Being able to autodetect the source language. + * Being able to autodetect the source language. When enabled, the + * source language is auto, and the sourceLanguage option and the + * CamelAwsTranslateSourceLanguage header are not used. * * The option is a: boolean type. * @@ -60,7 +62,9 @@ default Translate2EndpointBuilder autodetectSourceLanguage(boolean autodetectSou return this; } /** - * Being able to autodetect the source language. + * Being able to autodetect the source language. When enabled, the + * source language is auto, and the sourceLanguage option and the + * CamelAwsTranslateSourceLanguage header are not used. * * The option will be converted to a boolean type. * @@ -188,7 +192,9 @@ default Translate2EndpointBuilder region(String region) { return this; } /** - * Source language to use. + * Source language to use. When not set, the + * CamelAwsTranslateSourceLanguage header is used. It is not used when + * autodetectSourceLanguage is enabled. * * The option is a: java.lang.String type. * @@ -202,7 +208,8 @@ default Translate2EndpointBuilder sourceLanguage(String sourceLanguage) { return this; } /** - * Target language to use. + * Target language to use. When not set, the + * CamelAwsTranslateTargetLanguage header is used. * * The option is a: java.lang.String type. *