This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 81d06027f606 CAMEL-26249: camel-google-text-to-speech -
pojoRequest=true runs the operation, and listVoices lists all voices unless
languageCode is set (#28961)
81d06027f606 is described below
commit 81d06027f6069d9706afd6679e1b3b561474c5d1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Nov 2 22:56:13 2026 +0100
CAMEL-26249: camel-google-text-to-speech - pojoRequest=true runs the
operation, and listVoices lists all voices unless languageCode is set (#28961)
* CAMEL-26249: camel-google-text-to-speech - pojoRequest=true runs the
operation, and listVoices lists all voices unless languageCode is set
* CAMEL-26249: camel-google-text-to-speech - upgrade guide: with
pojoRequest=true the endpoint path must name the operation
Signed-off-by: Andrea Cosentino <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../catalog/components/google-text-to-speech.json | 6 +-
.../docs/google-text-to-speech-component.adoc | 32 +++-
.../google/texttospeech/google-text-to-speech.json | 6 +-
.../main/docs/google-text-to-speech-component.adoc | 32 +++-
.../GoogleCloudTextToSpeechConfiguration.java | 13 +-
.../GoogleCloudTextToSpeechEndpoint.java | 16 +-
.../GoogleCloudTextToSpeechProducer.java | 48 +++--
.../unit/GoogleCloudTextToSpeechProducerTest.java | 210 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 16 ++
...gleCloudTextToSpeechEndpointBuilderFactory.java | 4 +-
10 files changed, 344 insertions(+), 39 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-text-to-speech.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-text-to-speech.json
index 6b7c7c2d758d..77722aa67476 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-text-to-speech.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-text-to-speech.json
@@ -32,14 +32,14 @@
"CamelGoogleCloudTextToSpeechResponseObject": { "index": 1, "kind":
"header", "displayName": "", "group": "producer", "label": "", "required":
false, "javaType": "com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The response object resulting from the Google Cloud
Text-to-Speech API invocation", "constantName":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpe [...]
},
"bodyTypes": {
- "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"operation", "value": "synthesize", "carriers": [ "text", "bytes", "stream",
"file" ] }, { "option": "operation", "value": "listVoices", "carriers": [ "any"
] }, { "option": "pojoRequest", "value": "true", "carriers": [ "pojo" ],
"javaType": "com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest" } ],
"description": "The text to speak for synthesize: bytes, a stream or a file are
read as text, a Map or a POJO is spok [...]
- "producerOut": { "carriers": [ "bytes", "list" ], "byOption": [ {
"option": "operation", "value": "synthesize", "carriers": [ "bytes" ],
"javaType": "byte[]" }, { "option": "operation", "value": "listVoices",
"carriers": [ "list" ], "javaType":
"java.util.List<com.google.cloud.texttospeech.v1.Voice>" }, { "option":
"pojoRequest", "value": "true", "carriers": [ "bytes" ], "javaType": "byte[]" }
], "description": "The audio of synthesize as bytes, in the audioEncoding
option (MP3 by de [...]
+ "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"operation", "value": "synthesize", "carriers": [ "text", "bytes", "stream",
"file" ] }, { "option": "operation", "value": "listVoices", "carriers": [ "any"
] }, { "option": "pojoRequest", "value": "true", "carriers": [ "pojo" ] } ],
"description": "The text to speak for synthesize: bytes, a stream or a file are
read as text, a Map or a POJO is spoken as its toString(), so take the text out
of it first, and no body fail [...]
+ "producerOut": { "carriers": [ "bytes", "list" ], "byOption": [ {
"option": "operation", "value": "synthesize", "carriers": [ "bytes" ],
"javaType": "byte[]" }, { "option": "operation", "value": "listVoices",
"carriers": [ "list" ], "javaType":
"java.util.List<com.google.cloud.texttospeech.v1.Voice>" } ], "description":
"The audio of synthesize as bytes, in the audioEncoding option (MP3 by
default), with the SynthesizeSpeechResponse in the
CamelGoogleCloudTextToSpeechResponseObject h [...]
},
"properties": {
"operation": { "index": 0, "kind": "path", "displayName": "Operation",
"group": "common", "label": "common", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The operation name" },
"serviceAccountKey": { "index": 1, "kind": "parameter", "displayName":
"Service Account Key", "group": "common", "label": "common", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "Service account key to
authenticate an application as a service a [...]
"audioEncoding": { "index": 2, "kind": "parameter", "displayName": "Audio
Encoding", "group": "producer", "label": "producer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "MP3", "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The audio encoding of
the output. Supported val [...]
- "languageCode": { "index": 3, "kind": "parameter", "displayName":
"Language Code", "group": "producer", "label": "producer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "en-US",
"configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The language code for
the voice (e.g., en-US, f [...]
+ "languageCode": { "index": 3, "kind": "parameter", "displayName":
"Language Code", "group": "producer", "label": "producer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "en-US",
"configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The language code for
the voice (e.g., en-US, f [...]
"pitch": { "index": 4, "kind": "parameter", "displayName": "Pitch",
"group": "producer", "label": "producer", "required": false, "type": "number",
"javaType": "java.lang.Double", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The pitch, from -20.0
to 20.0 semitones. Default is 0.0" },
"pojoRequest": { "index": 5, "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.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "Specifies if the
request is a pojo request" },
"speakingRate": { "index": 6, "kind": "parameter", "displayName":
"Speaking Rate", "group": "producer", "label": "producer", "required": false,
"type": "number", "javaType": "java.lang.Double", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The speaking rate, from
0.25 to 4.0. Default is 1.0" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-text-to-speech-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-text-to-speech-component.adoc
index f7bbfc8957c6..d66caa0ebd86 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-text-to-speech-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/google-text-to-speech-component.adoc
@@ -116,7 +116,9 @@ For the `synthesize` operation, the message body should
contain the text to synt
For the `listVoices` operation, the message body is ignored.
-When `pojoRequest=true`, the body should be a
`com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest` instance instead.
+When `pojoRequest=true`, the body should be the request of the Google Cloud
SDK for the operation instead: a
+`com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest` for `synthesize`,
and a
+`com.google.cloud.texttospeech.v1.ListVoicesRequest` for `listVoices`. Another
body fails with an `InvalidPayloadException`.
=== Google Cloud Text-to-Speech Producer operations
@@ -126,7 +128,8 @@ Google Cloud Text-to-Speech component provides the
following operations on the p
- listVoices
The operation is specified as part of the endpoint URI (e.g.,
`google-text-to-speech://synthesize`).
-You can override the operation at runtime by setting the
`GoogleCloudTextToSpeechOperation` message header.
+You can override the operation at runtime by setting the
`CamelGoogleCloudTextToSpeechOperation` message header,
+also when `pojoRequest=true`.
=== Advanced component configuration
@@ -328,7 +331,9 @@ YAML::
----
====
-This operation will return a `List<Voice>` with the available voices for the
specified language.
+This operation will return a `List<Voice>` with the available voices for the
language in the `languageCode` option.
+Without the `languageCode` option, it returns the voices of all languages: the
default value of the option (`en-US`)
+applies to `synthesize` only.
- Using POJO request for full control:
@@ -362,6 +367,27 @@ from("direct:start")
When using `pojoRequest=true`, the body should be a `SynthesizeSpeechRequest`
and the audio bytes are returned as body.
+- Using POJO request to list voices:
+
+._Java-only: Google Cloud SDK ListVoicesRequest builder_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ ListVoicesRequest request = ListVoicesRequest.newBuilder()
+ .setLanguageCode("fr-FR")
+ .build();
+ exchange.getIn().setBody(request);
+ })
+
.to("google-text-to-speech://listVoices?serviceAccountKey=/home/user/Downloads/my-key.json&pojoRequest=true")
+ .log("body:${body}")
+ .to("mock:result");
+----
+
+When using `pojoRequest=true` with `listVoices`, the body should be a
`ListVoicesRequest` and the `List<Voice>` is returned as body.
+The `languageCode` option is not used then: the request says which voices to
list.
+
- Overriding the operation at runtime via header:
[tabs]
diff --git
a/components/camel-google/camel-google-text-to-speech/src/generated/resources/META-INF/org/apache/camel/component/google/texttospeech/google-text-to-speech.json
b/components/camel-google/camel-google-text-to-speech/src/generated/resources/META-INF/org/apache/camel/component/google/texttospeech/google-text-to-speech.json
index 6b7c7c2d758d..77722aa67476 100644
---
a/components/camel-google/camel-google-text-to-speech/src/generated/resources/META-INF/org/apache/camel/component/google/texttospeech/google-text-to-speech.json
+++
b/components/camel-google/camel-google-text-to-speech/src/generated/resources/META-INF/org/apache/camel/component/google/texttospeech/google-text-to-speech.json
@@ -32,14 +32,14 @@
"CamelGoogleCloudTextToSpeechResponseObject": { "index": 1, "kind":
"header", "displayName": "", "group": "producer", "label": "", "required":
false, "javaType": "com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The response object resulting from the Google Cloud
Text-to-Speech API invocation", "constantName":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpe [...]
},
"bodyTypes": {
- "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"operation", "value": "synthesize", "carriers": [ "text", "bytes", "stream",
"file" ] }, { "option": "operation", "value": "listVoices", "carriers": [ "any"
] }, { "option": "pojoRequest", "value": "true", "carriers": [ "pojo" ],
"javaType": "com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest" } ],
"description": "The text to speak for synthesize: bytes, a stream or a file are
read as text, a Map or a POJO is spok [...]
- "producerOut": { "carriers": [ "bytes", "list" ], "byOption": [ {
"option": "operation", "value": "synthesize", "carriers": [ "bytes" ],
"javaType": "byte[]" }, { "option": "operation", "value": "listVoices",
"carriers": [ "list" ], "javaType":
"java.util.List<com.google.cloud.texttospeech.v1.Voice>" }, { "option":
"pojoRequest", "value": "true", "carriers": [ "bytes" ], "javaType": "byte[]" }
], "description": "The audio of synthesize as bytes, in the audioEncoding
option (MP3 by de [...]
+ "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"operation", "value": "synthesize", "carriers": [ "text", "bytes", "stream",
"file" ] }, { "option": "operation", "value": "listVoices", "carriers": [ "any"
] }, { "option": "pojoRequest", "value": "true", "carriers": [ "pojo" ] } ],
"description": "The text to speak for synthesize: bytes, a stream or a file are
read as text, a Map or a POJO is spoken as its toString(), so take the text out
of it first, and no body fail [...]
+ "producerOut": { "carriers": [ "bytes", "list" ], "byOption": [ {
"option": "operation", "value": "synthesize", "carriers": [ "bytes" ],
"javaType": "byte[]" }, { "option": "operation", "value": "listVoices",
"carriers": [ "list" ], "javaType":
"java.util.List<com.google.cloud.texttospeech.v1.Voice>" } ], "description":
"The audio of synthesize as bytes, in the audioEncoding option (MP3 by
default), with the SynthesizeSpeechResponse in the
CamelGoogleCloudTextToSpeechResponseObject h [...]
},
"properties": {
"operation": { "index": 0, "kind": "path", "displayName": "Operation",
"group": "common", "label": "common", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The operation name" },
"serviceAccountKey": { "index": 1, "kind": "parameter", "displayName":
"Service Account Key", "group": "common", "label": "common", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "Service account key to
authenticate an application as a service a [...]
"audioEncoding": { "index": 2, "kind": "parameter", "displayName": "Audio
Encoding", "group": "producer", "label": "producer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "MP3", "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The audio encoding of
the output. Supported val [...]
- "languageCode": { "index": 3, "kind": "parameter", "displayName":
"Language Code", "group": "producer", "label": "producer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "en-US",
"configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The language code for
the voice (e.g., en-US, f [...]
+ "languageCode": { "index": 3, "kind": "parameter", "displayName":
"Language Code", "group": "producer", "label": "producer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "en-US",
"configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The language code for
the voice (e.g., en-US, f [...]
"pitch": { "index": 4, "kind": "parameter", "displayName": "Pitch",
"group": "producer", "label": "producer", "required": false, "type": "number",
"javaType": "java.lang.Double", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The pitch, from -20.0
to 20.0 semitones. Default is 0.0" },
"pojoRequest": { "index": 5, "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.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "Specifies if the
request is a pojo request" },
"speakingRate": { "index": 6, "kind": "parameter", "displayName":
"Speaking Rate", "group": "producer", "label": "producer", "required": false,
"type": "number", "javaType": "java.lang.Double", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConfiguration",
"configurationField": "configuration", "description": "The speaking rate, from
0.25 to 4.0. Default is 1.0" },
diff --git
a/components/camel-google/camel-google-text-to-speech/src/main/docs/google-text-to-speech-component.adoc
b/components/camel-google/camel-google-text-to-speech/src/main/docs/google-text-to-speech-component.adoc
index f7bbfc8957c6..d66caa0ebd86 100644
---
a/components/camel-google/camel-google-text-to-speech/src/main/docs/google-text-to-speech-component.adoc
+++
b/components/camel-google/camel-google-text-to-speech/src/main/docs/google-text-to-speech-component.adoc
@@ -116,7 +116,9 @@ For the `synthesize` operation, the message body should
contain the text to synt
For the `listVoices` operation, the message body is ignored.
-When `pojoRequest=true`, the body should be a
`com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest` instance instead.
+When `pojoRequest=true`, the body should be the request of the Google Cloud
SDK for the operation instead: a
+`com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest` for `synthesize`,
and a
+`com.google.cloud.texttospeech.v1.ListVoicesRequest` for `listVoices`. Another
body fails with an `InvalidPayloadException`.
=== Google Cloud Text-to-Speech Producer operations
@@ -126,7 +128,8 @@ Google Cloud Text-to-Speech component provides the
following operations on the p
- listVoices
The operation is specified as part of the endpoint URI (e.g.,
`google-text-to-speech://synthesize`).
-You can override the operation at runtime by setting the
`GoogleCloudTextToSpeechOperation` message header.
+You can override the operation at runtime by setting the
`CamelGoogleCloudTextToSpeechOperation` message header,
+also when `pojoRequest=true`.
=== Advanced component configuration
@@ -328,7 +331,9 @@ YAML::
----
====
-This operation will return a `List<Voice>` with the available voices for the
specified language.
+This operation will return a `List<Voice>` with the available voices for the
language in the `languageCode` option.
+Without the `languageCode` option, it returns the voices of all languages: the
default value of the option (`en-US`)
+applies to `synthesize` only.
- Using POJO request for full control:
@@ -362,6 +367,27 @@ from("direct:start")
When using `pojoRequest=true`, the body should be a `SynthesizeSpeechRequest`
and the audio bytes are returned as body.
+- Using POJO request to list voices:
+
+._Java-only: Google Cloud SDK ListVoicesRequest builder_
+
+[source,java]
+----
+from("direct:start")
+ .process(exchange -> {
+ ListVoicesRequest request = ListVoicesRequest.newBuilder()
+ .setLanguageCode("fr-FR")
+ .build();
+ exchange.getIn().setBody(request);
+ })
+
.to("google-text-to-speech://listVoices?serviceAccountKey=/home/user/Downloads/my-key.json&pojoRequest=true")
+ .log("body:${body}")
+ .to("mock:result");
+----
+
+When using `pojoRequest=true` with `listVoices`, the body should be a
`ListVoicesRequest` and the `List<Voice>` is returned as body.
+The `languageCode` option is not used then: the request says which voices to
list.
+
- Overriding the operation at runtime via header:
[tabs]
diff --git
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechConfiguration.java
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechConfiguration.java
index c7da0713cdb0..b29c6a9ff906 100644
---
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechConfiguration.java
+++
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechConfiguration.java
@@ -35,8 +35,11 @@ public class GoogleCloudTextToSpeechConfiguration implements
Cloneable, GoogleCo
private String serviceAccountKey;
@UriParam(label = "producer", defaultValue = "en-US",
- description = "The language code for the voice (e.g., en-US,
fr-FR)")
+ description = "The language code for the voice (e.g., en-US,
fr-FR). The default applies to synthesize"
+ + " only: listVoices lists the voices of all
languages unless this option is set")
private String languageCode = "en-US";
+ // whether languageCode was set: listVoices filters by the language only
then, not by the default
+ private boolean languageCodeSet;
@UriParam(label = "producer",
description = "The name of the voice. If not set, the service
selects a default voice for the specified language")
@@ -99,6 +102,14 @@ public class GoogleCloudTextToSpeechConfiguration
implements Cloneable, GoogleCo
*/
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
+ this.languageCodeSet = true;
+ }
+
+ /**
+ * Whether the language code was set, as opposed to holding the default
value.
+ */
+ boolean isLanguageCodeSet() {
+ return languageCodeSet;
}
public String getVoiceName() {
diff --git
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechEndpoint.java
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechEndpoint.java
index d97c942a3b12..baf2fc249b16 100644
---
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechEndpoint.java
+++
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechEndpoint.java
@@ -43,25 +43,25 @@ import org.apache.camel.util.ObjectHelper;
carriers = {
Carrier.TEXT,
Carrier.BYTES, Carrier.STREAM, Carrier.FILE }),
@BodyType.When(option = "operation",
value = "listVoices", carriers = Carrier.ANY),
- @BodyType.When(option =
"pojoRequest", value = "true", carriers = Carrier.POJO,
- javaType =
"com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest") },
+ @BodyType.When(option =
"pojoRequest", value = "true", carriers = Carrier.POJO) },
description = "The text to speak for
synthesize: bytes, a stream or a file are read as"
+ " text, a Map or a POJO is
spoken as its toString(), so take the text out of"
+ " it first, and no body
fails. listVoices does not use the body. With"
- + " pojoRequest=true a
SynthesizeSpeechRequest of the Google Cloud SDK, whatever"
- + " the operation: another
body fails"),
+ + " pojoRequest=true the
request of the Google Cloud SDK for the operation: a"
+ + " SynthesizeSpeechRequest
for synthesize, a ListVoicesRequest for listVoices;"
+ + " another body fails"),
producerOut = @BodyType(value = { Carrier.BYTES, Carrier.LIST },
byOption = {
@BodyType.When(option =
"operation", value = "synthesize", carriers = Carrier.BYTES,
javaType = "byte[]"),
@BodyType.When(option =
"operation", value = "listVoices", carriers = Carrier.LIST,
- javaType =
"java.util.List<com.google.cloud.texttospeech.v1.Voice>"),
- @BodyType.When(option =
"pojoRequest", value = "true", carriers = Carrier.BYTES,
- javaType = "byte[]")
},
+ javaType =
"java.util.List<com.google.cloud.texttospeech.v1.Voice>") },
description = "The audio of synthesize as
bytes, in the audioEncoding option (MP3 by"
+ " default), with the
SynthesizeSpeechResponse in the"
+ "
CamelGoogleCloudTextToSpeechResponseObject header. The List of the voices"
- + " for listVoices, of the
languageCode option (en-US by default)"))
+ + " for listVoices: of the
language in the languageCode option when it is"
+ + " set, of all languages
when it is not, and with pojoRequest=true of the"
+ + " ListVoicesRequest"))
public class GoogleCloudTextToSpeechEndpoint extends DefaultEndpoint
implements EndpointServiceLocation {
@UriParam
diff --git
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechProducer.java
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechProducer.java
index 3ccb7322786b..fa5d747cea06 100644
---
a/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechProducer.java
+++
b/components/camel-google/camel-google-text-to-speech/src/main/java/org/apache/camel/component/google/texttospeech/GoogleCloudTextToSpeechProducer.java
@@ -29,6 +29,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.support.DefaultProducer;
+import org.apache.camel.util.ObjectHelper;
public class GoogleCloudTextToSpeechProducer extends DefaultProducer {
@@ -41,24 +42,28 @@ public class GoogleCloudTextToSpeechProducer extends
DefaultProducer {
@Override
public void process(final Exchange exchange) throws Exception {
- if (getConfiguration().isPojoRequest()) {
- processPojo(exchange);
- } else {
- GoogleCloudTextToSpeechOperations operation =
determineOperation(exchange);
- switch (operation) {
- case synthesize:
+ GoogleCloudTextToSpeechOperations operation =
determineOperation(exchange);
+ switch (operation) {
+ case synthesize:
+ if (getConfiguration().isPojoRequest()) {
+ processSynthesizePojo(exchange);
+ } else {
processSynthesize(exchange);
- break;
- case listVoices:
+ }
+ break;
+ case listVoices:
+ if (getConfiguration().isPojoRequest()) {
+ processListVoicesPojo(exchange);
+ } else {
processListVoices(exchange);
- break;
- default:
- throw new IllegalArgumentException("Unsupported operation:
" + operation);
- }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported operation: " +
operation);
}
}
- private void processPojo(Exchange exchange) throws InvalidPayloadException
{
+ private void processSynthesizePojo(Exchange exchange) throws
InvalidPayloadException {
TextToSpeechClient client = endpoint.getClient();
SynthesizeSpeechRequest request =
exchange.getIn().getMandatoryBody(SynthesizeSpeechRequest.class);
@@ -103,15 +108,24 @@ public class GoogleCloudTextToSpeechProducer extends
DefaultProducer {
}
private void processListVoices(Exchange exchange) {
- TextToSpeechClient client = endpoint.getClient();
-
ListVoicesRequest.Builder requestBuilder =
ListVoicesRequest.newBuilder();
- if (getConfiguration().getLanguageCode() != null) {
+ // only a language code that was set filters the voices: the default
(en-US) is for synthesize
+ if (getConfiguration().isLanguageCodeSet() &&
ObjectHelper.isNotEmpty(getConfiguration().getLanguageCode())) {
requestBuilder.setLanguageCode(getConfiguration().getLanguageCode());
}
- ListVoicesResponse response =
client.listVoices(requestBuilder.build());
+ listVoices(exchange, requestBuilder.build());
+ }
+
+ private void processListVoicesPojo(Exchange exchange) throws
InvalidPayloadException {
+ listVoices(exchange,
exchange.getIn().getMandatoryBody(ListVoicesRequest.class));
+ }
+
+ private void listVoices(Exchange exchange, ListVoicesRequest request) {
+ TextToSpeechClient client = endpoint.getClient();
+
+ ListVoicesResponse response = client.listVoices(request);
Message message = exchange.getMessage();
message.setBody(response.getVoicesList());
diff --git
a/components/camel-google/camel-google-text-to-speech/src/test/java/org/apache/camel/component/google/texttospeech/unit/GoogleCloudTextToSpeechProducerTest.java
b/components/camel-google/camel-google-text-to-speech/src/test/java/org/apache/camel/component/google/texttospeech/unit/GoogleCloudTextToSpeechProducerTest.java
new file mode 100644
index 000000000000..2cbdf846475c
--- /dev/null
+++
b/components/camel-google/camel-google-text-to-speech/src/test/java/org/apache/camel/component/google/texttospeech/unit/GoogleCloudTextToSpeechProducerTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.texttospeech.unit;
+
+import java.util.List;
+
+import com.google.cloud.texttospeech.v1.AudioConfig;
+import com.google.cloud.texttospeech.v1.ListVoicesRequest;
+import com.google.cloud.texttospeech.v1.ListVoicesResponse;
+import com.google.cloud.texttospeech.v1.SynthesisInput;
+import com.google.cloud.texttospeech.v1.SynthesizeSpeechRequest;
+import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse;
+import com.google.cloud.texttospeech.v1.TextToSpeechClient;
+import com.google.cloud.texttospeech.v1.Voice;
+import com.google.cloud.texttospeech.v1.VoiceSelectionParams;
+import com.google.protobuf.ByteString;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import
org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechComponent;
+import
org.apache.camel.component.google.texttospeech.GoogleCloudTextToSpeechConstants;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+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.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;
+
+/**
+ * Verifies the requests the producer sends to a mocked {@link
TextToSpeechClient}.
+ */
+class GoogleCloudTextToSpeechProducerTest extends CamelTestSupport {
+
+ private static final Voice VOICE =
Voice.newBuilder().setName("fr-FR-Wavenet-A").addLanguageCodes("fr-FR").build();
+ private static final byte[] AUDIO = { 1, 2, (byte) 0xff };
+
+ private final TextToSpeechClient client = mock(TextToSpeechClient.class);
+
+ @Override
+ protected void bindToRegistry(Registry registry) {
+ registry.bind("ttsClient", client);
+ }
+
+ @BeforeEach
+ void stubClient() {
+ SynthesizeSpeechResponse response
+ =
SynthesizeSpeechResponse.newBuilder().setAudioContent(ByteString.copyFrom(AUDIO)).build();
+ when(client.listVoices(any(ListVoicesRequest.class)))
+
.thenReturn(ListVoicesResponse.newBuilder().addVoices(VOICE).build());
+
when(client.synthesizeSpeech(any(SynthesizeSpeechRequest.class))).thenReturn(response);
+ when(client.synthesizeSpeech(any(SynthesisInput.class),
any(VoiceSelectionParams.class), any(AudioConfig.class)))
+ .thenReturn(response);
+ }
+
+ @Test
+ void listVoicesWithPojoRequestSendsTheListVoicesRequestOfTheBody() {
+ ListVoicesRequest request =
ListVoicesRequest.newBuilder().setLanguageCode("fr-FR").build();
+
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient&pojoRequest=true",
+ e -> e.getIn().setBody(request));
+
+ assertNull(exchange.getException());
+ assertEquals(List.of(VOICE),
exchange.getMessage().getBody(List.class));
+ verify(client).listVoices(request);
+ verify(client,
never()).synthesizeSpeech(any(SynthesizeSpeechRequest.class));
+ }
+
+ @Test
+ void listVoicesWithPojoRequestDoesNotSynthesizeASynthesizeSpeechRequest() {
+ SynthesizeSpeechRequest request = SynthesizeSpeechRequest.newBuilder()
+
.setInput(SynthesisInput.newBuilder().setText("Hello")).build();
+
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient&pojoRequest=true",
+ e -> e.getIn().setBody(request));
+
+ assertInstanceOf(InvalidPayloadException.class,
exchange.getException());
+ verify(client,
never()).synthesizeSpeech(any(SynthesizeSpeechRequest.class));
+ verify(client, never()).listVoices(any(ListVoicesRequest.class));
+ }
+
+ @Test
+ void listVoicesWithPojoRequestAndNoBodyFails() {
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient&pojoRequest=true",
+ e -> e.getIn().setBody(null));
+
+ assertInstanceOf(InvalidPayloadException.class,
exchange.getException());
+ verify(client, never()).listVoices(any(ListVoicesRequest.class));
+ }
+
+ @Test
+ void theOperationHeaderSelectsListVoicesWithPojoRequest() {
+ ListVoicesRequest request =
ListVoicesRequest.newBuilder().setLanguageCode("de-DE").build();
+
+ Exchange exchange =
template.request("google-text-to-speech:synthesize?client=#ttsClient&pojoRequest=true",
e -> {
+ e.getIn().setHeader(GoogleCloudTextToSpeechConstants.OPERATION,
"listVoices");
+ e.getIn().setBody(request);
+ });
+
+ assertNull(exchange.getException());
+ assertEquals(List.of(VOICE),
exchange.getMessage().getBody(List.class));
+ verify(client).listVoices(request);
+ }
+
+ @Test
+ void aPathThatIsNoOperationFailsWithPojoRequest() {
+ SynthesizeSpeechRequest request = SynthesizeSpeechRequest.newBuilder()
+
.setInput(SynthesisInput.newBuilder().setText("Hello")).build();
+
+ Exchange exchange =
template.request("google-text-to-speech:speak?client=#ttsClient&pojoRequest=true",
+ e -> e.getIn().setBody(request));
+
+ assertInstanceOf(IllegalArgumentException.class,
exchange.getException());
+ verify(client,
never()).synthesizeSpeech(any(SynthesizeSpeechRequest.class));
+ }
+
+ @Test
+ void synthesizeWithPojoRequestSendsTheSynthesizeSpeechRequestOfTheBody() {
+ SynthesizeSpeechRequest request = SynthesizeSpeechRequest.newBuilder()
+ .setInput(SynthesisInput.newBuilder().setText("Hello"))
+
.setVoice(VoiceSelectionParams.newBuilder().setLanguageCode("it-IT"))
+ .build();
+
+ Exchange exchange =
template.request("google-text-to-speech:synthesize?client=#ttsClient&pojoRequest=true",
+ e -> e.getIn().setBody(request));
+
+ assertNull(exchange.getException());
+ assertArrayEquals(AUDIO, exchange.getMessage().getBody(byte[].class));
+ verify(client).synthesizeSpeech(request);
+ }
+
+ @Test
+ void listVoicesWithoutTheLanguageCodeOptionListsTheVoicesOfAllLanguages() {
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient",
+ e -> e.getIn().setBody(null));
+
+ assertNull(exchange.getException());
+ assertEquals("", sentListVoicesRequest().getLanguageCode());
+ assertEquals(List.of(VOICE),
exchange.getMessage().getBody(List.class));
+ }
+
+ @Test
+ void listVoicesWithTheLanguageCodeOptionListsTheVoicesOfThatLanguage() {
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient&languageCode=fr-FR",
+ e -> e.getIn().setBody(null));
+
+ assertNull(exchange.getException());
+ assertEquals("fr-FR", sentListVoicesRequest().getLanguageCode());
+ }
+
+ @Test
+ void listVoicesWithTheDefaultLanguageCodeSetOnTheEndpointFiltersByIt() {
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient&languageCode=en-US",
+ e -> e.getIn().setBody(null));
+
+ assertNull(exchange.getException());
+ assertEquals("en-US", sentListVoicesRequest().getLanguageCode());
+ }
+
+ @Test
+ void listVoicesWithTheLanguageCodeSetOnTheComponentFiltersByIt() {
+ context.getComponent("google-text-to-speech",
GoogleCloudTextToSpeechComponent.class).getConfiguration()
+ .setLanguageCode("de-DE");
+
+ Exchange exchange =
template.request("google-text-to-speech:listVoices?client=#ttsClient",
+ e -> e.getIn().setBody(null));
+
+ assertNull(exchange.getException());
+ assertEquals("de-DE", sentListVoicesRequest().getLanguageCode());
+ }
+
+ @Test
+ void synthesizeStillUsesTheDefaultLanguageCode() {
+ Exchange exchange =
template.request("google-text-to-speech:synthesize?client=#ttsClient",
+ e -> e.getIn().setBody("Hello"));
+
+ assertNull(exchange.getException());
+ ArgumentCaptor<VoiceSelectionParams> voice =
ArgumentCaptor.forClass(VoiceSelectionParams.class);
+ verify(client).synthesizeSpeech(any(SynthesisInput.class),
voice.capture(), any(AudioConfig.class));
+ assertEquals("en-US", voice.getValue().getLanguageCode());
+ assertArrayEquals(AUDIO, exchange.getMessage().getBody(byte[].class));
+ }
+
+ private ListVoicesRequest sentListVoicesRequest() {
+ ArgumentCaptor<ListVoicesRequest> request =
ArgumentCaptor.forClass(ListVoicesRequest.class);
+ verify(client).listVoices(request.capture());
+ return request.getValue();
+ }
+}
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 21b2df6d6dcf..49fb1457cf9c 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
@@ -2489,6 +2489,22 @@ new `compile` option of the endpoint (default `false`).
The module is now loaded
every producer, so stopping and starting a route does not read the module
again: remove the route and add it
again, or restart the application, to load a module that has changed.
+=== camel-google-text-to-speech - listVoices lists the voices of all languages
unless languageCode is set
+
+The `listVoices` operation of `google-text-to-speech` now lists the voices of
all languages when the `languageCode`
+option is not set. Before, it always listed only the voices of `en-US`, the
default value of the option, which is meant
+for `synthesize`. To keep listing the voices of one language only, set the
option on the endpoint, for example
+`google-text-to-speech:listVoices?languageCode=en-US`.
+
+With `pojoRequest=true`, the producer now runs the operation of the endpoint,
or of the
+`CamelGoogleCloudTextToSpeechOperation` header: `listVoices` takes a
`ListVoicesRequest` as body and gives the `List` of
+the voices. Before, it always called `synthesize` with a
`SynthesizeSpeechRequest` body, whatever the operation. A
+`listVoices` endpoint with `pojoRequest=true` now fails with an
`InvalidPayloadException` for a body that is not a
+`ListVoicesRequest`. The path of the endpoint must therefore name the
operation, `synthesize` or `listVoices`, unless
+the header does: an endpoint such as
`google-text-to-speech:speak?pojoRequest=true`, which synthesized before, now
fails
+with an `IllegalArgumentException`. A `CamelGoogleCloudTextToSpeechOperation`
header left on the message by an earlier
+step now applies with `pojoRequest=true` too.
+
=== camel-web3j - the consumer subscribes again after an error
When the subscription of a consumer ends with an error (for example a request
to the node that fails inside
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCloudTextToSpeechEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCloudTextToSpeechEndpointBuilderFactory.java
index b5d02ac6a8a0..fa58e21c4202 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCloudTextToSpeechEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCloudTextToSpeechEndpointBuilderFactory.java
@@ -76,7 +76,9 @@ public interface
GoogleCloudTextToSpeechEndpointBuilderFactory {
return this;
}
/**
- * The language code for the voice (e.g., en-US, fr-FR).
+ * The language code for the voice (e.g., en-US, fr-FR). The default
+ * applies to synthesize only: listVoices lists the voices of all
+ * languages unless this option is set.
*
* The option is a: <code>java.lang.String</code> type.
*