diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimpleDeserializationProblemHandler.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimpleDeserializationProblemHandler.java new file mode 100644 index 00000000000..f3c447b6fc1 --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimpleDeserializationProblemHandler.java @@ -0,0 +1,33 @@ +/* + * 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.kie.kogito.usertask.impl.json; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; + +public class SimpleDeserializationProblemHandler extends DeserializationProblemHandler { + @Override + public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) throws IOException { + return baseType; + } +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimplePolymorphicTypeValidator.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimplePolymorphicTypeValidator.java new file mode 100644 index 00000000000..6940b1a8e1b --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/json/SimplePolymorphicTypeValidator.java @@ -0,0 +1,44 @@ +/* + * 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.kie.kogito.usertask.impl.json; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; + +public class SimplePolymorphicTypeValidator extends PolymorphicTypeValidator { + + private static final long serialVersionUID = 6608109163132613995L; + + @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; + } +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java index 9c2fa55b1ac..7fd6603aa04 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java @@ -198,7 +198,7 @@ public List generateUserTask() { ConstructorDeclaration declaration = clazzDeclaration.findFirst(ConstructorDeclaration.class).get(); declaration.setName(className); - String taskNodeName = (String) info.getParameter(NODE_NAME); + String taskNodeName = (String) info.getParameter("TaskName"); Expression taskNameExpression = taskNodeName != null ? new StringLiteralExpr(taskNodeName) : new NullLiteralExpr(); BlockStmt block = declaration.getBody(); 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..7369db60545 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; @@ -47,12 +50,24 @@ import org.kie.kogito.services.uow.UnitOfWorkExecutor; import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.impl.json.SimpleDeserializationProblemHandler; +import org.kie.kogito.usertask.impl.json.SimplePolymorphicTypeValidator; import org.kie.kogito.usertask.view.UserTaskView; import org.kie.kogito.usertask.view.UserTaskTransitionView; 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 +75,20 @@ public class UserTasksResource { @Inject UserTaskService userTaskService; + @Inject + ObjectMapper objectMapper; + + ObjectMapper mapper; + + @jakarta.annotation.PostConstruct + public void init() { + mapper = objectMapper.copy(); + SimpleModule module = new SimpleModule(); + mapper.addHandler(new SimpleDeserializationProblemHandler()); + mapper.registerModule(module); + mapper.activateDefaultTypingAsProperty(new SimplePolymorphicTypeValidator(), DefaultTyping.NON_FINAL, "@type"); + } + @GET @Produces(MediaType.APPLICATION_JSON) public List list(@QueryParam("user") String user, @QueryParam("group") List groups) { @@ -80,7 +109,7 @@ public UserTaskView find(@PathParam("taskId") String taskId, @QueryParam("user") public UserTaskView transition( @PathParam("taskId") String taskId, @QueryParam("user") String user, - @QueryParam("group") List groups, + @QueryParam("group") List groups, TransitionInfo transitionInfo) { return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @@ -102,18 +131,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..ea5b3873931 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; @@ -30,6 +31,8 @@ import org.kie.kogito.process.impl.Sig; import org.kie.kogito.services.uow.UnitOfWorkExecutor; import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.impl.json.SimpleDeserializationProblemHandler; +import org.kie.kogito.usertask.impl.json.SimplePolymorphicTypeValidator; import org.kie.kogito.usertask.view.UserTaskTransitionView; import org.kie.kogito.usertask.view.UserTaskView; import org.springframework.http.HttpStatus; @@ -47,6 +50,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 +74,20 @@ public class UserTasksResource { @Autowired UserTaskService userTaskService; + @Autowired + ObjectMapper objectMapper; + + ObjectMapper mapper; + + @jakarta.annotation.PostConstruct + public void init() { + mapper = objectMapper.copy(); + SimpleModule module = new SimpleModule(); + mapper.addHandler(new SimpleDeserializationProblemHandler()); + mapper.registerModule(module); + mapper.activateDefaultTypingAsProperty(new SimplePolymorphicTypeValidator(), 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)); @@ -72,9 +102,10 @@ public UserTaskView find(@PathVariable("taskId") String taskId, @RequestParam("u public UserTaskView transition( @PathVariable("taskId") String taskId, @RequestParam("user") String user, - @RequestParam("group") List groups, + @RequestParam("group") List groups, @RequestBody TransitionInfo transitionInfo) { - return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @GetMapping(value = "/{taskId}/transition", produces = MediaType.APPLICATION_JSON_VALUE) @@ -84,22 +115,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(); } + }