This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26163-wordpress-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit f69dec1e33c6891d3842b27c3cbd14806cb539ba Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 18:00:35 2026 +0100 CAMEL-26163: camel-wordpress - convert a JSON text or Map body to a Post or User The post and user producers read the body with getBody(Post.class) and getBody(User.class). There is no converter from JSON text or a Map, so the body became null and the service failed with a bare NullPointerException ("Please define an object to create"). A JSON text (String, byte[] or InputStream) or a Map body is now converted to the Post or User class with Jackson, like the WordPress answers. Any other body fails with an InvalidPayloadException that names the type. The catalog body types and the component doc are updated. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/components/wordpress.json | 2 +- .../camel/catalog/docs/wordpress-component.adoc | 5 ++ .../camel/component/wordpress/wordpress.json | 2 +- .../src/main/docs/wordpress-component.adoc | 5 ++ .../component/wordpress/WordpressEndpoint.java | 6 +-- .../producer/AbstractWordpressProducer.java | 37 ++++++++++++- .../wordpress/producer/WordpressPostProducer.java | 8 +-- .../wordpress/producer/WordpressUserProducer.java | 10 ++-- .../wordpress/WordpressPostOperationTest.java | 63 ++++++++++++++++++++++ .../wordpress/WordpressUserOperationTest.java | 46 ++++++++++++++++ .../test/WordpressServerHttpRequestHandler.java | 20 +++++++ 11 files changed, 188 insertions(+), 16 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/wordpress.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/wordpress.json index 0c421197ddc7..6e98725b4abd 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/wordpress.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/wordpress.json @@ -41,7 +41,7 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": "operation", "value": "post", "carriers": [ "pojo" ], "javaType": "org.apache.camel.component.wordpress.api.model.Post" }, { "option": "operation", "value": "user", "carriers": [ "pojo" ], "javaType": "org.apache.camel.component.wordpress.api.model.User" } ], "description": "One message per post or user of the search at every poll, the same ones again on the next poll, or the one of the id option: a Post or a User of [...] - "producerIn": { "carriers": [ "pojo" ], "byOption": [ { "option": "operationDetail", "value": "delete", "carriers": [ "any" ] } ], "description": "The Post or User (org.apache.camel.component.wordpress.api.model) to create, or to update with the id option: JSON text, a Map or another body fails, so unmarshal it to the class first. A delete does not read the body" }, + "producerIn": { "carriers": [ "pojo", "text", "bytes", "stream", "map" ], "byOption": [ { "option": "operationDetail", "value": "delete", "carriers": [ "any" ] } ], "description": "The Post or User (org.apache.camel.component.wordpress.api.model) to create, or to update with the id option. JSON text or a Map is converted to the class with Jackson; another body fails. A delete does not read the body" }, "producerOut": { "carriers": [ "pojo" ], "description": "The Post or User that WordPress answers: the one created, updated or deleted" } }, "properties": { diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/wordpress-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/wordpress-component.adoc index 8444a9140264..c01ae96dfe80 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/wordpress-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/wordpress-component.adoc @@ -96,6 +96,11 @@ To be able to write, you must have an authorized user credentials (see Authentic - `wordpress:post?id=1` updates a post based on data `org.apache.camel.component.wordpress.api.model.Post` from the message body. - `wordpress:post:delete?id=1` deletes a specific post +The message body can also be JSON text (a `String`, `byte[]` or `InputStream`) or a `Map`, with the same field names +as the `Post` or `User` that WordPress answers (for example `first_name` for a user). +It is converted to the `Post` or `User` class with Jackson. +Any other body fails with an `InvalidPayloadException`. + === Authentication Producers that perform write operations, e.g., creating a new post, diff --git a/components/camel-wordpress/src/generated/resources/META-INF/org/apache/camel/component/wordpress/wordpress.json b/components/camel-wordpress/src/generated/resources/META-INF/org/apache/camel/component/wordpress/wordpress.json index 0c421197ddc7..6e98725b4abd 100644 --- a/components/camel-wordpress/src/generated/resources/META-INF/org/apache/camel/component/wordpress/wordpress.json +++ b/components/camel-wordpress/src/generated/resources/META-INF/org/apache/camel/component/wordpress/wordpress.json @@ -41,7 +41,7 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": "operation", "value": "post", "carriers": [ "pojo" ], "javaType": "org.apache.camel.component.wordpress.api.model.Post" }, { "option": "operation", "value": "user", "carriers": [ "pojo" ], "javaType": "org.apache.camel.component.wordpress.api.model.User" } ], "description": "One message per post or user of the search at every poll, the same ones again on the next poll, or the one of the id option: a Post or a User of [...] - "producerIn": { "carriers": [ "pojo" ], "byOption": [ { "option": "operationDetail", "value": "delete", "carriers": [ "any" ] } ], "description": "The Post or User (org.apache.camel.component.wordpress.api.model) to create, or to update with the id option: JSON text, a Map or another body fails, so unmarshal it to the class first. A delete does not read the body" }, + "producerIn": { "carriers": [ "pojo", "text", "bytes", "stream", "map" ], "byOption": [ { "option": "operationDetail", "value": "delete", "carriers": [ "any" ] } ], "description": "The Post or User (org.apache.camel.component.wordpress.api.model) to create, or to update with the id option. JSON text or a Map is converted to the class with Jackson; another body fails. A delete does not read the body" }, "producerOut": { "carriers": [ "pojo" ], "description": "The Post or User that WordPress answers: the one created, updated or deleted" } }, "properties": { diff --git a/components/camel-wordpress/src/main/docs/wordpress-component.adoc b/components/camel-wordpress/src/main/docs/wordpress-component.adoc index 8444a9140264..c01ae96dfe80 100644 --- a/components/camel-wordpress/src/main/docs/wordpress-component.adoc +++ b/components/camel-wordpress/src/main/docs/wordpress-component.adoc @@ -96,6 +96,11 @@ To be able to write, you must have an authorized user credentials (see Authentic - `wordpress:post?id=1` updates a post based on data `org.apache.camel.component.wordpress.api.model.Post` from the message body. - `wordpress:post:delete?id=1` deletes a specific post +The message body can also be JSON text (a `String`, `byte[]` or `InputStream`) or a `Map`, with the same field names +as the `Post` or `User` that WordPress answers (for example `first_name` for a user). +It is converted to the `Post` or `User` class with Jackson. +Any other body fails with an `InvalidPayloadException`. + === Authentication Producers that perform write operations, e.g., creating a new post, diff --git a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/WordpressEndpoint.java b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/WordpressEndpoint.java index 9fbfbd234030..d2877c0b6df0 100644 --- a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/WordpressEndpoint.java +++ b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/WordpressEndpoint.java @@ -60,12 +60,12 @@ import org.apache.camel.util.PropertiesHelper; description = "One message per post or user of the search at every poll, the same ones again" + " on the next poll, or the one of the id option: a Post or a User of" + " org.apache.camel.component.wordpress.api.model"), - producerIn = @BodyType(value = Carrier.POJO, + producerIn = @BodyType(value = { Carrier.POJO, Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.MAP }, byOption = @BodyType.When(option = "operationDetail", value = "delete", carriers = Carrier.ANY), description = "The Post or User (org.apache.camel.component.wordpress.api.model) to create, or" - + " to update with the id option: JSON text, a Map or another body fails, so" - + " unmarshal it to the class first. A delete does not read the body"), + + " to update with the id option. JSON text or a Map is converted to the class" + + " with Jackson; another body fails. A delete does not read the body"), producerOut = @BodyType(value = Carrier.POJO, description = "The Post or User that WordPress answers: the one created, updated or deleted")) public class WordpressEndpoint extends DefaultEndpoint implements EndpointServiceLocation { diff --git a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/AbstractWordpressProducer.java b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/AbstractWordpressProducer.java index b378629309f5..48c828f9bd0e 100644 --- a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/AbstractWordpressProducer.java +++ b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/AbstractWordpressProducer.java @@ -16,7 +16,13 @@ */ package org.apache.camel.component.wordpress.producer; +import java.io.InputStream; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.Exchange; +import org.apache.camel.InvalidPayloadException; +import org.apache.camel.Message; import org.apache.camel.component.wordpress.WordpressConfiguration; import org.apache.camel.component.wordpress.WordpressEndpoint; import org.apache.camel.component.wordpress.api.WordpressServiceProvider; @@ -27,6 +33,7 @@ import org.slf4j.LoggerFactory; public abstract class AbstractWordpressProducer<T> extends DefaultProducer { private static final Logger LOG = LoggerFactory.getLogger(AbstractWordpressProducer.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); private WordpressConfiguration configuration; @@ -61,9 +68,35 @@ public abstract class AbstractWordpressProducer<T> extends DefaultProducer { } } - protected abstract T processInsert(Exchange exchange); + /** + * Gets the message body as the given model class. A JSON text (String, byte[] or InputStream) or a Map is converted + * with Jackson, the same way as WordPress answers are read. Any other body that cannot be converted fails with an + * {@link InvalidPayloadException}. + */ + protected <B> B getMandatoryBody(Exchange exchange, Class<B> type) throws InvalidPayloadException { + final Message in = exchange.getIn(); + final Object body = in.getBody(); + if (type.isInstance(body)) { + return type.cast(body); + } + try { + if (body instanceof Map<?, ?> map) { + return MAPPER.convertValue(map, type); + } + if (body instanceof String || body instanceof byte[] || body instanceof InputStream) { + return MAPPER.readValue(in.getMandatoryBody(String.class), type); + } + } catch (InvalidPayloadException e) { + throw e; + } catch (Exception e) { + throw new InvalidPayloadException(exchange, type, in, e); + } + return in.getMandatoryBody(type); + } + + protected abstract T processInsert(Exchange exchange) throws Exception; - protected abstract T processUpdate(Exchange exchange); + protected abstract T processUpdate(Exchange exchange) throws Exception; protected abstract T processDelete(Exchange exchange); diff --git a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressPostProducer.java b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressPostProducer.java index 8eecf3e302d1..dc0ed9de82c4 100644 --- a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressPostProducer.java +++ b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressPostProducer.java @@ -38,15 +38,15 @@ public class WordpressPostProducer extends AbstractWordpressProducer<Post> { } @Override - protected Post processInsert(Exchange exchange) { + protected Post processInsert(Exchange exchange) throws Exception { LOG.debug("Trying to create a new blog post with {}", exchange.getIn().getBody()); - return servicePosts.create(exchange.getIn().getBody(Post.class)); + return servicePosts.create(getMandatoryBody(exchange, Post.class)); } @Override - protected Post processUpdate(Exchange exchange) { + protected Post processUpdate(Exchange exchange) throws Exception { LOG.debug("Trying to update the post {} with id {}", exchange.getIn().getBody(), this.getConfiguration().getId()); - return servicePosts.update(this.getConfiguration().getId(), exchange.getIn().getBody(Post.class)); + return servicePosts.update(this.getConfiguration().getId(), getMandatoryBody(exchange, Post.class)); } @Override diff --git a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressUserProducer.java b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressUserProducer.java index 85cba144733d..4d573f2f54b4 100644 --- a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressUserProducer.java +++ b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/producer/WordpressUserProducer.java @@ -41,15 +41,15 @@ public class WordpressUserProducer extends AbstractWordpressProducer<User> { } @Override - protected User processUpdate(Exchange exchange) { + protected User processUpdate(Exchange exchange) throws Exception { LOG.debug("Trying to update the user {} with id {}", exchange.getIn().getBody(), this.getConfiguration().getId()); - return serviceUsers.update(getConfiguration().getId(), exchange.getIn().getBody(User.class)); + return serviceUsers.update(getConfiguration().getId(), getMandatoryBody(exchange, User.class)); } @Override - protected User processInsert(Exchange exchange) { - LOG.debug("Trying to create a new user{}", exchange.getIn().getBody()); - return serviceUsers.create(exchange.getIn().getBody(User.class)); + protected User processInsert(Exchange exchange) throws Exception { + LOG.debug("Trying to create a new user {}", exchange.getIn().getBody()); + return serviceUsers.create(getMandatoryBody(exchange, User.class)); } } diff --git a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressPostOperationTest.java b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressPostOperationTest.java index 165d1b67db64..63c2fcddf6b6 100644 --- a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressPostOperationTest.java +++ b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressPostOperationTest.java @@ -16,16 +16,24 @@ */ package org.apache.camel.component.wordpress; +import java.util.Map; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.InvalidPayloadException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.component.wordpress.api.WordpressConstants; import org.apache.camel.component.wordpress.api.model.Content; import org.apache.camel.component.wordpress.api.model.Post; import org.apache.camel.component.wordpress.api.model.PublishableStatus; +import org.apache.camel.component.wordpress.api.test.WordpressServerHttpRequestHandler; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; public class WordpressPostOperationTest extends WordpressComponentTestSupport { @@ -83,6 +91,61 @@ public class WordpressPostOperationTest extends WordpressComponentTestSupport { MockEndpoint.assertIsSatisfied(context); } + @Test + public void testInsertPostFromJsonText() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultInsert"); + mock.expectedMessageCount(1); + + final Post response = (Post) template.requestBody("direct:insertPost", + "{\"author\": 2, \"title\": {\"raw\": \"hello from json text\"}}"); + assertThat(response.getId(), is(9)); + assertThat(WordpressServerHttpRequestHandler.getLastRequestBody(), containsString("\"hello from json text\"")); + + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testInsertPostFromJsonBytes() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultInsert"); + mock.expectedMessageCount(1); + + final Post response = (Post) template.requestBody("direct:insertPost", + "{\"author\": 2, \"title\": {\"raw\": \"hello from json bytes\"}}".getBytes()); + assertThat(response.getId(), is(9)); + assertThat(WordpressServerHttpRequestHandler.getLastRequestBody(), containsString("\"hello from json bytes\"")); + + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testUpdatePostFromMap() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultUpdate"); + mock.expectedMessageCount(1); + + final Post response = (Post) template.requestBody("direct:updatePost", + Map.of("author", 2, "title", Map.of("raw", "hello from map"))); + assertThat(response.getId(), is(9)); + assertThat(WordpressServerHttpRequestHandler.getLastRequestBody(), containsString("\"hello from map\"")); + + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testInsertPostWithInvalidBody() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultInsert"); + mock.expectedMessageCount(0); + + CamelExecutionException e = assertThrows(CamelExecutionException.class, + () -> template.requestBody("direct:insertPost", 123)); + assertThat(e.getCause(), instanceOf(InvalidPayloadException.class)); + + e = assertThrows(CamelExecutionException.class, + () -> template.requestBody("direct:insertPost", "not json")); + assertThat(e.getCause(), instanceOf(InvalidPayloadException.class)); + + MockEndpoint.assertIsSatisfied(context); + } + @Test public void testDeletePost() throws InterruptedException { MockEndpoint mock = getMockEndpoint("mock:resultDelete"); diff --git a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressUserOperationTest.java b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressUserOperationTest.java index f150861f90b1..b3c651403736 100644 --- a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressUserOperationTest.java +++ b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/WordpressUserOperationTest.java @@ -16,13 +16,21 @@ */ package org.apache.camel.component.wordpress; +import java.util.Map; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.InvalidPayloadException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.component.wordpress.api.model.User; +import org.apache.camel.component.wordpress.api.test.WordpressServerHttpRequestHandler; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; public class WordpressUserOperationTest extends WordpressComponentTestSupport { @@ -93,6 +101,44 @@ public class WordpressUserOperationTest extends WordpressComponentTestSupport { MockEndpoint.assertIsSatisfied(context); } + @Test + public void testInsertUserFromJsonText() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultInsert"); + mock.expectedMessageCount(1); + + final User response = (User) template.requestBody("direct:insertUser", + "{\"username\": \"bdenbrough\", \"email\": \"[email protected]\", \"first_name\": \"Bill\"}"); + assertThat(response.getId(), is(3)); + assertThat(WordpressServerHttpRequestHandler.getLastRequestBody(), containsString("\"first_name\":\"Bill\"")); + + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testUpdateUserFromMap() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultUpdate"); + mock.expectedMessageCount(1); + + final User response = (User) template.requestBody("direct:updateUser", + Map.of("email", "[email protected]", "last_name", "Denbrough")); + assertThat(response.getId(), is(1)); + assertThat(WordpressServerHttpRequestHandler.getLastRequestBody(), containsString("\"last_name\":\"Denbrough\"")); + + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testUpdateUserWithInvalidBody() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:resultUpdate"); + mock.expectedMessageCount(0); + + CamelExecutionException e = assertThrows(CamelExecutionException.class, + () -> template.requestBody("direct:updateUser", 123)); + assertThat(e.getCause(), instanceOf(InvalidPayloadException.class)); + + MockEndpoint.assertIsSatisfied(context); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { diff --git a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/api/test/WordpressServerHttpRequestHandler.java b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/api/test/WordpressServerHttpRequestHandler.java index 9cb834cd42c7..b22e63825bf0 100644 --- a/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/api/test/WordpressServerHttpRequestHandler.java +++ b/components/camel-wordpress/src/test/java/org/apache/camel/component/wordpress/api/test/WordpressServerHttpRequestHandler.java @@ -27,8 +27,11 @@ import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.HttpRequestWrapper; import org.apache.hc.core5.http.protocol.HttpContext; @@ -42,6 +45,8 @@ public class WordpressServerHttpRequestHandler implements HttpRequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger(WordpressServerHttpRequestHandler.class); + private static volatile String lastRequestBody; + private final Map<String, String> mockResourceJsonResponse; public WordpressServerHttpRequestHandler(String mockResourceJsonResponse) { @@ -63,12 +68,27 @@ public class WordpressServerHttpRequestHandler implements HttpRequestHandler { response.setEntity(new StringEntity("Forbidden", ContentType.TEXT_PLAIN)); return; } + final HttpEntity entity = request.getEntity(); + if (entity != null) { + try { + lastRequestBody = EntityUtils.toString(entity); + } catch (ParseException e) { + throw new IOException(e); + } + } final String responseBody = IOUtils .toString(this.getClass().getResourceAsStream(mockResourceJsonResponse.get(requestWrapper.getMethod()))); response.setCode(HttpStatus.SC_OK); response.setEntity(new StringEntity(responseBody, ContentType.APPLICATION_JSON)); } + /** + * The body of the last request with a body (create or update), to check what the producer sent. + */ + public static String getLastRequestBody() { + return lastRequestBody; + } + private boolean authenticate(HttpRequestWrapper request) { // read operations don't need to authenticate if (request.getMethod().contentEquals("GET")) {
