From 3367181fccc059ffed9b2e8fdc9e565aea8f2859 Mon Sep 17 00:00:00 2001 From: Enrique Gonzalez Martinez Date: Wed, 23 Oct 2024 17:30:34 +0200 Subject: [PATCH] [incubator-kie-issues-1557] Marshalling POJO Input/output in user task --- .../RestResourceUserTaskQuarkusTemplate.java | 58 ++++++++++++++-- .../RestResourceUserTaskSpringTemplate.java | 60 +++++++++++++++-- .../integrationtests/quarkus/TaskIT.java | 65 ++++++++++++++++++ .../integrationtests/springboot/TaskTest.java | 66 +++++++++++++++++++ 4 files changed, 241 insertions(+), 8 deletions(-) diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java index 8a7039263d0..c34c9f408e7 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java @@ -20,8 +20,11 @@ import java.util.Map; import java.util.List; +import java.io.IOException; import java.util.Collection; +import jakarta.inject.Inject; + import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; @@ -52,7 +55,17 @@ import org.kie.kogito.usertask.model.*; -import jakarta.inject.Inject; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity; +import com.fasterxml.jackson.databind.module.SimpleModule; @Path("/usertasks/instance") public class UserTasksResource { @@ -60,6 +73,41 @@ public class UserTasksResource { @Inject UserTaskService userTaskService; + ObjectMapper mapper; + + @Inject + public UserTasksResource(ObjectMapper objectMapper) { + mapper = objectMapper.copy(); + SimpleModule module = new SimpleModule(); + mapper.addHandler(new DeserializationProblemHandler() { + @Override + public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) throws IOException { + return baseType; + } + }); + mapper.registerModule(module); + + PolymorphicTypeValidator validator = new PolymorphicTypeValidator() { + + @Override + public Validity validateBaseType(MapperConfig config, JavaType baseType) { + return Validity.ALLOWED; + } + + @Override + public Validity validateSubClassName(MapperConfig config, JavaType baseType, String subClassName) throws JsonMappingException { + return Validity.ALLOWED; + } + + @Override + public Validity validateSubType(MapperConfig config, JavaType baseType, JavaType subType) throws JsonMappingException { + return Validity.ALLOWED; + } + + }; + mapper.activateDefaultTypingAsProperty(validator, DefaultTyping.NON_FINAL, "@type"); + } + @GET @Produces(MediaType.APPLICATION_JSON) public List list(@QueryParam("user") String user, @QueryParam("group") List groups) { @@ -102,18 +150,20 @@ public UserTaskView setOutput( @PathParam("taskId") String taskId, @QueryParam("user") String user, @QueryParam("group") List groups, - Map data) { + String body) throws Exception { + Map data = mapper.readValue(body, Map.class); return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @PUT @Path("/{taskId}/inputs") @Consumes(MediaType.APPLICATION_JSON) - public UserTaskView setOutput(@PathParam("id") String id, + public UserTaskView setInputs( @PathParam("taskId") String taskId, @QueryParam("user") String user, @QueryParam("group") List groups, - Map data) { + String body) throws Exception{ + Map data = mapper.readValue(body, Map.class); return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java index 4e0e7580276..dfdca586f93 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import java.io.IOException; import java.util.Collection; import org.jbpm.util.JsonSchemaUtil; @@ -47,6 +48,19 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity; +import com.fasterxml.jackson.databind.module.SimpleModule; + import org.springframework.beans.factory.annotation.Autowired; import org.kie.kogito.usertask.model.*; @@ -58,6 +72,42 @@ public class UserTasksResource { @Autowired UserTaskService userTaskService; + ObjectMapper mapper; + + @Autowired + public UserTasksResource(ObjectMapper objectMapper) { + mapper = objectMapper.copy(); + mapper = objectMapper.copy(); + SimpleModule module = new SimpleModule(); + mapper.addHandler(new DeserializationProblemHandler() { + @Override + public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) throws IOException { + return baseType; + } + }); + mapper.registerModule(module); + + PolymorphicTypeValidator validator = new PolymorphicTypeValidator() { + + @Override + public Validity validateBaseType(MapperConfig config, JavaType baseType) { + return Validity.ALLOWED; + } + + @Override + public Validity validateSubClassName(MapperConfig config, JavaType baseType, String subClassName) throws JsonMappingException { + return Validity.ALLOWED; + } + + @Override + public Validity validateSubType(MapperConfig config, JavaType baseType, JavaType subType) throws JsonMappingException { + return Validity.ALLOWED; + } + + }; + mapper.activateDefaultTypingAsProperty(validator, DefaultTyping.NON_FINAL, "@type"); + } + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List list(@RequestParam("user") String user, @RequestParam("group") List groups) { return userTaskService.list(IdentityProviders.of(user, groups)); @@ -84,22 +134,24 @@ public Collection transition( @RequestParam("group") List groups) { return userTaskService.allowedTransitions(taskId, IdentityProviders.of(user, groups)); } - + @PutMapping("/{taskId}/outputs") public UserTaskView setOutput( @PathVariable("taskId") String taskId, @RequestParam("user") String user, @RequestParam("group") List groups, - @RequestBody Map data) { + @RequestBody String body) throws Exception { + Map data = mapper.readValue(body, Map.class); return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @PutMapping("/{taskId}/inputs") - public UserTaskView setOutput(@PathVariable("id") String id, + public UserTaskView setInputs( @PathVariable("taskId") String taskId, @RequestParam("user") String user, @RequestParam("group") List groups, - @RequestBody Map data) { + @RequestBody String body) throws Exception { + Map data = mapper.readValue(body, Map.class); return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java index 0e5449be866..0388d2827ef 100644 --- a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java +++ b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java @@ -31,6 +31,10 @@ import org.kie.kogito.usertask.model.AttachmentInfo; import org.kie.kogito.usertask.model.CommentInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; + import io.quarkus.test.junit.QuarkusIntegrationTest; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -39,6 +43,7 @@ import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchema; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; @QuarkusIntegrationTest @@ -105,6 +110,66 @@ void testJsonSchema() throws IOException { } } + @Test + public void testInputOutputsViaJsonTypeProperty() throws Exception { + Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null); + + given() + .contentType(ContentType.JSON) + .when() + .body(Collections.singletonMap("traveller", traveller)) + .post("/approvals") + .then() + .statusCode(201) + .extract() + .path("id"); + + String taskId = given() + .contentType(ContentType.JSON) + .queryParam("user", "admin") + .queryParam("group", "managers") + .when() + .get("/usertasks/instance") + .then() + .statusCode(200) + .extract() + .path("[0].id"); + + traveller = new Traveller("pepe2", "rubiales2", "pepe.rubiales@gmail.com", "Spanish2", null); + ObjectMapper mapper = new ObjectMapper(); + mapper.activateDefaultTypingAsProperty(BasicPolymorphicTypeValidator.builder().build(), DefaultTyping.NON_FINAL, "@type"); + String jsonBody = mapper.writeValueAsString(Map.of("traveller", traveller)); + given().contentType(ContentType.JSON) + .when() + .queryParam("user", "admin") + .queryParam("group", "managers") + .pathParam("taskId", taskId) + .body(jsonBody) + .put("/usertasks/instance/{taskId}/inputs") + .then() + .log().body() + .statusCode(200) + .body("inputs.traveller.firstName", is(traveller.getFirstName())) + .body("inputs.traveller.lastName", is(traveller.getLastName())) + .body("inputs.traveller.email", is(traveller.getEmail())) + .body("inputs.traveller.nationality", is(traveller.getNationality())); + + given().contentType(ContentType.JSON) + .when() + .queryParam("user", "admin") + .queryParam("group", "managers") + .pathParam("taskId", taskId) + .body(jsonBody) + .put("/usertasks/instance/{taskId}/outputs") + .then() + .log().body() + .statusCode(200) + .body("outputs.traveller.firstName", is(traveller.getFirstName())) + .body("outputs.traveller.lastName", is(traveller.getLastName())) + .body("outputs.traveller.email", is(traveller.getEmail())) + .body("outputs.traveller.nationality", is(traveller.getNationality())); + } + @Test void testSaveTask() { Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish"); diff --git a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java index f00a5267e0f..66821bd1c0b 100644 --- a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java +++ b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java @@ -40,12 +40,17 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; + import io.restassured.http.ContentType; import static io.restassured.RestAssured.given; import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(SpringExtension.class) @@ -111,6 +116,66 @@ void testJsonSchemaFiles() { } } + @Test + public void testInputOutputsViaJsonTypeProperty() throws Exception { + Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null); + + given() + .contentType(ContentType.JSON) + .when() + .body(Collections.singletonMap("traveller", traveller)) + .post("/approvals") + .then() + .statusCode(201) + .extract() + .path("id"); + + String taskId = given() + .contentType(ContentType.JSON) + .queryParam("user", "admin") + .queryParam("group", "managers") + .when() + .get("/usertasks/instance") + .then() + .statusCode(200) + .extract() + .path("[0].id"); + + traveller = new Traveller("pepe2", "rubiales2", "pepe.rubiales@gmail.com", "Spanish2", null); + ObjectMapper mapper = new ObjectMapper(); + mapper.activateDefaultTypingAsProperty(BasicPolymorphicTypeValidator.builder().build(), DefaultTyping.NON_FINAL, "@type"); + String jsonBody = mapper.writeValueAsString(Map.of("traveller", traveller)); + given().contentType(ContentType.JSON) + .when() + .queryParam("user", "admin") + .queryParam("group", "managers") + .pathParam("taskId", taskId) + .body(jsonBody) + .put("/usertasks/instance/{taskId}/inputs") + .then() + .log().body() + .statusCode(200) + .body("inputs.traveller.firstName", is(traveller.getFirstName())) + .body("inputs.traveller.lastName", is(traveller.getLastName())) + .body("inputs.traveller.email", is(traveller.getEmail())) + .body("inputs.traveller.nationality", is(traveller.getNationality())); + + given().contentType(ContentType.JSON) + .when() + .queryParam("user", "admin") + .queryParam("group", "managers") + .pathParam("taskId", taskId) + .body(jsonBody) + .put("/usertasks/instance/{taskId}/outputs") + .then() + .log().body() + .statusCode(200) + .body("outputs.traveller.firstName", is(traveller.getFirstName())) + .body("outputs.traveller.lastName", is(traveller.getLastName())) + .body("outputs.traveller.email", is(traveller.getEmail())) + .body("outputs.traveller.nationality", is(traveller.getNationality())); + } + @Test void testCommentAndAttachment() { Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null); @@ -371,4 +436,5 @@ void testUpdateTaskInfo() { assertThat(downTaskInfo.getInputParams()).isNotNull(); assertThat(downTaskInfo.getInputParams().get("traveller")).isNull(); } + }