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 880af034ccf88dd281ae6bba6f62ecbbb09c06ff Author: Christoph Deppisch <[email protected]> AuthorDate: Wed Oct 7 16:31:50 2026 +0200 CAMEL-25333: Address review feedback on camel-openfeature component - Fix TOCTOU race in doStart() by replacing separate hasDomainBinding/ acquireClient/registerEndpoint calls with atomic acquireOrRegister - Fix provider leak on partial failure: if setProviderAndWait throws, the owned provider is shut down and temp file cleaned up - Move temp flag file ownership to DomainBinding so it is only deleted when the last endpoint on the domain stops, not when the first endpoint stops while others still use the shared provider - Add LOG.debug when implicit 'flags' bean is found in registry - Add LOG.warn when certPath is set but tls=false - Update flagsResource description: provider takes precedence, not mutually exclusive Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/catalog/components/openfeature.json | 20 ++--- .../camel/component/openfeature/openfeature.json | 4 +- .../openfeature/OpenFeatureComponent.java | 90 +++++++++++++++++----- .../openfeature/OpenFeatureConfiguration.java | 6 +- .../component/openfeature/OpenFeatureEndpoint.java | 45 +++++------ .../dsl/OpenfeatureComponentBuilderFactory.java | 11 +-- .../builder/endpoint/StaticEndpointBuilders.java | 22 +++++- .../dsl/OpenFeatureEndpointBuilderFactory.java | 46 ++++++----- 8 files changed, 150 insertions(+), 94 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 163924b3bbcf..5fb820910de7 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 @@ -14,7 +14,7 @@ "version": "4.23.0-SNAPSHOT", "scheme": "openfeature", "extendsScheme": "", - "syntax": "openfeature:domain", + "syntax": "openfeature:domain\/evaluationType", "async": false, "api": false, "consumerOnly": false, @@ -29,10 +29,10 @@ "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 [...] + "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant", "isEnabled" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. 'boolean' and 'isEnabled' u [...] "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 [...] + "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 [...] @@ -45,21 +45,21 @@ "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" }, + "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, isEnabled) 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." }, - "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 [...] + "evaluationType": { "index": 1, "kind": "path", "displayName": "Evaluation Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant", "isEnabled" ], "deprecated": false, "autowired": false, "secret": false, "description": "The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation (getBooleanValue). 'variant' uses string evaluation (getStringValue). When not set, the type is inferred from defa [...] + "certPath": { "index": 2, "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": 3, "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": 4, "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": 5, "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 [...] "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 [...] + "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 [...] 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 e5795988c0d6..5fb820910de7 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 @@ -32,7 +32,7 @@ "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant", "isEnabled" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. 'boolean' and 'isEnabled' u [...] "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 [...] + "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 [...] @@ -59,7 +59,7 @@ "defaultValue": { "index": 5, "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 [...] "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 [...] + "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 [...] 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 65bb25a1455f..17dca80e65c4 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 @@ -16,6 +16,9 @@ */ package org.apache.camel.component.openfeature; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -44,12 +47,48 @@ public class OpenFeatureComponent extends DefaultComponent { final FeatureProvider provider; final boolean owned; int refCount; + File tempFlagFile; DomainBinding(FeatureProvider provider, boolean owned) { this.provider = provider; this.owned = owned; this.refCount = 1; } + + void shutdown() { + if (owned) { + try { + provider.shutdown(); + } catch (Exception e) { + LOG.debug("Error shutting down owned provider: {}", e.getMessage(), e); + } + } + + if (tempFlagFile != null) { + try { + Files.deleteIfExists(tempFlagFile.toPath()); + } catch (IOException e) { + LOG.debug("Error deleting temp flag file: {}", e.getMessage(), e); + } + tempFlagFile = null; + } + } + } + + static class ProviderRegistration { + final FeatureProvider provider; + final boolean owned; + File tempFlagFile; + + ProviderRegistration(FeatureProvider provider, boolean owned) { + this.provider = provider; + this.owned = owned; + } + } + + @FunctionalInterface + interface ProviderSupplier { + ProviderRegistration get() throws Exception; } @Override @@ -79,6 +118,9 @@ public class OpenFeatureComponent extends DefaultComponent { protected void doStop() throws Exception { OpenFeatureAPI localApi = api; api = null; + for (DomainBinding binding : domainBindings.values()) { + binding.shutdown(); + } domainBindings.clear(); if (localApi != null) { try { @@ -90,22 +132,36 @@ public class OpenFeatureComponent extends DefaultComponent { super.doStop(); } - synchronized boolean hasDomainBinding(String domain) { - return domainBindings.containsKey(domain); - } - - synchronized Client acquireClient(String domain) { + synchronized Client acquireOrRegister(String domain, ProviderSupplier providerSupplier) throws Exception { DomainBinding existing = domainBindings.get(domain); - if (existing == null) { - throw new IllegalStateException("No binding exists for domain '" + domain + "'"); + if (existing != null) { + existing.refCount++; + return api.getClient(domain); } - existing.refCount++; - return api.getClient(domain); - } - synchronized Client registerEndpoint(String domain, FeatureProvider provider, boolean owned) throws Exception { - api.setProviderAndWait(domain, provider); - domainBindings.put(domain, new DomainBinding(provider, owned)); + ProviderRegistration reg = providerSupplier.get(); + try { + api.setProviderAndWait(domain, reg.provider); + } catch (Exception e) { + if (reg.owned) { + try { + reg.provider.shutdown(); + } catch (Exception suppressed) { + e.addSuppressed(suppressed); + } + } + if (reg.tempFlagFile != null) { + try { + Files.deleteIfExists(reg.tempFlagFile.toPath()); + } catch (IOException suppressed) { + e.addSuppressed(suppressed); + } + } + throw e; + } + DomainBinding binding = new DomainBinding(reg.provider, reg.owned); + binding.tempFlagFile = reg.tempFlagFile; + domainBindings.put(domain, binding); return api.getClient(domain); } @@ -115,13 +171,7 @@ public class OpenFeatureComponent extends DefaultComponent { 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); - } - } + binding.shutdown(); } } } 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 cb44bc145c1e..9b3a32acd73b 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 @@ -67,7 +67,7 @@ public class OpenFeatureConfiguration implements Cloneable { @UriParam(label = "common", description = "Camel resource URI pointing to a feature flag definition file in flagd format." - + " Mutually exclusive with flags and provider.") + + " Mutually exclusive with flags. When provider is also set, the provider takes precedence.") private String flagsResource; @UriParam(label = "common", @@ -182,8 +182,8 @@ public class OpenFeatureConfiguration implements Cloneable { } /** - * Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusive with flags and - * provider. + * Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusive with flags. + * When provider is also set, the provider takes precedence. */ public void setFlagsResource(String flagsResource) { this.flagsResource = flagsResource; 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 3abd2c293368..6cd113c07c5b 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 @@ -73,8 +73,6 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { private OpenFeatureConfiguration configuration; private volatile Client client; - private volatile File tempFlagFile; - private volatile boolean ownedProvider; public OpenFeatureEndpoint(String uri, OpenFeatureComponent component, String domain, String evaluationType, OpenFeatureConfiguration configuration) { @@ -108,12 +106,7 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { throw new IllegalArgumentException("domain must not be blank"); } - if (getComponent().hasDomainBinding(domain)) { - client = getComponent().acquireClient(domain); - } else { - FeatureProvider provider = resolveProvider(); - client = getComponent().registerEndpoint(domain, provider, ownedProvider); - } + client = getComponent().acquireOrRegister(domain, this::resolveProvider); } @Override @@ -122,14 +115,6 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { client = null; getComponent().unregisterEndpoint(domain); } - ownedProvider = false; - - File tmp = tempFlagFile; - tempFlagFile = null; - if (tmp != null) { - Files.deleteIfExists(tmp.toPath()); - } - super.doStop(); } @@ -326,7 +311,7 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { } } - private FeatureProvider resolveProvider() throws IOException { + private OpenFeatureComponent.ProviderRegistration resolveProvider() throws IOException { String providerRef = configuration.getProvider(); if (providerRef != null) { String beanName = providerRef.startsWith("#") ? providerRef.substring(1) : providerRef; @@ -336,27 +321,32 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { throw new IllegalArgumentException( "No FeatureProvider bean found in the registry with name: " + beanName); } - return provider; + return new OpenFeatureComponent.ProviderRegistration(provider, false); } FeatureProvider defaultProvider = getCamelContext().getRegistry() .lookupByNameAndType(DEFAULT_PROVIDER_BEAN, FeatureProvider.class); if (defaultProvider != null) { - return defaultProvider; + LOG.debug("Using FeatureProvider bean '{}' from registry", DEFAULT_PROVIDER_BEAN); + return new OpenFeatureComponent.ProviderRegistration(defaultProvider, false); } return createFlagdProvider(); } - private FeatureProvider createFlagdProvider() throws IOException { - ownedProvider = true; + private OpenFeatureComponent.ProviderRegistration createFlagdProvider() throws IOException { + if (configuration.getCertPath() != null && !configuration.isTls()) { + LOG.warn("certPath is set but tls=false — certPath will be ignored"); + } + if (configuration.getFlags() != null) { return createFileProviderFromContent(configuration.getFlags()); } if (configuration.getFlagsResource() != null) { String resource = configuration.getFlagsResource(); if (resource.startsWith("file:")) { - return createFileProviderFromPath(resource.substring(5)); + return new OpenFeatureComponent.ProviderRegistration( + createFileProviderFromPath(resource.substring(5)), true); } String content = loadResource(resource); return createFileProviderFromContent(content); @@ -372,7 +362,7 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { builder.certPath(configuration.getCertPath()); } } - return new FlagdProvider(builder.build()); + return new OpenFeatureComponent.ProviderRegistration(new FlagdProvider(builder.build()), true); } throw new IllegalArgumentException( "No provider found. Set a provider bean reference, register a '" + DEFAULT_PROVIDER_BEAN @@ -387,16 +377,19 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { return new FlagdProvider(options); } - private FlagdProvider createFileProviderFromContent(String flagContent) throws IOException { + private OpenFeatureComponent.ProviderRegistration createFileProviderFromContent(String flagContent) + throws IOException { File tmp = Files.createTempFile("camel-openfeature-", ".json").toFile(); Files.writeString(tmp.toPath(), flagContent, StandardCharsets.UTF_8); - tempFlagFile = tmp; FlagdOptions options = FlagdOptions.builder() .resolverType(Config.Resolver.FILE) .offlineFlagSourcePath(tmp.getAbsolutePath()) .build(); - return new FlagdProvider(options); + FlagdProvider provider = new FlagdProvider(options); + OpenFeatureComponent.ProviderRegistration reg = new OpenFeatureComponent.ProviderRegistration(provider, true); + reg.tempFlagFile = tmp; + return reg; } private String loadResource(String location) throws IOException { 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 2d89a44e7cae..73530e0410c6 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 @@ -139,10 +139,10 @@ public interface OpenfeatureComponentBuilderFactory { } /** - * The evaluation type. When set to 'boolean', boolean evaluation is - * used (getBooleanValue). When set to 'variant', string evaluation is - * used (getStringValue). When not set, the type is inferred from - * defaultValue. + * The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation + * (getBooleanValue). 'variant' uses string evaluation (getStringValue). + * When not set, the type is inferred from defaultValue. Can also be set + * as a path parameter in the endpoint URI, which takes precedence. * * The option is a: <code>java.lang.String</code> type. * @@ -190,7 +190,8 @@ public interface OpenfeatureComponentBuilderFactory { /** * Camel resource URI pointing to a feature flag definition file in - * flagd format. Mutually exclusive with flags and provider. + * flagd format. Mutually exclusive with flags. When provider is also + * set, the provider takes precedence. * * The option is a: <code>java.lang.String</code> type. * 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 5086645a3565..d1221528d7e9 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 @@ -12910,12 +12910,19 @@ public class StaticEndpointBuilders { * Since: 4.23 * Maven coordinates: org.apache.camel:camel-openfeature * - * Syntax: <code>openfeature:domain</code> + * Syntax: <code>openfeature:domain/evaluationType</code> * * Path parameter: domain (required) * The OpenFeature domain to bind the provider to. * - * @param path domain + * Path parameter: evaluationType + * The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation + * (getBooleanValue). 'variant' uses string evaluation (getStringValue). + * When not set, the type is inferred from defaultValue. + * There are 3 enums and the value can be one of: boolean, variant, + * isEnabled + * + * @param path domain/evaluationType * @return the dsl builder */ public static OpenFeatureEndpointBuilderFactory.OpenFeatureEndpointBuilder openfeature(String path) { @@ -12929,14 +12936,21 @@ public class StaticEndpointBuilders { * Since: 4.23 * Maven coordinates: org.apache.camel:camel-openfeature * - * Syntax: <code>openfeature:domain</code> + * Syntax: <code>openfeature:domain/evaluationType</code> * * Path parameter: domain (required) * The OpenFeature domain to bind the provider to. * + * Path parameter: evaluationType + * The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation + * (getBooleanValue). 'variant' uses string evaluation (getStringValue). + * When not set, the type is inferred from defaultValue. + * There are 3 enums and the value can be one of: boolean, variant, + * isEnabled + * * @param componentName to use a custom component name for the endpoint * instead of the default name - * @param path domain + * @param path domain/evaluationType * @return the dsl builder */ public static OpenFeatureEndpointBuilderFactory.OpenFeatureEndpointBuilder openfeature(String componentName, String path) { 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 b9df91a1b0ed..80adf22f0f3a 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 @@ -142,23 +142,6 @@ public interface OpenFeatureEndpointBuilderFactory { doSetProperty("defaultValue", defaultValue); return this; } - /** - * The evaluation type. When set to 'boolean', boolean evaluation is - * used (getBooleanValue). When set to 'variant', string evaluation is - * used (getStringValue). When not set, the type is inferred from - * defaultValue. - * - * The option is a: <code>java.lang.String</code> type. - * - * Group: common - * - * @param evaluationType the value to set - * @return the dsl builder - */ - default OpenFeatureEndpointBuilder evaluationType(String evaluationType) { - doSetProperty("evaluationType", evaluationType); - return this; - } /** * The feature flag key to evaluate. Can be overridden per message via * the CamelOpenFeatureFlagKey header. @@ -191,7 +174,8 @@ public interface OpenFeatureEndpointBuilderFactory { } /** * Camel resource URI pointing to a feature flag definition file in - * flagd format. Mutually exclusive with flags and provider. + * flagd format. Mutually exclusive with flags. When provider is also + * set, the provider takes precedence. * * The option is a: <code>java.lang.String</code> type. * @@ -392,12 +376,19 @@ public interface OpenFeatureEndpointBuilderFactory { * Since: 4.23 * Maven coordinates: org.apache.camel:camel-openfeature * - * Syntax: <code>openfeature:domain</code> + * Syntax: <code>openfeature:domain/evaluationType</code> * * Path parameter: domain (required) * The OpenFeature domain to bind the provider to. * - * @param path domain + * Path parameter: evaluationType + * The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation + * (getBooleanValue). 'variant' uses string evaluation (getStringValue). + * When not set, the type is inferred from defaultValue. + * There are 3 enums and the value can be one of: boolean, variant, + * isEnabled + * + * @param path domain/evaluationType * @return the dsl builder */ default OpenFeatureEndpointBuilder openfeature(String path) { @@ -412,14 +403,21 @@ public interface OpenFeatureEndpointBuilderFactory { * Since: 4.23 * Maven coordinates: org.apache.camel:camel-openfeature * - * Syntax: <code>openfeature:domain</code> + * Syntax: <code>openfeature:domain/evaluationType</code> * * Path parameter: domain (required) * The OpenFeature domain to bind the provider to. * + * Path parameter: evaluationType + * The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation + * (getBooleanValue). 'variant' uses string evaluation (getStringValue). + * When not set, the type is inferred from defaultValue. + * There are 3 enums and the value can be one of: boolean, variant, + * isEnabled + * * @param componentName to use a custom component name for the endpoint * instead of the default name - * @param path domain + * @param path domain/evaluationType * @return the dsl builder */ default OpenFeatureEndpointBuilder openfeature(String componentName, String path) { @@ -474,8 +472,8 @@ public interface OpenFeatureEndpointBuilderFactory { return "CamelOpenFeatureEvaluationContext"; } /** - * Sets the evaluation type (boolean, variant) for the OpenFeature - * evaluation. + * Sets the evaluation type (boolean, variant, isEnabled) for the + * OpenFeature evaluation. * * The option is a: {@code String} type. *
