This is an automated email from the ASF dual-hosted git repository.
acosentino 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 85baf356f659 CAMEL-22674 - Camel-Keycloak: Support Bulk Operations
(#19851)
85baf356f659 is described below
commit 85baf356f6591a9c5200d11b18cbdfc26dad2457
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Nov 7 20:14:12 2025 +0100
CAMEL-22674 - Camel-Keycloak: Support Bulk Operations (#19851)
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../apache/camel/catalog/components/keycloak.json | 14 +-
.../apache/camel/component/keycloak/keycloak.json | 14 +-
.../src/main/docs/keycloak-component.adoc | 556 +++++++++++++++++++++
.../component/keycloak/KeycloakConstants.java | 20 +
.../component/keycloak/KeycloakOperations.java | 8 +-
.../camel/component/keycloak/KeycloakProducer.java | 340 +++++++++++++
.../dsl/KeycloakEndpointBuilderFactory.java | 74 +++
7 files changed, 1017 insertions(+), 9 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
index f625f191741a..78b35662c72f 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
@@ -43,7 +43,7 @@
"ipAddress": { "index": 16, "kind": "property", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "property", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "property", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
+ "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
"operationTypes": { "index": 20, "kind": "property", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "property", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "property", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
@@ -59,7 +59,7 @@
"autowiredEnabled": { "index": 32, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"headers": {
- "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
+ "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
"CamelKeycloakRealmName": { "index": 1, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The realm name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REALM_NAME" },
"CamelKeycloakUserId": { "index": 2, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_ID" },
"CamelKeycloakUsername": { "index": 3, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The username", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAME" },
@@ -101,7 +101,13 @@
"CamelKeycloakRequiredAction": { "index": 39, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The required action type",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REQUIRED_ACTION" },
"CamelKeycloakActions": { "index": 40, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of actions to
execute", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ACTIONS" },
"CamelKeycloakRedirectUri": { "index": 41, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The redirect URI", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REDIRECT_URI" },
- "CamelKeycloakLifespan": { "index": 42, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The lifespan in seconds", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#LIFESPAN" }
+ "CamelKeycloakLifespan": { "index": 42, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The lifespan in seconds", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#LIFESPAN" },
+ "CamelKeycloakUsers": { "index": 43, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType":
"java.util.List<org.keycloak.representations.idm.UserRepresentation>",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The list of users for bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERS" },
+ "CamelKeycloakUserIds": { "index": 44, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of user IDs for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_IDS" },
+ "CamelKeycloakUsernames": { "index": 45, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of usernames for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAMES" },
+ "CamelKeycloakRoleNames": { "index": 46, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of role names for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ROLE_NAMES" },
+ "CamelKeycloakContinueOnError": { "index": 47, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Continue on error during bulk
operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#CONTINUE_ON_ERROR" },
+ "CamelKeycloakBatchSize": { "index": 48, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Batch size for bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#BATCH_SIZE" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Logical name" },
@@ -123,7 +129,7 @@
"ipAddress": { "index": 16, "kind": "parameter", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "parameter", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "parameter", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
+ "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
"operationTypes": { "index": 20, "kind": "parameter", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "parameter", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "parameter", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
diff --git
a/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
b/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
index f625f191741a..78b35662c72f 100644
---
a/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
+++
b/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
@@ -43,7 +43,7 @@
"ipAddress": { "index": 16, "kind": "property", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "property", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "property", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
+ "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
"operationTypes": { "index": 20, "kind": "property", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "property", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "property", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
@@ -59,7 +59,7 @@
"autowiredEnabled": { "index": 32, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"headers": {
- "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
+ "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
"CamelKeycloakRealmName": { "index": 1, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The realm name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REALM_NAME" },
"CamelKeycloakUserId": { "index": 2, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_ID" },
"CamelKeycloakUsername": { "index": 3, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The username", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAME" },
@@ -101,7 +101,13 @@
"CamelKeycloakRequiredAction": { "index": 39, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The required action type",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REQUIRED_ACTION" },
"CamelKeycloakActions": { "index": 40, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of actions to
execute", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ACTIONS" },
"CamelKeycloakRedirectUri": { "index": 41, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The redirect URI", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REDIRECT_URI" },
- "CamelKeycloakLifespan": { "index": 42, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The lifespan in seconds", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#LIFESPAN" }
+ "CamelKeycloakLifespan": { "index": 42, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The lifespan in seconds", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#LIFESPAN" },
+ "CamelKeycloakUsers": { "index": 43, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType":
"java.util.List<org.keycloak.representations.idm.UserRepresentation>",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The list of users for bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERS" },
+ "CamelKeycloakUserIds": { "index": 44, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of user IDs for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_IDS" },
+ "CamelKeycloakUsernames": { "index": 45, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of usernames for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAMES" },
+ "CamelKeycloakRoleNames": { "index": 46, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"java.util.List<String>", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The list of role names for
bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ROLE_NAMES" },
+ "CamelKeycloakContinueOnError": { "index": 47, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Continue on error during bulk
operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#CONTINUE_ON_ERROR" },
+ "CamelKeycloakBatchSize": { "index": 48, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "Integer",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Batch size for bulk operations", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#BATCH_SIZE" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Logical name" },
@@ -123,7 +129,7 @@
"ipAddress": { "index": 16, "kind": "parameter", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "parameter", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "parameter", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
+ "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
"operationTypes": { "index": 20, "kind": "parameter", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "parameter", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "parameter", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
diff --git a/components/camel-keycloak/src/main/docs/keycloak-component.adoc
b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
index 90aedb295caa..07fb79288fd4 100644
--- a/components/camel-keycloak/src/main/docs/keycloak-component.adoc
+++ b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
@@ -1396,6 +1396,562 @@
template.sendBodyAndHeaders("keycloak:admin?operation=createResourcePermission&p
template.sendBodyAndHeaders("keycloak:admin?operation=listResourcePermissions",
null, permHeaders);
----
+=== Bulk Operations
+
+Bulk operations allow you to perform multiple operations in a single request,
improving efficiency and reducing network overhead. These operations are
particularly useful for provisioning, migrations, and large-scale
administrative tasks.
+
+==== Bulk Create Users
+
+Create multiple users in a single operation with detailed results for each
user.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Create multiple users at once
+List<UserRepresentation> users = new ArrayList<>();
+
+for (int i = 1; i <= 100; i++) {
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername("user" + i);
+ user.setEmail("user" + i + "@company.com");
+ user.setFirstName("User");
+ user.setLastName("" + i);
+ user.setEnabled(true);
+ users.add(user);
+}
+
+Map<String, Object> headers = new HashMap<>();
+headers.put(KeycloakConstants.REALM_NAME, "my-realm");
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true); // Continue even if
some users fail
+
+Map<String, Object> result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkCreateUsers", users, headers, Map.class);
+
+// Result contains summary and details
+System.out.println("Total: " + result.get("total"));
+System.out.println("Success: " + result.get("success"));
+System.out.println("Failed: " + result.get("failed"));
+
+// Detailed results for each user
+List<Map<String, Object>> results = (List<Map<String, Object>>)
result.get("results");
+for (Map<String, Object> userResult : results) {
+ System.out.println("User: " + userResult.get("username") + " - " +
userResult.get("status"));
+}
+----
+
+YAML::
++
+[source,yaml]
+----
+# Bulk create users route
+- route:
+ from:
+ uri: direct:bulk-create-users
+ steps:
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkCreateUsers
+ - log: "Created ${body[success]} out of ${body[total]} users"
+----
+====
+
+==== Bulk Delete Users
+
+Delete multiple users by user IDs or usernames in a single operation.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Delete by user IDs
+List<String> userIds = List.of("user-id-1", "user-id-2", "user-id-3");
+
+Map<String, Object> headers = new HashMap<>();
+headers.put(KeycloakConstants.REALM_NAME, "my-realm");
+headers.put(KeycloakConstants.USER_IDS, userIds);
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
+
+Map<String, Object> result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkDeleteUsers", null, headers, Map.class);
+
+// Or delete by usernames
+List<String> usernames = List.of("user1", "user2", "user3");
+headers.put(KeycloakConstants.USERNAMES, usernames);
+headers.remove(KeycloakConstants.USER_IDS);
+
+result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkDeleteUsers", null, headers, Map.class);
+
+System.out.println("Deleted " + result.get("success") + " users");
+----
+
+YAML::
++
+[source,yaml]
+----
+# Bulk delete users by usernames
+- route:
+ from:
+ uri: direct:bulk-delete-users
+ steps:
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakUsernames
+ simple: "${body}" # List of usernames
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkDeleteUsers
+ - log: "Deleted ${body[success]} out of ${body[total]} users"
+----
+====
+
+==== Bulk Assign Roles to User
+
+Assign multiple roles to a single user in one operation.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Assign multiple roles to one user
+List<String> roleNames = List.of("admin", "manager", "developer");
+
+Map<String, Object> headers = new HashMap<>();
+headers.put(KeycloakConstants.REALM_NAME, "my-realm");
+headers.put(KeycloakConstants.USER_ID, "user-id-123");
+headers.put(KeycloakConstants.ROLE_NAMES, roleNames);
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
+
+Map<String, Object> result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkAssignRolesToUser", null, headers,
Map.class);
+
+System.out.println("Assigned " + result.get("assigned") + " roles to user");
+
+// Detailed results
+List<Map<String, Object>> results = (List<Map<String, Object>>)
result.get("results");
+for (Map<String, Object> roleResult : results) {
+ System.out.println("Role: " + roleResult.get("roleName") + " - " +
roleResult.get("status"));
+}
+----
+
+YAML::
++
+[source,yaml]
+----
+# Assign multiple roles to a user
+- route:
+ from:
+ uri: direct:assign-roles-to-user
+ steps:
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakUserId
+ simple: "${body[userId]}"
+ - setHeader:
+ name: CamelKeycloakRoleNames
+ simple: "${body[roles]}" # List of role names
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkAssignRolesToUser
+ - log: "Assigned ${body[assigned]} roles to user"
+----
+====
+
+==== Bulk Assign Role to Users
+
+Assign a single role to multiple users in one operation.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Assign one role to multiple users
+List<String> userIds = List.of("user-id-1", "user-id-2", "user-id-3");
+
+Map<String, Object> headers = new HashMap<>();
+headers.put(KeycloakConstants.REALM_NAME, "my-realm");
+headers.put(KeycloakConstants.ROLE_NAME, "developer");
+headers.put(KeycloakConstants.USER_IDS, userIds);
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
+
+Map<String, Object> result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkAssignRoleToUsers", null, headers,
Map.class);
+
+System.out.println("Assigned role to " + result.get("success") + " users");
+
+// Or use usernames instead of IDs
+List<String> usernames = List.of("user1", "user2", "user3");
+headers.put(KeycloakConstants.USERNAMES, usernames);
+headers.remove(KeycloakConstants.USER_IDS);
+
+result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkAssignRoleToUsers", null, headers,
Map.class);
+----
+
+YAML::
++
+[source,yaml]
+----
+# Assign a role to multiple users
+- route:
+ from:
+ uri: direct:assign-role-to-users
+ steps:
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakRoleName
+ constant: "developer"
+ - setHeader:
+ name: CamelKeycloakUsernames
+ simple: "${body}" # List of usernames
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkAssignRoleToUsers
+ - log: "Assigned role to ${body[success]} out of ${body[total]} users"
+----
+====
+
+==== Bulk Update Users
+
+Update multiple users in a single operation.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Update multiple users
+List<UserRepresentation> users = new ArrayList<>();
+
+// Fetch users and update them
+List<UserRepresentation> existingUsers = template.requestBodyAndHeader(
+ "keycloak:admin?operation=listUsers", null,
+ KeycloakConstants.REALM_NAME, "my-realm", List.class);
+
+for (UserRepresentation user : existingUsers) {
+ // Update user properties
+ user.setFirstName("Updated");
+ user.setEnabled(true);
+ users.add(user);
+}
+
+Map<String, Object> headers = new HashMap<>();
+headers.put(KeycloakConstants.REALM_NAME, "my-realm");
+headers.put(KeycloakConstants.USERS, users);
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
+
+Map<String, Object> result = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=bulkUpdateUsers", null, headers, Map.class);
+
+System.out.println("Updated " + result.get("success") + " users");
+
+// Detailed results
+List<Map<String, Object>> results = (List<Map<String, Object>>)
result.get("results");
+for (Map<String, Object> userResult : results) {
+ System.out.println("User: " + userResult.get("username") + " - " +
userResult.get("status"));
+}
+----
+
+YAML::
++
+[source,yaml]
+----
+# Bulk update users
+- route:
+ from:
+ uri: direct:bulk-update-users
+ steps:
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkUpdateUsers
+ - log: "Updated ${body[success]} out of ${body[total]} users"
+----
+====
+
+==== Bulk Operations Response Format
+
+All bulk operations return a consistent response format with the following
structure:
+
+[source,json]
+----
+{
+ "total": 10, // Total number of items processed
+ "success": 8, // Number of successful operations
+ "failed": 2, // Number of failed operations
+ "results": [ // Detailed results for each item
+ {
+ "username": "user1",
+ "status": "success",
+ "statusCode": 201
+ },
+ {
+ "username": "user2",
+ "status": "failed",
+ "error": "User already exists"
+ }
+ ]
+}
+----
+
+==== Error Handling in Bulk Operations
+
+Bulk operations support the `CamelKeycloakContinueOnError` header to control
error handling behavior:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Continue processing even if some operations fail
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, true);
+
+// Stop on first error (default behavior)
+headers.put(KeycloakConstants.CONTINUE_ON_ERROR, false);
+----
+
+YAML::
++
+[source,yaml]
+----
+# Continue on error
+- setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+
+# Stop on first error
+- setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: false
+----
+====
+
+==== Complete Bulk Operations Example
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+public class BulkUserProvisioningRoute extends RouteBuilder {
+ @Override
+ public void configure() throws Exception {
+ // Provision users from CSV file
+ from("file:data/incoming?noop=true")
+ .routeId("bulk-user-provisioning")
+ .log("Processing user provisioning file: ${header.CamelFileName}")
+
+ // Parse CSV to user objects
+ .unmarshal().csv()
+ .process(exchange -> {
+ List<List<String>> csvData =
exchange.getIn().getBody(List.class);
+ List<UserRepresentation> users = new ArrayList<>();
+
+ // Skip header row
+ for (int i = 1; i < csvData.size(); i++) {
+ List<String> row = csvData.get(i);
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(row.get(0));
+ user.setEmail(row.get(1));
+ user.setFirstName(row.get(2));
+ user.setLastName(row.get(3));
+ user.setEnabled(true);
+ users.add(user);
+ }
+
+ exchange.getIn().setBody(users);
+ })
+
+ // Bulk create users
+ .setHeader(KeycloakConstants.REALM_NAME, constant("my-realm"))
+ .setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
+ .to("keycloak:admin?operation=bulkCreateUsers")
+
+ // Log results
+ .process(exchange -> {
+ Map<String, Object> result =
exchange.getIn().getBody(Map.class);
+ log.info("User provisioning completed: {} succeeded, {} failed
out of {}",
+ result.get("success"), result.get("failed"),
result.get("total"));
+ })
+
+ // Assign default role to all successfully created users
+ .filter(simple("${body[success]} > 0"))
+ .process(exchange -> {
+ // Extract user IDs from results
+ Map<String, Object> createResult =
exchange.getIn().getBody(Map.class);
+ // Get created users and assign role...
+ });
+
+ // Bulk role assignment from JSON
+ from("rest:post:/users/assign-roles")
+ .routeId("bulk-assign-roles")
+ .log("Bulk assigning roles to users")
+ .unmarshal().json()
+ .setHeader(KeycloakConstants.REALM_NAME, simple("${body[realm]}"))
+ .setHeader(KeycloakConstants.USER_ID, simple("${body[userId]}"))
+ .setBody(simple("${body[roles]}"))
+ .setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
+ .to("keycloak:admin?operation=bulkAssignRolesToUser")
+ .marshal().json()
+ .setHeader("Content-Type", constant("application/json"));
+
+ // Cleanup inactive users
+ from("timer:cleanup?period=86400000") // Daily
+ .routeId("cleanup-inactive-users")
+ .log("Starting inactive user cleanup")
+ .setHeader(KeycloakConstants.REALM_NAME, constant("my-realm"))
+ .to("keycloak:admin?operation=listUsers")
+ .process(exchange -> {
+ List<UserRepresentation> allUsers =
exchange.getIn().getBody(List.class);
+ List<String> inactiveUserIds = new ArrayList<>();
+
+ // Identify inactive users (custom logic)
+ for (UserRepresentation user : allUsers) {
+ // Check last login, attributes, etc.
+ if (isInactive(user)) {
+ inactiveUserIds.add(user.getId());
+ }
+ }
+
+ exchange.getIn().setBody(inactiveUserIds);
+ })
+ .filter(simple("${body.size} > 0"))
+ .setHeader(KeycloakConstants.CONTINUE_ON_ERROR, constant(true))
+ .to("keycloak:admin?operation=bulkDeleteUsers")
+ .log("Deleted ${body[success]} inactive users");
+ }
+
+ private boolean isInactive(UserRepresentation user) {
+ // Custom logic to determine if user is inactive
+ return false;
+ }
+}
+----
+
+YAML::
++
+[source,yaml]
+----
+# Bulk user provisioning from CSV
+- route:
+ id: bulk-user-provisioning
+ from:
+ uri: file:data/incoming?noop=true
+ steps:
+ - log: "Processing user provisioning file: ${header.CamelFileName}"
+ - unmarshal:
+ csv: {}
+ - process:
+ ref: csvToUsersProcessor
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkCreateUsers
+ - log: "Provisioning completed: ${body[success]} succeeded,
${body[failed]} failed"
+
+# Bulk role assignment API
+- rest:
+ post:
+ - uri: /users/assign-roles
+ to: direct:bulk-assign-roles
+
+- route:
+ id: bulk-assign-roles
+ from:
+ uri: direct:bulk-assign-roles
+ steps:
+ - unmarshal:
+ json: {}
+ - setHeader:
+ name: CamelKeycloakRealmName
+ simple: "${body[realm]}"
+ - setHeader:
+ name: CamelKeycloakUserId
+ simple: "${body[userId]}"
+ - setBody:
+ simple: "${body[roles]}"
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkAssignRolesToUser
+ - marshal:
+ json: {}
+
+# Daily cleanup of inactive users
+- route:
+ id: cleanup-inactive-users
+ from:
+ uri: timer:cleanup?period=86400000
+ steps:
+ - log: "Starting inactive user cleanup"
+ - setHeader:
+ name: CamelKeycloakRealmName
+ constant: "my-realm"
+ - to:
+ uri: keycloak:admin?operation=listUsers
+ - process:
+ ref: identifyInactiveUsersProcessor
+ - filter:
+ simple: "${body.size} > 0"
+ steps:
+ - setHeader:
+ name: CamelKeycloakContinueOnError
+ constant: true
+ - to:
+ uri: keycloak:admin?operation=bulkDeleteUsers
+ - log: "Deleted ${body[success]} inactive users"
+----
+====
+
+==== Best Practices for Bulk Operations
+
+1. **Use Continue on Error**: Always set `continueOnError=true` for bulk
operations to get complete feedback on all items
+2. **Monitor Results**: Check the results map to identify and handle failures
appropriately
+3. **Batch Size**: For very large datasets, consider splitting into smaller
batches (e.g., 100-500 users per batch)
+4. **Error Handling**: Implement proper error handling and retry logic for
failed items
+5. **Logging**: Log detailed results for audit and troubleshooting purposes
+6. **Testing**: Test bulk operations with small datasets first before running
in production
+7. **Performance**: Bulk operations are more efficient than individual
operations but still require appropriate timeout settings
+8. **Transactions**: Note that Keycloak operations are not transactional -
some items may succeed while others fail
+
=== Complete Producer Example
[tabs]
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
index 8ec28b169dbb..dfd4a2f9c72b 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
@@ -154,6 +154,26 @@ public final class KeycloakConstants {
@Metadata(description = "The lifespan in seconds", javaType = "Integer")
public static final String LIFESPAN = "CamelKeycloakLifespan";
+ // Bulk operations constants
+ @Metadata(description = "The list of users for bulk operations",
+ javaType =
"java.util.List<org.keycloak.representations.idm.UserRepresentation>")
+ public static final String USERS = "CamelKeycloakUsers";
+
+ @Metadata(description = "The list of user IDs for bulk operations",
javaType = "java.util.List<String>")
+ public static final String USER_IDS = "CamelKeycloakUserIds";
+
+ @Metadata(description = "The list of usernames for bulk operations",
javaType = "java.util.List<String>")
+ public static final String USERNAMES = "CamelKeycloakUsernames";
+
+ @Metadata(description = "The list of role names for bulk operations",
javaType = "java.util.List<String>")
+ public static final String ROLE_NAMES = "CamelKeycloakRoleNames";
+
+ @Metadata(description = "Continue on error during bulk operations",
javaType = "Boolean")
+ public static final String CONTINUE_ON_ERROR =
"CamelKeycloakContinueOnError";
+
+ @Metadata(description = "Batch size for bulk operations", javaType =
"Integer")
+ public static final String BATCH_SIZE = "CamelKeycloakBatchSize";
+
private KeycloakConstants() {
// Utility class
}
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
index 0499d978b6cf..5743a5a00718 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
@@ -108,5 +108,11 @@ public enum KeycloakOperations {
executeActionsEmail,
// Client Secret Management
getClientSecret,
- regenerateClientSecret
+ regenerateClientSecret,
+ // Bulk operations
+ bulkCreateUsers,
+ bulkDeleteUsers,
+ bulkAssignRolesToUser,
+ bulkAssignRoleToUsers,
+ bulkUpdateUsers
}
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
index bd60adb2d749..b5635daa81d3 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
@@ -29,6 +29,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.Message;
import org.apache.camel.support.DefaultProducer;
+import org.apache.camel.util.CastUtils;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.URISupport;
import org.keycloak.admin.client.Keycloak;
@@ -315,6 +316,21 @@ public class KeycloakProducer extends DefaultProducer {
case regenerateClientSecret:
regenerateClientSecret(getEndpoint().getKeycloakClient(),
exchange);
break;
+ case bulkCreateUsers:
+ bulkCreateUsers(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case bulkDeleteUsers:
+ bulkDeleteUsers(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case bulkAssignRolesToUser:
+ bulkAssignRolesToUser(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case bulkAssignRoleToUsers:
+ bulkAssignRoleToUsers(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case bulkUpdateUsers:
+ bulkUpdateUsers(getEndpoint().getKeycloakClient(), exchange);
+ break;
default:
throw new IllegalArgumentException("Unsupported operation: " +
operation);
}
@@ -1993,6 +2009,330 @@ public class KeycloakProducer extends DefaultProducer {
message.setBody(newSecret);
}
+ // Bulk operations
+ private void bulkCreateUsers(Keycloak keycloakClient, Exchange exchange)
throws InvalidPayloadException {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ List<UserRepresentation> users =
exchange.getIn().getHeader(KeycloakConstants.USERS, List.class);
+ if (users == null || users.isEmpty()) {
+ // Try to get from body
+ Object payload = exchange.getIn().getMandatoryBody();
+ if (payload instanceof List) {
+ users = CastUtils.cast((List<?>) payload);
+ } else {
+ throw new IllegalArgumentException("Users list must be
provided via header or body");
+ }
+ }
+
+ boolean continueOnError
+ =
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
+ List<Map<String, Object>> results = new ArrayList<>();
+ int successCount = 0;
+ int failureCount = 0;
+
+ for (UserRepresentation user : users) {
+ Map<String, Object> result = new HashMap<>();
+ result.put("username", user.getUsername());
+ try {
+ Response response =
keycloakClient.realm(realmName).users().create(user);
+ result.put("status", "success");
+ result.put("statusCode", response.getStatus());
+ successCount++;
+ response.close();
+ } catch (Exception e) {
+ result.put("status", "failed");
+ result.put("error", e.getMessage());
+ failureCount++;
+ if (!continueOnError) {
+ throw new RuntimeException("Failed to create user: " +
user.getUsername(), e);
+ }
+ }
+ results.add(result);
+ }
+
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("total", users.size());
+ summary.put("success", successCount);
+ summary.put("failed", failureCount);
+ summary.put("results", results);
+
+ Message message = getMessageForResponse(exchange);
+ message.setBody(summary);
+ }
+
+ private void bulkDeleteUsers(Keycloak keycloakClient, Exchange exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ List<String> userIds =
exchange.getIn().getHeader(KeycloakConstants.USER_IDS, List.class);
+ if (userIds == null || userIds.isEmpty()) {
+ // Try usernames
+ List<String> usernames =
exchange.getIn().getHeader(KeycloakConstants.USERNAMES, List.class);
+ if (usernames == null || usernames.isEmpty()) {
+ // Try to get from body
+ Object payload = exchange.getIn().getBody();
+ if (payload instanceof List) {
+ userIds = CastUtils.cast((List<?>) payload);
+ } else {
+ throw new IllegalArgumentException("User IDs or usernames
must be provided via header or body");
+ }
+ } else {
+ // Convert usernames to user IDs
+ userIds = new ArrayList<>();
+ for (String username : usernames) {
+ List<UserRepresentation> foundUsers
+ =
keycloakClient.realm(realmName).users().searchByUsername(username, true);
+ if (!foundUsers.isEmpty()) {
+ userIds.add(foundUsers.get(0).getId());
+ }
+ }
+ }
+ }
+
+ boolean continueOnError
+ =
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
+ List<Map<String, Object>> results = new ArrayList<>();
+ int successCount = 0;
+ int failureCount = 0;
+
+ for (String userId : userIds) {
+ Map<String, Object> result = new HashMap<>();
+ result.put("userId", userId);
+ try {
+ Response response =
keycloakClient.realm(realmName).users().delete(userId);
+ result.put("status", "success");
+ result.put("statusCode", response.getStatus());
+ successCount++;
+ response.close();
+ } catch (Exception e) {
+ result.put("status", "failed");
+ result.put("error", e.getMessage());
+ failureCount++;
+ if (!continueOnError) {
+ throw new RuntimeException("Failed to delete user: " +
userId, e);
+ }
+ }
+ results.add(result);
+ }
+
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("total", userIds.size());
+ summary.put("success", successCount);
+ summary.put("failed", failureCount);
+ summary.put("results", results);
+
+ Message message = getMessageForResponse(exchange);
+ message.setBody(summary);
+ }
+
+ private void bulkAssignRolesToUser(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String userId = exchange.getIn().getHeader(KeycloakConstants.USER_ID,
String.class);
+ if (ObjectHelper.isEmpty(userId)) {
+ throw new IllegalArgumentException(MISSING_USER_ID);
+ }
+
+ List<String> roleNames =
exchange.getIn().getHeader(KeycloakConstants.ROLE_NAMES, List.class);
+ if (roleNames == null || roleNames.isEmpty()) {
+ // Try to get from body
+ Object payload = exchange.getIn().getBody();
+ if (payload instanceof List) {
+ roleNames = CastUtils.cast((List<?>) payload);
+ } else {
+ throw new IllegalArgumentException("Role names must be
provided via header or body");
+ }
+ }
+
+ boolean continueOnError
+ =
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
+ List<Map<String, Object>> results = new ArrayList<>();
+ List<RoleRepresentation> rolesToAssign = new ArrayList<>();
+ int successCount = 0;
+ int failureCount = 0;
+
+ // Collect all roles first
+ for (String roleName : roleNames) {
+ Map<String, Object> result = new HashMap<>();
+ result.put("roleName", roleName);
+ try {
+ RoleRepresentation role =
keycloakClient.realm(realmName).roles().get(roleName).toRepresentation();
+ rolesToAssign.add(role);
+ result.put("status", "found");
+ successCount++;
+ } catch (Exception e) {
+ result.put("status", "not_found");
+ result.put("error", e.getMessage());
+ failureCount++;
+ if (!continueOnError) {
+ throw new RuntimeException("Failed to find role: " +
roleName, e);
+ }
+ }
+ results.add(result);
+ }
+
+ // Assign all roles at once if any were found
+ if (!rolesToAssign.isEmpty()) {
+ try {
+
keycloakClient.realm(realmName).users().get(userId).roles().realmLevel().add(rolesToAssign);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to assign roles to user: "
+ userId, e);
+ }
+ }
+
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("total", roleNames.size());
+ summary.put("success", successCount);
+ summary.put("failed", failureCount);
+ summary.put("assigned", rolesToAssign.size());
+ summary.put("results", results);
+
+ Message message = getMessageForResponse(exchange);
+ message.setBody(summary);
+ }
+
+ private void bulkAssignRoleToUsers(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String roleName =
exchange.getIn().getHeader(KeycloakConstants.ROLE_NAME, String.class);
+ if (ObjectHelper.isEmpty(roleName)) {
+ throw new IllegalArgumentException(MISSING_ROLE_NAME);
+ }
+
+ // Get the role first
+ RoleRepresentation role;
+ try {
+ role =
keycloakClient.realm(realmName).roles().get(roleName).toRepresentation();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to find role: " + roleName, e);
+ }
+
+ List<String> userIds =
exchange.getIn().getHeader(KeycloakConstants.USER_IDS, List.class);
+ if (userIds == null || userIds.isEmpty()) {
+ // Try usernames
+ List<String> usernames =
exchange.getIn().getHeader(KeycloakConstants.USERNAMES, List.class);
+ if (usernames == null || usernames.isEmpty()) {
+ // Try to get from body
+ Object payload = exchange.getIn().getBody();
+ if (payload instanceof List) {
+ userIds = CastUtils.cast((List<?>) payload);
+ } else {
+ throw new IllegalArgumentException("User IDs or usernames
must be provided via header or body");
+ }
+ } else {
+ // Convert usernames to user IDs
+ userIds = new ArrayList<>();
+ for (String username : usernames) {
+ List<UserRepresentation> foundUsers
+ =
keycloakClient.realm(realmName).users().searchByUsername(username, true);
+ if (!foundUsers.isEmpty()) {
+ userIds.add(foundUsers.get(0).getId());
+ }
+ }
+ }
+ }
+
+ boolean continueOnError
+ =
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
+ List<Map<String, Object>> results = new ArrayList<>();
+ int successCount = 0;
+ int failureCount = 0;
+
+ for (String userId : userIds) {
+ Map<String, Object> result = new HashMap<>();
+ result.put("userId", userId);
+ try {
+
keycloakClient.realm(realmName).users().get(userId).roles().realmLevel().add(List.of(role));
+ result.put("status", "success");
+ successCount++;
+ } catch (Exception e) {
+ result.put("status", "failed");
+ result.put("error", e.getMessage());
+ failureCount++;
+ if (!continueOnError) {
+ throw new RuntimeException("Failed to assign role to user:
" + userId, e);
+ }
+ }
+ results.add(result);
+ }
+
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("total", userIds.size());
+ summary.put("success", successCount);
+ summary.put("failed", failureCount);
+ summary.put("roleName", roleName);
+ summary.put("results", results);
+
+ Message message = getMessageForResponse(exchange);
+ message.setBody(summary);
+ }
+
+ private void bulkUpdateUsers(Keycloak keycloakClient, Exchange exchange)
throws InvalidPayloadException {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ List<UserRepresentation> users =
exchange.getIn().getHeader(KeycloakConstants.USERS, List.class);
+ if (users == null || users.isEmpty()) {
+ // Try to get from body
+ Object payload = exchange.getIn().getMandatoryBody();
+ if (payload instanceof List) {
+ users = CastUtils.cast((List<?>) payload);
+ } else {
+ throw new IllegalArgumentException("Users list must be
provided via header or body");
+ }
+ }
+
+ boolean continueOnError
+ =
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
+ List<Map<String, Object>> results = new ArrayList<>();
+ int successCount = 0;
+ int failureCount = 0;
+
+ for (UserRepresentation user : users) {
+ Map<String, Object> result = new HashMap<>();
+ result.put("userId", user.getId());
+ result.put("username", user.getUsername());
+ try {
+ if (ObjectHelper.isEmpty(user.getId())) {
+ throw new IllegalArgumentException("User ID is required
for update operation");
+ }
+
keycloakClient.realm(realmName).users().get(user.getId()).update(user);
+ result.put("status", "success");
+ successCount++;
+ } catch (Exception e) {
+ result.put("status", "failed");
+ result.put("error", e.getMessage());
+ failureCount++;
+ if (!continueOnError) {
+ throw new RuntimeException("Failed to update user: " +
user.getId(), e);
+ }
+ }
+ results.add(result);
+ }
+
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("total", users.size());
+ summary.put("success", successCount);
+ summary.put("failed", failureCount);
+ summary.put("results", results);
+
+ Message message = getMessageForResponse(exchange);
+ message.setBody(summary);
+ }
+
public static Message getMessageForResponse(final Exchange exchange) {
return exchange.getMessage();
}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
index 1c7cda271641..d49f150ef56a 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
@@ -3040,6 +3040,80 @@ public interface KeycloakEndpointBuilderFactory {
public String keycloakLifespan() {
return "CamelKeycloakLifespan";
}
+ /**
+ * The list of users for bulk operations.
+ *
+ * The option is a: {@code
+ * java.util.List<org.keycloak.representations.idm.UserRepresentation>}
+ * type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakUsers}.
+ */
+ public String keycloakUsers() {
+ return "CamelKeycloakUsers";
+ }
+ /**
+ * The list of user IDs for bulk operations.
+ *
+ * The option is a: {@code java.util.List<String>} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakUserIds}.
+ */
+ public String keycloakUserIds() {
+ return "CamelKeycloakUserIds";
+ }
+ /**
+ * The list of usernames for bulk operations.
+ *
+ * The option is a: {@code java.util.List<String>} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakUsernames}.
+ */
+ public String keycloakUsernames() {
+ return "CamelKeycloakUsernames";
+ }
+ /**
+ * The list of role names for bulk operations.
+ *
+ * The option is a: {@code java.util.List<String>} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakRoleNames}.
+ */
+ public String keycloakRoleNames() {
+ return "CamelKeycloakRoleNames";
+ }
+ /**
+ * Continue on error during bulk operations.
+ *
+ * The option is a: {@code Boolean} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakContinueOnError}.
+ */
+ public String keycloakContinueOnError() {
+ return "CamelKeycloakContinueOnError";
+ }
+ /**
+ * Batch size for bulk operations.
+ *
+ * The option is a: {@code Integer} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakBatchSize}.
+ */
+ public String keycloakBatchSize() {
+ return "CamelKeycloakBatchSize";
+ }
}
static KeycloakEndpointBuilder endpointBuilder(String componentName,
String path) {
class KeycloakEndpointBuilderImpl extends AbstractEndpointBuilder
implements KeycloakEndpointBuilder, AdvancedKeycloakEndpointBuilder {