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 d2a45d6c6e27 CAMEL-26196: camel-keycloak - convert a pojoRequest body,
fail a create or delete with a non-2xx status, and close its Response (#29024)
d2a45d6c6e27 is described below
commit d2a45d6c6e27561ffdc45a2f93ec1ef2dcbc322d
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 08:40:45 2026 +0100
CAMEL-26196: camel-keycloak - convert a pojoRequest body, fail a create or
delete with a non-2xx status, and close its Response (#29024)
* CAMEL-26196: camel-keycloak - convert a pojoRequest body, fail a crea…
…te or delete with a non-2xx status, and close its Response
With pojoRequest=true, a body that was not the Keycloak representation of
the
operation did nothing: no call, no error, and the body was kept. The body is
now converted (type converter, or a Map or JSON text), and fails with an
InvalidPayloadException when it does not convert.
The operations that give the JAX-RS Response of Keycloak (createUser,
deleteUser, createGroup, createOrganization, ...) completed on a 409 or 404,
and never closed the Response. A status other than 2xx now fails with a
WebApplicationException, and the Response is closed (its status and Location
can still be read). deleteOrganization, removeOrganizationMember and
unlinkOrganizationIdentityProvider ignored their Response the same way.
bulkCreateUsers and bulkUpdateUsers cast a List of Map to UserRepresentation
and failed with a raw ClassCastException; each element is now converted.
bulkCreateUsers and bulkDeleteUsers counted a 409 or 404 as a success.
* Merge remote-tracking branch 'origin/main' into CAMEL-26196-keycloak-…
…pojo-response
# Conflicts:
# docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
* Merge remote-tracking branch 'origin/main' into CAMEL-26196-keycloak-…
…pojo-response
# Conflicts:
# docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../apache/camel/catalog/components/keycloak.json | 4 +-
.../camel/catalog/docs/keycloak-producer.adoc | 26 +-
.../apache/camel/component/keycloak/keycloak.json | 4 +-
.../src/main/docs/keycloak-producer.adoc | 26 +-
.../camel/component/keycloak/KeycloakEndpoint.java | 15 +-
.../camel/component/keycloak/KeycloakProducer.java | 417 ++++++++++++---------
...KeycloakProducerPojoRequestAndResponseTest.java | 344 +++++++++++++++++
.../component/keycloak/KeycloakProducerTest.java | 10 +
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 15 +
9 files changed, 672 insertions(+), 189 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 3c9bd12bf19d..d6844636797c 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
@@ -129,8 +129,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.EventRepresentation", "byOption": [ {
"option": "eventType", "value": "admin-events", "carriers": [ "pojo" ],
"javaType": "org.keycloak.representations.idm.AdminEventRepresentation" } ],
"description": "One message per event: the EventRepresentation of a user event
such as a login, or the AdminEventRepresentation with eventType=admin-events.
The CamelKeycloakEventType header says event or admin-event" },
- "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"pojoRequest", "value": "true", "carriers": [ "pojo" ] } ], "description":
"Mostly not read: the operations take their input from headers, such as
CamelKeycloakRealmName and CamelKeycloakUserId. With pojoRequest=true the
create and update operations take the Keycloak representation, such as a
UserRepresentation for createUser; a body of another type does nothing and
keeps the body. The bulk operations take a List when t [...]
- "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "createRealm", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "deleteRealm",
"carriers": [ "text" ], "javaType": "java.lang.String" }, { "option":
"operation", "value": "getRealm", "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.RealmRepresentation" }, { "option":
"operation", "value": "updateRealm", "carriers": [ "text" ], "ja [...]
+ "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"pojoRequest", "value": "true", "carriers": [ "pojo" ] } ], "description":
"Mostly not read: the operations take their input from headers, such as
CamelKeycloakRealmName and CamelKeycloakUserId. With pojoRequest=true the
create and update operations take the Keycloak representation, such as a
UserRepresentation for createUser, or a Map or JSON text of it; a body that
does not convert fails. The bulk operations take a Li [...]
+ "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "createRealm", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "deleteRealm",
"carriers": [ "text" ], "javaType": "java.lang.String" }, { "option":
"operation", "value": "getRealm", "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.RealmRepresentation" }, { "option":
"operation", "value": "updateRealm", "carriers": [ "text" ], "ja [...]
},
"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" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc
index 320d98815f57..980b9e7bf553 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc
@@ -132,6 +132,29 @@ All producer operations follow the same pattern: set the
operation name in the U
template.sendBodyAndHeaders("keycloak:admin?operation=<operationName>", null,
headers);
----
+=== Representation bodies
+
+With `pojoRequest=true`, the create and update operations take the Keycloak
representation as the body, such as a
+`UserRepresentation` for `createUser` and `updateUser`. A body of another type
is converted: a `Map` (such as
+unmarshalled JSON) or JSON text is mapped to the representation. A body that
does not convert fails the exchange
+with an `InvalidPayloadException`.
+
+The elements of the `List` of `bulkCreateUsers` and `bulkUpdateUsers` are
converted the same way; an element that
+does not convert fails the exchange before any user is created or updated.
+
+=== Results of create and delete operations
+
+`createUser`, `deleteUser`, `createGroup`, `createClient`,
`createClientScope`, `createIdentityProvider`,
+`createResource`, `createResourcePolicy`, `createResourcePermission`,
`createOrganization`, `updateOrganization`,
+`addOrganizationMember` and `linkOrganizationIdentityProvider` give the JAX-RS
`Response` of Keycloak as the body.
+The Response is closed, but its status and headers can still be read, such as
the `Location` header with the URL
+(ending with the id) of the created resource.
+
+When Keycloak answers with a status other than 2xx, such as 409 (already
exists) or 404 (not found), the exchange
+fails with a `jakarta.ws.rs.WebApplicationException`, whose `getResponse()`
gives the status. The same goes for
+`deleteOrganization`, `removeOrganizationMember` and
`unlinkOrganizationIdentityProvider`. The bulk operations
+record such a status as a failed item.
+
== User Operations
[tabs]
@@ -672,7 +695,8 @@ All bulk operations return a consistent response format:
{
"username": "user2",
"status": "failed",
- "error": "User already exists"
+ "statusCode": 409,
+ "error": "Keycloak createUser failed with status 409:
{\"errorMessage\":\"User exists with same username\"}"
}
]
}
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 3c9bd12bf19d..d6844636797c 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
@@ -129,8 +129,8 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.EventRepresentation", "byOption": [ {
"option": "eventType", "value": "admin-events", "carriers": [ "pojo" ],
"javaType": "org.keycloak.representations.idm.AdminEventRepresentation" } ],
"description": "One message per event: the EventRepresentation of a user event
such as a login, or the AdminEventRepresentation with eventType=admin-events.
The CamelKeycloakEventType header says event or admin-event" },
- "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"pojoRequest", "value": "true", "carriers": [ "pojo" ] } ], "description":
"Mostly not read: the operations take their input from headers, such as
CamelKeycloakRealmName and CamelKeycloakUserId. With pojoRequest=true the
create and update operations take the Keycloak representation, such as a
UserRepresentation for createUser; a body of another type does nothing and
keeps the body. The bulk operations take a List when t [...]
- "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "createRealm", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "deleteRealm",
"carriers": [ "text" ], "javaType": "java.lang.String" }, { "option":
"operation", "value": "getRealm", "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.RealmRepresentation" }, { "option":
"operation", "value": "updateRealm", "carriers": [ "text" ], "ja [...]
+ "producerIn": { "carriers": [ "any" ], "byOption": [ { "option":
"pojoRequest", "value": "true", "carriers": [ "pojo" ] } ], "description":
"Mostly not read: the operations take their input from headers, such as
CamelKeycloakRealmName and CamelKeycloakUserId. With pojoRequest=true the
create and update operations take the Keycloak representation, such as a
UserRepresentation for createUser, or a Map or JSON text of it; a body that
does not convert fails. The bulk operations take a Li [...]
+ "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "createRealm", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "deleteRealm",
"carriers": [ "text" ], "javaType": "java.lang.String" }, { "option":
"operation", "value": "getRealm", "carriers": [ "pojo" ], "javaType":
"org.keycloak.representations.idm.RealmRepresentation" }, { "option":
"operation", "value": "updateRealm", "carriers": [ "text" ], "ja [...]
},
"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" },
diff --git a/components/camel-keycloak/src/main/docs/keycloak-producer.adoc
b/components/camel-keycloak/src/main/docs/keycloak-producer.adoc
index 320d98815f57..980b9e7bf553 100644
--- a/components/camel-keycloak/src/main/docs/keycloak-producer.adoc
+++ b/components/camel-keycloak/src/main/docs/keycloak-producer.adoc
@@ -132,6 +132,29 @@ All producer operations follow the same pattern: set the
operation name in the U
template.sendBodyAndHeaders("keycloak:admin?operation=<operationName>", null,
headers);
----
+=== Representation bodies
+
+With `pojoRequest=true`, the create and update operations take the Keycloak
representation as the body, such as a
+`UserRepresentation` for `createUser` and `updateUser`. A body of another type
is converted: a `Map` (such as
+unmarshalled JSON) or JSON text is mapped to the representation. A body that
does not convert fails the exchange
+with an `InvalidPayloadException`.
+
+The elements of the `List` of `bulkCreateUsers` and `bulkUpdateUsers` are
converted the same way; an element that
+does not convert fails the exchange before any user is created or updated.
+
+=== Results of create and delete operations
+
+`createUser`, `deleteUser`, `createGroup`, `createClient`,
`createClientScope`, `createIdentityProvider`,
+`createResource`, `createResourcePolicy`, `createResourcePermission`,
`createOrganization`, `updateOrganization`,
+`addOrganizationMember` and `linkOrganizationIdentityProvider` give the JAX-RS
`Response` of Keycloak as the body.
+The Response is closed, but its status and headers can still be read, such as
the `Location` header with the URL
+(ending with the id) of the created resource.
+
+When Keycloak answers with a status other than 2xx, such as 409 (already
exists) or 404 (not found), the exchange
+fails with a `jakarta.ws.rs.WebApplicationException`, whose `getResponse()`
gives the status. The same goes for
+`deleteOrganization`, `removeOrganizationMember` and
`unlinkOrganizationIdentityProvider`. The bulk operations
+record such a status as a failed item.
+
== User Operations
[tabs]
@@ -672,7 +695,8 @@ All bulk operations return a consistent response format:
{
"username": "user2",
"status": "failed",
- "error": "User already exists"
+ "statusCode": 409,
+ "error": "Keycloak createUser failed with status 409:
{\"errorMessage\":\"User exists with same username\"}"
}
]
}
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakEndpoint.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakEndpoint.java
index a85258706031..0cc36f10b725 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakEndpoint.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakEndpoint.java
@@ -50,10 +50,11 @@ import org.keycloak.admin.client.KeycloakBuilder;
description = "Mostly not read: the
operations take their input from headers, such as"
+ " CamelKeycloakRealmName and
CamelKeycloakUserId. With pojoRequest=true the"
+ " create and update
operations take the Keycloak representation, such as a"
- + " UserRepresentation for
createUser; a body of another type does nothing"
- + " and keeps the body. The
bulk operations take a List when"
- + " the header is not set: of
UserRepresentation for bulkCreateUsers and"
- + " bulkUpdateUsers, of String
ids or role names for the others."
+ + " UserRepresentation for
createUser, or a Map or JSON text of it; a body"
+ + " that does not convert
fails. The bulk operations take a List when"
+ + " the header is not set: of
UserRepresentation (or a Map or JSON text of"
+ + " it) for bulkCreateUsers
and bulkUpdateUsers, of String ids or role names"
+ + " for the others."
+ " introspectToken,
revokeAccessToken and revokeRefreshToken take the token"
+ " as text when the
CamelKeycloakToken header is not set"),
producerOut = @BodyType(value = Carrier.UNKNOWN,
@@ -299,8 +300,10 @@ import org.keycloak.admin.client.KeycloakBuilder;
description = "Depends on the operation,
which the CamelKeycloakOperation header can set:"
+ " get gives the Keycloak
representation, such as a UserRepresentation, and"
+ " list and search a List of
them. Most create operations, deleteUser and"
- + " some organization
operations give the JAX-RS Response, whose status says"
- + " whether it worked (a 409
or 404 is no error); the others"
+ + " some organization
operations give the closed JAX-RS Response, whose"
+ + " status and Location
header (the URL of the created resource) can be"
+ + " read; a status other than
2xx fails with a WebApplicationException. The"
+ + " others give"
+ " a confirmation text such
as Realm deleted successfully. The bulk"
+ " operations give a Map
with total, success, failed and results"))
public class KeycloakEndpoint extends ScheduledPollEndpoint implements
EndpointServiceLocation {
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 0a9913dc2cbb..9a9c5f5cea29 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
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.keycloak;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
@@ -23,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.apache.camel.Endpoint;
@@ -63,6 +66,7 @@ import
org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import
org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.util.JsonSerialization;
/**
* A Producer which sends messages to Keycloak Admin API
@@ -442,14 +446,107 @@ public class KeycloakProducer extends DefaultProducer {
return (KeycloakEndpoint) super.getEndpoint();
}
+ /**
+ * The body of a pojoRequest operation as the given Keycloak
representation. Another type is converted: by a type
+ * converter, or from a Map (such as unmarshalled JSON) or JSON text. A
body that does not convert fails.
+ */
+ private static <T> T getPojoRequest(Exchange exchange, Class<T> type)
throws InvalidPayloadException {
+ Message in = exchange.getIn();
+ Object body = in.getMandatoryBody();
+ T answer;
+ try {
+ answer = toRepresentation(exchange, body, type);
+ } catch (IOException | IllegalArgumentException e) {
+ throw new InvalidPayloadException(exchange, type, in, e);
+ }
+ if (answer == null) {
+ throw new InvalidPayloadException(exchange, type, in);
+ }
+ return answer;
+ }
+
+ /**
+ * Converts each element of a List body or header to the given Keycloak
representation, the same way as
+ * {@link #getPojoRequest(Exchange, Class)}. An element that does not
convert fails before any call to Keycloak.
+ */
+ private static <T> List<T> toRepresentations(Exchange exchange, List<?>
list, Class<T> type) {
+ List<T> answer = new ArrayList<>(list.size());
+ for (int i = 0; i < list.size(); i++) {
+ Object element = list.get(i);
+ T value = null;
+ if (element != null) {
+ try {
+ value = toRepresentation(exchange, element, type);
+ } catch (IOException | IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Element " + i + " of the list cannot be converted
to " + type.getSimpleName() + ": "
+ + e.getMessage(),
+ e);
+ }
+ }
+ if (value == null) {
+ throw new IllegalArgumentException(
+ "Element " + i + " of the list must be a " +
type.getSimpleName() + ", a Map or JSON text, but was: "
+ + (element != null ?
element.getClass().getName() : "null"));
+ }
+ answer.add(value);
+ }
+ return answer;
+ }
+
+ private static <T> T toRepresentation(Exchange exchange, Object value,
Class<T> type) throws IOException {
+ if (type.isInstance(value)) {
+ return type.cast(value);
+ }
+ T answer = exchange.getContext().getTypeConverter().tryConvertTo(type,
exchange, value);
+ if (answer != null) {
+ return answer;
+ }
+ if (value instanceof Map) {
+ return JsonSerialization.mapper.convertValue(value, type);
+ }
+ if (value instanceof CharSequence || value instanceof byte[] || value
instanceof InputStream) {
+ String json =
exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange,
value);
+ if (json != null) {
+ return JsonSerialization.readValue(json, type);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fails on a non-2xx status of a Response that Keycloak returned, and
closes it. The closed Response is returned:
+ * its status and headers, such as the Location of a created resource, can
still be read.
+ */
+ private static Response checkResponse(Response response, String operation)
{
+ try {
+ int status = response.getStatus();
+ if (status < 200 || status >= 300) {
+ String error = null;
+ try {
+ if (response.hasEntity()) {
+ error = response.readEntity(String.class);
+ }
+ } catch (Exception e) {
+ // ignore, the status is enough
+ }
+ throw new WebApplicationException(
+ "Keycloak " + operation + " failed with status " +
status
+ +
(ObjectHelper.isNotEmpty(error) ? ": " + error : ""),
+ response);
+ }
+ } finally {
+ response.close();
+ }
+ return response;
+ }
+
private void createRealm(Keycloak keycloakClient, Exchange exchange)
throws InvalidPayloadException {
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RealmRepresentation rr) {
- keycloakClient.realms().create(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Realm created successfully");
- }
+ RealmRepresentation rr = getPojoRequest(exchange,
RealmRepresentation.class);
+ keycloakClient.realms().create(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Realm created successfully");
} else {
RealmRepresentation realm = new RealmRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.REALM_NAME)))
{
@@ -494,12 +591,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RealmRepresentation rr) {
- keycloakClient.realm(realmName).update(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Realm updated successfully");
- }
+ RealmRepresentation rr = getPojoRequest(exchange,
RealmRepresentation.class);
+ keycloakClient.realm(realmName).update(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Realm updated successfully");
} else {
throw new IllegalArgumentException("Update realm requires POJO
request with RealmRepresentation");
}
@@ -512,12 +607,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof UserRepresentation ur) {
- Response response =
keycloakClient.realm(realmName).users().create(ur);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ UserRepresentation ur = getPojoRequest(exchange,
UserRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).users().create(ur);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createUser"));
} else {
UserRepresentation user = new UserRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.USERNAME)))
{
@@ -539,7 +632,7 @@ public class KeycloakProducer extends DefaultProducer {
}
Response response =
keycloakClient.realm(realmName).users().create(user);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "createUser"));
}
}
@@ -556,7 +649,7 @@ public class KeycloakProducer extends DefaultProducer {
Response response =
keycloakClient.realm(realmName).users().delete(userId);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "deleteUser"));
}
private void getUser(Keycloak keycloakClient, Exchange exchange) {
@@ -587,12 +680,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof UserRepresentation ur) {
- keycloakClient.realm(realmName).users().get(userId).update(ur);
- Message message = getMessageForResponse(exchange);
- message.setBody("User updated successfully");
- }
+ UserRepresentation ur = getPojoRequest(exchange,
UserRepresentation.class);
+ keycloakClient.realm(realmName).users().get(userId).update(ur);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("User updated successfully");
} else {
throw new IllegalArgumentException("Update user requires POJO
request with UserRepresentation");
}
@@ -616,12 +707,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RoleRepresentation rr) {
- keycloakClient.realm(realmName).roles().create(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Role created successfully");
- }
+ RoleRepresentation rr = getPojoRequest(exchange,
RoleRepresentation.class);
+ keycloakClient.realm(realmName).roles().create(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Role created successfully");
} else {
RoleRepresentation role = new RoleRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.ROLE_NAME)))
{
@@ -684,12 +773,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RoleRepresentation rr) {
-
keycloakClient.realm(realmName).roles().get(roleName).update(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Role updated successfully");
- }
+ RoleRepresentation rr = getPojoRequest(exchange,
RoleRepresentation.class);
+ keycloakClient.realm(realmName).roles().get(roleName).update(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Role updated successfully");
} else {
throw new IllegalArgumentException("Update role requires POJO
request with RoleRepresentation");
}
@@ -757,12 +844,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof GroupRepresentation gr) {
- Response response =
keycloakClient.realm(realmName).groups().add(gr);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ GroupRepresentation gr = getPojoRequest(exchange,
GroupRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).groups().add(gr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createGroup"));
} else {
GroupRepresentation group = new GroupRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.GROUP_NAME)))
{
@@ -773,7 +858,7 @@ public class KeycloakProducer extends DefaultProducer {
}
Response response =
keycloakClient.realm(realmName).groups().add(group);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "createGroup"));
}
}
@@ -821,12 +906,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof GroupRepresentation gr) {
-
keycloakClient.realm(realmName).groups().group(groupId).update(gr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Group updated successfully");
- }
+ GroupRepresentation gr = getPojoRequest(exchange,
GroupRepresentation.class);
+ keycloakClient.realm(realmName).groups().group(groupId).update(gr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Group updated successfully");
} else {
throw new IllegalArgumentException("Update group requires POJO
request with GroupRepresentation");
}
@@ -908,12 +991,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ClientRepresentation cr) {
- Response response =
keycloakClient.realm(realmName).clients().create(cr);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ ClientRepresentation cr = getPojoRequest(exchange,
ClientRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).clients().create(cr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createClient"));
} else {
ClientRepresentation client = new ClientRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.CLIENT_ID)))
{
@@ -925,7 +1006,7 @@ public class KeycloakProducer extends DefaultProducer {
}
Response response =
keycloakClient.realm(realmName).clients().create(client);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "createClient"));
}
}
@@ -973,12 +1054,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ClientRepresentation cr) {
-
keycloakClient.realm(realmName).clients().get(clientUuid).update(cr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Client updated successfully");
- }
+ ClientRepresentation cr = getPojoRequest(exchange,
ClientRepresentation.class);
+
keycloakClient.realm(realmName).clients().get(clientUuid).update(cr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Client updated successfully");
} else {
throw new IllegalArgumentException("Update client requires POJO
request with ClientRepresentation");
}
@@ -1052,12 +1131,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RoleRepresentation rr) {
-
keycloakClient.realm(realmName).clients().get(clientUuid).roles().create(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Client role created successfully");
- }
+ RoleRepresentation rr = getPojoRequest(exchange,
RoleRepresentation.class);
+
keycloakClient.realm(realmName).clients().get(clientUuid).roles().create(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Client role created successfully");
} else {
RoleRepresentation role = new RoleRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.ROLE_NAME)))
{
@@ -1136,13 +1213,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof RoleRepresentation rr) {
-
keycloakClient.realm(realmName).clients().get(clientUuid).roles().get(roleName)
- .update(rr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Client role updated successfully");
- }
+ RoleRepresentation rr = getPojoRequest(exchange,
RoleRepresentation.class);
+
keycloakClient.realm(realmName).clients().get(clientUuid).roles().get(roleName)
+ .update(rr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Client role updated successfully");
} else {
throw new IllegalArgumentException("Update client role requires
POJO request with RoleRepresentation");
}
@@ -1407,12 +1482,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ClientScopeRepresentation csr) {
- Response response =
keycloakClient.realm(realmName).clientScopes().create(csr);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ ClientScopeRepresentation csr = getPojoRequest(exchange,
ClientScopeRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).clientScopes().create(csr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createClientScope"));
} else {
ClientScopeRepresentation clientScope = new
ClientScopeRepresentation();
if
(ObjectHelper.isNotEmpty(exchange.getIn().getHeader(KeycloakConstants.CLIENT_SCOPE_NAME)))
{
@@ -1423,7 +1496,7 @@ public class KeycloakProducer extends DefaultProducer {
}
Response response =
keycloakClient.realm(realmName).clientScopes().create(clientScope);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "createClientScope"));
}
}
@@ -1472,12 +1545,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ClientScopeRepresentation csr) {
-
keycloakClient.realm(realmName).clientScopes().get(clientScopeId).update(csr);
- Message message = getMessageForResponse(exchange);
- message.setBody("Client scope updated successfully");
- }
+ ClientScopeRepresentation csr = getPojoRequest(exchange,
ClientScopeRepresentation.class);
+
keycloakClient.realm(realmName).clientScopes().get(clientScopeId).update(csr);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Client scope updated successfully");
} else {
throw new IllegalArgumentException("Update client scope requires
POJO request with ClientScopeRepresentation");
}
@@ -1502,13 +1573,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof IdentityProviderRepresentation
idpRepresentation) {
- Response response
- =
keycloakClient.realm(realmName).identityProviders().create(idpRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ IdentityProviderRepresentation idpRepresentation =
getPojoRequest(exchange, IdentityProviderRepresentation.class);
+ Response response
+ =
keycloakClient.realm(realmName).identityProviders().create(idpRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createIdentityProvider"));
} else {
throw new IllegalArgumentException(
"Create identity provider requires POJO request with
IdentityProviderRepresentation");
@@ -1560,13 +1629,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof IdentityProviderRepresentation
idpRepresentation) {
-
keycloakClient.realm(realmName).identityProviders().get(idpAlias)
- .update(idpRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody("Identity provider updated successfully");
- }
+ IdentityProviderRepresentation idpRepresentation =
getPojoRequest(exchange, IdentityProviderRepresentation.class);
+ keycloakClient.realm(realmName).identityProviders().get(idpAlias)
+ .update(idpRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Identity provider updated successfully");
} else {
throw new IllegalArgumentException(
"Update identity provider requires POJO request with
IdentityProviderRepresentation");
@@ -1597,13 +1664,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ResourceRepresentation
resourceRepresentation) {
- Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().resources()
- .create(resourceRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ ResourceRepresentation resourceRepresentation =
getPojoRequest(exchange, ResourceRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().resources()
+ .create(resourceRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createResource"));
} else {
throw new IllegalArgumentException("Create resource requires POJO
request with ResourceRepresentation");
}
@@ -1670,13 +1735,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ResourceRepresentation
resourceRepresentation) {
-
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().resources().resource(resourceId)
- .update(resourceRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody("Resource updated successfully");
- }
+ ResourceRepresentation resourceRepresentation =
getPojoRequest(exchange, ResourceRepresentation.class);
+
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().resources().resource(resourceId)
+ .update(resourceRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Resource updated successfully");
} else {
throw new IllegalArgumentException("Update resource requires POJO
request with ResourceRepresentation");
}
@@ -1711,13 +1774,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof PolicyRepresentation policyRepresentation) {
- Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies()
- .create(policyRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ PolicyRepresentation policyRepresentation =
getPojoRequest(exchange, PolicyRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies()
+ .create(policyRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createResourcePolicy"));
} else {
throw new IllegalArgumentException("Create policy requires POJO
request with PolicyRepresentation");
}
@@ -1784,13 +1845,11 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof PolicyRepresentation policyRepresentation) {
-
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies().policy(policyId)
- .update(policyRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody("Policy updated successfully");
- }
+ PolicyRepresentation policyRepresentation =
getPojoRequest(exchange, PolicyRepresentation.class);
+
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies().policy(policyId)
+ .update(policyRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Policy updated successfully");
} else {
throw new IllegalArgumentException("Update policy requires POJO
request with PolicyRepresentation");
}
@@ -1825,13 +1884,12 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof ResourcePermissionRepresentation
permissionRepresentation) {
- Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().permissions()
- .resource().create(permissionRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ ResourcePermissionRepresentation permissionRepresentation
+ = getPojoRequest(exchange,
ResourcePermissionRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().permissions()
+ .resource().create(permissionRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response,
"createResourcePermission"));
} else {
throw new IllegalArgumentException("Create permission requires
POJO request with ResourcePermissionRepresentation");
}
@@ -1900,14 +1958,12 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof PolicyRepresentation policyRepresentation) {
- // Use policy endpoint for permissions
-
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies().policy(permissionId)
- .update(policyRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody("Permission updated successfully");
- }
+ PolicyRepresentation policyRepresentation =
getPojoRequest(exchange, PolicyRepresentation.class);
+ // Use policy endpoint for permissions
+
keycloakClient.realm(realmName).clients().get(clientUuid).authorization().policies().policy(permissionId)
+ .update(policyRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Permission updated successfully");
} else {
throw new IllegalArgumentException("Update permission requires
POJO request with PolicyRepresentation");
}
@@ -2330,16 +2386,18 @@ public class KeycloakProducer extends DefaultProducer {
throw new IllegalArgumentException(MISSING_REALM_NAME);
}
- List<UserRepresentation> users =
exchange.getIn().getHeader(KeycloakConstants.USERS, List.class);
- if (users == null || users.isEmpty()) {
+ List<?> list = exchange.getIn().getHeader(KeycloakConstants.USERS,
List.class);
+ if (list == null || list.isEmpty()) {
// Try to get from body
Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof List) {
- users = CastUtils.cast((List<?>) payload);
+ if (payload instanceof List<?> l) {
+ list = l;
} else {
throw new IllegalArgumentException("Users list must be
provided via header or body");
}
}
+ // the elements can be Maps (such as unmarshalled JSON) or JSON text
as well
+ List<UserRepresentation> users = toRepresentations(exchange, list,
UserRepresentation.class);
boolean continueOnError
=
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
@@ -2350,13 +2408,16 @@ public class KeycloakProducer extends DefaultProducer {
for (UserRepresentation user : users) {
Map<String, Object> result = new HashMap<>();
result.put("username", user.getUsername());
- try (Response response =
keycloakClient.realm(realmName).users().create(user)) {
+ try {
+ Response response =
checkResponse(keycloakClient.realm(realmName).users().create(user),
"createUser");
result.put("status", "success");
result.put("statusCode", response.getStatus());
successCount++;
- response.close();
} catch (Exception e) {
result.put("status", "failed");
+ if (e instanceof WebApplicationException wae) {
+ result.put("statusCode", wae.getResponse().getStatus());
+ }
result.put("error", e.getMessage());
failureCount++;
if (!continueOnError) {
@@ -2417,13 +2478,15 @@ public class KeycloakProducer extends DefaultProducer {
Map<String, Object> result = new HashMap<>();
result.put("userId", userId);
try {
- Response response =
keycloakClient.realm(realmName).users().delete(userId);
+ Response response =
checkResponse(keycloakClient.realm(realmName).users().delete(userId),
"deleteUser");
result.put("status", "success");
result.put("statusCode", response.getStatus());
successCount++;
- response.close();
} catch (Exception e) {
result.put("status", "failed");
+ if (e instanceof WebApplicationException wae) {
+ result.put("statusCode", wae.getResponse().getStatus());
+ }
result.put("error", e.getMessage());
failureCount++;
if (!continueOnError) {
@@ -2597,16 +2660,18 @@ public class KeycloakProducer extends DefaultProducer {
throw new IllegalArgumentException(MISSING_REALM_NAME);
}
- List<UserRepresentation> users =
exchange.getIn().getHeader(KeycloakConstants.USERS, List.class);
- if (users == null || users.isEmpty()) {
+ List<?> list = exchange.getIn().getHeader(KeycloakConstants.USERS,
List.class);
+ if (list == null || list.isEmpty()) {
// Try to get from body
Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof List) {
- users = CastUtils.cast((List<?>) payload);
+ if (payload instanceof List<?> l) {
+ list = l;
} else {
throw new IllegalArgumentException("Users list must be
provided via header or body");
}
}
+ // the elements can be Maps (such as unmarshalled JSON) or JSON text
as well
+ List<UserRepresentation> users = toRepresentations(exchange, list,
UserRepresentation.class);
boolean continueOnError
=
exchange.getIn().getHeader(KeycloakConstants.CONTINUE_ON_ERROR, Boolean.FALSE,
Boolean.class);
@@ -2654,12 +2719,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof OrganizationRepresentation
orgRepresentation) {
- Response response =
keycloakClient.realm(realmName).organizations().create(orgRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ OrganizationRepresentation orgRepresentation =
getPojoRequest(exchange, OrganizationRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).organizations().create(orgRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "createOrganization"));
} else {
String orgName =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_NAME, String.class);
if (ObjectHelper.isEmpty(orgName)) {
@@ -2690,7 +2753,7 @@ public class KeycloakProducer extends DefaultProducer {
Response response =
keycloakClient.realm(realmName).organizations().create(org);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "createOrganization"));
}
}
@@ -2706,12 +2769,10 @@ public class KeycloakProducer extends DefaultProducer {
}
if (getConfiguration().isPojoRequest()) {
- Object payload = exchange.getIn().getMandatoryBody();
- if (payload instanceof OrganizationRepresentation
orgRepresentation) {
- Response response =
keycloakClient.realm(realmName).organizations().get(orgId).update(orgRepresentation);
- Message message = getMessageForResponse(exchange);
- message.setBody(response);
- }
+ OrganizationRepresentation orgRepresentation =
getPojoRequest(exchange, OrganizationRepresentation.class);
+ Response response =
keycloakClient.realm(realmName).organizations().get(orgId).update(orgRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(checkResponse(response, "updateOrganization"));
} else {
throw new IllegalArgumentException("Update organization requires
POJO request with OrganizationRepresentation");
}
@@ -2728,7 +2789,7 @@ public class KeycloakProducer extends DefaultProducer {
throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
}
- keycloakClient.realm(realmName).organizations().get(orgId).delete();
+
checkResponse(keycloakClient.realm(realmName).organizations().get(orgId).delete(),
"deleteOrganization");
Message message = getMessageForResponse(exchange);
message.setBody("Organization deleted successfully");
}
@@ -2811,7 +2872,7 @@ public class KeycloakProducer extends DefaultProducer {
Response response =
keycloakClient.realm(realmName).organizations().get(orgId).members().addMember(userId);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response, "addOrganizationMember"));
}
private void removeOrganizationMember(Keycloak keycloakClient, Exchange
exchange) {
@@ -2830,7 +2891,8 @@ public class KeycloakProducer extends DefaultProducer {
throw new IllegalArgumentException(MISSING_ORGANIZATION_MEMBER_ID);
}
-
keycloakClient.realm(realmName).organizations().get(orgId).members().member(userId).delete();
+
checkResponse(keycloakClient.realm(realmName).organizations().get(orgId).members().member(userId).delete(),
+ "removeOrganizationMember");
Message message = getMessageForResponse(exchange);
message.setBody("Organization member removed successfully");
}
@@ -2878,7 +2940,7 @@ public class KeycloakProducer extends DefaultProducer {
Response response
=
keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().addIdentityProvider(idpAlias);
Message message = getMessageForResponse(exchange);
- message.setBody(response);
+ message.setBody(checkResponse(response,
"linkOrganizationIdentityProvider"));
}
private void unlinkOrganizationIdentityProvider(Keycloak keycloakClient,
Exchange exchange) {
@@ -2897,7 +2959,8 @@ public class KeycloakProducer extends DefaultProducer {
throw new IllegalArgumentException(MISSING_ORGANIZATION_IDP_ALIAS);
}
-
keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().get(idpAlias).delete();
+
checkResponse(keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().get(idpAlias).delete(),
+ "unlinkOrganizationIdentityProvider");
Message message = getMessageForResponse(exchange);
message.setBody("Organization identity provider unlinked
successfully");
}
diff --git
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerPojoRequestAndResponseTest.java
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerPojoRequestAndResponseTest.java
new file mode 100644
index 000000000000..2c7445518e48
--- /dev/null
+++
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerPojoRequestAndResponseTest.java
@@ -0,0 +1,344 @@
+/*
+ * 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.keycloak;
+
+import java.util.List;
+import java.util.Map;
+
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.GroupsResource;
+import org.keycloak.admin.client.resource.OrganizationResource;
+import org.keycloak.admin.client.resource.OrganizationsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * A pojoRequest body of another type is converted or fails, and the Response
of a create or delete is checked and
+ * closed.
+ */
+public class KeycloakProducerPojoRequestAndResponseTest extends
CamelTestSupport {
+
+ @BindToRegistry("keycloakClient")
+ private Keycloak keycloakClient = Mockito.mock(Keycloak.class);
+
+ private RealmResource realmResource = Mockito.mock(RealmResource.class);
+ private UsersResource usersResource = Mockito.mock(UsersResource.class);
+ private UserResource userResource = Mockito.mock(UserResource.class);
+ private GroupsResource groupsResource = Mockito.mock(GroupsResource.class);
+ private OrganizationsResource organizationsResource =
Mockito.mock(OrganizationsResource.class);
+ private OrganizationResource organizationResource =
Mockito.mock(OrganizationResource.class);
+
+ @BeforeEach
+ void setupMocks() {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.users()).thenReturn(usersResource);
+ when(usersResource.get(anyString())).thenReturn(userResource);
+ when(realmResource.groups()).thenReturn(groupsResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:createUserPojo")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=createUser&pojoRequest=true");
+ from("direct:updateUserPojo")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=updateUser&pojoRequest=true");
+ from("direct:createGroup")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=createGroup");
+ from("direct:deleteUser")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=deleteUser");
+ from("direct:deleteOrganization")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=deleteOrganization");
+ from("direct:bulkCreateUsers")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=bulkCreateUsers");
+ from("direct:bulkUpdateUsers")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=bulkUpdateUsers");
+ from("direct:bulkDeleteUsers")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=bulkDeleteUsers");
+ }
+ };
+ }
+
+ private static Response response(int status) {
+ Response response = Mockito.mock(Response.class);
+ when(response.getStatus()).thenReturn(status);
+ return response;
+ }
+
+ @Test
+ public void testCreateUserPojoFromJsonText() {
+ Response created = response(201);
+
when(usersResource.create(any(UserRepresentation.class))).thenReturn(created);
+
+ Exchange out = template.request("direct:createUserPojo", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+
e.getIn().setBody("{\"username\":\"alice\",\"email\":\"[email protected]\",\"enabled\":true}");
+ });
+
+ assertNull(out.getException());
+ ArgumentCaptor<UserRepresentation> captor =
ArgumentCaptor.forClass(UserRepresentation.class);
+ verify(usersResource).create(captor.capture());
+ assertEquals("alice", captor.getValue().getUsername());
+ assertEquals("[email protected]", captor.getValue().getEmail());
+ assertSame(created, out.getMessage().getBody());
+ verify(created).close();
+ }
+
+ @Test
+ public void testUpdateUserPojoFromMap() {
+ Exchange out = template.request("direct:updateUserPojo", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.USER_ID, "user-1");
+ e.getIn().setBody(Map.of("firstName", "Alice", "lastName",
"Smith"));
+ });
+
+ assertNull(out.getException());
+ ArgumentCaptor<UserRepresentation> captor =
ArgumentCaptor.forClass(UserRepresentation.class);
+ verify(userResource).update(captor.capture());
+ assertEquals("Alice", captor.getValue().getFirstName());
+ assertEquals("Smith", captor.getValue().getLastName());
+ assertEquals("User updated successfully", out.getMessage().getBody());
+ }
+
+ @Test
+ public void testCreateUserPojoWrongBodyFails() {
+ Exchange out = template.request("direct:createUserPojo", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody(42);
+ });
+
+ InvalidPayloadException cause =
assertInstanceOf(InvalidPayloadException.class, out.getException());
+ assertEquals(UserRepresentation.class, cause.getType());
+ verify(usersResource, never()).create(any(UserRepresentation.class));
+ }
+
+ @Test
+ public void testCreateUserPojoInvalidJsonFails() {
+ Exchange out = template.request("direct:createUserPojo", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody("not json");
+ });
+
+ InvalidPayloadException cause =
assertInstanceOf(InvalidPayloadException.class, out.getException());
+ assertEquals(UserRepresentation.class, cause.getType());
+ assertNotNull(cause.getCause());
+ verify(usersResource, never()).create(any(UserRepresentation.class));
+ }
+
+ @Test
+ public void testCreateUserConflictFails() {
+ Response conflict = response(409);
+ when(conflict.hasEntity()).thenReturn(true);
+
when(conflict.readEntity(String.class)).thenReturn("{\"errorMessage\":\"User
exists with same username\"}");
+
when(usersResource.create(any(UserRepresentation.class))).thenReturn(conflict);
+
+ Exchange out = template.request("direct:createUserPojo", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody(new UserRepresentation());
+ });
+
+ WebApplicationException cause =
assertInstanceOf(WebApplicationException.class, out.getException());
+ assertEquals(409, cause.getResponse().getStatus());
+ assertTrue(cause.getMessage().contains("createUser failed with status
409"), cause.getMessage());
+ assertTrue(cause.getMessage().contains("User exists with same
username"), cause.getMessage());
+ verify(conflict).close();
+ }
+
+ @Test
+ public void testCreateGroupConflictFails() {
+ Response conflict = response(409);
+
when(groupsResource.add(any(GroupRepresentation.class))).thenReturn(conflict);
+
+ Exchange out = template.request("direct:createGroup", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.GROUP_NAME, "admins");
+ });
+
+ WebApplicationException cause =
assertInstanceOf(WebApplicationException.class, out.getException());
+ assertEquals(409, cause.getResponse().getStatus());
+ verify(conflict).close();
+ }
+
+ @Test
+ public void testDeleteUserNotFoundFails() {
+ Response notFound = response(404);
+ when(usersResource.delete("missing")).thenReturn(notFound);
+
+ Exchange out = template.request("direct:deleteUser", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.USER_ID, "missing");
+ });
+
+ WebApplicationException cause =
assertInstanceOf(WebApplicationException.class, out.getException());
+ assertEquals(404, cause.getResponse().getStatus());
+ verify(notFound).close();
+ }
+
+ @Test
+ public void testDeleteUserClosesResponse() {
+ Response noContent = response(204);
+ when(usersResource.delete("user-1")).thenReturn(noContent);
+
+ Exchange out = template.request("direct:deleteUser", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.USER_ID, "user-1");
+ });
+
+ assertNull(out.getException());
+ assertSame(noContent, out.getMessage().getBody());
+ verify(noContent).close();
+ }
+
+ @Test
+ public void testDeleteOrganizationNotFoundFails() {
+ Response notFound = response(404);
+ when(organizationResource.delete()).thenReturn(notFound);
+
+ Exchange out = template.request("direct:deleteOrganization", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID, "missing");
+ });
+
+ WebApplicationException cause =
assertInstanceOf(WebApplicationException.class, out.getException());
+ assertEquals(404, cause.getResponse().getStatus());
+ verify(notFound).close();
+ }
+
+ @Test
+ public void testBulkCreateUsersFromListOfMap() {
+ Response created = response(201);
+
when(usersResource.create(any(UserRepresentation.class))).thenReturn(created);
+
+ Exchange out = template.request("direct:bulkCreateUsers", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody(List.of(Map.of("username", "alice"),
"{\"username\":\"bob\"}"));
+ });
+
+ assertNull(out.getException());
+ ArgumentCaptor<UserRepresentation> captor =
ArgumentCaptor.forClass(UserRepresentation.class);
+ verify(usersResource, times(2)).create(captor.capture());
+ assertEquals("alice", captor.getAllValues().get(0).getUsername());
+ assertEquals("bob", captor.getAllValues().get(1).getUsername());
+ verify(created, times(2)).close();
+
+ Map<?, ?> summary = out.getMessage().getBody(Map.class);
+ assertEquals(2, summary.get("success"));
+ assertEquals(0, summary.get("failed"));
+ }
+
+ @Test
+ public void testBulkCreateUsersWrongElementFails() {
+ Exchange out = template.request("direct:bulkCreateUsers", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody(List.of(Map.of("username", "alice"), 42));
+ });
+
+ IllegalArgumentException cause =
assertInstanceOf(IllegalArgumentException.class, out.getException());
+ assertTrue(cause.getMessage().contains("Element 1 of the list must be
a UserRepresentation"), cause.getMessage());
+ verify(usersResource, never()).create(any(UserRepresentation.class));
+ }
+
+ @Test
+ public void testBulkCreateUsersConflictIsFailed() {
+ Response created = response(201);
+ Response conflict = response(409);
+
when(usersResource.create(any(UserRepresentation.class))).thenReturn(created,
conflict);
+
+ Exchange out = template.request("direct:bulkCreateUsers", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.CONTINUE_ON_ERROR, true);
+ e.getIn().setBody(List.of(Map.of("username", "alice"),
Map.of("username", "bob")));
+ });
+
+ assertNull(out.getException());
+ Map<?, ?> summary = out.getMessage().getBody(Map.class);
+ assertEquals(1, summary.get("success"));
+ assertEquals(1, summary.get("failed"));
+ List<?> results = (List<?>) summary.get("results");
+ Map<?, ?> second = (Map<?, ?>) results.get(1);
+ assertEquals("failed", second.get("status"));
+ assertEquals(409, second.get("statusCode"));
+ verify(created).close();
+ verify(conflict).close();
+ }
+
+ @Test
+ public void testBulkUpdateUsersFromListOfMap() {
+ Exchange out = template.request("direct:bulkUpdateUsers", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setBody(List.of(Map.of("id", "user-1", "firstName",
"Alice")));
+ });
+
+ assertNull(out.getException());
+ ArgumentCaptor<UserRepresentation> captor =
ArgumentCaptor.forClass(UserRepresentation.class);
+ verify(userResource).update(captor.capture());
+ assertEquals("Alice", captor.getValue().getFirstName());
+ }
+
+ @Test
+ public void testBulkDeleteUsersNotFoundIsFailed() {
+ Response noContent = response(204);
+ Response notFound = response(404);
+ when(usersResource.delete("user-1")).thenReturn(noContent);
+ when(usersResource.delete("missing")).thenReturn(notFound);
+
+ Exchange out = template.request("direct:bulkDeleteUsers", e -> {
+ e.getIn().setHeader(KeycloakConstants.REALM_NAME, "testRealm");
+ e.getIn().setHeader(KeycloakConstants.CONTINUE_ON_ERROR, true);
+ e.getIn().setBody(List.of("user-1", "missing"));
+ });
+
+ assertNull(out.getException());
+ Map<?, ?> summary = out.getMessage().getBody(Map.class);
+ assertEquals(1, summary.get("success"));
+ assertEquals(1, summary.get("failed"));
+ verify(noContent).close();
+ verify(notFound).close();
+ }
+}
diff --git
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
index f149a7e7fd94..aa547c25e3d6 100644
---
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
+++
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
@@ -25,6 +25,7 @@ import org.apache.camel.BindToRegistry;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientsResource;
@@ -79,6 +80,12 @@ public class KeycloakProducerTest extends CamelTestSupport {
= Mockito.mock(OrganizationIdentityProviderResource.class);
private Response response = Mockito.mock(Response.class);
+ @BeforeEach
+ void stubCreatedResponse() {
+ // a failed status fails the exchange, so the shared Response is a
success
+ when(response.getStatus()).thenReturn(201);
+ }
+
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@@ -462,6 +469,7 @@ public class KeycloakProducerTest extends CamelTestSupport {
when(keycloakClient.realm(anyString())).thenReturn(realmResource);
when(realmResource.organizations()).thenReturn(organizationsResource);
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+ when(organizationResource.delete()).thenReturn(response);
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(1);
@@ -515,6 +523,7 @@ public class KeycloakProducerTest extends CamelTestSupport {
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
when(organizationResource.members()).thenReturn(organizationMembersResource);
when(organizationMembersResource.member(anyString())).thenReturn(organizationMemberResource);
+ when(organizationMemberResource.delete()).thenReturn(response);
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(1);
@@ -571,6 +580,7 @@ public class KeycloakProducerTest extends CamelTestSupport {
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
when(organizationResource.identityProviders()).thenReturn(organizationIdpsResource);
when(organizationIdpsResource.get(anyString())).thenReturn(organizationIdpResource);
+ when(organizationIdpResource.delete()).thenReturn(response);
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(1);
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index b4535f50c030..abce6acc36ae 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -823,6 +823,21 @@ number, without an error, so nothing was set in the
replicated log and the excha
exchange with an `InvalidPayloadException`. Marshal such a body first (for
example to JSON) or convert it to a
`String`. A message without a body is still skipped.
+=== camel-keycloak - a failed create or delete fails the exchange, and a
pojoRequest body is converted
+
+The producer operations that give the JAX-RS `Response` of Keycloak as the
body, such as `createUser`, `deleteUser`,
+`createGroup`, `createClient` and `createOrganization`, now fail the exchange
with a
+`jakarta.ws.rs.WebApplicationException` when Keycloak answers with a status
other than 2xx, such as 409 (already
+exists) or 404 (not found). Before, the exchange completed and the route had
to check the status of the `Response`.
+`deleteOrganization`, `removeOrganizationMember` and
`unlinkOrganizationIdentityProvider` fail the same way. The
+`Response` body is now closed: its status and headers, such as `Location`, can
still be read, but not its entity. The
+bulk operations count such a status as a failed item.
+
+With `pojoRequest=true`, a body that is not the Keycloak representation of the
operation is converted: a `Map` or
+JSON text is mapped to it. A body that does not convert fails with an
`InvalidPayloadException`; before, the operation
+did nothing and the body was kept. The elements of the `List` of
`bulkCreateUsers` and `bulkUpdateUsers` are
+converted the same way; an element that does not convert fails the exchange
before any call to Keycloak.
+
=== camel-splunk-hec - bytes, stream and file bodies are sent as text
The producer now sends a byte array, an `InputStream`, a `Reader` or a file
body (`File`, `Path` or the file of the