Skip to content

Commit

Permalink
[incubator-kie-issues-1557] Marshalling POJO Input/output in user task (
Browse files Browse the repository at this point in the history
#3749)

* [incubator-kie-issues-1557] Marshalling POJO Input/output in user task
  • Loading branch information
elguardian authored Oct 31, 2024
1 parent f9d01d7 commit 1738ffb
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public List<GeneratedFile> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,19 +50,45 @@
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 {

@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<UserTaskView> list(@QueryParam("user") String user, @QueryParam("group") List<String> groups) {
Expand All @@ -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<String> groups,
@QueryParam("group") List<String> groups,
TransitionInfo transitionInfo) {
return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new);
}
Expand All @@ -102,18 +131,20 @@ public UserTaskView setOutput(
@PathParam("taskId") String taskId,
@QueryParam("user") String user,
@QueryParam("group") List<String> groups,
Map<String, Object> data) {
String body) throws Exception {
Map<String, Object> 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<String> groups,
Map<String, Object> data) {
String body) throws Exception {
Map<String, Object> data = mapper.readValue(body, Map.class);
return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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.*;
Expand All @@ -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<UserTaskView> list(@RequestParam("user") String user, @RequestParam("group") List<String> groups) {
return userTaskService.list(IdentityProviders.of(user, groups));
Expand All @@ -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<String> groups,
@RequestParam("group") List<String> 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)
Expand All @@ -84,22 +115,24 @@ public Collection<UserTaskTransitionView> transition(
@RequestParam("group") List<String> 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<String> groups,
@RequestBody Map<String, Object> data) {
@RequestBody String body) throws Exception {
Map<String, Object> 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<String> groups,
@RequestBody Map<String, Object> data) {
@RequestBody String body) throws Exception {
Map<String, Object> data = mapper.readValue(body, Map.class);
return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 1738ffb

Please sign in to comment.