You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/10/04 14:29:03 UTC
(camel) branch main updated: CAMEL-25184: camel-openfga - add the readTuples, readChanges and expand operations (#27195)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new cc5a06b04130 CAMEL-25184: camel-openfga - add the readTuples, readChanges and expand operations (#27195)
cc5a06b04130 is described below
commit cc5a06b04130fad4819f1454c48302de0e20d196
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Sun Oct 4 16:28:56 2026 +0200
CAMEL-25184: camel-openfga - add the readTuples, readChanges and expand operations (#27195)
Co-Authored-By: Claude Opus 5 <no...@anthropic.com>
---
.../apache/camel/catalog/components/openfga.json | 105 +++----
.../camel/catalog/docs/openfga-component.adoc | 87 +++++-
.../openfga/OpenFgaComponentConfigurer.java | 18 ++
.../openfga/OpenFgaEndpointConfigurer.java | 18 ++
.../openfga/OpenFgaEndpointUriFactory.java | 5 +-
.../apache/camel/component/openfga/openfga.json | 105 +++----
.../src/main/docs/openfga-component.adoc | 87 +++++-
.../camel/component/openfga/OpenFgaAuthorizer.java | 28 ++
.../component/openfga/OpenFgaConfiguration.java | 49 ++++
.../camel/component/openfga/OpenFgaConstants.java | 10 +
.../camel/component/openfga/OpenFgaEndpoint.java | 37 ++-
.../camel/component/openfga/OpenFgaOperation.java | 16 ++
.../camel/component/openfga/OpenFgaProducer.java | 249 +++++++++++++++++
.../apache/camel/component/openfga/OpenFgaIT.java | 99 +++++++
.../openfga/OpenFgaReadOperationsTest.java | 305 +++++++++++++++++++++
.../dsl/OpenfgaComponentBuilderFactory.java | 59 ++++
.../builder/endpoint/StaticEndpointBuilders.java | 10 +-
.../dsl/OpenFgaEndpointBuilderFactory.java | 98 ++++++-
18 files changed, 1271 insertions(+), 114 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfga.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfga.json
index 5a7bc30f1688..8530743048ce 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfga.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openfga.json
@@ -30,31 +30,34 @@
"configuration": { "index": 3, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfga.OpenFgaConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration." },
"consistency": { "index": 4, "kind": "property", "displayName": "Consistency", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The consistency the query is answered with. O [...]
"contextualTuples": { "index": 5, "kind": "property", "displayName": "Contextual Tuples", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Relationship tuples supplied for the duration of one check and never stored, as semicolon-separated u [...]
- "lazyStartProducer": { "index": 6, "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 [...]
- "object": { "index": 7, "kind": "property", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressio [...]
- "relation": { "index": 8, "kind": "property", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lite [...]
- "relations": { "index": 9, "kind": "property", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
- "storeId": { "index": 10, "kind": "property", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
- "type": { "index": 11, "kind": "property", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
- "user": { "index": 12, "kind": "property", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against ea [...]
- "userFilters": { "index": 13, "kind": "property", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
- "autowiredEnabled": { "index": 14, "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 [...]
- "connectTimeout": { "index": 15, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component a [...]
- "maxParallelRequests": { "index": 16, "kind": "property", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is issu [...]
- "maxRetries": { "index": 17, "kind": "property", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate limi [...]
- "openFgaClient": { "index": 18, "kind": "property", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every o [...]
- "readTimeout": { "index": 19, "kind": "property", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
- "healthCheckConsumerEnabled": { "index": 20, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
- "healthCheckProducerEnabled": { "index": 21, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on produce [...]
- "apiAudience": { "index": 22, "kind": "property", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
- "apiToken": { "index": 23, "kind": "property", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started with [...]
- "apiTokenIssuer": { "index": 24, "kind": "property", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
- "clientId": { "index": 25, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OIDC [...]
- "clientSecret": { "index": 26, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
- "failOpen": { "index": 27, "kind": "property", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be ask [...]
- "scopes": { "index": 28, "kind": "property", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
- "sslContextParameters": { "index": 29, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
- "useGlobalSslContextParameters": { "index": 30, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." }
+ "continuationToken": { "index": 6, "kind": "property", "displayName": "Continuation Token", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The page to read from, for readTuples and readChanges. Evaluated as a Simple expression against the [...]
+ "lazyStartProducer": { "index": 7, "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 [...]
+ "object": { "index": 8, "kind": "property", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressio [...]
+ "pageSize": { "index": 9, "kind": "property", "displayName": "Page Size", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many entries a readTuples or readChanges page returns. Left unset, OpenFGA's own default applies. A page is one [...]
+ "relation": { "index": 10, "kind": "property", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
+ "relations": { "index": 11, "kind": "property", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
+ "startTime": { "index": 12, "kind": "property", "displayName": "Start Time", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The earliest change readChanges returns, as an ISO-8601 timestamp such as {code 2026-10-01T00:00:00Z}. Without it [...]
+ "storeId": { "index": 13, "kind": "property", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
+ "type": { "index": 14, "kind": "property", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
+ "user": { "index": 15, "kind": "property", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against ea [...]
+ "userFilters": { "index": 16, "kind": "property", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
+ "autowiredEnabled": { "index": 17, "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 [...]
+ "connectTimeout": { "index": 18, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component a [...]
+ "maxParallelRequests": { "index": 19, "kind": "property", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is issu [...]
+ "maxRetries": { "index": 20, "kind": "property", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate limi [...]
+ "openFgaClient": { "index": 21, "kind": "property", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every o [...]
+ "readTimeout": { "index": 22, "kind": "property", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
+ "healthCheckConsumerEnabled": { "index": 23, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
+ "healthCheckProducerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on produce [...]
+ "apiAudience": { "index": 25, "kind": "property", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
+ "apiToken": { "index": 26, "kind": "property", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started with [...]
+ "apiTokenIssuer": { "index": 27, "kind": "property", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
+ "clientId": { "index": 28, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OIDC [...]
+ "clientSecret": { "index": 29, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
+ "failOpen": { "index": 30, "kind": "property", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be ask [...]
+ "scopes": { "index": 31, "kind": "property", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
+ "sslContextParameters": { "index": 32, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
+ "useGlobalSslContextParameters": { "index": 33, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." }
},
"headers": {
"CamelOpenFgaAllowed": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The allow\/deny verdict of the authorization check. Always overwritten by the component, so a value set by an inbound message never survives into the route.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#ALLOWED" },
@@ -64,36 +67,40 @@
"CamelOpenFgaObject": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The object the check was made against, as resolved from the endpoint's object expression. Set for observability; it is not read as an input.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#OBJECT" },
"CamelOpenFgaRelation": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The relation that was checked. Set for observability; it is not read as an input and cannot be used to demand a weaker permission than the endpoint configured.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#RE [...]
"CamelOpenFgaStoreId": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The identifier of the OpenFGA store that was consulted, so an audit trail records which relationship graph produced the verdict.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#STORE_ID" },
- "CamelOpenFgaWrittenTuples": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the writeTuples operation wrote.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#WRITTEN_TUPLES" },
- "CamelOpenFgaDeletedTuples": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the deleteTuples operation deleted.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#DELETED_TUPLES" }
+ "CamelOpenFgaContinuationToken": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The continuation token the page came back with. Feed it back through the continuationToken option to read on. The two operations end differently: readTuples returns no token on its last page, so the header is absent once the [...]
+ "CamelOpenFgaWrittenTuples": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the writeTuples operation wrote.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#WRITTEN_TUPLES" },
+ "CamelOpenFgaDeletedTuples": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the deleteTuples operation deleted.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#DELETED_TUPLES" }
},
"properties": {
- "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openfga.OpenFgaOperation", "enum": [ "check", "batchCheck", "listObjects", "listRelations", "listUsers", "writeTuples", "deleteTuples" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. The operation is taken from the endpoint only: i [...]
+ "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openfga.OpenFgaOperation", "enum": [ "check", "batchCheck", "listObjects", "listRelations", "listUsers", "readTuples", "readChanges", "expand", "writeTuples", "deleteTuples" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. The opera [...]
"apiUrl": { "index": 1, "kind": "parameter", "displayName": "Api Url", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "http:\/\/localhost:8080", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The base URL of the OpenFGA HTTP API, without a trailing path. The default as [...]
"authorizationModelId": { "index": 2, "kind": "parameter", "displayName": "Authorization Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the authorization model revision to evaluate against. Leave it empty to u [...]
"conditionContext": { "index": 3, "kind": "parameter", "displayName": "Condition Context", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Context passed to the CEL expressions of any conditioned relation t [...]
"consistency": { "index": 4, "kind": "parameter", "displayName": "Consistency", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The consistency the query is answered with. [...]
"contextualTuples": { "index": 5, "kind": "parameter", "displayName": "Contextual Tuples", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Relationship tuples supplied for the duration of one check and never stored, as semicolon-separated [...]
- "object": { "index": 6, "kind": "parameter", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressi [...]
- "relation": { "index": 7, "kind": "parameter", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
- "relations": { "index": 8, "kind": "parameter", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
- "storeId": { "index": 9, "kind": "parameter", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
- "type": { "index": 10, "kind": "parameter", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
- "user": { "index": 11, "kind": "parameter", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against e [...]
- "userFilters": { "index": 12, "kind": "parameter", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
- "lazyStartProducer": { "index": 13, "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 [...]
- "connectTimeout": { "index": 14, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component [...]
- "maxParallelRequests": { "index": 15, "kind": "parameter", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is iss [...]
- "maxRetries": { "index": 16, "kind": "parameter", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate lim [...]
- "openFgaClient": { "index": 17, "kind": "parameter", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every [...]
- "readTimeout": { "index": 18, "kind": "parameter", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
- "apiAudience": { "index": 19, "kind": "parameter", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
- "apiToken": { "index": 20, "kind": "parameter", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started wit [...]
- "apiTokenIssuer": { "index": 21, "kind": "parameter", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
- "clientId": { "index": 22, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OID [...]
- "clientSecret": { "index": 23, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
- "failOpen": { "index": 24, "kind": "parameter", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be as [...]
- "scopes": { "index": 25, "kind": "parameter", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
- "sslContextParameters": { "index": 26, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
+ "continuationToken": { "index": 6, "kind": "parameter", "displayName": "Continuation Token", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The page to read from, for readTuples and readChanges. Evaluated as a Simple expression against th [...]
+ "object": { "index": 7, "kind": "parameter", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressi [...]
+ "pageSize": { "index": 8, "kind": "parameter", "displayName": "Page Size", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many entries a readTuples or readChanges page returns. Left unset, OpenFGA's own default applies. A page is on [...]
+ "relation": { "index": 9, "kind": "parameter", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
+ "relations": { "index": 10, "kind": "parameter", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only th [...]
+ "startTime": { "index": 11, "kind": "parameter", "displayName": "Start Time", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The earliest change readChanges returns, as an ISO-8601 timestamp such as {code 2026-10-01T00:00:00Z}. Without it [...]
+ "storeId": { "index": 12, "kind": "parameter", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model [...]
+ "type": { "index": 13, "kind": "parameter", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
+ "user": { "index": 14, "kind": "parameter", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against e [...]
+ "userFilters": { "index": 15, "kind": "parameter", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
+ "lazyStartProducer": { "index": 16, "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 [...]
+ "connectTimeout": { "index": 17, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component [...]
+ "maxParallelRequests": { "index": 18, "kind": "parameter", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is iss [...]
+ "maxRetries": { "index": 19, "kind": "parameter", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate lim [...]
+ "openFgaClient": { "index": 20, "kind": "parameter", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every [...]
+ "readTimeout": { "index": 21, "kind": "parameter", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
+ "apiAudience": { "index": 22, "kind": "parameter", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
+ "apiToken": { "index": 23, "kind": "parameter", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started wit [...]
+ "apiTokenIssuer": { "index": 24, "kind": "parameter", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
+ "clientId": { "index": 25, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OID [...]
+ "clientSecret": { "index": 26, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
+ "failOpen": { "index": 27, "kind": "parameter", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be as [...]
+ "scopes": { "index": 28, "kind": "parameter", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
+ "sslContextParameters": { "index": 29, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
}
}
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfga-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfga-component.adoc
index f7c91f578588..00067a8708c6 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfga-component.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfga-component.adoc
@@ -52,8 +52,8 @@ Maven users will need to add the following dependency to their `pom.xml`.
openfga:operation[?options]
------------------------------------------------------------
-Where `operation` is one of `check`, `batchCheck`, `listObjects`, `listRelations`, `listUsers`, `writeTuples` or
-`deleteTuples`.
+Where `operation` is one of `check`, `batchCheck`, `listObjects`, `listRelations`, `listUsers`, `readTuples`,
+`readChanges`, `expand`, `writeTuples` or `deleteTuples`.
// component options: START
include::partial$component-configure-options.adoc[]
@@ -383,9 +383,90 @@ stricter, but it would silently answer a different question than the endpoint wa
Contextual tuples supplied by the *message* are deliberately not supported. If you need them, the tuple has to come
from somewhere the caller cannot reach, which in practice means the endpoint.
+=== Reading the graph
+
+`readTuples` answers "what access exists", as opposed to `check`'s "may this subject do this". Here `user`, `relation`
+and `object` are a *filter* rather than a subject, and the body comes back as a list of maps keyed `user`, `relation`,
+`object` and `timestamp` — deliberately the keys `writeTuples` and `deleteTuples` accept, so a route can revoke what
+it just read without reshaping anything:
+
+[source,java]
+------------------------------------------------------------
+from("direct:revokeEverythingBobHas")
+ .to("openfga:readTuples?storeId={{fga.store}}&object=document:&user=user:bob")
+ .to("openfga:deleteTuples?storeId={{fga.store}}");
+------------------------------------------------------------
+
+[NOTE]
+.What OpenFGA accepts as a read filter
+====
+The filter is narrower than "every part is optional". Measured against OpenFGA 1.21.0:
+
+* no filter at all — reads the whole store, a page at a time;
+* `object=document:` *plus* a `user` — reads that user's tuples of that object type;
+* `object=document:budget` — reads that object's tuples, with or without a user or relation;
+* `user` alone, `relation` alone, or `object=document:` alone — **rejected**, because OpenFGA requires an object type
+ as soon as any filter is given and will not accept an empty object id and an empty user together.
+
+The component checks this before the call and names the option to change, rather than letting an opaque HTTP 400
+through. Note this is also why a type-only `document:` is accepted here but not as an `object` anywhere else: a read
+filter and an identifier have different rules.
+
+What "optional" does *not* mean is that a part may go missing at runtime. Only an option the endpoint never set counts
+as "do not filter on this"; an option that *is* set and evaluates to nothing — `user=${header.who}` on a message with
+no `who` header — is refused. Dropping it would make the read wider than the route asked for, and with the
+`readTuples` -> `deleteTuples` route above that turns "revoke Bob's access" into "revoke everyone's". A filter that
+silently widens when a value goes missing is the read-side form of failing open.
+====
+
+`readChanges` reads the change log, which is the building block for keeping a cache or a projection in step with the
+graph. Each entry adds an `operation` of `WRITE` or `DELETE`.
+
+Both are paged. `pageSize` bounds one request, and the token the page returns arrives on
+`CamelOpenFgaContinuationToken`; feed it back through `continuationToken` to read on.
+
+A `readChanges` poller has to carry that token from one exchange to the *next*, which an exchange property cannot do:
+every timer fire is a new exchange, so a property left on the previous one is gone and each poll would start from the
+beginning again. Use a global variable, which outlives the exchange:
+
+[source,java]
+------------------------------------------------------------
+from("timer:sync?period=30000")
+ .to("openfga:readChanges?storeId={{fga.store}}&type=document"
+ + "&continuationToken=${variable.global:fgaToken}")
+ .setVariable("global:fgaToken", header("CamelOpenFgaContinuationToken"))
+ .split(body()).to("direct:applyChange");
+------------------------------------------------------------
+
+A global variable lives in the `CamelContext`, so it does not survive a restart. A poller that must resume across one
+should keep the token wherever the deployment already persists state rather than rely on it.
+
+`startTime` bounds where a first read begins — without it a first `readChanges` on a busy store pages through the whole
+history before reaching anything current.
+
+[IMPORTANT]
+.The two read operations signal "done" differently
+====
+Measured against OpenFGA 1.21.0, not assumed:
+
+* `readTuples` returns no token on its last page, so `CamelOpenFgaContinuationToken` is **absent** once the read is
+ finished. Looping until the header is missing is the right way to drain it.
+* `readChanges` returns a token **every** time, including when nothing has happened — it hands the token you sent
+ straight back with an empty page. Looping until the header is missing would therefore never end. The signal there is
+ an **empty body**, and the token it keeps returning is the bookmark the next poll resumes from, which is why the
+ component leaves it in place instead of clearing it.
+====
+
+`expand` is the odd one out: it is the only operation that does not leave a `List` on the body, because its answer is
+a tree of usersets rather than a list. The `UsersetTree` goes on the body as the SDK returns it — flattening it is
+exactly what would destroy the information the operation exists to provide. Reach for it when a check answered
+something surprising and you need to see how it got there.
+
== What this component does not do yet
-* The `expand`, `readTuples` and `readChanges` operations, and store or authorization-model management.
+* Store and authorization-model management — `createStore`, `deleteStore`, `writeAuthorizationModel` and the rest.
+ These are administrative rather than integration operations, and a component that can delete the store it
+ authorizes against is a different proposition from one that can only ask it questions.
== Examples
diff --git a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaComponentConfigurer.java b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaComponentConfigurer.java
index 1498cf4e0888..e837a76ae5db 100644
--- a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaComponentConfigurer.java
+++ b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaComponentConfigurer.java
@@ -54,6 +54,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "consistency": getOrCreateConfiguration(target).setConsistency(property(camelContext, java.lang.String.class, value)); return true;
case "contextualtuples":
case "contextualTuples": getOrCreateConfiguration(target).setContextualTuples(property(camelContext, java.lang.String.class, value)); return true;
+ case "continuationtoken":
+ case "continuationToken": getOrCreateConfiguration(target).setContinuationToken(property(camelContext, java.lang.String.class, value)); return true;
case "failopen":
case "failOpen": getOrCreateConfiguration(target).setFailOpen(property(camelContext, boolean.class, value)); return true;
case "healthcheckconsumerenabled":
@@ -69,6 +71,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "object": getOrCreateConfiguration(target).setObject(property(camelContext, java.lang.String.class, value)); return true;
case "openfgaclient":
case "openFgaClient": getOrCreateConfiguration(target).setOpenFgaClient(property(camelContext, dev.openfga.sdk.api.client.OpenFgaClient.class, value)); return true;
+ case "pagesize":
+ case "pageSize": getOrCreateConfiguration(target).setPageSize(property(camelContext, java.lang.Integer.class, value)); return true;
case "readtimeout":
case "readTimeout": getOrCreateConfiguration(target).setReadTimeout(property(camelContext, java.time.Duration.class, value).toMillis()); return true;
case "relation": getOrCreateConfiguration(target).setRelation(property(camelContext, java.lang.String.class, value)); return true;
@@ -76,6 +80,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "scopes": getOrCreateConfiguration(target).setScopes(property(camelContext, java.lang.String.class, value)); return true;
case "sslcontextparameters":
case "sslContextParameters": getOrCreateConfiguration(target).setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "starttime":
+ case "startTime": getOrCreateConfiguration(target).setStartTime(property(camelContext, java.lang.String.class, value)); return true;
case "storeid":
case "storeId": getOrCreateConfiguration(target).setStoreId(property(camelContext, java.lang.String.class, value)); return true;
case "type": getOrCreateConfiguration(target).setType(property(camelContext, java.lang.String.class, value)); return true;
@@ -120,6 +126,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "consistency": return java.lang.String.class;
case "contextualtuples":
case "contextualTuples": return java.lang.String.class;
+ case "continuationtoken":
+ case "continuationToken": return java.lang.String.class;
case "failopen":
case "failOpen": return boolean.class;
case "healthcheckconsumerenabled":
@@ -135,6 +143,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "object": return java.lang.String.class;
case "openfgaclient":
case "openFgaClient": return dev.openfga.sdk.api.client.OpenFgaClient.class;
+ case "pagesize":
+ case "pageSize": return java.lang.Integer.class;
case "readtimeout":
case "readTimeout": return long.class;
case "relation": return java.lang.String.class;
@@ -142,6 +152,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "scopes": return java.lang.String.class;
case "sslcontextparameters":
case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "starttime":
+ case "startTime": return java.lang.String.class;
case "storeid":
case "storeId": return java.lang.String.class;
case "type": return java.lang.String.class;
@@ -182,6 +194,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "consistency": return getOrCreateConfiguration(target).getConsistency();
case "contextualtuples":
case "contextualTuples": return getOrCreateConfiguration(target).getContextualTuples();
+ case "continuationtoken":
+ case "continuationToken": return getOrCreateConfiguration(target).getContinuationToken();
case "failopen":
case "failOpen": return getOrCreateConfiguration(target).isFailOpen();
case "healthcheckconsumerenabled":
@@ -197,6 +211,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "object": return getOrCreateConfiguration(target).getObject();
case "openfgaclient":
case "openFgaClient": return getOrCreateConfiguration(target).getOpenFgaClient();
+ case "pagesize":
+ case "pageSize": return getOrCreateConfiguration(target).getPageSize();
case "readtimeout":
case "readTimeout": return getOrCreateConfiguration(target).getReadTimeout();
case "relation": return getOrCreateConfiguration(target).getRelation();
@@ -204,6 +220,8 @@ public class OpenFgaComponentConfigurer extends PropertyConfigurerSupport implem
case "scopes": return getOrCreateConfiguration(target).getScopes();
case "sslcontextparameters":
case "sslContextParameters": return getOrCreateConfiguration(target).getSslContextParameters();
+ case "starttime":
+ case "startTime": return getOrCreateConfiguration(target).getStartTime();
case "storeid":
case "storeId": return getOrCreateConfiguration(target).getStoreId();
case "type": return getOrCreateConfiguration(target).getType();
diff --git a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointConfigurer.java b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointConfigurer.java
index 5a700f40e83d..ec6009c86e61 100644
--- a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointConfigurer.java
+++ b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointConfigurer.java
@@ -44,6 +44,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "consistency": target.getConfiguration().setConsistency(property(camelContext, java.lang.String.class, value)); return true;
case "contextualtuples":
case "contextualTuples": target.getConfiguration().setContextualTuples(property(camelContext, java.lang.String.class, value)); return true;
+ case "continuationtoken":
+ case "continuationToken": target.getConfiguration().setContinuationToken(property(camelContext, java.lang.String.class, value)); return true;
case "failopen":
case "failOpen": target.getConfiguration().setFailOpen(property(camelContext, boolean.class, value)); return true;
case "lazystartproducer":
@@ -55,6 +57,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "object": target.getConfiguration().setObject(property(camelContext, java.lang.String.class, value)); return true;
case "openfgaclient":
case "openFgaClient": target.getConfiguration().setOpenFgaClient(property(camelContext, dev.openfga.sdk.api.client.OpenFgaClient.class, value)); return true;
+ case "pagesize":
+ case "pageSize": target.getConfiguration().setPageSize(property(camelContext, java.lang.Integer.class, value)); return true;
case "readtimeout":
case "readTimeout": target.getConfiguration().setReadTimeout(property(camelContext, java.time.Duration.class, value).toMillis()); return true;
case "relation": target.getConfiguration().setRelation(property(camelContext, java.lang.String.class, value)); return true;
@@ -62,6 +66,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "scopes": target.getConfiguration().setScopes(property(camelContext, java.lang.String.class, value)); return true;
case "sslcontextparameters":
case "sslContextParameters": target.getConfiguration().setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "starttime":
+ case "startTime": target.getConfiguration().setStartTime(property(camelContext, java.lang.String.class, value)); return true;
case "storeid":
case "storeId": target.getConfiguration().setStoreId(property(camelContext, java.lang.String.class, value)); return true;
case "type": target.getConfiguration().setType(property(camelContext, java.lang.String.class, value)); return true;
@@ -101,6 +107,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "consistency": return java.lang.String.class;
case "contextualtuples":
case "contextualTuples": return java.lang.String.class;
+ case "continuationtoken":
+ case "continuationToken": return java.lang.String.class;
case "failopen":
case "failOpen": return boolean.class;
case "lazystartproducer":
@@ -112,6 +120,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "object": return java.lang.String.class;
case "openfgaclient":
case "openFgaClient": return dev.openfga.sdk.api.client.OpenFgaClient.class;
+ case "pagesize":
+ case "pageSize": return java.lang.Integer.class;
case "readtimeout":
case "readTimeout": return long.class;
case "relation": return java.lang.String.class;
@@ -119,6 +129,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "scopes": return java.lang.String.class;
case "sslcontextparameters":
case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "starttime":
+ case "startTime": return java.lang.String.class;
case "storeid":
case "storeId": return java.lang.String.class;
case "type": return java.lang.String.class;
@@ -154,6 +166,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "consistency": return target.getConfiguration().getConsistency();
case "contextualtuples":
case "contextualTuples": return target.getConfiguration().getContextualTuples();
+ case "continuationtoken":
+ case "continuationToken": return target.getConfiguration().getContinuationToken();
case "failopen":
case "failOpen": return target.getConfiguration().isFailOpen();
case "lazystartproducer":
@@ -165,6 +179,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "object": return target.getConfiguration().getObject();
case "openfgaclient":
case "openFgaClient": return target.getConfiguration().getOpenFgaClient();
+ case "pagesize":
+ case "pageSize": return target.getConfiguration().getPageSize();
case "readtimeout":
case "readTimeout": return target.getConfiguration().getReadTimeout();
case "relation": return target.getConfiguration().getRelation();
@@ -172,6 +188,8 @@ public class OpenFgaEndpointConfigurer extends PropertyConfigurerSupport impleme
case "scopes": return target.getConfiguration().getScopes();
case "sslcontextparameters":
case "sslContextParameters": return target.getConfiguration().getSslContextParameters();
+ case "starttime":
+ case "startTime": return target.getConfiguration().getStartTime();
case "storeid":
case "storeId": return target.getConfiguration().getStoreId();
case "type": return target.getConfiguration().getType();
diff --git a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointUriFactory.java b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointUriFactory.java
index 239cabd3832e..baf28a461248 100644
--- a/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointUriFactory.java
+++ b/components/camel-openfga/src/generated/java/org/apache/camel/component/openfga/OpenFgaEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class OpenFgaEndpointUriFactory extends org.apache.camel.support.componen
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(27);
+ Set<String> props = new HashSet<>(30);
props.add("apiAudience");
props.add("apiToken");
props.add("apiTokenIssuer");
@@ -36,6 +36,7 @@ public class OpenFgaEndpointUriFactory extends org.apache.camel.support.componen
props.add("connectTimeout");
props.add("consistency");
props.add("contextualTuples");
+ props.add("continuationToken");
props.add("failOpen");
props.add("lazyStartProducer");
props.add("maxParallelRequests");
@@ -43,11 +44,13 @@ public class OpenFgaEndpointUriFactory extends org.apache.camel.support.componen
props.add("object");
props.add("openFgaClient");
props.add("operation");
+ props.add("pageSize");
props.add("readTimeout");
props.add("relation");
props.add("relations");
props.add("scopes");
props.add("sslContextParameters");
+ props.add("startTime");
props.add("storeId");
props.add("type");
props.add("user");
diff --git a/components/camel-openfga/src/generated/resources/META-INF/org/apache/camel/component/openfga/openfga.json b/components/camel-openfga/src/generated/resources/META-INF/org/apache/camel/component/openfga/openfga.json
index 5a7bc30f1688..8530743048ce 100644
--- a/components/camel-openfga/src/generated/resources/META-INF/org/apache/camel/component/openfga/openfga.json
+++ b/components/camel-openfga/src/generated/resources/META-INF/org/apache/camel/component/openfga/openfga.json
@@ -30,31 +30,34 @@
"configuration": { "index": 3, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.openfga.OpenFgaConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration." },
"consistency": { "index": 4, "kind": "property", "displayName": "Consistency", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The consistency the query is answered with. O [...]
"contextualTuples": { "index": 5, "kind": "property", "displayName": "Contextual Tuples", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Relationship tuples supplied for the duration of one check and never stored, as semicolon-separated u [...]
- "lazyStartProducer": { "index": 6, "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 [...]
- "object": { "index": 7, "kind": "property", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressio [...]
- "relation": { "index": 8, "kind": "property", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lite [...]
- "relations": { "index": 9, "kind": "property", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
- "storeId": { "index": 10, "kind": "property", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
- "type": { "index": 11, "kind": "property", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
- "user": { "index": 12, "kind": "property", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against ea [...]
- "userFilters": { "index": 13, "kind": "property", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
- "autowiredEnabled": { "index": 14, "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 [...]
- "connectTimeout": { "index": 15, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component a [...]
- "maxParallelRequests": { "index": 16, "kind": "property", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is issu [...]
- "maxRetries": { "index": 17, "kind": "property", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate limi [...]
- "openFgaClient": { "index": 18, "kind": "property", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every o [...]
- "readTimeout": { "index": 19, "kind": "property", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
- "healthCheckConsumerEnabled": { "index": 20, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
- "healthCheckProducerEnabled": { "index": 21, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on produce [...]
- "apiAudience": { "index": 22, "kind": "property", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
- "apiToken": { "index": 23, "kind": "property", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started with [...]
- "apiTokenIssuer": { "index": 24, "kind": "property", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
- "clientId": { "index": 25, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OIDC [...]
- "clientSecret": { "index": 26, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
- "failOpen": { "index": 27, "kind": "property", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be ask [...]
- "scopes": { "index": 28, "kind": "property", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
- "sslContextParameters": { "index": 29, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
- "useGlobalSslContextParameters": { "index": 30, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." }
+ "continuationToken": { "index": 6, "kind": "property", "displayName": "Continuation Token", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The page to read from, for readTuples and readChanges. Evaluated as a Simple expression against the [...]
+ "lazyStartProducer": { "index": 7, "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 [...]
+ "object": { "index": 8, "kind": "property", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressio [...]
+ "pageSize": { "index": 9, "kind": "property", "displayName": "Page Size", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many entries a readTuples or readChanges page returns. Left unset, OpenFGA's own default applies. A page is one [...]
+ "relation": { "index": 10, "kind": "property", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
+ "relations": { "index": 11, "kind": "property", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
+ "startTime": { "index": 12, "kind": "property", "displayName": "Start Time", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The earliest change readChanges returns, as an ISO-8601 timestamp such as {code 2026-10-01T00:00:00Z}. Without it [...]
+ "storeId": { "index": 13, "kind": "property", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
+ "type": { "index": 14, "kind": "property", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
+ "user": { "index": 15, "kind": "property", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against ea [...]
+ "userFilters": { "index": 16, "kind": "property", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
+ "autowiredEnabled": { "index": 17, "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 [...]
+ "connectTimeout": { "index": 18, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component a [...]
+ "maxParallelRequests": { "index": 19, "kind": "property", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is issu [...]
+ "maxRetries": { "index": 20, "kind": "property", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate limi [...]
+ "openFgaClient": { "index": 21, "kind": "property", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every o [...]
+ "readTimeout": { "index": 22, "kind": "property", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
+ "healthCheckConsumerEnabled": { "index": 23, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" },
+ "healthCheckProducerEnabled": { "index": 24, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on produce [...]
+ "apiAudience": { "index": 25, "kind": "property", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
+ "apiToken": { "index": 26, "kind": "property", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started with [...]
+ "apiTokenIssuer": { "index": 27, "kind": "property", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
+ "clientId": { "index": 28, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OIDC [...]
+ "clientSecret": { "index": 29, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
+ "failOpen": { "index": 30, "kind": "property", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be ask [...]
+ "scopes": { "index": 31, "kind": "property", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
+ "sslContextParameters": { "index": 32, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
+ "useGlobalSslContextParameters": { "index": 33, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." }
},
"headers": {
"CamelOpenFgaAllowed": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The allow\/deny verdict of the authorization check. Always overwritten by the component, so a value set by an inbound message never survives into the route.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#ALLOWED" },
@@ -64,36 +67,40 @@
"CamelOpenFgaObject": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The object the check was made against, as resolved from the endpoint's object expression. Set for observability; it is not read as an input.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#OBJECT" },
"CamelOpenFgaRelation": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The relation that was checked. Set for observability; it is not read as an input and cannot be used to demand a weaker permission than the endpoint configured.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#RE [...]
"CamelOpenFgaStoreId": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The identifier of the OpenFGA store that was consulted, so an audit trail records which relationship graph produced the verdict.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#STORE_ID" },
- "CamelOpenFgaWrittenTuples": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the writeTuples operation wrote.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#WRITTEN_TUPLES" },
- "CamelOpenFgaDeletedTuples": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the deleteTuples operation deleted.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#DELETED_TUPLES" }
+ "CamelOpenFgaContinuationToken": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The continuation token the page came back with. Feed it back through the continuationToken option to read on. The two operations end differently: readTuples returns no token on its last page, so the header is absent once the [...]
+ "CamelOpenFgaWrittenTuples": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the writeTuples operation wrote.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#WRITTEN_TUPLES" },
+ "CamelOpenFgaDeletedTuples": { "index": 9, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "How many relationship tuples the deleteTuples operation deleted.", "constantName": "org.apache.camel.component.openfga.OpenFgaConstants#DELETED_TUPLES" }
},
"properties": {
- "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openfga.OpenFgaOperation", "enum": [ "check", "batchCheck", "listObjects", "listRelations", "listUsers", "writeTuples", "deleteTuples" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. The operation is taken from the endpoint only: i [...]
+ "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openfga.OpenFgaOperation", "enum": [ "check", "batchCheck", "listObjects", "listRelations", "listUsers", "readTuples", "readChanges", "expand", "writeTuples", "deleteTuples" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform. The opera [...]
"apiUrl": { "index": 1, "kind": "parameter", "displayName": "Api Url", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "http:\/\/localhost:8080", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The base URL of the OpenFGA HTTP API, without a trailing path. The default as [...]
"authorizationModelId": { "index": 2, "kind": "parameter", "displayName": "Authorization Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the authorization model revision to evaluate against. Leave it empty to u [...]
"conditionContext": { "index": 3, "kind": "parameter", "displayName": "Condition Context", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Context passed to the CEL expressions of any conditioned relation t [...]
"consistency": { "index": 4, "kind": "parameter", "displayName": "Consistency", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "UNSPECIFIED", "MINIMIZE_LATENCY", "HIGHER_CONSISTENCY" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The consistency the query is answered with. [...]
"contextualTuples": { "index": 5, "kind": "parameter", "displayName": "Contextual Tuples", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Relationship tuples supplied for the duration of one check and never stored, as semicolon-separated [...]
- "object": { "index": 6, "kind": "parameter", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressi [...]
- "relation": { "index": 7, "kind": "parameter", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
- "relations": { "index": 8, "kind": "parameter", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only the [...]
- "storeId": { "index": 9, "kind": "parameter", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model, [...]
- "type": { "index": 10, "kind": "parameter", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
- "user": { "index": 11, "kind": "parameter", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against e [...]
- "userFilters": { "index": 12, "kind": "parameter", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
- "lazyStartProducer": { "index": 13, "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 [...]
- "connectTimeout": { "index": 14, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component [...]
- "maxParallelRequests": { "index": 15, "kind": "parameter", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is iss [...]
- "maxRetries": { "index": 16, "kind": "parameter", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate lim [...]
- "openFgaClient": { "index": 17, "kind": "parameter", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every [...]
- "readTimeout": { "index": 18, "kind": "parameter", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
- "apiAudience": { "index": 19, "kind": "parameter", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
- "apiToken": { "index": 20, "kind": "parameter", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started wit [...]
- "apiTokenIssuer": { "index": 21, "kind": "parameter", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
- "clientId": { "index": 22, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OID [...]
- "clientSecret": { "index": 23, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
- "failOpen": { "index": 24, "kind": "parameter", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be as [...]
- "scopes": { "index": 25, "kind": "parameter", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
- "sslContextParameters": { "index": 26, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
+ "continuationToken": { "index": 6, "kind": "parameter", "displayName": "Continuation Token", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The page to read from, for readTuples and readChanges. Evaluated as a Simple expression against th [...]
+ "object": { "index": 7, "kind": "parameter", "displayName": "Object", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object being accessed, as an OpenFGA object identifier such as {code document:budget}. Evaluated as a Simple expressi [...]
+ "pageSize": { "index": 8, "kind": "parameter", "displayName": "Page Size", "group": "producer", "label": "", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many entries a readTuples or readChanges page returns. Left unset, OpenFGA's own default applies. A page is on [...]
+ "relation": { "index": 9, "kind": "parameter", "displayName": "Relation", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The relation to demand, such as reader or owner. Evaluated as a Simple expression against each exchange, though a lit [...]
+ "relations": { "index": 10, "kind": "parameter", "displayName": "Relations", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of relations the listRelations operation asks about, for example reader,writer,owner. Only th [...]
+ "startTime": { "index": 11, "kind": "parameter", "displayName": "Start Time", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The earliest change readChanges returns, as an ISO-8601 timestamp such as {code 2026-10-01T00:00:00Z}. Without it [...]
+ "storeId": { "index": 12, "kind": "parameter", "displayName": "Store Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The identifier of the OpenFGA store holding the relationship tuples and the authorization model [...]
+ "type": { "index": 13, "kind": "parameter", "displayName": "Type", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The object type to enumerate for the listObjects operation, for example document. This is a type name from the authorization [...]
+ "user": { "index": 14, "kind": "parameter", "displayName": "User", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The subject to authorize, as an OpenFGA user identifier such as {code user:anne}. Evaluated as a Simple expression against e [...]
+ "userFilters": { "index": 15, "kind": "parameter", "displayName": "User Filters", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Comma-separated list of user filters for the listUsers operation, naming which kinds of subject to return. An [...]
+ "lazyStartProducer": { "index": 16, "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 [...]
+ "connectTimeout": { "index": 17, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for the connection to OpenFGA to be established. The component [...]
+ "maxParallelRequests": { "index": 18, "kind": "parameter", "displayName": "Max Parallel Requests", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 10, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many of a batchCheck's checks may be in flight at once. The batch is iss [...]
+ "maxRetries": { "index": 19, "kind": "parameter", "displayName": "Max Retries", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 3, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How many times the SDK retries a request that failed in a way worth retrying, such as a rate lim [...]
+ "openFgaClient": { "index": 20, "kind": "parameter", "displayName": "Open Fga Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.openfga.sdk.api.client.OpenFgaClient", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "An existing OpenFgaClient to use. When set, every [...]
+ "readTimeout": { "index": 21, "kind": "parameter", "displayName": "Read Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "10000", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "How long to wait for one request to OpenFGA to complete once connected. A request that [...]
+ "apiAudience": { "index": 22, "kind": "parameter", "displayName": "Api Audience", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The audience to request the access token for in the client-credentials flow." },
+ "apiToken": { "index": 23, "kind": "parameter", "displayName": "Api Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Pre-shared token sent to OpenFGA in the Authorization header, for a server started wit [...]
+ "apiTokenIssuer": { "index": 24, "kind": "parameter", "displayName": "Api Token Issuer", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "The token endpoint the client-credentials flow exchanges its credentials at." },
+ "clientId": { "index": 25, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client identifier for the OAuth 2.0 client-credentials flow, for a server that authenticates through an OID [...]
+ "clientSecret": { "index": 26, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Client secret for the OAuth 2.0 client-credentials flow." },
+ "failOpen": { "index": 27, "kind": "parameter", "displayName": "Fail Open", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "defaultValue": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Whether to let the exchange proceed when OpenFGA could not be as [...]
+ "scopes": { "index": 28, "kind": "parameter", "displayName": "Scopes", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "Space-separated scopes to request in the client-credentials flow." },
+ "sslContextParameters": { "index": 29, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfga.OpenFgaConfiguration", "configurationField": "configuration", "description": "TLS configuration for the connection to OpenFGA. [...]
}
}
diff --git a/components/camel-openfga/src/main/docs/openfga-component.adoc b/components/camel-openfga/src/main/docs/openfga-component.adoc
index f7c91f578588..00067a8708c6 100644
--- a/components/camel-openfga/src/main/docs/openfga-component.adoc
+++ b/components/camel-openfga/src/main/docs/openfga-component.adoc
@@ -52,8 +52,8 @@ Maven users will need to add the following dependency to their `pom.xml`.
openfga:operation[?options]
------------------------------------------------------------
-Where `operation` is one of `check`, `batchCheck`, `listObjects`, `listRelations`, `listUsers`, `writeTuples` or
-`deleteTuples`.
+Where `operation` is one of `check`, `batchCheck`, `listObjects`, `listRelations`, `listUsers`, `readTuples`,
+`readChanges`, `expand`, `writeTuples` or `deleteTuples`.
// component options: START
include::partial$component-configure-options.adoc[]
@@ -383,9 +383,90 @@ stricter, but it would silently answer a different question than the endpoint wa
Contextual tuples supplied by the *message* are deliberately not supported. If you need them, the tuple has to come
from somewhere the caller cannot reach, which in practice means the endpoint.
+=== Reading the graph
+
+`readTuples` answers "what access exists", as opposed to `check`'s "may this subject do this". Here `user`, `relation`
+and `object` are a *filter* rather than a subject, and the body comes back as a list of maps keyed `user`, `relation`,
+`object` and `timestamp` — deliberately the keys `writeTuples` and `deleteTuples` accept, so a route can revoke what
+it just read without reshaping anything:
+
+[source,java]
+------------------------------------------------------------
+from("direct:revokeEverythingBobHas")
+ .to("openfga:readTuples?storeId={{fga.store}}&object=document:&user=user:bob")
+ .to("openfga:deleteTuples?storeId={{fga.store}}");
+------------------------------------------------------------
+
+[NOTE]
+.What OpenFGA accepts as a read filter
+====
+The filter is narrower than "every part is optional". Measured against OpenFGA 1.21.0:
+
+* no filter at all — reads the whole store, a page at a time;
+* `object=document:` *plus* a `user` — reads that user's tuples of that object type;
+* `object=document:budget` — reads that object's tuples, with or without a user or relation;
+* `user` alone, `relation` alone, or `object=document:` alone — **rejected**, because OpenFGA requires an object type
+ as soon as any filter is given and will not accept an empty object id and an empty user together.
+
+The component checks this before the call and names the option to change, rather than letting an opaque HTTP 400
+through. Note this is also why a type-only `document:` is accepted here but not as an `object` anywhere else: a read
+filter and an identifier have different rules.
+
+What "optional" does *not* mean is that a part may go missing at runtime. Only an option the endpoint never set counts
+as "do not filter on this"; an option that *is* set and evaluates to nothing — `user=${header.who}` on a message with
+no `who` header — is refused. Dropping it would make the read wider than the route asked for, and with the
+`readTuples` -> `deleteTuples` route above that turns "revoke Bob's access" into "revoke everyone's". A filter that
+silently widens when a value goes missing is the read-side form of failing open.
+====
+
+`readChanges` reads the change log, which is the building block for keeping a cache or a projection in step with the
+graph. Each entry adds an `operation` of `WRITE` or `DELETE`.
+
+Both are paged. `pageSize` bounds one request, and the token the page returns arrives on
+`CamelOpenFgaContinuationToken`; feed it back through `continuationToken` to read on.
+
+A `readChanges` poller has to carry that token from one exchange to the *next*, which an exchange property cannot do:
+every timer fire is a new exchange, so a property left on the previous one is gone and each poll would start from the
+beginning again. Use a global variable, which outlives the exchange:
+
+[source,java]
+------------------------------------------------------------
+from("timer:sync?period=30000")
+ .to("openfga:readChanges?storeId={{fga.store}}&type=document"
+ + "&continuationToken=${variable.global:fgaToken}")
+ .setVariable("global:fgaToken", header("CamelOpenFgaContinuationToken"))
+ .split(body()).to("direct:applyChange");
+------------------------------------------------------------
+
+A global variable lives in the `CamelContext`, so it does not survive a restart. A poller that must resume across one
+should keep the token wherever the deployment already persists state rather than rely on it.
+
+`startTime` bounds where a first read begins — without it a first `readChanges` on a busy store pages through the whole
+history before reaching anything current.
+
+[IMPORTANT]
+.The two read operations signal "done" differently
+====
+Measured against OpenFGA 1.21.0, not assumed:
+
+* `readTuples` returns no token on its last page, so `CamelOpenFgaContinuationToken` is **absent** once the read is
+ finished. Looping until the header is missing is the right way to drain it.
+* `readChanges` returns a token **every** time, including when nothing has happened — it hands the token you sent
+ straight back with an empty page. Looping until the header is missing would therefore never end. The signal there is
+ an **empty body**, and the token it keeps returning is the bookmark the next poll resumes from, which is why the
+ component leaves it in place instead of clearing it.
+====
+
+`expand` is the odd one out: it is the only operation that does not leave a `List` on the body, because its answer is
+a tree of usersets rather than a list. The `UsersetTree` goes on the body as the SDK returns it — flattening it is
+exactly what would destroy the information the operation exists to provide. Reach for it when a check answered
+something surprising and you need to see how it got there.
+
== What this component does not do yet
-* The `expand`, `readTuples` and `readChanges` operations, and store or authorization-model management.
+* Store and authorization-model management — `createStore`, `deleteStore`, `writeAuthorizationModel` and the rest.
+ These are administrative rather than integration operations, and a component that can delete the store it
+ authorizes against is a different proposition from one that can only ask it questions.
== Examples
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaAuthorizer.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaAuthorizer.java
index ef8a779725e2..25a97ee59c55 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaAuthorizer.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaAuthorizer.java
@@ -71,6 +71,7 @@ public class OpenFgaAuthorizer {
private final Expression relation;
private final List<Expression[]> contextualTuples;
private final Map<String, Object> conditionContext;
+ private final Expression continuationToken;
private final ConsistencyPreference consistency;
private final boolean failOpen;
private final long awaitTimeoutMillis;
@@ -84,6 +85,7 @@ public class OpenFgaAuthorizer {
this.relation = compile(camelContext, configuration.getRelation());
this.contextualTuples = compileContextualTuples(camelContext, configuration.getContextualTuples());
this.conditionContext = configuration.getConditionContext();
+ this.continuationToken = compile(camelContext, configuration.getContinuationToken());
this.consistency = parseConsistency(configuration.getConsistency());
this.failOpen = configuration.isFailOpen();
this.awaitTimeoutMillis = awaitTimeout(configuration);
@@ -481,6 +483,24 @@ public class OpenFgaAuthorizer {
return user != null || relation != null || object != null;
}
+ /**
+ * Whether the {@code user} option was configured at all, as opposed to having been configured and then resolving to
+ * nothing on this exchange. The read filter needs the two kept apart: only an option that was never set means "do
+ * not filter on this", for the same reason {@link #hasConfiguredTuple()} asks the compiled expressions rather than
+ * the evaluated values.
+ */
+ boolean hasConfiguredUser() {
+ return user != null;
+ }
+
+ boolean hasConfiguredRelation() {
+ return relation != null;
+ }
+
+ boolean hasConfiguredObject() {
+ return object != null;
+ }
+
/**
* Evaluates the {@code user}, {@code relation} and {@code object} options without the check-path guards and without
* touching the decision headers, for the operations that write relationship tuples. There the values are not a
@@ -499,6 +519,14 @@ public class OpenFgaAuthorizer {
return evaluate(exchange, object);
}
+ /**
+ * Evaluates the {@code continuationToken} option, so a route can page by feeding back the token the previous page
+ * left on the message.
+ */
+ String rawContinuationToken(Exchange exchange) {
+ return evaluate(exchange, continuationToken);
+ }
+
ConsistencyPreference getConsistency() {
return consistency;
}
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConfiguration.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConfiguration.java
index ac7dfe70f98b..7833533fbb23 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConfiguration.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConfiguration.java
@@ -103,6 +103,15 @@ public class OpenFgaConfiguration implements Cloneable {
@UriParam(label = "advanced", defaultValue = "10")
private int maxParallelRequests = 10;
+ @UriParam
+ private Integer pageSize;
+
+ @UriParam
+ private String continuationToken;
+
+ @UriParam
+ private String startTime;
+
@UriParam(label = "advanced",
description = "An existing OpenFgaClient to use. When set, every option describing how to reach the"
+ " server - apiUrl, storeId, the credentials, the timeouts and sslContextParameters - is"
@@ -467,6 +476,46 @@ public class OpenFgaConfiguration implements Cloneable {
this.maxParallelRequests = maxParallelRequests;
}
+ public Integer getPageSize() {
+ return pageSize;
+ }
+
+ /**
+ * How many entries a {@code readTuples} or {@code readChanges} page returns. Left unset, OpenFGA's own default
+ * applies. A page is one request: this bounds the answer, not the number of requests a route makes.
+ */
+ public void setPageSize(Integer pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ public String getContinuationToken() {
+ return continuationToken;
+ }
+
+ /**
+ * The page to read from, for {@code readTuples} and {@code readChanges}. Evaluated as a Simple expression against
+ * the exchange, so a route can feed back the token the previous page returned -
+ * <code>${header.CamelOpenFgaContinuationToken}</code> - and page through without the token being configured
+ * statically.
+ */
+ public void setContinuationToken(String continuationToken) {
+ this.continuationToken = continuationToken;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * The earliest change {@code readChanges} returns, as an ISO-8601 timestamp such as {@code 2026-10-01T00:00:00Z}.
+ * Without it a first read starts at the beginning of the store's change log, which on a busy store is a lot of
+ * history to page through before reaching anything current. Parsed when the endpoint starts, so a malformed value
+ * fails there rather than on the first exchange.
+ */
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
/**
* An already-configured {@link OpenFgaClient} to use instead of letting the endpoint build one.
*/
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConstants.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConstants.java
index 4d2a61482462..508c713a53cd 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConstants.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaConstants.java
@@ -76,6 +76,16 @@ public final class OpenFgaConstants {
javaType = "String")
public static final String STORE_ID = HEADER_PREFIX + "StoreId";
+ @Metadata(label = "producer",
+ description = "The continuation token the page came back with. Feed it back through the"
+ + " continuationToken option to read on. The two operations end differently: readTuples"
+ + " returns no token on its last page, so the header is absent once the read is done,"
+ + " whereas readChanges always returns a token - an empty body is what says the log has"
+ + " been read up to date, and that last token is what lets the next poll resume instead"
+ + " of replaying the whole log.",
+ javaType = "String")
+ public static final String CONTINUATION_TOKEN = HEADER_PREFIX + "ContinuationToken";
+
@Metadata(label = "producer", description = "How many relationship tuples the writeTuples operation wrote.",
javaType = "Integer")
public static final String WRITTEN_TUPLES = HEADER_PREFIX + "WrittenTuples";
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaEndpoint.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaEndpoint.java
index b63841f63313..9d8238c54501 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaEndpoint.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaEndpoint.java
@@ -16,6 +16,9 @@
*/
package org.apache.camel.component.openfga;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeParseException;
+
import javax.net.ssl.SSLContext;
import dev.openfga.sdk.api.client.OpenFgaClient;
@@ -56,6 +59,7 @@ public class OpenFgaEndpoint extends DefaultEndpoint {
private volatile OpenFgaAuthorizer authorizer;
private volatile SSLContext sslContext;
+ private volatile OffsetDateTime startTime;
public OpenFgaEndpoint(final String uri, final Component component, final OpenFgaConfiguration configuration) {
super(uri, component);
@@ -76,6 +80,7 @@ public class OpenFgaEndpoint extends DefaultEndpoint {
sslContext = createSslContext();
client = OpenFgaClientFactory.createClient(configuration, sslContext);
}
+ this.startTime = parseStartTime();
validateOperationOptions();
authorizer = new OpenFgaAuthorizer(client, configuration, getCamelContext());
}
@@ -108,9 +113,14 @@ public class OpenFgaEndpoint extends DefaultEndpoint {
require(configuration.getObject(), "object", "names the object to list users of");
require(configuration.getRelation(), "relation", "names the relation to list users for");
}
+ case expand -> {
+ require(configuration.getObject(), "object", "names the object whose relation is expanded");
+ require(configuration.getRelation(), "relation", "names the relation to expand");
+ }
default -> {
// writeTuples and deleteTuples take their tuples from the message body, falling back to the
- // user/relation/object triple, so there is nothing that must be configured up front
+ // user/relation/object triple; readTuples treats user, relation and object as an optional filter and
+ // readChanges needs nothing at all - so none of them has anything that must be configured up front
}
}
}
@@ -122,6 +132,31 @@ public class OpenFgaEndpoint extends DefaultEndpoint {
}
}
+ /**
+ * Parses {@code startTime} when the endpoint starts, so a malformed timestamp stops the route rather than failing
+ * the first exchange that happens to reach a readChanges.
+ */
+ private OffsetDateTime parseStartTime() {
+ if (ObjectHelper.isEmpty(configuration.getStartTime())) {
+ return null;
+ }
+ try {
+ return OffsetDateTime.parse(configuration.getStartTime());
+ } catch (DateTimeParseException e) {
+ throw new IllegalArgumentException(
+ "startTime '" + configuration.getStartTime() + "' is not an ISO-8601 timestamp, for example"
+ + " 2026-10-01T00:00:00Z",
+ e);
+ }
+ }
+
+ /**
+ * The parsed {@code startTime}, or null when none was configured.
+ */
+ OffsetDateTime getStartTime() {
+ return startTime;
+ }
+
/**
* An endpoint without a pinned {@code authorizationModelId} evaluates against whatever model the store considers
* latest, which moves the moment somebody writes a new one. That is a reasonable default for getting started and a
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaOperation.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaOperation.java
index fd5b279b9c2d..e28e47b005d0 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaOperation.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaOperation.java
@@ -52,6 +52,22 @@ public enum OpenFgaOperation {
*/
listUsers,
+ /**
+ * Read the stored relationship tuples, optionally filtered by the configured user, relation and object. Paged.
+ */
+ readTuples,
+
+ /**
+ * Read the store's change log, so a route can keep an external projection in step with the graph. Paged.
+ */
+ readChanges,
+
+ /**
+ * Expand a relation on an object into its userset tree. An introspection aid: this is what you reach for when a
+ * check answered something surprising and you need to see how it got there.
+ */
+ expand,
+
/**
* Grant access by writing relationship tuples.
*/
diff --git a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaProducer.java b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaProducer.java
index baa6b7ef66c6..71e0e37fd0d5 100644
--- a/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaProducer.java
+++ b/components/camel-openfga/src/main/java/org/apache/camel/component/openfga/OpenFgaProducer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.openfga;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -26,19 +27,31 @@ import java.util.function.Consumer;
import dev.openfga.sdk.api.client.model.ClientBatchCheckClientResponse;
import dev.openfga.sdk.api.client.model.ClientCheckRequest;
+import dev.openfga.sdk.api.client.model.ClientExpandRequest;
+import dev.openfga.sdk.api.client.model.ClientExpandResponse;
import dev.openfga.sdk.api.client.model.ClientListObjectsRequest;
import dev.openfga.sdk.api.client.model.ClientListRelationsRequest;
import dev.openfga.sdk.api.client.model.ClientListUsersRequest;
+import dev.openfga.sdk.api.client.model.ClientReadChangesRequest;
+import dev.openfga.sdk.api.client.model.ClientReadChangesResponse;
+import dev.openfga.sdk.api.client.model.ClientReadRequest;
+import dev.openfga.sdk.api.client.model.ClientReadResponse;
import dev.openfga.sdk.api.client.model.ClientTupleKey;
import dev.openfga.sdk.api.client.model.ClientTupleKeyWithoutCondition;
import dev.openfga.sdk.api.configuration.ClientBatchCheckClientOptions;
+import dev.openfga.sdk.api.configuration.ClientExpandOptions;
import dev.openfga.sdk.api.configuration.ClientListObjectsOptions;
import dev.openfga.sdk.api.configuration.ClientListRelationsOptions;
import dev.openfga.sdk.api.configuration.ClientListUsersOptions;
+import dev.openfga.sdk.api.configuration.ClientReadChangesOptions;
+import dev.openfga.sdk.api.configuration.ClientReadOptions;
import dev.openfga.sdk.api.model.ConsistencyPreference;
import dev.openfga.sdk.api.model.FgaObject;
+import dev.openfga.sdk.api.model.TupleChange;
+import dev.openfga.sdk.api.model.TupleKey;
import dev.openfga.sdk.api.model.User;
import dev.openfga.sdk.api.model.UserTypeFilter;
+import dev.openfga.sdk.api.model.UsersetTree;
import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.health.HealthCheckHelper;
@@ -113,6 +126,9 @@ public class OpenFgaProducer extends DefaultProducer {
case listRelations -> listRelations(exchange, authorizer);
case listUsers -> listUsers(exchange, authorizer);
case writeTuples -> writeTuples(exchange, authorizer);
+ case readTuples -> readTuples(exchange, authorizer);
+ case readChanges -> readChanges(exchange, authorizer);
+ case expand -> expand(exchange, authorizer);
case deleteTuples -> deleteTuples(exchange, authorizer);
// unreachable today; here so that adding an operation to the enum without wiring it up fails loudly
// instead of silently letting the exchange through an endpoint that was asked to authorize it
@@ -304,6 +320,239 @@ public class OpenFgaProducer extends DefaultProducer {
exchange.getMessage().setBody(identifiers);
}
+ /**
+ * Reads the stored relationship tuples, filtered by whichever of user, relation and object the endpoint configured.
+ * <p/>
+ * Unlike every other operation these three are optional here: a part the endpoint never set means "do not filter on
+ * this", and leaving all three unset reads the whole store a page at a time. That is why the raw resolvers are used
+ * rather than the check-path ones, which treat a blank value as a missing identity and deny. A part that WAS set
+ * and resolves to nothing is a different thing entirely - see {@link #readFilterPart}.
+ */
+ private void readTuples(Exchange exchange, OpenFgaAuthorizer authorizer) throws Exception {
+ String filterUser = readFilterPart(authorizer.hasConfiguredUser(), authorizer.rawUser(exchange), "user");
+ String filterRelation
+ = readFilterPart(authorizer.hasConfiguredRelation(), authorizer.rawRelation(exchange), "relation");
+ String filterObject = readFilterPart(authorizer.hasConfiguredObject(), authorizer.rawObject(exchange), "object");
+ validateReadFilter(filterUser, filterRelation, filterObject);
+
+ ClientReadRequest request = new ClientReadRequest();
+ if (filterUser != null) {
+ request.user(filterUser);
+ }
+ if (filterRelation != null) {
+ request.relation(filterRelation);
+ }
+ if (filterObject != null) {
+ request._object(filterObject);
+ }
+
+ ClientReadOptions options = new ClientReadOptions();
+ if (getEndpoint().getConfiguration().getPageSize() != null) {
+ options.pageSize(getEndpoint().getConfiguration().getPageSize());
+ }
+ String token = authorizer.rawContinuationToken(exchange);
+ if (ObjectHelper.isNotEmpty(token)) {
+ options.continuationToken(token);
+ }
+ if (authorizer.getConsistency() != null) {
+ options.consistency(authorizer.getConsistency());
+ }
+
+ ClientReadResponse response = authorizer.await(exchange, "read the stored tuples",
+ () -> authorizer.getClient().read(request, options));
+
+ List<Map<String, Object>> tuples = new ArrayList<>();
+ if (response.getTuples() != null) {
+ // qualified because this class already has its own Tuple, the holder the write paths use; that one is
+ // referenced far more often, so it keeps the simple name
+ for (dev.openfga.sdk.api.model.Tuple tuple : response.getTuples()) {
+ TupleKey key = tuple.getKey();
+ if (key == null) {
+ continue;
+ }
+ // the user/relation/object keys are deliberately the ones writeTuples and deleteTuples accept, so a
+ // route can feed what it read straight back into a revoke without reshaping it
+ Map<String, Object> entry = new LinkedHashMap<>();
+ entry.put("user", key.getUser());
+ entry.put("relation", key.getRelation());
+ entry.put("object", key.getObject());
+ entry.put("timestamp", tuple.getTimestamp());
+ tuples.add(entry);
+ }
+ }
+ setContinuationToken(exchange, response.getContinuationToken());
+ exchange.getMessage().setBody(tuples);
+ }
+
+ /**
+ * Reads the store's change log, so a route can keep something outside OpenFGA in step with the graph.
+ */
+ private void readChanges(Exchange exchange, OpenFgaAuthorizer authorizer) throws Exception {
+ ClientReadChangesRequest request = new ClientReadChangesRequest();
+ if (ObjectHelper.isNotEmpty(getEndpoint().getConfiguration().getType())) {
+ request.type(getEndpoint().getConfiguration().getType());
+ }
+ if (getEndpoint().getStartTime() != null) {
+ request.startTime(getEndpoint().getStartTime());
+ }
+
+ ClientReadChangesOptions options = new ClientReadChangesOptions();
+ if (getEndpoint().getConfiguration().getPageSize() != null) {
+ options.pageSize(getEndpoint().getConfiguration().getPageSize());
+ }
+ String token = authorizer.rawContinuationToken(exchange);
+ if (ObjectHelper.isNotEmpty(token)) {
+ options.continuationToken(token);
+ }
+
+ ClientReadChangesResponse response = authorizer.await(exchange, "read the change log",
+ () -> authorizer.getClient().readChanges(request, options));
+
+ List<Map<String, Object>> changes = new ArrayList<>();
+ if (response.getChanges() != null) {
+ for (TupleChange change : response.getChanges()) {
+ TupleKey key = change.getTupleKey();
+ if (key == null) {
+ continue;
+ }
+ Map<String, Object> entry = new LinkedHashMap<>();
+ entry.put("user", key.getUser());
+ entry.put("relation", key.getRelation());
+ entry.put("object", key.getObject());
+ // the enum's name rather than the enum, so a route can compare against "WRITE" without importing
+ // an SDK type, and so the body marshals without a custom serializer
+ entry.put("operation", change.getOperation() != null ? change.getOperation().name() : null);
+ entry.put("timestamp", change.getTimestamp());
+ changes.add(entry);
+ }
+ }
+ setContinuationToken(exchange, response.getContinuationToken());
+ exchange.getMessage().setBody(changes);
+ }
+
+ /**
+ * Expands a relation on an object into its userset tree.
+ * <p/>
+ * This is the one operation that does not leave a {@code List<String>} on the body, because its answer is not a
+ * list: it is a tree of usersets explaining how a relation resolves. The {@link UsersetTree} goes on the body as it
+ * comes back from the SDK rather than being flattened, since flattening is exactly what destroys the information
+ * the operation exists to provide.
+ */
+ private void expand(Exchange exchange, OpenFgaAuthorizer authorizer) throws Exception {
+ OpenFgaAuthorizer.clearDecisionHeaders(exchange);
+ String object = authorizer.resolveObject(exchange);
+ String relation = authorizer.resolveRelation(exchange);
+ if (object == null || relation == null) {
+ exchange.getMessage().setBody(null);
+ return;
+ }
+
+ ClientExpandRequest request = new ClientExpandRequest()
+ ._object(object)
+ .relation(relation);
+ ClientExpandOptions options = new ClientExpandOptions();
+ applyModelAndConsistency(authorizer, options::authorizationModelId, options::consistency);
+
+ ClientExpandResponse response = authorizer.await(exchange, "expand " + relation + " on " + object,
+ () -> authorizer.getClient().expand(request, options));
+ exchange.getMessage().setBody(response.getTree());
+ }
+
+ /**
+ * Resolves one part of a read filter, keeping "never configured" and "configured but resolved to nothing" apart.
+ * <p/>
+ * Only an option the endpoint never set means "do not filter on this". An option that IS set and evaluates to blank
+ * - {@code user=${header.who}} on an exchange carrying no such header - is a failure to resolve, and dropping it
+ * would make the read WIDER than the route asked for: a filter meant to select one subject's tuples would instead
+ * return every tuple on the object, and the documented {@code readTuples -> deleteTuples} route would then revoke
+ * all of them. Widening a filter on a value that went missing is the read-side form of failing open, so it is
+ * refused. Same reasoning as {@link OpenFgaAuthorizer#hasConfiguredTuple()} on the write path.
+ */
+ private static String readFilterPart(boolean configured, String value, String option) {
+ if (!configured) {
+ return null;
+ }
+ String trimmed = trimmedOrNull(value);
+ if (trimmed == null) {
+ throw new IllegalArgumentException(
+ option + " is configured for the readTuples operation but resolved to nothing on this exchange:"
+ + " dropping it would read more than was asked for, so it is refused rather than widened");
+ }
+ return trimmed;
+ }
+
+ private static String trimmedOrNull(String value) {
+ if (ObjectHelper.isEmpty(value)) {
+ return null;
+ }
+ String trimmed = value.trim();
+ return trimmed.isEmpty() ? null : trimmed;
+ }
+
+ /**
+ * Checks a read filter against what OpenFGA's Read API actually accepts, which is narrower than "every part is
+ * optional".
+ * <p/>
+ * Measured against OpenFGA 1.21.0: no filter at all reads the whole store, {@code object=document:} with a user or
+ * a full {@code object=document:budget} are accepted, and a filter of user alone, relation alone, or object type
+ * alone is rejected with an HTTP 400 reading <em>"the 'tuple_key' field was provided but the object type field is
+ * required and both the object id and user cannot be empty"</em>. Catching it here turns that into something that
+ * names the option to change.
+ * <p/>
+ * Note that the object is deliberately not put through {@link OpenFgaIdentifiers#validateTupleValue}: a type-only
+ * {@code document:} is a legitimate read filter and not a legitimate identifier, so the two have different rules.
+ */
+ private static void validateReadFilter(String user, String relation, String object) {
+ if (user == null && relation == null && object == null) {
+ return;
+ }
+ // only the parts that were actually given: here an unset part means "do not filter on this", which is the
+ // opposite of what it means on the write path, where reject() must still fire for a configured part that
+ // resolved to nothing
+ if (user != null) {
+ reject(OpenFgaIdentifiers.validateTupleValue(user), "user", user, false);
+ }
+ if (relation != null) {
+ reject(OpenFgaIdentifiers.validateRelation(relation), "relation", relation, false);
+ }
+ if (object == null) {
+ throw new IllegalArgumentException(
+ "A readTuples filter must include object: OpenFGA requires an object type as soon as any filter is"
+ + " given. Use object=<type>: to filter on the type alone, or leave"
+ + " user, relation and object all unset to read the whole store");
+ }
+ int separator = object.indexOf(':');
+ if (separator <= 0) {
+ throw new IllegalArgumentException(
+ "The object filter '" + object + "' must name a type, as in document: or document:budget");
+ }
+ if (separator == object.length() - 1 && user == null) {
+ throw new IllegalArgumentException(
+ "A readTuples filter of object type alone is not enough for OpenFGA: give an object id"
+ + " (object=" + object + "<id>) or a user (user=<type>:<id>) alongside"
+ + " object=" + object);
+ }
+ }
+
+ /**
+ * Records the page token, and removes a token a previous exchange left behind when the server returned none -
+ * otherwise a route looping on the header would read the same final page for ever.
+ * <p/>
+ * Which of the two happens is the server's choice, not this component's, and the two read operations differ
+ * (measured against OpenFGA 1.21.0): the Read API returns an empty token on its last page, so the header really
+ * does disappear once readTuples is done; readChanges echoes the request's token back with an empty page instead,
+ * because a change log is a tailing cursor rather than a finite list - that token is the bookmark the next poll
+ * resumes from, so it is deliberately left in place and an empty body is what signals "up to date". Removing it
+ * there would break incremental sync rather than improve it.
+ */
+ private static void setContinuationToken(Exchange exchange, String token) {
+ if (ObjectHelper.isNotEmpty(token)) {
+ exchange.getMessage().setHeader(OpenFgaConstants.CONTINUATION_TOKEN, token);
+ } else {
+ exchange.getMessage().removeHeader(OpenFgaConstants.CONTINUATION_TOKEN);
+ }
+ }
+
/**
* Writes relationship tuples, granting access.
*/
diff --git a/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaIT.java b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaIT.java
index 6db1883771f7..9d62dd013e17 100644
--- a/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaIT.java
+++ b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaIT.java
@@ -120,6 +120,15 @@ class OpenFgaIT extends CamelTestSupport {
from("direct:guarded")
.policy(policy)
.to("mock:allowed");
+
+ // the readChanges poller exactly as the component docs describe it: a global variable carries the
+ // token from one exchange to the next, which an exchange property cannot do
+ from("direct:syncChanges")
+ .to(openfga("readChanges", "pageSize=1&continuationToken=${variable.global:fgaToken}"))
+ .setVariable("global:fgaToken", header(OpenFgaConstants.CONTINUATION_TOKEN));
+
+ from("direct:resetSync")
+ .removeVariable("global:fgaToken");
}
};
}
@@ -237,6 +246,96 @@ class OpenFgaIT extends CamelTestSupport {
.isEqualTo(false);
}
+ @Test
+ void readsTheStoredTuplesInAShapeDeleteTuplesAccepts() {
+ Exchange read = template.request(openfga("readTuples", "object=document:&user=user:bob"), e -> {
+ });
+
+ assertThat(read.getException()).isNull();
+ assertThat(read.getMessage().getBody(List.class)).isNotEmpty();
+ // bob's only stored tuple is reader on the roadmap
+ assertThat(read.getMessage().getBody(List.class)).anySatisfy(entry -> {
+ Map<?, ?> tuple = (Map<?, ?>) entry;
+ assertThat(tuple.get("user")).isEqualTo("user:bob");
+ assertThat(tuple.get("relation")).isEqualTo("reader");
+ assertThat(tuple.get("object")).isEqualTo("document:roadmap");
+ });
+ }
+
+ @Test
+ void readsTheChangeLogAndPagesWithTheContinuationToken() {
+ Exchange firstPage = template.request(openfga("readChanges", "pageSize=1"), e -> {
+ });
+
+ assertThat(firstPage.getException()).isNull();
+ assertThat(firstPage.getMessage().getBody(List.class)).hasSize(1);
+ assertThat(((Map<?, ?>) firstPage.getMessage().getBody(List.class).get(0)).get("operation"))
+ .isEqualTo("WRITE");
+
+ String token = firstPage.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN, String.class);
+ assertThat(token).isNotBlank();
+
+ Exchange secondPage = template.request(
+ openfga("readChanges", "pageSize=1&continuationToken=${header.resume}"),
+ e -> e.getMessage().setHeader("resume", token));
+
+ assertThat(secondPage.getException()).isNull();
+ assertThat(secondPage.getMessage().getBody(List.class)).hasSize(1);
+ // a different change than the first page, which is what paging is for
+ assertThat(secondPage.getMessage().getBody(List.class).get(0))
+ .isNotEqualTo(firstPage.getMessage().getBody(List.class).get(0));
+ }
+
+ @Test
+ void aGlobalVariableCarriesThePagingTokenBetweenExchanges() {
+ template.sendBody("direct:resetSync", "");
+
+ Exchange firstPoll = template.request("direct:syncChanges", e -> {
+ });
+ Exchange secondPoll = template.request("direct:syncChanges", e -> {
+ });
+
+ assertThat(firstPoll.getException()).isNull();
+ assertThat(secondPoll.getException()).isNull();
+ assertThat(firstPoll.getMessage().getBody(List.class)).hasSize(1);
+ assertThat(secondPoll.getMessage().getBody(List.class)).hasSize(1);
+ // with an exchange property the second poll would have started over and returned the same change again
+ assertThat(secondPoll.getMessage().getBody(List.class).get(0))
+ .isNotEqualTo(firstPoll.getMessage().getBody(List.class).get(0));
+ }
+
+ @Test
+ void readChangesHandsTheTokenBackWhenTheLogIsExhausted() {
+ String[] token = { null };
+ List<?> changes;
+ int guard = 0;
+ do {
+ Exchange out = template.request(
+ openfga("readChanges", "pageSize=50&continuationToken=${header.resume}"),
+ e -> e.getMessage().setHeader("resume", token[0]));
+ assertThat(out.getException()).isNull();
+ changes = out.getMessage().getBody(List.class);
+ token[0] = out.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN, String.class);
+ assertThat(token[0]).as("readChanges returns a token on every page, the empty one included").isNotBlank();
+ } while (!changes.isEmpty() && ++guard < 20);
+
+ // the asymmetry the docs spell out: readChanges never drops the token, so an EMPTY BODY - not an absent
+ // header - says the log has been read up to date. A route looping until the header disappeared would not end
+ assertThat(changes).isEmpty();
+ assertThat(token[0]).isNotBlank();
+ }
+
+ @Test
+ void expandShowsHowARelationResolves() {
+ Exchange out = template.request(openfga("expand", "object=document:budget&relation=reader"), e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+ // anne reads the budget through owner, so the tree must mention the relation rather than just listing her
+ assertThat(out.getMessage().getBody()).isNotNull();
+ assertThat(out.getMessage().getBody().toString()).contains("document:budget#reader");
+ }
+
@Test
void listsTheObjectsASubjectCanRead() {
Exchange out = template.request(
diff --git a/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaReadOperationsTest.java b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaReadOperationsTest.java
new file mode 100644
index 000000000000..14c58e11fe11
--- /dev/null
+++ b/components/camel-openfga/src/test/java/org/apache/camel/component/openfga/OpenFgaReadOperationsTest.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openfga;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import dev.openfga.sdk.api.client.OpenFgaClient;
+import dev.openfga.sdk.api.client.model.ClientExpandRequest;
+import dev.openfga.sdk.api.client.model.ClientExpandResponse;
+import dev.openfga.sdk.api.client.model.ClientReadChangesRequest;
+import dev.openfga.sdk.api.client.model.ClientReadChangesResponse;
+import dev.openfga.sdk.api.client.model.ClientReadRequest;
+import dev.openfga.sdk.api.client.model.ClientReadResponse;
+import dev.openfga.sdk.api.client.model.ClientTupleKeyWithoutCondition;
+import dev.openfga.sdk.api.client.model.ClientWriteResponse;
+import dev.openfga.sdk.api.configuration.ClientReadChangesOptions;
+import dev.openfga.sdk.api.configuration.ClientReadOptions;
+import dev.openfga.sdk.api.model.Node;
+import dev.openfga.sdk.api.model.Tuple;
+import dev.openfga.sdk.api.model.TupleChange;
+import dev.openfga.sdk.api.model.TupleKey;
+import dev.openfga.sdk.api.model.TupleOperation;
+import dev.openfga.sdk.api.model.UsersetTree;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.Exchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class OpenFgaReadOperationsTest extends CamelTestSupport {
+
+ private static final String STORE = "01HQMVAJXYZ0000000000000";
+ private static final String BASE = "?openFgaClient=#fgaClient&storeId=" + STORE;
+
+ @BindToRegistry("fgaClient")
+ private final OpenFgaClient client = mock(OpenFgaClient.class);
+
+ private static Tuple storedTuple(String user, String relation, String object) {
+ return new Tuple()
+ .key(new TupleKey().user(user).relation(relation)._object(object))
+ .timestamp(OffsetDateTime.parse("2026-10-01T10:00:00Z"));
+ }
+
+ private void givenStoredTuples(String token, Tuple... tuples) throws Exception {
+ ClientReadResponse response = mock(ClientReadResponse.class);
+ when(response.getTuples()).thenReturn(List.of(tuples));
+ when(response.getContinuationToken()).thenReturn(token);
+ when(client.read(any(ClientReadRequest.class), any())).thenReturn(CompletableFuture.completedFuture(response));
+ }
+
+ @Test
+ void readTuplesReturnsTheShapeTheWriteOperationsAccept() throws Exception {
+ givenStoredTuples(null, storedTuple("user:anne", "owner", "document:budget"));
+
+ Exchange out = template.request("openfga:readTuples" + BASE, e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getBody(List.class)).singleElement()
+ .satisfies(entry -> {
+ Map<?, ?> tuple = (Map<?, ?>) entry;
+ assertThat(tuple.get("user")).isEqualTo("user:anne");
+ assertThat(tuple.get("relation")).isEqualTo("owner");
+ assertThat(tuple.get("object")).isEqualTo("document:budget");
+ assertThat(tuple.get("timestamp")).isEqualTo(OffsetDateTime.parse("2026-10-01T10:00:00Z"));
+ });
+ }
+
+ @Test
+ void whatReadTuplesReturnsCanBeRevokedWithoutReshaping() throws Exception {
+ // the point of using user/relation/object as the keys: a read feeds a revoke directly
+ givenStoredTuples(null, storedTuple("user:bob", "reader", "document:roadmap"));
+ when(client.deleteTuples(anyList()))
+ .thenReturn(CompletableFuture.completedFuture(mock(ClientWriteResponse.class)));
+
+ Exchange read = template.request("openfga:readTuples" + BASE, e -> {
+ });
+ Exchange revoked = template.request("openfga:deleteTuples" + BASE,
+ e -> e.getMessage().setBody(read.getMessage().getBody()));
+
+ assertThat(revoked.getException()).isNull();
+ assertThat(revoked.getMessage().getHeader(OpenFgaConstants.DELETED_TUPLES)).isEqualTo(1);
+ ArgumentCaptor<List<ClientTupleKeyWithoutCondition>> captor = ArgumentCaptor.captor();
+ verify(client).deleteTuples(captor.capture());
+ assertThat(captor.getValue()).singleElement()
+ .satisfies(tuple -> {
+ assertThat(tuple.getUser()).isEqualTo("user:bob");
+ assertThat(tuple.getObject()).isEqualTo("document:roadmap");
+ });
+ }
+
+ @Test
+ void readTuplesAppliesTheConfiguredFilterAndPaging() throws Exception {
+ givenStoredTuples("tok-2", storedTuple("user:anne", "owner", "document:budget"));
+
+ Exchange out = template.request(
+ "openfga:readTuples" + BASE + "&user=user:anne&relation=owner&object=document:&pageSize=25"
+ + "&continuationToken=${header.resume}",
+ e -> e.getMessage().setHeader("resume", "tok-1"));
+
+ ArgumentCaptor<ClientReadRequest> request = ArgumentCaptor.forClass(ClientReadRequest.class);
+ ArgumentCaptor<ClientReadOptions> options = ArgumentCaptor.forClass(ClientReadOptions.class);
+ verify(client).read(request.capture(), options.capture());
+ assertThat(request.getValue().getUser()).isEqualTo("user:anne");
+ assertThat(request.getValue().getRelation()).isEqualTo("owner");
+ assertThat(request.getValue().getObject()).isEqualTo("document:");
+ assertThat(options.getValue().getPageSize()).isEqualTo(25);
+ assertThat(options.getValue().getContinuationToken()).isEqualTo("tok-1");
+ assertThat(out.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN)).isEqualTo("tok-2");
+ }
+
+ @Test
+ void readTuplesRemovesAStaleTokenOnTheLastPage() throws Exception {
+ givenStoredTuples(null, storedTuple("user:anne", "owner", "document:budget"));
+
+ // a route looping on the header would otherwise read the final page for ever
+ Exchange out = template.request("openfga:readTuples" + BASE,
+ e -> e.getMessage().setHeader(OpenFgaConstants.CONTINUATION_TOKEN, "tok-from-the-previous-page"));
+
+ assertThat(out.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN)).isNull();
+ }
+
+ @Test
+ void readTuplesRefusesAFilterOpenFgaWouldReject() {
+ // measured against OpenFGA 1.21.0: a user-only filter comes back as HTTP 400 because the Read API wants an
+ // object type as soon as any filter is given. Catching it here names the option to change
+ Exchange userOnly = template.request("openfga:readTuples" + BASE + "&user=user:bob", e -> {
+ });
+ assertThat(userOnly.getException()).isInstanceOf(IllegalArgumentException.class);
+ assertThat(userOnly.getException()).hasMessageContaining("must include object");
+
+ Exchange typeOnly = template.request("openfga:readTuples" + BASE + "&object=document:", e -> {
+ });
+ assertThat(typeOnly.getException()).isInstanceOf(IllegalArgumentException.class);
+ assertThat(typeOnly.getException()).hasMessageContaining("object type alone is not enough");
+ }
+
+ @Test
+ void readTuplesAcceptsATypeOnlyObjectAlongsideAUser() throws Exception {
+ givenStoredTuples(null, storedTuple("user:bob", "reader", "document:roadmap"));
+
+ Exchange out = template.request("openfga:readTuples" + BASE + "&object=document:&user=user:bob", e -> {
+ });
+
+ // document: is a legitimate read filter though it is not a legitimate identifier anywhere else
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getBody(List.class)).hasSize(1);
+ }
+
+ @Test
+ void readTuplesRefusesAFilterThatCouldNotBeATupleValue() throws Exception {
+ Exchange out = template.request("openfga:readTuples" + BASE + "&object=not-an-identifier", e -> {
+ });
+
+ assertThat(out.getException()).isInstanceOf(IllegalArgumentException.class);
+ assertThat(out.getException()).hasMessageContaining("must name a type");
+ }
+
+ @Test
+ void readTuplesRefusesAFilterPartThatWentMissing() throws Exception {
+ givenStoredTuples(null, storedTuple("user:bob", "reader", "document:budget"));
+
+ // object=document:budget on its own is a legal filter, so dropping an unresolved user would quietly read
+ // every tuple on that document - and the documented readTuples -> deleteTuples route would revoke them all
+ Exchange out = template.request("openfga:readTuples" + BASE + "&object=document:budget&user=${header.who}",
+ e -> {
+ });
+
+ assertThat(out.getException()).isInstanceOf(IllegalArgumentException.class);
+ assertThat(out.getException()).hasMessageContaining("user is configured for the readTuples operation");
+ verify(client, never()).read(any(ClientReadRequest.class), any());
+ }
+
+ @Test
+ void readTuplesWithNoFilterConfiguredStillReadsEverything() throws Exception {
+ givenStoredTuples(null, storedTuple("user:bob", "reader", "document:budget"));
+
+ Exchange out = template.request("openfga:readTuples" + BASE, e -> {
+ });
+
+ // an option that was never set is the only thing that means "do not filter on this"
+ assertThat(out.getException()).isNull();
+ ArgumentCaptor<ClientReadRequest> request = ArgumentCaptor.forClass(ClientReadRequest.class);
+ verify(client).read(request.capture(), any());
+ assertThat(request.getValue().getUser()).isNull();
+ assertThat(request.getValue().getRelation()).isNull();
+ assertThat(request.getValue().getObject()).isNull();
+ }
+
+ @Test
+ void readChangesReportsTheOperationAsItsName() throws Exception {
+ ClientReadChangesResponse response = mock(ClientReadChangesResponse.class);
+ when(response.getChanges()).thenReturn(List.of(
+ new TupleChange()
+ .tupleKey(new TupleKey().user("user:anne").relation("owner")._object("document:budget"))
+ .operation(TupleOperation.WRITE)
+ .timestamp(OffsetDateTime.parse("2026-10-01T10:00:00Z")),
+ new TupleChange()
+ .tupleKey(new TupleKey().user("user:bob").relation("reader")._object("document:roadmap"))
+ .operation(TupleOperation.DELETE)
+ .timestamp(OffsetDateTime.parse("2026-10-01T11:00:00Z"))));
+ when(response.getContinuationToken()).thenReturn("tok-9");
+ when(client.readChanges(any(ClientReadChangesRequest.class), any()))
+ .thenReturn(CompletableFuture.completedFuture(response));
+
+ Exchange out = template.request(
+ "openfga:readChanges" + BASE + "&type=document&pageSize=50&startTime=2026-10-01T00:00:00Z", e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getBody(List.class)).hasSize(2);
+ assertThat(((Map<?, ?>) out.getMessage().getBody(List.class).get(0)).get("operation")).isEqualTo("WRITE");
+ assertThat(((Map<?, ?>) out.getMessage().getBody(List.class).get(1)).get("operation")).isEqualTo("DELETE");
+ assertThat(out.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN)).isEqualTo("tok-9");
+
+ ArgumentCaptor<ClientReadChangesRequest> request = ArgumentCaptor.forClass(ClientReadChangesRequest.class);
+ ArgumentCaptor<ClientReadChangesOptions> options = ArgumentCaptor.forClass(ClientReadChangesOptions.class);
+ verify(client).readChanges(request.capture(), options.capture());
+ assertThat(request.getValue().getType()).isEqualTo("document");
+ assertThat(request.getValue().getStartTime()).isEqualTo(OffsetDateTime.parse("2026-10-01T00:00:00Z"));
+ assertThat(options.getValue().getPageSize()).isEqualTo(50);
+ }
+
+ @Test
+ void readChangesKeepsTheTokenWhenNothingHasChanged() throws Exception {
+ ClientReadChangesResponse response = mock(ClientReadChangesResponse.class);
+ when(response.getChanges()).thenReturn(List.of());
+ when(response.getContinuationToken()).thenReturn("tok-9");
+ when(client.readChanges(any(ClientReadChangesRequest.class), any()))
+ .thenReturn(CompletableFuture.completedFuture(response));
+
+ Exchange out = template.request("openfga:readChanges" + BASE + "&continuationToken=tok-9", e -> {
+ });
+
+ // measured against OpenFGA 1.21.0: readChanges hands the token straight back with an empty page rather than
+ // dropping it, so an empty body - not an absent header - is what says the log has been read up to date. A
+ // route that looped until the header disappeared would never end, which is why the docs split the two
+ assertThat(out.getException()).isNull();
+ assertThat(out.getMessage().getBody(List.class)).isEmpty();
+ assertThat(out.getMessage().getHeader(OpenFgaConstants.CONTINUATION_TOKEN)).isEqualTo("tok-9");
+ }
+
+ @Test
+ void refusesAStartTimeThatIsNotATimestampWhenTheEndpointStarts() {
+ assertThatThrownBy(() -> {
+ OpenFgaEndpoint endpoint
+ = context.getEndpoint("openfga:readChanges" + BASE + "&startTime=yesterday", OpenFgaEndpoint.class);
+ endpoint.start();
+ }).hasStackTraceContaining("startTime 'yesterday' is not an ISO-8601 timestamp");
+ }
+
+ @Test
+ void expandPutsTheUsersetTreeOnTheBody() throws Exception {
+ UsersetTree tree = new UsersetTree().root(new Node().name("document:budget#reader"));
+ ClientExpandResponse response = mock(ClientExpandResponse.class);
+ when(response.getTree()).thenReturn(tree);
+ when(client.expand(any(ClientExpandRequest.class), any()))
+ .thenReturn(CompletableFuture.completedFuture(response));
+
+ Exchange out = template.request(
+ "openfga:expand" + BASE + "&object=document:budget&relation=reader", e -> {
+ });
+
+ assertThat(out.getException()).isNull();
+ // the one operation that does not leave a List<String>: flattening a tree destroys what it exists to show
+ assertThat(out.getMessage().getBody()).isSameAs(tree);
+ assertThat(out.getMessage().getBody(UsersetTree.class).getRoot().getName())
+ .isEqualTo("document:budget#reader");
+ }
+
+ @Test
+ void expandRequiresAnObjectAndARelation() {
+ assertThatThrownBy(() -> {
+ OpenFgaEndpoint endpoint
+ = context.getEndpoint("openfga:expand" + BASE + "&object=document:budget", OpenFgaEndpoint.class);
+ endpoint.start();
+ }).hasStackTraceContaining("relation is required for the expand operation");
+ }
+}
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfgaComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfgaComponentBuilderFactory.java
index c30b97b4b886..8c640b2008ed 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfgaComponentBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenfgaComponentBuilderFactory.java
@@ -192,6 +192,25 @@ public interface OpenfgaComponentBuilderFactory {
return this;
}
+ /**
+ * The page to read from, for readTuples and readChanges. Evaluated as a
+ * Simple expression against the exchange, so a route can feed back the
+ * token the previous page returned -
+ * ${header.CamelOpenFgaContinuationToken} - and page through without
+ * the token being configured statically.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param continuationToken the value to set
+ * @return the dsl builder
+ */
+ default OpenfgaComponentBuilder continuationToken(java.lang.String continuationToken) {
+ doSetProperty("continuationToken", continuationToken);
+ return this;
+ }
+
/**
* Whether the producer should be started lazy (on the first message).
@@ -237,6 +256,23 @@ public interface OpenfgaComponentBuilderFactory {
return this;
}
+ /**
+ * How many entries a readTuples or readChanges page returns. Left
+ * unset, OpenFGA's own default applies. A page is one request: this
+ * bounds the answer, not the number of requests a route makes.
+ *
+ * The option is a: <code>java.lang.Integer</code> type.
+ *
+ * Group: producer
+ *
+ * @param pageSize the value to set
+ * @return the dsl builder
+ */
+ default OpenfgaComponentBuilder pageSize(java.lang.Integer pageSize) {
+ doSetProperty("pageSize", pageSize);
+ return this;
+ }
+
/**
* The relation to demand, such as reader or owner. Evaluated as a
* Simple expression against each exchange, though a literal is what you
@@ -274,6 +310,26 @@ public interface OpenfgaComponentBuilderFactory {
return this;
}
+ /**
+ * The earliest change readChanges returns, as an ISO-8601 timestamp
+ * such as {code 2026-10-01T00:00:00Z}. Without it a first read starts
+ * at the beginning of the store's change log, which on a busy store is
+ * a lot of history to page through before reaching anything current.
+ * Parsed when the endpoint starts, so a malformed value fails there
+ * rather than on the first exchange.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param startTime the value to set
+ * @return the dsl builder
+ */
+ default OpenfgaComponentBuilder startTime(java.lang.String startTime) {
+ doSetProperty("startTime", startTime);
+ return this;
+ }
+
/**
* The identifier of the OpenFGA store holding the relationship tuples
* and the authorization model, as returned by {code fga store create}.
@@ -710,10 +766,13 @@ public interface OpenfgaComponentBuilderFactory {
case "configuration": ((OpenFgaComponent) component).setConfiguration((org.apache.camel.component.openfga.OpenFgaConfiguration) value); return true;
case "consistency": getOrCreateConfiguration((OpenFgaComponent) component).setConsistency((java.lang.String) value); return true;
case "contextualTuples": getOrCreateConfiguration((OpenFgaComponent) component).setContextualTuples((java.lang.String) value); return true;
+ case "continuationToken": getOrCreateConfiguration((OpenFgaComponent) component).setContinuationToken((java.lang.String) value); return true;
case "lazyStartProducer": ((OpenFgaComponent) component).setLazyStartProducer((boolean) value); return true;
case "object": getOrCreateConfiguration((OpenFgaComponent) component).setObject((java.lang.String) value); return true;
+ case "pageSize": getOrCreateConfiguration((OpenFgaComponent) component).setPageSize((java.lang.Integer) value); return true;
case "relation": getOrCreateConfiguration((OpenFgaComponent) component).setRelation((java.lang.String) value); return true;
case "relations": getOrCreateConfiguration((OpenFgaComponent) component).setRelations((java.lang.String) value); return true;
+ case "startTime": getOrCreateConfiguration((OpenFgaComponent) component).setStartTime((java.lang.String) value); return true;
case "storeId": getOrCreateConfiguration((OpenFgaComponent) component).setStoreId((java.lang.String) value); return true;
case "type": getOrCreateConfiguration((OpenFgaComponent) component).setType((java.lang.String) value); return true;
case "user": getOrCreateConfiguration((OpenFgaComponent) component).setUser((java.lang.String) value); return true;
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 453691724f51..428401406594 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
@@ -12874,8 +12874,9 @@ public class StaticEndpointBuilders {
* it is deliberately not overridable by a message header, so that an
* inbound message cannot turn a check into a tuple write, nor a check for
* one relation into a check for a weaker one.
- * There are 7 enums and the value can be one of: check, batchCheck,
- * listObjects, listRelations, listUsers, writeTuples, deleteTuples
+ * There are 10 enums and the value can be one of: check, batchCheck,
+ * listObjects, listRelations, listUsers, readTuples, readChanges, expand,
+ * writeTuples, deleteTuples
*
* @param path operation
* @return the dsl builder
@@ -12899,8 +12900,9 @@ public class StaticEndpointBuilders {
* it is deliberately not overridable by a message header, so that an
* inbound message cannot turn a check into a tuple write, nor a check for
* one relation into a check for a weaker one.
- * There are 7 enums and the value can be one of: check, batchCheck,
- * listObjects, listRelations, listUsers, writeTuples, deleteTuples
+ * There are 10 enums and the value can be one of: check, batchCheck,
+ * listObjects, listRelations, listUsers, readTuples, readChanges, expand,
+ * writeTuples, deleteTuples
*
* @param componentName to use a custom component name for the endpoint
* instead of the default name
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFgaEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFgaEndpointBuilderFactory.java
index defe63105cac..580bee6c6a62 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFgaEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenFgaEndpointBuilderFactory.java
@@ -190,6 +190,24 @@ public interface OpenFgaEndpointBuilderFactory {
doSetProperty("contextualTuples", contextualTuples);
return this;
}
+ /**
+ * The page to read from, for readTuples and readChanges. Evaluated as a
+ * Simple expression against the exchange, so a route can feed back the
+ * token the previous page returned -
+ * ${header.CamelOpenFgaContinuationToken} - and page through without
+ * the token being configured statically.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param continuationToken the value to set
+ * @return the dsl builder
+ */
+ default OpenFgaEndpointBuilder continuationToken(String continuationToken) {
+ doSetProperty("continuationToken", continuationToken);
+ return this;
+ }
/**
* The object being accessed, as an OpenFGA object identifier such as
* {code document:budget}. Evaluated as a Simple expression against each
@@ -209,6 +227,39 @@ public interface OpenFgaEndpointBuilderFactory {
doSetProperty("object", object);
return this;
}
+ /**
+ * How many entries a readTuples or readChanges page returns. Left
+ * unset, OpenFGA's own default applies. A page is one request: this
+ * bounds the answer, not the number of requests a route makes.
+ *
+ * The option is a: <code>java.lang.Integer</code> type.
+ *
+ * Group: producer
+ *
+ * @param pageSize the value to set
+ * @return the dsl builder
+ */
+ default OpenFgaEndpointBuilder pageSize(Integer pageSize) {
+ doSetProperty("pageSize", pageSize);
+ return this;
+ }
+ /**
+ * How many entries a readTuples or readChanges page returns. Left
+ * unset, OpenFGA's own default applies. A page is one request: this
+ * bounds the answer, not the number of requests a route makes.
+ *
+ * The option will be converted to a <code>java.lang.Integer</code>
+ * type.
+ *
+ * Group: producer
+ *
+ * @param pageSize the value to set
+ * @return the dsl builder
+ */
+ default OpenFgaEndpointBuilder pageSize(String pageSize) {
+ doSetProperty("pageSize", pageSize);
+ return this;
+ }
/**
* The relation to demand, such as reader or owner. Evaluated as a
* Simple expression against each exchange, though a literal is what you
@@ -244,6 +295,25 @@ public interface OpenFgaEndpointBuilderFactory {
doSetProperty("relations", relations);
return this;
}
+ /**
+ * The earliest change readChanges returns, as an ISO-8601 timestamp
+ * such as {code 2026-10-01T00:00:00Z}. Without it a first read starts
+ * at the beginning of the store's change log, which on a busy store is
+ * a lot of history to page through before reaching anything current.
+ * Parsed when the endpoint starts, so a malformed value fails there
+ * rather than on the first exchange.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: producer
+ *
+ * @param startTime the value to set
+ * @return the dsl builder
+ */
+ default OpenFgaEndpointBuilder startTime(String startTime) {
+ doSetProperty("startTime", startTime);
+ return this;
+ }
/**
* The identifier of the OpenFGA store holding the relationship tuples
* and the authorization model, as returned by {code fga store create}.
@@ -782,8 +852,9 @@ public interface OpenFgaEndpointBuilderFactory {
* only: it is deliberately not overridable by a message header, so that
* an inbound message cannot turn a check into a tuple write, nor a
* check for one relation into a check for a weaker one.
- * There are 7 enums and the value can be one of: check, batchCheck,
- * listObjects, listRelations, listUsers, writeTuples, deleteTuples
+ * There are 10 enums and the value can be one of: check, batchCheck,
+ * listObjects, listRelations, listUsers, readTuples, readChanges,
+ * expand, writeTuples, deleteTuples
*
* @param path operation
* @return the dsl builder
@@ -807,8 +878,9 @@ public interface OpenFgaEndpointBuilderFactory {
* only: it is deliberately not overridable by a message header, so that
* an inbound message cannot turn a check into a tuple write, nor a
* check for one relation into a check for a weaker one.
- * There are 7 enums and the value can be one of: check, batchCheck,
- * listObjects, listRelations, listUsers, writeTuples, deleteTuples
+ * There are 10 enums and the value can be one of: check, batchCheck,
+ * listObjects, listRelations, listUsers, readTuples, readChanges,
+ * expand, writeTuples, deleteTuples
*
* @param componentName to use a custom component name for the endpoint
* instead of the default name
@@ -933,6 +1005,24 @@ public interface OpenFgaEndpointBuilderFactory {
public String openFgaStoreId() {
return "CamelOpenFgaStoreId";
}
+ /**
+ * The continuation token the page came back with. Feed it back through
+ * the continuationToken option to read on. The two operations end
+ * differently: readTuples returns no token on its last page, so the
+ * header is absent once the read is done, whereas readChanges always
+ * returns a token - an empty body is what says the log has been read up
+ * to date, and that last token is what lets the next poll resume
+ * instead of replaying the whole log.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code OpenFgaContinuationToken}.
+ */
+ public String openFgaContinuationToken() {
+ return "CamelOpenFgaContinuationToken";
+ }
/**
* How many relationship tuples the writeTuples operation wrote.
*