You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by cd...@apache.org on 2026/10/08 20:14:43 UTC
(camel) 02/12: CAMEL-25333: Address review feedback on camel-openfeature component
This is an automated email from the ASF dual-hosted git repository.
christophd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
commit c23565275d4607b3cfc573b1bd659c69359fd9dc
Author: Christoph Deppisch <ch...@web.de>
AuthorDate: Tue Oct 6 19:41:18 2026 +0200
CAMEL-25333: Address review feedback on camel-openfeature component
Fix boolean evaluation switching to string when context is present,
language leaking settings into exchange properties, context values
coerced to strings, shared expression mutation, provider lifecycle
issues, and security/metadata improvements.
Changes:
- Fix isBooleanEvaluation to derive type solely from evaluationType
and defaultValue, not from context presence
- Add explicit evaluate overload for language path that does not
read from or write to the exchange
- Preserve typed context values (Boolean, Integer, Double, etc.)
using Value constructors instead of String.valueOf
- Fix language evaluate(exchange, Boolean.class) to not mutate
shared evaluationType field
- Reference-count endpoints per domain in the component; release
provider when last user stops; warn on conflicting configs
- Only shut down owned providers, not user-supplied beans
- Use Files.createTempFile for owner-only permissions (CWE-732)
- Pass file: resource paths directly to flagd for live reload
- Add headersClass to @UriEndpoint; change category to CLOUD
- Use getBooleanDetails/getStringDetails; expose variant, reason,
and error code as result headers; log warnings on errors
- Let endpoint-level provider override inherited flagd options
- Add contextFromBody option (default false) for opt-in Map body
- Add tls, certPath, deadline options for remote flagd
- Pin flagd-core and flagd-api transitive dependency versions
- Add targeted-boolean flag to test definitions
- Add tests: boolean flag with targeting key returns Boolean,
language does not leak into subsequent producer calls,
targeted boolean predicate verifies both outcomes
- Update component and language documentation
- Regenerate catalog, BOM, and generated metadata
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../camel/catalog/components/openfeature.json | 63 +++---
.../camel/catalog/docs/openfeature-component.adoc | 78 ++++---
.../camel/catalog/docs/openfeature-language.adoc | 12 +-
.../OpenFeatureComponentConfigurer.java | 18 ++
.../openfeature/OpenFeatureEndpointConfigurer.java | 18 ++
.../openfeature/OpenFeatureEndpointUriFactory.java | 6 +-
.../camel/component/openfeature/openfeature.json | 63 +++---
.../src/main/docs/openfeature-component.adoc | 78 ++++---
.../src/main/docs/openfeature-language.adoc | 12 +-
.../openfeature/OpenFeatureComponent.java | 66 +++++-
.../openfeature/OpenFeatureConfiguration.java | 80 ++++---
.../openfeature/OpenFeatureConstants.java | 12 ++
.../component/openfeature/OpenFeatureEndpoint.java | 219 +++++++++++++++-----
.../language/openfeature/OpenFeatureLanguage.java | 47 +++--
.../openfeature/OpenFeatureLanguageTest.java | 60 +++++-
.../openfeature/OpenFeatureProducerTest.java | 61 +++++-
.../src/test/resources/openfeature/flags.json | 12 ++
.../component/ComponentsBuilderFactory.java | 2 +-
.../dsl/OpenfeatureComponentBuilderFactory.java | 79 ++++++-
.../builder/endpoint/EndpointHeaderBuilders.java | 13 ++
.../builder/endpoint/StaticEndpointBuilders.java | 4 +-
.../dsl/OpenFeatureEndpointBuilderFactory.java | 229 ++++++++++++++++++++-
parent/pom.xml | 13 ++
23 files changed, 1014 insertions(+), 231 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfeature.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfeature.json
index 76d4c260b81c..163924b3bbcf 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfeature.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfeature.json
@@ -6,7 +6,7 @@
"description": "Evaluate feature flags using the OpenFeature specification with flagd.",
"deprecated": false,
"firstVersion": "4.23.0",
- "label": "core",
+ "label": "cloud",
"javaType": "org.apache.camel.component.openfeature.OpenFeatureComponent",
"supportLevel": "Preview",
"groupId": "org.apache.camel",
@@ -24,30 +24,47 @@
"remote": true
},
"componentProperties": {
- "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by OpenFeature endpoints." },
- "defaultValue": { "index": 1, "kind": "property", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not set [...]
- "evaluationType": { "index": 2, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluatio [...]
- "flagKey": { "index": 3, "kind": "property", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
- "flags": { "index": 4, "kind": "property", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
- "flagsResource": { "index": 5, "kind": "property", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusi [...]
- "host": { "index": 6, "kind": "property", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
- "port": { "index": 7, "kind": "property", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
- "provider": { "index": 8, "kind": "property", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsReso [...]
- "lazyStartProducer": { "index": 9, "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 [...]
- "resultProperty": { "index": 10, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
- "autowiredEnabled": { "index": 11, "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 [...]
+ "certPath": { "index": 0, "kind": "property", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." },
+ "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by OpenFeature endpoints." },
+ "contextFromBody": { "index": 2, "kind": "property", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When false [...]
+ "deadline": { "index": 3, "kind": "property", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." },
+ "defaultValue": { "index": 4, "kind": "property", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not set [...]
+ "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluatio [...]
+ "flagKey": { "index": 6, "kind": "property", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
+ "flags": { "index": 7, "kind": "property", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
+ "flagsResource": { "index": 8, "kind": "property", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusi [...]
+ "host": { "index": 9, "kind": "property", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
+ "port": { "index": 10, "kind": "property", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
+ "provider": { "index": 11, "kind": "property", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags, fla [...]
+ "tls": { "index": 12, "kind": "property", "displayName": "Tls", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Whether to use TLS for the remote flagd connection." },
+ "lazyStartProducer": { "index": 13, "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 fai [...]
+ "resultProperty": { "index": 14, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
+ "autowiredEnabled": { "index": 15, "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 [...]
+ },
+ "headers": {
+ "CamelOpenFeatureFlagKey": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Overrides the configured flag key for this message.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#FLAG_KEY" },
+ "CamelOpenFeatureTargetingKey": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the targeting key for the OpenFeature evaluation context.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#TARGETING_KEY" },
+ "CamelOpenFeatureEvaluationContext": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A Map of additional evaluation context key-value pairs.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_CONTEXT" },
+ "CamelOpenFeatureEvaluationType": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the evaluation type (boolean, variant) for the OpenFeature evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_TYPE" },
+ "CamelOpenFeatureVariant": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The variant name returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_VARIANT" },
+ "CamelOpenFeatureReason": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The reason string returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_REASON" },
+ "CamelOpenFeatureErrorCode": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The error code when the evaluation failed (e.g. FLAG_NOT_FOUND, TYPE_MISMATCH).", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_ERROR_CODE" }
},
"properties": {
"domain": { "index": 0, "kind": "path", "displayName": "Domain", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The OpenFeature domain to bind the provider to." },
- "defaultValue": { "index": 1, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...]
- "evaluationType": { "index": 2, "kind": "parameter", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluati [...]
- "flagKey": { "index": 3, "kind": "parameter", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
- "flags": { "index": 4, "kind": "parameter", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
- "flagsResource": { "index": 5, "kind": "parameter", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclus [...]
- "host": { "index": 6, "kind": "parameter", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
- "port": { "index": 7, "kind": "parameter", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
- "provider": { "index": 8, "kind": "parameter", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsRes [...]
- "resultProperty": { "index": 9, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
- "lazyStartProducer": { "index": 10, "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 produ [...]
+ "certPath": { "index": 1, "kind": "parameter", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." },
+ "contextFromBody": { "index": 2, "kind": "parameter", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When fals [...]
+ "deadline": { "index": 3, "kind": "parameter", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." },
+ "defaultValue": { "index": 4, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...]
+ "evaluationType": { "index": 5, "kind": "parameter", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluati [...]
+ "flagKey": { "index": 6, "kind": "parameter", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
+ "flags": { "index": 7, "kind": "parameter", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
+ "flagsResource": { "index": 8, "kind": "parameter", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclus [...]
+ "host": { "index": 9, "kind": "parameter", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
+ "port": { "index": 10, "kind": "parameter", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
+ "provider": { "index": 11, "kind": "parameter", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags, fl [...]
+ "tls": { "index": 12, "kind": "parameter", "displayName": "Tls", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Whether to use TLS for the remote flagd connection." },
+ "resultProperty": { "index": 13, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message [...]
+ "lazyStartProducer": { "index": 14, "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 produ [...]
}
}
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc
index aba11eda4c60..46bdfbe4734b 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc
@@ -7,7 +7,6 @@
:supportlevel: Preview
:tabs-sync-option:
:component-header: Only producer is supported
-:core:
*Since Camel {since}*
@@ -60,14 +59,14 @@ The component resolves the feature flag provider in this order:
1. *Explicit provider bean* (`provider`): a bean reference (e.g., `#myProvider`)
to a custom `FeatureProvider` looked up from the Camel registry.
+ When set, `provider` takes precedence over `flags`, `flagsResource`,
+ and `host`.
2. *Default bean lookup*: a bean named `flags` of type `FeatureProvider`
in the Camel registry.
3. *Flagd fallback* (`flags`, `flagsResource`, or `host`): creates a flagd
provider automatically. Use `flags` or `flagsResource` for local file-based
evaluation, or `host` for a remote flagd gRPC service.
-The `provider` option is mutually exclusive with `flags` and `flagsResource`.
-
== Evaluation type
The evaluation type (boolean or string) is determined by the `evaluationType`
@@ -85,20 +84,6 @@ When `evaluationType` is not set, the type is inferred from `defaultValue`:
The default is `"false"` with no `evaluationType`, so boolean evaluation is
the default.
-== Message headers
-
-The producer supports the following message headers (all also checked as
-exchange properties):
-
-[width="100%",cols="20%,10%,70%",options="header"]
-|===
-| Header / Property | Type | Description
-| `CamelOpenFeatureFlagKey` | `String` | Overrides the configured `flagKey` for this message.
-| `CamelOpenFeatureTargetingKey` | `String` | Sets the targeting key for the evaluation context.
-| `CamelOpenFeatureEvaluationContext` | `Map<String, Object>` | Evaluation context key-value pairs.
-| `CamelOpenFeatureEvaluationType` | `String` | Overrides the evaluation type (`boolean` or `variant`) for this message.
-|===
-
== Evaluation context and targeting key
The OpenFeature evaluation context carries metadata about the current
@@ -113,12 +98,30 @@ The evaluation context is built from these sources:
1. *`CamelOpenFeatureEvaluationContext` header or exchange property*: when
set to a `Map<String, Object>`, entries are added to the context. An
entry with key `"targetingKey"` is treated as the targeting key.
-2. *Message body* (fallback): when no `CamelOpenFeatureEvaluationContext`
- header or property is set and the body is a `Map<String, Object>`,
- body entries are used as the context instead.
+ Context values preserve their original types (Boolean, Integer, Double,
+ String, etc.) for accurate provider-side evaluation.
+2. *Message body* (opt-in): when `contextFromBody=true` is set and no
+ `CamelOpenFeatureEvaluationContext` header or property is present,
+ a `Map<String, Object>` body is used as the context.
+ This is disabled by default to prevent untrusted inbound messages from
+ supplying targeting attributes.
3. *`CamelOpenFeatureTargetingKey` header or exchange property*: when set,
overrides the targeting key from any of the above sources.
+== Evaluation result details
+
+The producer exposes evaluation details as message headers after each
+evaluation:
+
+* `CamelOpenFeatureVariant` — the variant name returned by the provider
+* `CamelOpenFeatureReason` — the reason string (e.g. `TARGETING_MATCH`,
+ `DEFAULT`, `STATIC`)
+* `CamelOpenFeatureErrorCode` — the error code when evaluation failed
+ (e.g. `FLAG_NOT_FOUND`, `TYPE_MISMATCH`)
+
+When an evaluation error occurs, a warning is logged with the flag key,
+error code, and error message.
+
== Usage
The examples below use flagd flag definitions in
@@ -275,27 +278,44 @@ from("direct:start")
.toD("direct:orders-${exchangeProperty.orderVariant}");
----
-The evaluation context is provided via the message body as a `Map`:
+The evaluation context is provided via a header — useful when the message
+body carries business data:
[source,java]
----
-template.requestBody("direct:start",
- Map.of("targetingKey", "order-123", "customer_tier", "ENTERPRISE"));
+from("direct:start")
+ .setHeader("CamelOpenFeatureTargetingKey", constant("order-123"))
+ .setHeader("CamelOpenFeatureEvaluationContext",
+ constant(Map.of("customer_tier", "ENTERPRISE")))
+ .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant")
+ .toD("direct:orders-${exchangeProperty.orderVariant}");
----
-Alternatively, set the context via headers — useful when the message body
-carries business data:
+Alternatively, enable `contextFromBody=true` to use a `Map` body as the
+evaluation context:
[source,java]
----
from("direct:start")
- .setHeader("CamelOpenFeatureTargetingKey", constant("order-123"))
- .setHeader("CamelOpenFeatureEvaluationContext",
- constant(Map.of("customer_tier", "ENTERPRISE")))
- .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant")
+ .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant"
+ + "&resultProperty=orderVariant&contextFromBody=true")
.toD("direct:orders-${exchangeProperty.orderVariant}");
----
+=== Remote flagd service
+
+For a remote flagd service, configure `host` (and optionally `port`,
+`tls`, `certPath`, and `deadline`):
+
+[source,properties]
+----
+camel.component.openfeature.host = flagd.example.com
+camel.component.openfeature.port = 8013
+camel.component.openfeature.tls = true
+camel.component.openfeature.cert-path = /etc/tls/ca.crt
+camel.component.openfeature.deadline = 1000
+----
+
=== Custom provider
OpenFeature is a vendor-neutral specification. Beyond the built-in flagd
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc
index 2cd50ab7549d..61fba4be34a0 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc
@@ -33,6 +33,11 @@ The language evaluates the given flag key as a boolean and returns the
result. It uses the `openfeature:flags` endpoint by default, inheriting
component-level configuration such as `flagsResource` or `host`.
+The language does not modify the exchange — it reads targeting context
+from message headers but does not set any properties or headers itself.
+This means it is safe to use before a producer `to("openfeature:...")`
+call without leaking settings.
+
=== Java DSL
[source,java]
@@ -66,7 +71,7 @@ keeps the message body untouched:
[source,java]
----
from("direct:start")
- .setProperty("CamelOpenFeatureTargetingKey", simple("${body[order_id]}"))
+ .setHeader("CamelOpenFeatureTargetingKey", simple("${body[order_id]}"))
.setProperty("routingAlgorithm", language("openfeature", "new-routing-algorithm"))
.toD("direct:route-${exchangeProperty.routingAlgorithm}");
----
@@ -87,7 +92,7 @@ providers for consistent fractional rollouts.
Set the targeting key and context attributes as message headers before the
language evaluates:
-* `CamelOpenFeatureTargetingKey` (header or exchange property): sets the
+* `CamelOpenFeatureTargetingKey` (header): sets the
targeting key for the evaluation.
* `CamelOpenFeatureEvaluationContext` (header): a `Map<String, Object>` of additional
context key-value pairs.
@@ -134,7 +139,8 @@ from("direct:start")
`createExpression` defaults to variant (string) evaluation and returns an
`Expression`. When called with `evaluate(exchange, Boolean.class)`, it
-automatically switches to boolean evaluation:
+automatically switches to boolean evaluation for that call without
+affecting other callers:
[source,java]
----
diff --git a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureComponentConfigurer.java b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureComponentConfigurer.java
index ad86250d9606..f46e007db277 100644
--- a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureComponentConfigurer.java
+++ b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureComponentConfigurer.java
@@ -32,7 +32,12 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true;
+ case "certpath":
+ case "certPath": getOrCreateConfiguration(target).setCertPath(property(camelContext, java.lang.String.class, value)); return true;
case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.openfeature.OpenFeatureConfiguration.class, value)); return true;
+ case "contextfrombody":
+ case "contextFromBody": getOrCreateConfiguration(target).setContextFromBody(property(camelContext, boolean.class, value)); return true;
+ case "deadline": getOrCreateConfiguration(target).setDeadline(property(camelContext, int.class, value)); return true;
case "defaultvalue":
case "defaultValue": getOrCreateConfiguration(target).setDefaultValue(property(camelContext, java.lang.String.class, value)); return true;
case "evaluationtype":
@@ -49,6 +54,7 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
case "provider": getOrCreateConfiguration(target).setProvider(property(camelContext, java.lang.String.class, value)); return true;
case "resultproperty":
case "resultProperty": getOrCreateConfiguration(target).setResultProperty(property(camelContext, java.lang.String.class, value)); return true;
+ case "tls": getOrCreateConfiguration(target).setTls(property(camelContext, boolean.class, value)); return true;
default: return false;
}
}
@@ -58,7 +64,12 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled": return boolean.class;
+ case "certpath":
+ case "certPath": return java.lang.String.class;
case "configuration": return org.apache.camel.component.openfeature.OpenFeatureConfiguration.class;
+ case "contextfrombody":
+ case "contextFromBody": return boolean.class;
+ case "deadline": return int.class;
case "defaultvalue":
case "defaultValue": return java.lang.String.class;
case "evaluationtype":
@@ -75,6 +86,7 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
case "provider": return java.lang.String.class;
case "resultproperty":
case "resultProperty": return java.lang.String.class;
+ case "tls": return boolean.class;
default: return null;
}
}
@@ -85,7 +97,12 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
switch (ignoreCase ? name.toLowerCase() : name) {
case "autowiredenabled":
case "autowiredEnabled": return target.isAutowiredEnabled();
+ case "certpath":
+ case "certPath": return getOrCreateConfiguration(target).getCertPath();
case "configuration": return target.getConfiguration();
+ case "contextfrombody":
+ case "contextFromBody": return getOrCreateConfiguration(target).isContextFromBody();
+ case "deadline": return getOrCreateConfiguration(target).getDeadline();
case "defaultvalue":
case "defaultValue": return getOrCreateConfiguration(target).getDefaultValue();
case "evaluationtype":
@@ -102,6 +119,7 @@ public class OpenFeatureComponentConfigurer extends PropertyConfigurerSupport im
case "provider": return getOrCreateConfiguration(target).getProvider();
case "resultproperty":
case "resultProperty": return getOrCreateConfiguration(target).getResultProperty();
+ case "tls": return getOrCreateConfiguration(target).isTls();
default: return null;
}
}
diff --git a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java
index c398c44abe3a..772592acfc52 100644
--- a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java
+++ b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java
@@ -23,6 +23,11 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) {
OpenFeatureEndpoint target = (OpenFeatureEndpoint) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "certpath":
+ case "certPath": target.getConfiguration().setCertPath(property(camelContext, java.lang.String.class, value)); return true;
+ case "contextfrombody":
+ case "contextFromBody": target.getConfiguration().setContextFromBody(property(camelContext, boolean.class, value)); return true;
+ case "deadline": target.getConfiguration().setDeadline(property(camelContext, int.class, value)); return true;
case "defaultvalue":
case "defaultValue": target.getConfiguration().setDefaultValue(property(camelContext, java.lang.String.class, value)); return true;
case "evaluationtype":
@@ -39,6 +44,7 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
case "provider": target.getConfiguration().setProvider(property(camelContext, java.lang.String.class, value)); return true;
case "resultproperty":
case "resultProperty": target.getConfiguration().setResultProperty(property(camelContext, java.lang.String.class, value)); return true;
+ case "tls": target.getConfiguration().setTls(property(camelContext, boolean.class, value)); return true;
default: return false;
}
}
@@ -46,6 +52,11 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
@Override
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "certpath":
+ case "certPath": return java.lang.String.class;
+ case "contextfrombody":
+ case "contextFromBody": return boolean.class;
+ case "deadline": return int.class;
case "defaultvalue":
case "defaultValue": return java.lang.String.class;
case "evaluationtype":
@@ -62,6 +73,7 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
case "provider": return java.lang.String.class;
case "resultproperty":
case "resultProperty": return java.lang.String.class;
+ case "tls": return boolean.class;
default: return null;
}
}
@@ -70,6 +82,11 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
OpenFeatureEndpoint target = (OpenFeatureEndpoint) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "certpath":
+ case "certPath": return target.getConfiguration().getCertPath();
+ case "contextfrombody":
+ case "contextFromBody": return target.getConfiguration().isContextFromBody();
+ case "deadline": return target.getConfiguration().getDeadline();
case "defaultvalue":
case "defaultValue": return target.getConfiguration().getDefaultValue();
case "evaluationtype":
@@ -86,6 +103,7 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp
case "provider": return target.getConfiguration().getProvider();
case "resultproperty":
case "resultProperty": return target.getConfiguration().getResultProperty();
+ case "tls": return target.getConfiguration().isTls();
default: return null;
}
}
diff --git a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java
index acdf722afe28..048115df7c04 100644
--- a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java
+++ b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java
@@ -24,7 +24,10 @@ public class OpenFeatureEndpointUriFactory extends org.apache.camel.support.comp
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(11);
+ Set<String> props = new HashSet<>(15);
+ props.add("certPath");
+ props.add("contextFromBody");
+ props.add("deadline");
props.add("defaultValue");
props.add("domain");
props.add("evaluationType");
@@ -36,6 +39,7 @@ public class OpenFeatureEndpointUriFactory extends org.apache.camel.support.comp
props.add("port");
props.add("provider");
props.add("resultProperty");
+ props.add("tls");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
SECRET_PROPERTY_NAMES = Collections.emptySet();
ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
diff --git a/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json b/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json
index 76d4c260b81c..163924b3bbcf 100644
--- a/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json
+++ b/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json
@@ -6,7 +6,7 @@
"description": "Evaluate feature flags using the OpenFeature specification with flagd.",
"deprecated": false,
"firstVersion": "4.23.0",
- "label": "core",
+ "label": "cloud",
"javaType": "org.apache.camel.component.openfeature.OpenFeatureComponent",
"supportLevel": "Preview",
"groupId": "org.apache.camel",
@@ -24,30 +24,47 @@
"remote": true
},
"componentProperties": {
- "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by OpenFeature endpoints." },
- "defaultValue": { "index": 1, "kind": "property", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not set [...]
- "evaluationType": { "index": 2, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluatio [...]
- "flagKey": { "index": 3, "kind": "property", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
- "flags": { "index": 4, "kind": "property", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
- "flagsResource": { "index": 5, "kind": "property", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusi [...]
- "host": { "index": 6, "kind": "property", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
- "port": { "index": 7, "kind": "property", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
- "provider": { "index": 8, "kind": "property", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsReso [...]
- "lazyStartProducer": { "index": 9, "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 [...]
- "resultProperty": { "index": 10, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
- "autowiredEnabled": { "index": 11, "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 [...]
+ "certPath": { "index": 0, "kind": "property", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." },
+ "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by OpenFeature endpoints." },
+ "contextFromBody": { "index": 2, "kind": "property", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When false [...]
+ "deadline": { "index": 3, "kind": "property", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." },
+ "defaultValue": { "index": 4, "kind": "property", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not set [...]
+ "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluatio [...]
+ "flagKey": { "index": 6, "kind": "property", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
+ "flags": { "index": 7, "kind": "property", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
+ "flagsResource": { "index": 8, "kind": "property", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusi [...]
+ "host": { "index": 9, "kind": "property", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
+ "port": { "index": 10, "kind": "property", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
+ "provider": { "index": 11, "kind": "property", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags, fla [...]
+ "tls": { "index": 12, "kind": "property", "displayName": "Tls", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Whether to use TLS for the remote flagd connection." },
+ "lazyStartProducer": { "index": 13, "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 fai [...]
+ "resultProperty": { "index": 14, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
+ "autowiredEnabled": { "index": 15, "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 [...]
+ },
+ "headers": {
+ "CamelOpenFeatureFlagKey": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Overrides the configured flag key for this message.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#FLAG_KEY" },
+ "CamelOpenFeatureTargetingKey": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the targeting key for the OpenFeature evaluation context.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#TARGETING_KEY" },
+ "CamelOpenFeatureEvaluationContext": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A Map of additional evaluation context key-value pairs.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_CONTEXT" },
+ "CamelOpenFeatureEvaluationType": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the evaluation type (boolean, variant) for the OpenFeature evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_TYPE" },
+ "CamelOpenFeatureVariant": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The variant name returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_VARIANT" },
+ "CamelOpenFeatureReason": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The reason string returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_REASON" },
+ "CamelOpenFeatureErrorCode": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The error code when the evaluation failed (e.g. FLAG_NOT_FOUND, TYPE_MISMATCH).", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_ERROR_CODE" }
},
"properties": {
"domain": { "index": 0, "kind": "path", "displayName": "Domain", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The OpenFeature domain to bind the provider to." },
- "defaultValue": { "index": 1, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...]
- "evaluationType": { "index": 2, "kind": "parameter", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluati [...]
- "flagKey": { "index": 3, "kind": "parameter", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
- "flags": { "index": 4, "kind": "parameter", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
- "flagsResource": { "index": 5, "kind": "parameter", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclus [...]
- "host": { "index": 6, "kind": "parameter", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
- "port": { "index": 7, "kind": "parameter", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
- "provider": { "index": 8, "kind": "parameter", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsRes [...]
- "resultProperty": { "index": 9, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message b [...]
- "lazyStartProducer": { "index": 10, "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 produ [...]
+ "certPath": { "index": 1, "kind": "parameter", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." },
+ "contextFromBody": { "index": 2, "kind": "parameter", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When fals [...]
+ "deadline": { "index": 3, "kind": "parameter", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." },
+ "defaultValue": { "index": 4, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...]
+ "evaluationType": { "index": 5, "kind": "parameter", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluati [...]
+ "flagKey": { "index": 6, "kind": "parameter", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." },
+ "flags": { "index": 7, "kind": "parameter", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." },
+ "flagsResource": { "index": 8, "kind": "parameter", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclus [...]
+ "host": { "index": 9, "kind": "parameter", "displayName": "Host", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service host. When set, the flagd RPC resolver is used." },
+ "port": { "index": 10, "kind": "parameter", "displayName": "Port", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 8013, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Remote flagd service port." },
+ "provider": { "index": 11, "kind": "parameter", "displayName": "Provider", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags, fl [...]
+ "tls": { "index": 12, "kind": "parameter", "displayName": "Tls", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Whether to use TLS for the remote flagd connection." },
+ "resultProperty": { "index": 13, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Store the evaluation result in this exchange property, preserving the original message [...]
+ "lazyStartProducer": { "index": 14, "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 produ [...]
}
}
diff --git a/components/camel-openfeature/src/main/docs/openfeature-component.adoc b/components/camel-openfeature/src/main/docs/openfeature-component.adoc
index aba11eda4c60..46bdfbe4734b 100644
--- a/components/camel-openfeature/src/main/docs/openfeature-component.adoc
+++ b/components/camel-openfeature/src/main/docs/openfeature-component.adoc
@@ -7,7 +7,6 @@
:supportlevel: Preview
:tabs-sync-option:
:component-header: Only producer is supported
-:core:
*Since Camel {since}*
@@ -60,14 +59,14 @@ The component resolves the feature flag provider in this order:
1. *Explicit provider bean* (`provider`): a bean reference (e.g., `#myProvider`)
to a custom `FeatureProvider` looked up from the Camel registry.
+ When set, `provider` takes precedence over `flags`, `flagsResource`,
+ and `host`.
2. *Default bean lookup*: a bean named `flags` of type `FeatureProvider`
in the Camel registry.
3. *Flagd fallback* (`flags`, `flagsResource`, or `host`): creates a flagd
provider automatically. Use `flags` or `flagsResource` for local file-based
evaluation, or `host` for a remote flagd gRPC service.
-The `provider` option is mutually exclusive with `flags` and `flagsResource`.
-
== Evaluation type
The evaluation type (boolean or string) is determined by the `evaluationType`
@@ -85,20 +84,6 @@ When `evaluationType` is not set, the type is inferred from `defaultValue`:
The default is `"false"` with no `evaluationType`, so boolean evaluation is
the default.
-== Message headers
-
-The producer supports the following message headers (all also checked as
-exchange properties):
-
-[width="100%",cols="20%,10%,70%",options="header"]
-|===
-| Header / Property | Type | Description
-| `CamelOpenFeatureFlagKey` | `String` | Overrides the configured `flagKey` for this message.
-| `CamelOpenFeatureTargetingKey` | `String` | Sets the targeting key for the evaluation context.
-| `CamelOpenFeatureEvaluationContext` | `Map<String, Object>` | Evaluation context key-value pairs.
-| `CamelOpenFeatureEvaluationType` | `String` | Overrides the evaluation type (`boolean` or `variant`) for this message.
-|===
-
== Evaluation context and targeting key
The OpenFeature evaluation context carries metadata about the current
@@ -113,12 +98,30 @@ The evaluation context is built from these sources:
1. *`CamelOpenFeatureEvaluationContext` header or exchange property*: when
set to a `Map<String, Object>`, entries are added to the context. An
entry with key `"targetingKey"` is treated as the targeting key.
-2. *Message body* (fallback): when no `CamelOpenFeatureEvaluationContext`
- header or property is set and the body is a `Map<String, Object>`,
- body entries are used as the context instead.
+ Context values preserve their original types (Boolean, Integer, Double,
+ String, etc.) for accurate provider-side evaluation.
+2. *Message body* (opt-in): when `contextFromBody=true` is set and no
+ `CamelOpenFeatureEvaluationContext` header or property is present,
+ a `Map<String, Object>` body is used as the context.
+ This is disabled by default to prevent untrusted inbound messages from
+ supplying targeting attributes.
3. *`CamelOpenFeatureTargetingKey` header or exchange property*: when set,
overrides the targeting key from any of the above sources.
+== Evaluation result details
+
+The producer exposes evaluation details as message headers after each
+evaluation:
+
+* `CamelOpenFeatureVariant` — the variant name returned by the provider
+* `CamelOpenFeatureReason` — the reason string (e.g. `TARGETING_MATCH`,
+ `DEFAULT`, `STATIC`)
+* `CamelOpenFeatureErrorCode` — the error code when evaluation failed
+ (e.g. `FLAG_NOT_FOUND`, `TYPE_MISMATCH`)
+
+When an evaluation error occurs, a warning is logged with the flag key,
+error code, and error message.
+
== Usage
The examples below use flagd flag definitions in
@@ -275,27 +278,44 @@ from("direct:start")
.toD("direct:orders-${exchangeProperty.orderVariant}");
----
-The evaluation context is provided via the message body as a `Map`:
+The evaluation context is provided via a header — useful when the message
+body carries business data:
[source,java]
----
-template.requestBody("direct:start",
- Map.of("targetingKey", "order-123", "customer_tier", "ENTERPRISE"));
+from("direct:start")
+ .setHeader("CamelOpenFeatureTargetingKey", constant("order-123"))
+ .setHeader("CamelOpenFeatureEvaluationContext",
+ constant(Map.of("customer_tier", "ENTERPRISE")))
+ .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant")
+ .toD("direct:orders-${exchangeProperty.orderVariant}");
----
-Alternatively, set the context via headers — useful when the message body
-carries business data:
+Alternatively, enable `contextFromBody=true` to use a `Map` body as the
+evaluation context:
[source,java]
----
from("direct:start")
- .setHeader("CamelOpenFeatureTargetingKey", constant("order-123"))
- .setHeader("CamelOpenFeatureEvaluationContext",
- constant(Map.of("customer_tier", "ENTERPRISE")))
- .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant")
+ .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant"
+ + "&resultProperty=orderVariant&contextFromBody=true")
.toD("direct:orders-${exchangeProperty.orderVariant}");
----
+=== Remote flagd service
+
+For a remote flagd service, configure `host` (and optionally `port`,
+`tls`, `certPath`, and `deadline`):
+
+[source,properties]
+----
+camel.component.openfeature.host = flagd.example.com
+camel.component.openfeature.port = 8013
+camel.component.openfeature.tls = true
+camel.component.openfeature.cert-path = /etc/tls/ca.crt
+camel.component.openfeature.deadline = 1000
+----
+
=== Custom provider
OpenFeature is a vendor-neutral specification. Beyond the built-in flagd
diff --git a/components/camel-openfeature/src/main/docs/openfeature-language.adoc b/components/camel-openfeature/src/main/docs/openfeature-language.adoc
index 2cd50ab7549d..61fba4be34a0 100644
--- a/components/camel-openfeature/src/main/docs/openfeature-language.adoc
+++ b/components/camel-openfeature/src/main/docs/openfeature-language.adoc
@@ -33,6 +33,11 @@ The language evaluates the given flag key as a boolean and returns the
result. It uses the `openfeature:flags` endpoint by default, inheriting
component-level configuration such as `flagsResource` or `host`.
+The language does not modify the exchange — it reads targeting context
+from message headers but does not set any properties or headers itself.
+This means it is safe to use before a producer `to("openfeature:...")`
+call without leaking settings.
+
=== Java DSL
[source,java]
@@ -66,7 +71,7 @@ keeps the message body untouched:
[source,java]
----
from("direct:start")
- .setProperty("CamelOpenFeatureTargetingKey", simple("${body[order_id]}"))
+ .setHeader("CamelOpenFeatureTargetingKey", simple("${body[order_id]}"))
.setProperty("routingAlgorithm", language("openfeature", "new-routing-algorithm"))
.toD("direct:route-${exchangeProperty.routingAlgorithm}");
----
@@ -87,7 +92,7 @@ providers for consistent fractional rollouts.
Set the targeting key and context attributes as message headers before the
language evaluates:
-* `CamelOpenFeatureTargetingKey` (header or exchange property): sets the
+* `CamelOpenFeatureTargetingKey` (header): sets the
targeting key for the evaluation.
* `CamelOpenFeatureEvaluationContext` (header): a `Map<String, Object>` of additional
context key-value pairs.
@@ -134,7 +139,8 @@ from("direct:start")
`createExpression` defaults to variant (string) evaluation and returns an
`Expression`. When called with `evaluate(exchange, Boolean.class)`, it
-automatically switches to boolean evaluation:
+automatically switches to boolean evaluation for that call without
+affecting other callers:
[source,java]
----
diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java
index fa3c8c978a0d..969146a2900e 100644
--- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java
+++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java
@@ -17,7 +17,6 @@
package org.apache.camel.component.openfeature;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import dev.openfeature.sdk.Client;
@@ -27,15 +26,31 @@ import org.apache.camel.Endpoint;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Component("openfeature")
public class OpenFeatureComponent extends DefaultComponent {
+ private static final Logger LOG = LoggerFactory.getLogger(OpenFeatureComponent.class);
+
@Metadata
private OpenFeatureConfiguration configuration = new OpenFeatureConfiguration();
private volatile OpenFeatureAPI api;
- private final Set<String> initializedDomains = ConcurrentHashMap.newKeySet();
+ private final Map<String, DomainBinding> domainBindings = new ConcurrentHashMap<>();
+
+ private static class DomainBinding {
+ final FeatureProvider provider;
+ final boolean owned;
+ int refCount;
+
+ DomainBinding(FeatureProvider provider, boolean owned) {
+ this.provider = provider;
+ this.owned = owned;
+ this.refCount = 1;
+ }
+ }
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
@@ -52,22 +67,55 @@ public class OpenFeatureComponent extends DefaultComponent {
@Override
protected void doStop() throws Exception {
- OpenFeatureAPI a = api;
api = null;
- initializedDomains.clear();
- if (a != null) {
- a.shutdown();
+ for (DomainBinding binding : domainBindings.values()) {
+ if (binding.owned) {
+ try {
+ binding.provider.shutdown();
+ } catch (Exception e) {
+ LOG.debug("Error shutting down owned provider: {}", e.getMessage(), e);
+ }
+ }
}
+ domainBindings.clear();
super.doStop();
}
- synchronized Client registerProviderAndGetClient(String domain, FeatureProvider provider) throws Exception {
- if (initializedDomains.add(domain)) {
- api.setProviderAndWait(domain, provider);
+ synchronized Client registerEndpoint(String domain, FeatureProvider provider, boolean owned) throws Exception {
+ DomainBinding existing = domainBindings.get(domain);
+ if (existing != null) {
+ existing.refCount++;
+ if (existing.provider != provider) {
+ LOG.warn("Domain '{}' already has a registered provider; this endpoint's provider settings are ignored.",
+ domain);
+ if (owned) {
+ provider.shutdown();
+ }
+ }
+ return api.getClient(domain);
}
+ api.setProviderAndWait(domain, provider);
+ domainBindings.put(domain, new DomainBinding(provider, owned));
return api.getClient(domain);
}
+ synchronized void unregisterEndpoint(String domain) {
+ DomainBinding binding = domainBindings.get(domain);
+ if (binding != null) {
+ binding.refCount--;
+ if (binding.refCount <= 0) {
+ domainBindings.remove(domain);
+ if (binding.owned) {
+ try {
+ binding.provider.shutdown();
+ } catch (Exception e) {
+ LOG.debug("Error shutting down owned provider for domain '{}': {}", domain, e.getMessage(), e);
+ }
+ }
+ }
+ }
+ }
+
public OpenFeatureConfiguration getConfiguration() {
return configuration;
}
diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java
index 1a943ceb07b4..a71e0651fa8f 100644
--- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java
+++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java
@@ -16,8 +16,6 @@
*/
package org.apache.camel.component.openfeature;
-import dev.openfeature.sdk.MutableContext;
-import org.apache.camel.Exchange;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriParams;
@@ -50,6 +48,18 @@ public class OpenFeatureConfiguration implements Cloneable {
@UriParam(label = "common", defaultValue = "8013", description = "Remote flagd service port.")
private int port = 8013;
+ @UriParam(label = "common", defaultValue = "false",
+ description = "Whether to use TLS for the remote flagd connection.")
+ private boolean tls;
+
+ @UriParam(label = "common",
+ description = "Path to the TLS certificate for the remote flagd connection.")
+ private String certPath;
+
+ @UriParam(label = "common", defaultValue = "500",
+ description = "Deadline in milliseconds for the remote flagd connection.")
+ private int deadline = 500;
+
@UriParam(label = "common",
description = "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource.")
private String flags;
@@ -61,9 +71,15 @@ public class OpenFeatureConfiguration implements Cloneable {
@UriParam(label = "common",
description = "Bean reference to a custom FeatureProvider (e.g. #myProvider)."
- + " Mutually exclusive with flags and flagsResource.")
+ + " When set, takes precedence over flags, flagsResource, and host.")
private String provider;
+ @UriParam(label = "common", defaultValue = "false",
+ description = "When true, a Map message body is used as the evaluation context."
+ + " When false (default), the body is not used as context."
+ + " The CamelOpenFeatureEvaluationContext header is always used regardless of this setting.")
+ private boolean contextFromBody;
+
public String getFlagKey() {
return flagKey;
}
@@ -124,6 +140,33 @@ public class OpenFeatureConfiguration implements Cloneable {
this.port = port;
}
+ public boolean isTls() {
+ return tls;
+ }
+
+ /** Whether to use TLS for the remote flagd connection. */
+ public void setTls(boolean tls) {
+ this.tls = tls;
+ }
+
+ public String getCertPath() {
+ return certPath;
+ }
+
+ /** Path to the TLS certificate for the remote flagd connection. */
+ public void setCertPath(String certPath) {
+ this.certPath = certPath;
+ }
+
+ public int getDeadline() {
+ return deadline;
+ }
+
+ /** Deadline in milliseconds for the remote flagd connection. */
+ public void setDeadline(int deadline) {
+ this.deadline = deadline;
+ }
+
public String getFlags() {
return flags;
}
@@ -150,32 +193,20 @@ public class OpenFeatureConfiguration implements Cloneable {
}
/**
- * Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsResource.
+ * Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags,
+ * flagsResource, and host.
*/
public void setProvider(String provider) {
this.provider = provider;
}
- public boolean isBooleanEvaluation(Exchange exchange, MutableContext ctx) {
- String evalType = exchange.getIn().getHeader(OpenFeatureConstants.EVALUATION_TYPE, String.class);
- if (evalType == null) {
- evalType = exchange.getProperty(OpenFeatureConstants.EVALUATION_TYPE, String.class);
- }
- if (evalType == null) {
- evalType = evaluationType;
- }
-
- if ("boolean".equalsIgnoreCase(evalType)) {
- return true;
- }
- if ("variant".equalsIgnoreCase(evalType)) {
- return false;
- }
- if (!ctx.asMap().isEmpty()) {
- return false;
- }
+ public boolean isContextFromBody() {
+ return contextFromBody;
+ }
- return "true".equalsIgnoreCase(defaultValue) || "false".equalsIgnoreCase(defaultValue);
+ /** When true, a Map message body is used as the evaluation context. */
+ public void setContextFromBody(boolean contextFromBody) {
+ this.contextFromBody = contextFromBody;
}
public OpenFeatureConfiguration copy() {
@@ -187,9 +218,6 @@ public class OpenFeatureConfiguration implements Cloneable {
}
void validate() {
- if (provider != null && (flags != null || flagsResource != null)) {
- throw new IllegalArgumentException("provider is mutually exclusive with flags and flagsResource");
- }
if (flags != null && flagsResource != null) {
throw new IllegalArgumentException("flags and flagsResource are mutually exclusive");
}
diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java
index 9b4e9eced0e5..fb836b014b90 100644
--- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java
+++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java
@@ -36,6 +36,18 @@ public final class OpenFeatureConstants {
javaType = "String", applicableFor = "openfeature")
public static final String EVALUATION_TYPE = "CamelOpenFeatureEvaluationType";
+ @Metadata(description = "The variant name returned by the provider for this evaluation.",
+ javaType = "String", applicableFor = "openfeature")
+ public static final String EVALUATION_VARIANT = "CamelOpenFeatureVariant";
+
+ @Metadata(description = "The reason string returned by the provider for this evaluation.",
+ javaType = "String", applicableFor = "openfeature")
+ public static final String EVALUATION_REASON = "CamelOpenFeatureReason";
+
+ @Metadata(description = "The error code when the evaluation failed (e.g. FLAG_NOT_FOUND, TYPE_MISMATCH).",
+ javaType = "String", applicableFor = "openfeature")
+ public static final String EVALUATION_ERROR_CODE = "CamelOpenFeatureErrorCode";
+
private OpenFeatureConstants() {
}
}
diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java
index 5696f56bed30..d9e444422490 100644
--- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java
+++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java
@@ -21,6 +21,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.time.Instant;
+import java.util.HashMap;
import java.util.Map;
import dev.openfeature.contrib.providers.flagd.Config;
@@ -28,7 +30,9 @@ import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.Value;
import org.apache.camel.Category;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
@@ -40,12 +44,15 @@ import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.support.ResourceHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Evaluate feature flags using the OpenFeature specification with flagd. */
@UriEndpoint(firstVersion = "4.23.0", scheme = "openfeature", title = "OpenFeature", syntax = "openfeature:domain",
- producerOnly = true, category = { Category.CORE })
+ producerOnly = true, category = { Category.CLOUD }, headersClass = OpenFeatureConstants.class)
public class OpenFeatureEndpoint extends DefaultEndpoint {
+ private static final Logger LOG = LoggerFactory.getLogger(OpenFeatureEndpoint.class);
private static final String DEFAULT_PROVIDER_BEAN = "flags";
@UriPath
@@ -93,18 +100,16 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
FeatureProvider provider = resolveProvider();
resolvedProvider = provider;
- client = getComponent().registerProviderAndGetClient(domain, provider);
+ client = getComponent().registerEndpoint(domain, provider, ownedProvider);
}
@Override
protected void doStop() throws Exception {
client = null;
-
- if (ownedProvider && resolvedProvider != null) {
- resolvedProvider.shutdown();
- }
resolvedProvider = null;
+ getComponent().unregisterEndpoint(domain);
+
File tmp = tempFlagFile;
tempFlagFile = null;
if (tmp != null) {
@@ -114,47 +119,133 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
super.doStop();
}
+ /**
+ * Evaluate a flag from the producer path. Reads flag key, evaluation type, targeting key and context from exchange
+ * headers/properties/configuration. Sets result detail headers on the exchange.
+ */
+ public Object evaluate(Exchange exchange) {
+ Client c = client;
+ if (c == null) {
+ throw new IllegalStateException("OpenFeature endpoint is not started");
+ }
+ String flagKey = resolveFlagKey(exchange);
+ String evalType = resolveEvaluationType(exchange);
+ MutableContext ctx = buildContext(exchange);
+
+ if (isBooleanEvaluation(evalType)) {
+ boolean defaultVal = Boolean.parseBoolean(configuration.getDefaultValue());
+ FlagEvaluationDetails<Boolean> details = c.getBooleanDetails(flagKey, defaultVal, ctx);
+ setResultHeaders(exchange, details);
+ if (details.getErrorCode() != null) {
+ LOG.warn("OpenFeature evaluation error for flag '{}': {} - {}",
+ flagKey, details.getErrorCode(), details.getErrorMessage());
+ }
+ return details.getValue();
+ } else {
+ FlagEvaluationDetails<String> details = c.getStringDetails(flagKey, configuration.getDefaultValue(), ctx);
+ setResultHeaders(exchange, details);
+ if (details.getErrorCode() != null) {
+ LOG.warn("OpenFeature evaluation error for flag '{}': {} - {}",
+ flagKey, details.getErrorCode(), details.getErrorMessage());
+ }
+ return details.getValue();
+ }
+ }
+
+ /**
+ * Evaluate a flag from the language path with explicit parameters. Does not read from or write to the exchange — no
+ * side effects on headers or properties.
+ */
+ public Object evaluate(
+ Exchange exchange, String flagKey, String evaluationType, String targetingKey, Map<String, Object> contextMap) {
+ Client c = client;
+ if (c == null) {
+ throw new IllegalStateException("OpenFeature endpoint is not started");
+ }
+ MutableContext ctx = buildMutableContext(targetingKey, contextMap);
+
+ if (isBooleanEvaluation(evaluationType)) {
+ boolean defaultVal = Boolean.parseBoolean(configuration.getDefaultValue());
+ return c.getBooleanValue(flagKey, defaultVal, ctx);
+ } else {
+ return c.getStringValue(flagKey, configuration.getDefaultValue(), ctx);
+ }
+ }
+
@SuppressWarnings("unchecked")
MutableContext buildContext(Exchange exchange) {
- MutableContext ctx = new MutableContext();
+ Map<String, Object> contextMap = null;
- // 1. CamelOpenFeatureEvaluationContext header
Object contextHeader = exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_CONTEXT);
if (contextHeader == null) {
contextHeader = exchange.getProperty(OpenFeatureConstants.EVALUATION_CONTEXT);
}
if (contextHeader instanceof Map) {
- addMapToContext(ctx, (Map<String, Object>) contextHeader);
- } else {
- // Fallback to body map entries
+ contextMap = (Map<String, Object>) contextHeader;
+ } else if (configuration.isContextFromBody()) {
Object body = exchange.getMessage().getBody();
if (body instanceof Map) {
- addMapToContext(ctx, (Map<String, Object>) body);
+ contextMap = (Map<String, Object>) body;
}
}
- // 2. CamelOpenFeatureTargetingKey header or exchange property
String targetingKey = exchange.getMessage().getHeader(OpenFeatureConstants.TARGETING_KEY, String.class);
if (targetingKey == null) {
targetingKey = exchange.getProperty(OpenFeatureConstants.TARGETING_KEY, String.class);
}
- if (targetingKey != null) {
- ctx.setTargetingKey(targetingKey);
- }
- return ctx;
+ return buildMutableContext(targetingKey, contextMap);
}
- private static void addMapToContext(MutableContext ctx, Map<String, Object> map) {
- for (Map.Entry<String, Object> entry : map.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue() != null ? String.valueOf(entry.getValue()) : "";
- if ("targetingKey".equals(key)) {
- ctx.setTargetingKey(value);
+ static MutableContext buildMutableContext(String targetingKey, Map<String, Object> contextMap) {
+ if (contextMap == null || contextMap.isEmpty()) {
+ if (targetingKey != null) {
+ return new MutableContext(targetingKey);
+ }
+ return new MutableContext();
+ }
+
+ Map<String, Value> attributes = new HashMap<>();
+ String tk = targetingKey;
+ for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
+ if ("targetingKey".equals(entry.getKey())) {
+ if (tk == null && entry.getValue() != null) {
+ tk = String.valueOf(entry.getValue());
+ }
} else {
- ctx.add(key, value);
+ attributes.put(entry.getKey(), toValue(entry.getValue()));
}
}
+
+ if (tk != null) {
+ return new MutableContext(tk, attributes);
+ }
+ return new MutableContext(attributes);
+ }
+
+ private static Value toValue(Object obj) {
+ if (obj == null) {
+ return new Value();
+ }
+ if (obj instanceof Boolean) {
+ return new Value((Boolean) obj);
+ }
+ if (obj instanceof String) {
+ return new Value((String) obj);
+ }
+ if (obj instanceof Integer) {
+ return new Value((Integer) obj);
+ }
+ if (obj instanceof Long) {
+ return new Value(((Long) obj).intValue());
+ }
+ if (obj instanceof Double) {
+ return new Value((Double) obj);
+ }
+ if (obj instanceof Instant) {
+ return new Value((Instant) obj);
+ }
+ return new Value(String.valueOf(obj));
}
String resolveFlagKey(Exchange exchange) {
@@ -173,24 +264,41 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
return key;
}
- public Object evaluate(Exchange exchange) {
- Client c = client;
- if (c == null) {
- throw new IllegalStateException("OpenFeature endpoint is not started");
+ private String resolveEvaluationType(Exchange exchange) {
+ String evalType = exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_TYPE, String.class);
+ if (evalType == null) {
+ evalType = exchange.getProperty(OpenFeatureConstants.EVALUATION_TYPE, String.class);
}
- String flagKey = resolveFlagKey(exchange);
- MutableContext ctx = buildContext(exchange);
+ if (evalType == null) {
+ evalType = configuration.getEvaluationType();
+ }
+ return evalType;
+ }
- if (configuration.isBooleanEvaluation(exchange, ctx)) {
- boolean defaultVal = Boolean.parseBoolean(configuration.getDefaultValue());
- return c.getBooleanValue(flagKey, defaultVal, ctx);
- } else {
- return c.getStringValue(flagKey, configuration.getDefaultValue(), ctx);
+ private boolean isBooleanEvaluation(String evalType) {
+ if ("boolean".equalsIgnoreCase(evalType)) {
+ return true;
+ }
+ if ("variant".equalsIgnoreCase(evalType)) {
+ return false;
+ }
+ String dv = configuration.getDefaultValue();
+ return "true".equalsIgnoreCase(dv) || "false".equalsIgnoreCase(dv);
+ }
+
+ private void setResultHeaders(Exchange exchange, FlagEvaluationDetails<?> details) {
+ if (details.getVariant() != null) {
+ exchange.getMessage().setHeader(OpenFeatureConstants.EVALUATION_VARIANT, details.getVariant());
+ }
+ if (details.getReason() != null) {
+ exchange.getMessage().setHeader(OpenFeatureConstants.EVALUATION_REASON, details.getReason());
+ }
+ if (details.getErrorCode() != null) {
+ exchange.getMessage().setHeader(OpenFeatureConstants.EVALUATION_ERROR_CODE, details.getErrorCode().name());
}
}
private FeatureProvider resolveProvider() throws IOException {
- // 1. Explicit provider bean reference
String providerRef = configuration.getProvider();
if (providerRef != null) {
String beanName = providerRef.startsWith("#") ? providerRef.substring(1) : providerRef;
@@ -204,7 +312,6 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
return provider;
}
- // 2. Registry lookup for default bean
FeatureProvider defaultProvider = getCamelContext().getRegistry()
.lookupByNameAndType(DEFAULT_PROVIDER_BEAN, FeatureProvider.class);
if (defaultProvider != null) {
@@ -212,33 +319,50 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
return defaultProvider;
}
- // 3. Fallback to FlagdProvider
ownedProvider = true;
return createFlagdProvider();
}
private FeatureProvider createFlagdProvider() throws IOException {
if (configuration.getFlags() != null) {
- return createFileProvider(configuration.getFlags());
+ return createFileProviderFromContent(configuration.getFlags());
}
if (configuration.getFlagsResource() != null) {
- String content = loadResource(configuration.getFlagsResource());
- return createFileProvider(content);
+ String resource = configuration.getFlagsResource();
+ if (resource.startsWith("file:")) {
+ return createFileProviderFromPath(resource.substring(5));
+ }
+ String content = loadResource(resource);
+ return createFileProviderFromContent(content);
}
if (configuration.getHost() != null) {
- FlagdOptions options = FlagdOptions.builder()
+ FlagdOptions.FlagdOptionsBuilder builder = FlagdOptions.builder()
.host(configuration.getHost())
.port(configuration.getPort())
- .build();
- return new FlagdProvider(options);
+ .deadline(configuration.getDeadline());
+ if (configuration.isTls()) {
+ builder.tls(true);
+ if (configuration.getCertPath() != null) {
+ builder.certPath(configuration.getCertPath());
+ }
+ }
+ return new FlagdProvider(builder.build());
}
throw new IllegalArgumentException(
"No provider found. Set a provider bean reference, register a '" + DEFAULT_PROVIDER_BEAN
+ "' bean of type FeatureProvider, or configure flags, flagsResource, or host for the default flagd provider.");
}
- private FlagdProvider createFileProvider(String flagContent) throws IOException {
- File tmp = File.createTempFile("camel-openfeature-", ".json");
+ private FlagdProvider createFileProviderFromPath(String path) {
+ FlagdOptions options = FlagdOptions.builder()
+ .resolverType(Config.Resolver.FILE)
+ .offlineFlagSourcePath(path)
+ .build();
+ return new FlagdProvider(options);
+ }
+
+ private FlagdProvider createFileProviderFromContent(String flagContent) throws IOException {
+ File tmp = Files.createTempFile("camel-openfeature-", ".json").toFile();
Files.writeString(tmp.toPath(), flagContent, StandardCharsets.UTF_8);
tempFlagFile = tmp;
@@ -287,7 +411,8 @@ public class OpenFeatureEndpoint extends DefaultEndpoint {
}
/**
- * Bean reference to a custom FeatureProvider (e.g. #myProvider). Mutually exclusive with flags and flagsResource.
+ * Bean reference to a custom FeatureProvider (e.g. #myProvider). When set, takes precedence over flags,
+ * flagsResource, and host.
*/
public void setProvider(String provider) {
configuration.setProvider(provider);
diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/language/openfeature/OpenFeatureLanguage.java b/components/camel-openfeature/src/main/java/org/apache/camel/language/openfeature/OpenFeatureLanguage.java
index b177082d1826..4996ae730ddb 100644
--- a/components/camel-openfeature/src/main/java/org/apache/camel/language/openfeature/OpenFeatureLanguage.java
+++ b/components/camel-openfeature/src/main/java/org/apache/camel/language/openfeature/OpenFeatureLanguage.java
@@ -102,9 +102,8 @@ public class OpenFeatureLanguage extends LanguageSupport {
private final String endpointUri;
private final String targetingKey;
private final Map<String, Object> contextMap;
+ private final String evaluationType;
private OpenFeatureEndpoint endpoint;
- private boolean useFlagKeyHeader;
- private String evaluationType;
private Evaluation(String flagKey, String endpointUri, String targetingKey,
Map<String, Object> contextMap, String evaluationType) {
@@ -121,43 +120,43 @@ public class OpenFeatureLanguage extends LanguageSupport {
if (endpointUri == null || !endpointUri.startsWith("openfeature:")) {
throw new IllegalArgumentException("OpenFeature language endpoint must be an openfeature: URI");
}
- String uri = endpointUri;
- if (uri.contains("flagKey=")) {
- useFlagKeyHeader = true;
- } else {
- uri += (uri.contains("?") ? "&" : "?") + "flagKey=" + flagKey;
- useFlagKeyHeader = false;
- }
- endpoint = context.getEndpoint(uri, OpenFeatureEndpoint.class);
+ endpoint = context.getEndpoint(endpointUri, OpenFeatureEndpoint.class);
}
+ @SuppressWarnings("unchecked")
@Override
public <T> T evaluate(Exchange exchange, Class<T> type) {
- if (Boolean.class.isAssignableFrom(type)) {
- evaluationType = "boolean";
+ String localEvalType = evaluationType;
+ if (type != null && (Boolean.class.isAssignableFrom(type) || boolean.class == type)) {
+ localEvalType = "boolean";
}
- return super.evaluate(exchange, type);
+ Object result = doEvaluate(exchange, localEvalType);
+ return exchange.getContext().getTypeConverter().convertTo(type, exchange, result);
}
@Override
public Object evaluate(Exchange exchange) {
+ return doEvaluate(exchange, evaluationType);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object doEvaluate(Exchange exchange, String evalType) {
try {
if (endpoint == null) {
throw new IllegalStateException("OpenFeature expression must be initialized");
}
- if (useFlagKeyHeader) {
- exchange.setProperty(OpenFeatureConstants.FLAG_KEY, flagKey);
- }
- if (targetingKey != null) {
- exchange.setProperty(OpenFeatureConstants.TARGETING_KEY, targetingKey);
- }
- if (contextMap != null && !contextMap.isEmpty()) {
- exchange.setProperty(OpenFeatureConstants.EVALUATION_CONTEXT, contextMap);
+ String tk = targetingKey;
+ if (tk == null) {
+ tk = exchange.getMessage().getHeader(OpenFeatureConstants.TARGETING_KEY, String.class);
}
- if (evaluationType != null) {
- exchange.setProperty(OpenFeatureConstants.EVALUATION_TYPE, evaluationType);
+ Map<String, Object> ctx = contextMap;
+ if (ctx == null) {
+ Object ctxHeader = exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_CONTEXT);
+ if (ctxHeader instanceof Map) {
+ ctx = (Map<String, Object>) ctxHeader;
+ }
}
- return endpoint.evaluate(exchange);
+ return endpoint.evaluate(exchange, flagKey, evalType, tk, ctx);
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}
diff --git a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java
index 158ac7aebac8..01882f76092f 100644
--- a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java
+++ b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java
@@ -71,9 +71,15 @@ class OpenFeatureLanguageTest extends CamelTestSupport {
.end();
from("direct:with-context")
- .filter().language("openfeature", "enrichment-enabled")
+ .filter().language("openfeature", "targeted-boolean")
.to("mock:context-filtered")
.end();
+
+ from("direct:language-then-producer")
+ .filter().language("openfeature", "enrichment-enabled")
+ .to("mock:lang-filtered")
+ .end()
+ .to("openfeature:flags?flagKey=hazmat-compliance-v2&evaluationType=variant&resultProperty=variant");
}
};
}
@@ -118,7 +124,7 @@ class OpenFeatureLanguageTest extends CamelTestSupport {
}
@Test
- void testEvaluationContextViaHeaders() throws Exception {
+ void testTargetedBooleanWithContextHeaders() throws Exception {
MockEndpoint filtered = getMockEndpoint("mock:context-filtered");
filtered.expectedMessageCount(1);
@@ -129,10 +135,34 @@ class OpenFeatureLanguageTest extends CamelTestSupport {
filtered.assertIsSatisfied();
}
+ @Test
+ void testTargetedBooleanBlocksForStandard() throws Exception {
+ MockEndpoint filtered = getMockEndpoint("mock:context-filtered");
+ filtered.expectedMessageCount(0);
+
+ template.sendBodyAndHeaders("direct:with-context", "test-message",
+ Map.of(OpenFeatureConstants.TARGETING_KEY, "user-456",
+ OpenFeatureConstants.EVALUATION_CONTEXT, Map.of("customer_tier", "STANDARD")));
+
+ filtered.assertIsSatisfied();
+ }
+
+ @Test
+ void testLanguageDoesNotLeakIntoProducer() {
+ Exchange exchange = template.request("direct:language-then-producer", e -> {
+ e.getMessage().setBody("test-message");
+ e.getMessage().setHeader(OpenFeatureConstants.EVALUATION_CONTEXT,
+ Map.of("targetingKey", "order-123", "customer_tier", "ENTERPRISE"));
+ });
+ assertThat(exchange.getProperty("variant")).isEqualTo("v2");
+ assertThat(exchange.getProperty(OpenFeatureConstants.EVALUATION_TYPE)).isNull();
+ assertThat(exchange.getProperty(OpenFeatureConstants.FLAG_KEY)).isNull();
+ }
+
@Test
void testCreatePredicateWithContext() throws Exception {
Predicate flag = context.resolveLanguage("openfeature").createPredicate(
- "enrichment-enabled",
+ "targeted-boolean",
new Object[] { null, "user-42", Map.of("customer_tier", "ENTERPRISE") });
context.addRoutes(new RouteBuilder() {
@@ -153,6 +183,30 @@ class OpenFeatureLanguageTest extends CamelTestSupport {
filtered.assertIsSatisfied();
}
+ @Test
+ void testCreatePredicateBlocksForStandard() throws Exception {
+ Predicate flag = context.resolveLanguage("openfeature").createPredicate(
+ "targeted-boolean",
+ new Object[] { null, "user-42", Map.of("customer_tier", "STANDARD") });
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:predicate-context-standard")
+ .filter(flag)
+ .to("mock:predicate-filtered-standard")
+ .end();
+ }
+ });
+
+ MockEndpoint filtered = getMockEndpoint("mock:predicate-filtered-standard");
+ filtered.expectedMessageCount(0);
+
+ template.sendBody("direct:predicate-context-standard", "test-message");
+
+ filtered.assertIsSatisfied();
+ }
+
@Test
void testCreateExpressionWithContext() {
Expression expr = context.resolveLanguage("openfeature").createExpression(
diff --git a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java
index de9cf35a8db9..9d300c70d50e 100644
--- a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java
+++ b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java
@@ -50,7 +50,8 @@ class OpenFeatureProducerTest extends CamelTestSupport {
from("direct:targeted-flag")
.to("openfeature:test-targeted?flagKey=hazmat-compliance-v2&defaultValue=v1&flagsResource="
- + FLAGS_RESOURCE);
+ + FLAGS_RESOURCE
+ + "&contextFromBody=true");
from("direct:variant-type")
.to("openfeature:test-variant?flagKey=new-routing-algorithm&evaluationType=variant&flagsResource="
@@ -59,6 +60,15 @@ class OpenFeatureProducerTest extends CamelTestSupport {
from("direct:boolean-type-explicit")
.to("openfeature:test-bool-type?flagKey=enrichment-enabled&evaluationType=boolean&flagsResource="
+ FLAGS_RESOURCE);
+
+ from("direct:targeted-boolean")
+ .to("openfeature:test-targeted-bool?flagKey=targeted-boolean&flagsResource="
+ + FLAGS_RESOURCE
+ + "&contextFromBody=true");
+
+ from("direct:targeted-boolean-header")
+ .to("openfeature:test-targeted-bool-hdr?flagKey=targeted-boolean&flagsResource="
+ + FLAGS_RESOURCE);
}
};
}
@@ -181,4 +191,53 @@ class OpenFeatureProducerTest extends CamelTestSupport {
Object result = template.requestBody("direct:inline-flag", "ignored");
assertThat(result).isEqualTo(true);
}
+
+ @Test
+ void testBooleanFlagWithTargetingKeyReturnsBooleanNotString() {
+ Exchange exchange = template.request("direct:targeted-boolean", e -> {
+ e.getMessage().setBody(Map.of("customer_tier", "ENTERPRISE"));
+ e.getMessage().setHeader(OpenFeatureConstants.TARGETING_KEY, "user-123");
+ });
+ Object result = exchange.getMessage().getBody();
+ assertThat(result).isInstanceOf(Boolean.class);
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ void testBooleanFlagWithTargetingKeyDefaultVariant() {
+ Exchange exchange = template.request("direct:targeted-boolean", e -> {
+ e.getMessage().setBody(Map.of("customer_tier", "STANDARD"));
+ e.getMessage().setHeader(OpenFeatureConstants.TARGETING_KEY, "user-456");
+ });
+ Object result = exchange.getMessage().getBody();
+ assertThat(result).isInstanceOf(Boolean.class);
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ void testBooleanFlagWithMapBodyReturnsBoolean() {
+ Object result = template.requestBody("direct:targeted-boolean",
+ Map.of("targetingKey", "user-789", "customer_tier", "VIP"));
+ assertThat(result).isInstanceOf(Boolean.class);
+ assertThat(result).isEqualTo(true);
+ }
+
+ @Test
+ void testMapBodyNotUsedAsContextByDefault() {
+ Object result = template.requestBody("direct:targeted-boolean-header",
+ Map.of("targetingKey", "user-789", "customer_tier", "ENTERPRISE"));
+ assertThat(result).isInstanceOf(Boolean.class);
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ void testEvaluationDetailsHeaders() {
+ Exchange exchange = template.request("direct:boolean-flag", e -> e.getMessage().setBody("ignored"));
+ assertThat(exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_VARIANT, String.class))
+ .isNotNull();
+ assertThat(exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_REASON, String.class))
+ .isNotNull();
+ assertThat(exchange.getMessage().getHeader(OpenFeatureConstants.EVALUATION_ERROR_CODE))
+ .isNull();
+ }
}
diff --git a/components/camel-openfeature/src/test/resources/openfeature/flags.json b/components/camel-openfeature/src/test/resources/openfeature/flags.json
index 7932f035210c..cc1fff2f034a 100644
--- a/components/camel-openfeature/src/test/resources/openfeature/flags.json
+++ b/components/camel-openfeature/src/test/resources/openfeature/flags.json
@@ -36,6 +36,18 @@
"state": "DISABLED",
"variants": { "on": true, "off": false },
"defaultVariant": "on"
+ },
+ "targeted-boolean": {
+ "state": "ENABLED",
+ "variants": { "on": true, "off": false },
+ "defaultVariant": "off",
+ "targeting": {
+ "if": [
+ { "in": [{ "var": "customer_tier" }, ["ENTERPRISE", "VIP"]] },
+ "on",
+ "off"
+ ]
+ }
}
}
}
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
index 2de48ead300f..c6cc2d7c4830 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
@@ -3899,7 +3899,7 @@ public interface ComponentsBuilderFactory {
* OpenFeature (camel-openfeature)
* Evaluate feature flags using the OpenFeature specification with flagd.
*
- * Category: core
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfeatureComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfeatureComponentBuilderFactory.java
index 5bedde702bb2..2d89a44e7cae 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfeatureComponentBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfeatureComponentBuilderFactory.java
@@ -35,7 +35,7 @@ public interface OpenfeatureComponentBuilderFactory {
* OpenFeature (camel-openfeature)
* Evaluate feature flags using the OpenFeature specification with flagd.
*
- * Category: core
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
@@ -50,6 +50,21 @@ public interface OpenfeatureComponentBuilderFactory {
*/
interface OpenfeatureComponentBuilder extends ComponentBuilder<OpenFeatureComponent> {
+ /**
+ * Path to the TLS certificate for the remote flagd connection.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: common
+ *
+ * @param certPath the value to set
+ * @return the dsl builder
+ */
+ default OpenfeatureComponentBuilder certPath(java.lang.String certPath) {
+ doSetProperty("certPath", certPath);
+ return this;
+ }
+
/**
* Default configuration shared by OpenFeature endpoints.
*
@@ -67,6 +82,43 @@ public interface OpenfeatureComponentBuilderFactory {
}
+ /**
+ * When true, a Map message body is used as the evaluation context. When
+ * false (default), the body is not used as context. The
+ * CamelOpenFeatureEvaluationContext header is always used regardless of
+ * this setting.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param contextFromBody the value to set
+ * @return the dsl builder
+ */
+ default OpenfeatureComponentBuilder contextFromBody(boolean contextFromBody) {
+ doSetProperty("contextFromBody", contextFromBody);
+ return this;
+ }
+
+
+ /**
+ * Deadline in milliseconds for the remote flagd connection.
+ *
+ * The option is a: <code>int</code> type.
+ *
+ * Default: 500
+ * Group: common
+ *
+ * @param deadline the value to set
+ * @return the dsl builder
+ */
+ default OpenfeatureComponentBuilder deadline(int deadline) {
+ doSetProperty("deadline", deadline);
+ return this;
+ }
+
+
/**
* Default value when flag evaluation fails. When evaluationType is not
* set, also determines the evaluation type: true or false
@@ -185,8 +237,8 @@ public interface OpenfeatureComponentBuilderFactory {
}
/**
- * Bean reference to a custom FeatureProvider (e.g. #myProvider).
- * Mutually exclusive with flags and flagsResource.
+ * Bean reference to a custom FeatureProvider (e.g. #myProvider). When
+ * set, takes precedence over flags, flagsResource, and host.
*
* The option is a: <code>java.lang.String</code> type.
*
@@ -201,6 +253,23 @@ public interface OpenfeatureComponentBuilderFactory {
}
+ /**
+ * Whether to use TLS for the remote flagd connection.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param tls the value to set
+ * @return the dsl builder
+ */
+ default OpenfeatureComponentBuilder tls(boolean tls) {
+ doSetProperty("tls", tls);
+ return this;
+ }
+
+
/**
* Whether the producer should be started lazy (on the first message).
* By starting lazy you can use this to allow CamelContext and routes to
@@ -283,7 +352,10 @@ public interface OpenfeatureComponentBuilderFactory {
String name,
Object value) {
switch (name) {
+ case "certPath": getOrCreateConfiguration((OpenFeatureComponent) component).setCertPath((java.lang.String) value); return true;
case "configuration": ((OpenFeatureComponent) component).setConfiguration((org.apache.camel.component.openfeature.OpenFeatureConfiguration) value); return true;
+ case "contextFromBody": getOrCreateConfiguration((OpenFeatureComponent) component).setContextFromBody((boolean) value); return true;
+ case "deadline": getOrCreateConfiguration((OpenFeatureComponent) component).setDeadline((int) value); return true;
case "defaultValue": getOrCreateConfiguration((OpenFeatureComponent) component).setDefaultValue((java.lang.String) value); return true;
case "evaluationType": getOrCreateConfiguration((OpenFeatureComponent) component).setEvaluationType((java.lang.String) value); return true;
case "flagKey": getOrCreateConfiguration((OpenFeatureComponent) component).setFlagKey((java.lang.String) value); return true;
@@ -292,6 +364,7 @@ public interface OpenfeatureComponentBuilderFactory {
case "host": getOrCreateConfiguration((OpenFeatureComponent) component).setHost((java.lang.String) value); return true;
case "port": getOrCreateConfiguration((OpenFeatureComponent) component).setPort((int) value); return true;
case "provider": getOrCreateConfiguration((OpenFeatureComponent) component).setProvider((java.lang.String) value); return true;
+ case "tls": getOrCreateConfiguration((OpenFeatureComponent) component).setTls((boolean) value); return true;
case "lazyStartProducer": ((OpenFeatureComponent) component).setLazyStartProducer((boolean) value); return true;
case "resultProperty": getOrCreateConfiguration((OpenFeatureComponent) component).setResultProperty((java.lang.String) value); return true;
case "autowiredEnabled": ((OpenFeatureComponent) component).setAutowiredEnabled((boolean) value); return true;
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
index 1bdb1846d220..7e39461b9f4f 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java
@@ -3048,6 +3048,19 @@ public class EndpointHeaderBuilders {
public static OpenAIEndpointBuilderFactory.OpenAIHeaderNameBuilder openai() {
return OpenAIEndpointBuilderFactory.OpenAIHeaderNameBuilder.INSTANCE;
}
+ /**
+ * OpenFeature (camel-openfeature)
+ * Evaluate feature flags using the OpenFeature specification with flagd.
+ *
+ * Category: cloud
+ * Since: 4.23
+ * Maven coordinates: org.apache.camel:camel-openfeature
+ *
+ * @return the dsl builder for the headers' name.
+ */
+ public static OpenFeatureEndpointBuilderFactory.OpenFeatureHeaderNameBuilder openfeature() {
+ return OpenFeatureEndpointBuilderFactory.OpenFeatureHeaderNameBuilder.INSTANCE;
+ }
/**
* OpenFGA (camel-openfga)
* Authorize an Exchange against an OpenFGA relationship graph, and maintain
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
index 48d05145860c..5086645a3565 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
@@ -12906,7 +12906,7 @@ public class StaticEndpointBuilders {
* OpenFeature (camel-openfeature)
* Evaluate feature flags using the OpenFeature specification with flagd.
*
- * Category: core
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
@@ -12925,7 +12925,7 @@ public class StaticEndpointBuilders {
* OpenFeature (camel-openfeature)
* Evaluate feature flags using the OpenFeature specification with flagd.
*
- * Category: core
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFeatureEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFeatureEndpointBuilderFactory.java
index 062e3aa4fb4f..b9df91a1b0ed 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFeatureEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFeatureEndpointBuilderFactory.java
@@ -44,6 +44,86 @@ public interface OpenFeatureEndpointBuilderFactory {
return (AdvancedOpenFeatureEndpointBuilder) this;
}
+ /**
+ * Path to the TLS certificate for the remote flagd connection.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: common
+ *
+ * @param certPath the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder certPath(String certPath) {
+ doSetProperty("certPath", certPath);
+ return this;
+ }
+ /**
+ * When true, a Map message body is used as the evaluation context. When
+ * false (default), the body is not used as context. The
+ * CamelOpenFeatureEvaluationContext header is always used regardless of
+ * this setting.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param contextFromBody the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder contextFromBody(boolean contextFromBody) {
+ doSetProperty("contextFromBody", contextFromBody);
+ return this;
+ }
+ /**
+ * When true, a Map message body is used as the evaluation context. When
+ * false (default), the body is not used as context. The
+ * CamelOpenFeatureEvaluationContext header is always used regardless of
+ * this setting.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param contextFromBody the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder contextFromBody(String contextFromBody) {
+ doSetProperty("contextFromBody", contextFromBody);
+ return this;
+ }
+ /**
+ * Deadline in milliseconds for the remote flagd connection.
+ *
+ * The option is a: <code>int</code> type.
+ *
+ * Default: 500
+ * Group: common
+ *
+ * @param deadline the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder deadline(int deadline) {
+ doSetProperty("deadline", deadline);
+ return this;
+ }
+ /**
+ * Deadline in milliseconds for the remote flagd connection.
+ *
+ * The option will be converted to a <code>int</code> type.
+ *
+ * Default: 500
+ * Group: common
+ *
+ * @param deadline the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder deadline(String deadline) {
+ doSetProperty("deadline", deadline);
+ return this;
+ }
/**
* Default value when flag evaluation fails. When evaluationType is not
* set, also determines the evaluation type: true or false
@@ -169,8 +249,8 @@ public interface OpenFeatureEndpointBuilderFactory {
return this;
}
/**
- * Bean reference to a custom FeatureProvider (e.g. #myProvider).
- * Mutually exclusive with flags and flagsResource.
+ * Bean reference to a custom FeatureProvider (e.g. #myProvider). When
+ * set, takes precedence over flags, flagsResource, and host.
*
* The option is a: <code>java.lang.String</code> type.
*
@@ -183,6 +263,36 @@ public interface OpenFeatureEndpointBuilderFactory {
doSetProperty("provider", provider);
return this;
}
+ /**
+ * Whether to use TLS for the remote flagd connection.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param tls the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder tls(boolean tls) {
+ doSetProperty("tls", tls);
+ return this;
+ }
+ /**
+ * Whether to use TLS for the remote flagd connection.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: common
+ *
+ * @param tls the value to set
+ * @return the dsl builder
+ */
+ default OpenFeatureEndpointBuilder tls(String tls) {
+ doSetProperty("tls", tls);
+ return this;
+ }
/**
* Store the evaluation result in this exchange property, preserving the
* original message body.
@@ -264,7 +374,21 @@ public interface OpenFeatureEndpointBuilderFactory {
* Evaluate feature flags using the OpenFeature specification with
* flagd.
*
- * Category: core
+ * Category: cloud
+ * Since: 4.23
+ * Maven coordinates: org.apache.camel:camel-openfeature
+ *
+ * @return the dsl builder for the headers' name.
+ */
+ default OpenFeatureHeaderNameBuilder openfeature() {
+ return OpenFeatureHeaderNameBuilder.INSTANCE;
+ }
+ /**
+ * OpenFeature (camel-openfeature)
+ * Evaluate feature flags using the OpenFeature specification with
+ * flagd.
+ *
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
@@ -284,7 +408,7 @@ public interface OpenFeatureEndpointBuilderFactory {
* Evaluate feature flags using the OpenFeature specification with
* flagd.
*
- * Category: core
+ * Category: cloud
* Since: 4.23
* Maven coordinates: org.apache.camel:camel-openfeature
*
@@ -303,6 +427,103 @@ public interface OpenFeatureEndpointBuilderFactory {
}
}
+ /**
+ * The builder of headers' name for the OpenFeature component.
+ */
+ public static class OpenFeatureHeaderNameBuilder {
+ /**
+ * The internal instance of the builder used to access to all the
+ * methods representing the name of headers.
+ */
+ public static final OpenFeatureHeaderNameBuilder INSTANCE = new OpenFeatureHeaderNameBuilder();
+
+ /**
+ * Overrides the configured flag key for this message.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureFlagKey}.
+ */
+ public String openFeatureFlagKey() {
+ return "CamelOpenFeatureFlagKey";
+ }
+ /**
+ * Sets the targeting key for the OpenFeature evaluation context.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureTargetingKey}.
+ */
+ public String openFeatureTargetingKey() {
+ return "CamelOpenFeatureTargetingKey";
+ }
+ /**
+ * A Map of additional evaluation context key-value pairs.
+ *
+ * The option is a: {@code java.util.Map} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureEvaluationContext}.
+ */
+ public String openFeatureEvaluationContext() {
+ return "CamelOpenFeatureEvaluationContext";
+ }
+ /**
+ * Sets the evaluation type (boolean, variant) for the OpenFeature
+ * evaluation.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureEvaluationType}.
+ */
+ public String openFeatureEvaluationType() {
+ return "CamelOpenFeatureEvaluationType";
+ }
+ /**
+ * The variant name returned by the provider for this evaluation.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureVariant}.
+ */
+ public String openFeatureVariant() {
+ return "CamelOpenFeatureVariant";
+ }
+ /**
+ * The reason string returned by the provider for this evaluation.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureReason}.
+ */
+ public String openFeatureReason() {
+ return "CamelOpenFeatureReason";
+ }
+ /**
+ * The error code when the evaluation failed (e.g. FLAG_NOT_FOUND,
+ * TYPE_MISMATCH).
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFeatureErrorCode}.
+ */
+ public String openFeatureErrorCode() {
+ return "CamelOpenFeatureErrorCode";
+ }
+ }
static OpenFeatureEndpointBuilder endpointBuilder(String componentName, String path) {
class OpenFeatureEndpointBuilderImpl extends AbstractEndpointBuilder implements OpenFeatureEndpointBuilder, AdvancedOpenFeatureEndpointBuilder {
public OpenFeatureEndpointBuilderImpl(String path) {
diff --git a/parent/pom.xml b/parent/pom.xml
index 42118f5cbd18..2eaef6fbb8ce 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -421,6 +421,8 @@
<openfeature-sdk-version>1.22.1</openfeature-sdk-version>
<openfga-version>0.11.0</openfga-version>
<flagd-provider-version>0.14.2</flagd-provider-version>
+ <flagd-core-version>2.0.1</flagd-core-version>
+ <flagd-api-version>1.0.0</flagd-api-version>
<mcp-java-sdk-version>2.0.1</mcp-java-sdk-version>
<mssql-jdbc-version>12.10.0.jre11</mssql-jdbc-version>
<ojdbc-version>23.8.0.25.04</ojdbc-version>
@@ -3909,6 +3911,17 @@
<artifactId>javax.inject</artifactId>
<version>${javax-inject-version}</version>
</dependency>
+ <!-- flagd declares open version ranges for these transitive deps; pin them -->
+ <dependency>
+ <groupId>dev.openfeature.contrib.tools</groupId>
+ <artifactId>flagd-core</artifactId>
+ <version>${flagd-core-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>dev.openfeature.contrib.tools</groupId>
+ <artifactId>flagd-api</artifactId>
+ <version>${flagd-api-version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>