diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java new file mode 100644 index 00000000000..26923edcd21 --- /dev/null +++ b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/AbstractExceptionsHandler.java @@ -0,0 +1,142 @@ +/* + * 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.resource.exceptions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kie.kogito.handler.ExceptionHandler; +import org.kie.kogito.internal.process.runtime.MessageException; +import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException; +import org.kie.kogito.internal.process.workitem.InvalidTransitionException; +import org.kie.kogito.internal.process.workitem.NotAuthorizedException; +import org.kie.kogito.internal.process.workitem.WorkItemExecutionException; +import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException; +import org.kie.kogito.process.NodeInstanceNotFoundException; +import org.kie.kogito.process.NodeNotFoundException; +import org.kie.kogito.process.ProcessInstanceDuplicatedException; +import org.kie.kogito.process.ProcessInstanceExecutionException; +import org.kie.kogito.process.ProcessInstanceNotFoundException; +import org.kie.kogito.process.VariableViolationException; +import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException; +import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; +import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.nodeInstanceNotFoundMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.nodeNotFoundMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceDuplicatedMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceExecutionMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.processInstanceNotFoundExceptionMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.variableViolationMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.workItemExecutionMessageException; +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.workItemNotFoundMessageException; +import static org.kie.kogito.resource.exceptions.RestExceptionHandler.newExceptionHandler; + +public abstract class AbstractExceptionsHandler { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractExceptionsHandler.class); + + RestExceptionHandler DEFAULT_HANDLER = newExceptionHandler(Exception.class, this::badRequest); + + private Map, RestExceptionHandler> mapper; + + private List errorHandlers; + + protected AbstractExceptionsHandler() { + this(Collections.emptyList()); + } + + protected AbstractExceptionsHandler(Iterable errorHandlers) { + List> handlers = List.> of( + newExceptionHandler(InvalidLifeCyclePhaseException.class, this::badRequest), + newExceptionHandler(UserTaskTransitionException.class, this::badRequest), + newExceptionHandler(UserTaskInstanceNotFoundException.class, this::notFound), + newExceptionHandler(UserTaskInstanceNotAuthorizedException.class, this::forbidden), + newExceptionHandler(InvalidTransitionException.class, this::badRequest), + newExceptionHandler(NodeInstanceNotFoundException.class, nodeInstanceNotFoundMessageException(), this::notFound), + newExceptionHandler(NodeNotFoundException.class, nodeNotFoundMessageException(), this::notFound), + newExceptionHandler(NotAuthorizedException.class, this::forbidden), + newExceptionHandler(ProcessInstanceDuplicatedException.class, processInstanceDuplicatedMessageException(), this::conflict), + newExceptionHandler(ProcessInstanceExecutionException.class, processInstanceExecutionMessageException(), this::internalError), + newExceptionHandler(ProcessInstanceNotFoundException.class, processInstanceNotFoundExceptionMessageException(), this::notFound), + newExceptionHandler(WorkItemNotFoundException.class, workItemNotFoundMessageException(), this::notFound), + newExceptionHandler(VariableViolationException.class, variableViolationMessageException(), this::badRequest), + newExceptionHandler(WorkItemExecutionException.class, workItemExecutionMessageException(), this::fromErrorCode), + newExceptionHandler(IllegalArgumentException.class, this::badRequest), + newExceptionHandler(MessageException.class, this::badRequest)); + + this.mapper = new HashMap<>(); + for (RestExceptionHandler handler : handlers) { + this.mapper.put(handler.getType(), handler); + } + this.errorHandlers = new ArrayList<>(); + errorHandlers.iterator().forEachRemaining(this.errorHandlers::add); + + } + + private T fromErrorCode(ExceptionBodyMessage message) { + switch (message.getErrorCode()) { + case "400": + return badRequest(message); + case "403": + return forbidden(message); + case "404": + return notFound(message); + case "409": + return conflict(message); + default: + return internalError(message); + } + } + + protected abstract T badRequest(ExceptionBodyMessage body); + + protected abstract T conflict(ExceptionBodyMessage body); + + protected abstract T internalError(ExceptionBodyMessage body); + + protected abstract T notFound(ExceptionBodyMessage body); + + protected abstract T forbidden(ExceptionBodyMessage body); + + public T mapException(Exception exceptionThrown) { + + var handler = mapper.getOrDefault(exceptionThrown.getClass(), DEFAULT_HANDLER); + ExceptionBodyMessage message = handler.getContent(exceptionThrown); + + Throwable rootCause = exceptionThrown.getCause(); + while (rootCause != null) { + if (mapper.containsKey(rootCause.getClass())) { + handler = mapper.get(rootCause.getClass()); + message.merge(handler.getContent(rootCause)); + } + rootCause = rootCause.getCause(); + } + // we invoked the error handlers + errorHandlers.forEach(e -> e.handle(exceptionThrown)); + T response = handler.buildResponse(message); + LOG.debug("mapping exception {} with response {}", exceptionThrown, message.getBody()); + return response; + } +} diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java deleted file mode 100644 index c326eeebe76..00000000000 --- a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.resource.exceptions; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.kie.kogito.internal.process.runtime.MessageException; -import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException; -import org.kie.kogito.internal.process.workitem.InvalidTransitionException; -import org.kie.kogito.internal.process.workitem.NotAuthorizedException; -import org.kie.kogito.internal.process.workitem.WorkItemExecutionException; -import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException; -import org.kie.kogito.process.NodeInstanceNotFoundException; -import org.kie.kogito.process.NodeNotFoundException; -import org.kie.kogito.process.ProcessInstanceDuplicatedException; -import org.kie.kogito.process.ProcessInstanceExecutionException; -import org.kie.kogito.process.ProcessInstanceNotFoundException; -import org.kie.kogito.process.VariableViolationException; -import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException; -import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; -import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException; - -public abstract class BaseExceptionsHandler { - - public static final String MESSAGE = "message"; - public static final String PROCESS_INSTANCE_ID = "processInstanceId"; - private static final String TASK_ID = "taskId"; - public static final String VARIABLE = "variable"; - public static final String NODE_INSTANCE_ID = "nodeInstanceId"; - public static final String NODE_ID = "nodeId"; - public static final String FAILED_NODE_ID = "failedNodeId"; - public static final String ID = "id"; - private final Map, FunctionHolder> mapper; - - private static class FunctionHolder { - private final Function contentGenerator; - private final Function> responseGenerator; - - public FunctionHolder(Function contentGenerator, Function> responseGenerator) { - this.contentGenerator = contentGenerator; - this.responseGenerator = responseGenerator; - } - - public Function getContentGenerator() { - return contentGenerator; - } - - public Function> getResponseGenerator() { - return responseGenerator; - } - } - - private final FunctionHolder defaultHolder = new FunctionHolder<>(ex -> ex, ex -> BaseExceptionsHandler.this::internalError); - private final FunctionHolder messageFunctionHolder = new FunctionHolder<>(ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest); - - protected BaseExceptionsHandler() { - mapper = new HashMap<>(); - mapper.put(InvalidLifeCyclePhaseException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); - - mapper.put(UserTaskTransitionException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); - - mapper.put(UserTaskInstanceNotFoundException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::notFound)); - - mapper.put(UserTaskInstanceNotAuthorizedException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::forbidden)); - - mapper.put(InvalidTransitionException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); - - mapper.put(NodeInstanceNotFoundException.class, new FunctionHolder<>( - ex -> { - NodeInstanceNotFoundException exception = (NodeInstanceNotFoundException) ex; - Map response = new HashMap<>(); - response.put(MESSAGE, exception.getMessage()); - response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); - response.put(NODE_INSTANCE_ID, exception.getNodeInstanceId()); - return response; - }, ex -> BaseExceptionsHandler.this::notFound)); - - mapper.put(NodeNotFoundException.class, new FunctionHolder<>( - ex -> { - NodeNotFoundException exception = (NodeNotFoundException) ex; - Map response = new HashMap<>(); - response.put(MESSAGE, exception.getMessage()); - response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); - response.put(NODE_ID, exception.getNodeId()); - return response; - }, ex -> BaseExceptionsHandler.this::notFound)); - - mapper.put(NotAuthorizedException.class, new FunctionHolder<>( - ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::forbidden)); - - mapper.put(ProcessInstanceDuplicatedException.class, new FunctionHolder<>( - ex -> { - ProcessInstanceDuplicatedException exception = (ProcessInstanceDuplicatedException) ex; - Map response = new HashMap<>(); - response.put(MESSAGE, exception.getMessage()); - response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); - return response; - }, ex -> BaseExceptionsHandler.this::conflict)); - - mapper.put(ProcessInstanceExecutionException.class, new FunctionHolder<>( - ex -> { - ProcessInstanceExecutionException exception = (ProcessInstanceExecutionException) ex; - Map response = new HashMap<>(); - response.put(ID, exception.getProcessInstanceId()); - response.put(FAILED_NODE_ID, exception.getFailedNodeId()); - response.put(MESSAGE, exception.getErrorMessage()); - return response; - }, ex -> BaseExceptionsHandler.this::internalError)); - - mapper.put(ProcessInstanceNotFoundException.class, new FunctionHolder<>( - ex -> { - ProcessInstanceNotFoundException exception = (ProcessInstanceNotFoundException) ex; - Map response = new HashMap<>(); - response.put(MESSAGE, exception.getMessage()); - response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); - return response; - }, ex -> BaseExceptionsHandler.this::notFound)); - - mapper.put(WorkItemNotFoundException.class, new FunctionHolder<>(ex -> { - WorkItemNotFoundException exception = (WorkItemNotFoundException) ex; - return Map.of(MESSAGE, exception.getMessage(), TASK_ID, exception.getWorkItemId()); - }, ex -> BaseExceptionsHandler.this::notFound)); - - mapper.put(VariableViolationException.class, new FunctionHolder<>( - ex -> { - VariableViolationException exception = (VariableViolationException) ex; - Map response = new HashMap<>(); - response.put(MESSAGE, exception.getMessage() + " : " + exception.getErrorMessage()); - response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); - response.put(VARIABLE, exception.getVariableName()); - return response; - }, ex -> BaseExceptionsHandler.this::badRequest)); - - mapper.put(WorkItemExecutionException.class, new FunctionHolder<>( - ex -> Map.of(MESSAGE, ex.getMessage()), - ex -> fromErrorCode(((WorkItemExecutionException) ex).getErrorCode()))); - mapper.put(IllegalArgumentException.class, messageFunctionHolder); - mapper.put(MessageException.class, messageFunctionHolder); - } - - private Function fromErrorCode(String errorCode) { - switch (errorCode) { - case "400": - return this::badRequest; - case "403": - return this::forbidden; - case "404": - return this::notFound; - case "409": - return this::conflict; - default: - return this::internalError; - } - } - - protected abstract T badRequest(R body); - - protected abstract T conflict(R body); - - protected abstract T internalError(R body); - - protected abstract T notFound(R body); - - protected abstract T forbidden(R body); - - public T mapException(R exception) { - FunctionHolder holder = (FunctionHolder) mapper.getOrDefault(exception.getClass(), defaultHolder); - U body = holder.getContentGenerator().apply(exception); - Throwable rootCause = exception.getCause(); - while (rootCause != null) { - if (mapper.containsKey(rootCause.getClass())) { - holder = (FunctionHolder) mapper.get(rootCause.getClass()); - exception = (R) rootCause; - } - rootCause = rootCause.getCause(); - } - return holder.getResponseGenerator().apply(exception).apply(body); - } -} diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java new file mode 100644 index 00000000000..b491b111f40 --- /dev/null +++ b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessage.java @@ -0,0 +1,57 @@ +/* + * 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.resource.exceptions; + +import java.util.HashMap; +import java.util.Map; + +public class ExceptionBodyMessage { + + public static final String MESSAGE = "message"; + public static final String PROCESS_INSTANCE_ID = "processInstanceId"; + public static final String TASK_ID = "taskId"; + public static final String VARIABLE = "variable"; + public static final String NODE_INSTANCE_ID = "nodeInstanceId"; + public static final String NODE_ID = "nodeId"; + public static final String FAILED_NODE_ID = "failedNodeId"; + public static final String ID = "id"; + public static final String ERROR_CODE = "errorCode"; + + private Map body; + + public ExceptionBodyMessage() { + body = new HashMap<>(); + } + + public ExceptionBodyMessage(Map body) { + this.body = new HashMap<>(body); + } + + public Map getBody() { + return body; + } + + public String getErrorCode() { + return body.getOrDefault(ERROR_CODE, ""); + } + + public void merge(ExceptionBodyMessage content) { + this.body.putAll(content.body); + } +} diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java new file mode 100644 index 00000000000..0bc1e6fd817 --- /dev/null +++ b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionBodyMessageFunctions.java @@ -0,0 +1,124 @@ +/* + * 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.resource.exceptions; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.kie.kogito.internal.process.workitem.WorkItemExecutionException; +import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException; +import org.kie.kogito.process.NodeInstanceNotFoundException; +import org.kie.kogito.process.NodeNotFoundException; +import org.kie.kogito.process.ProcessInstanceDuplicatedException; +import org.kie.kogito.process.ProcessInstanceExecutionException; +import org.kie.kogito.process.ProcessInstanceNotFoundException; +import org.kie.kogito.process.VariableViolationException; + +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessage.ERROR_CODE; + +public class ExceptionBodyMessageFunctions { + + public static final String MESSAGE = "message"; + public static final String PROCESS_INSTANCE_ID = "processInstanceId"; + public static final String TASK_ID = "taskId"; + public static final String VARIABLE = "variable"; + public static final String NODE_INSTANCE_ID = "nodeInstanceId"; + public static final String NODE_ID = "nodeId"; + public static final String FAILED_NODE_ID = "failedNodeId"; + public static final String ID = "id"; + + public static Function defaultMessageException() { + return ex -> new ExceptionBodyMessage(Collections.singletonMap(MESSAGE, ex.getMessage())); + } + + public static Function nodeInstanceNotFoundMessageException() { + return ex -> { + Map response = new HashMap<>(); + response.put(MESSAGE, ex.getMessage()); + response.put(PROCESS_INSTANCE_ID, ex.getProcessInstanceId()); + response.put(NODE_INSTANCE_ID, ex.getNodeInstanceId()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function nodeNotFoundMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(MESSAGE, exception.getMessage()); + response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); + response.put(NODE_ID, exception.getNodeId()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function processInstanceDuplicatedMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(MESSAGE, exception.getMessage()); + response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function processInstanceExecutionMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(ID, exception.getProcessInstanceId()); + response.put(FAILED_NODE_ID, exception.getFailedNodeId()); + response.put(MESSAGE, exception.getErrorMessage()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function processInstanceNotFoundExceptionMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(MESSAGE, exception.getMessage()); + response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function workItemNotFoundMessageException() { + return exception -> { + return new ExceptionBodyMessage(Map.of(MESSAGE, exception.getMessage(), TASK_ID, exception.getWorkItemId())); + }; + } + + public static Function workItemExecutionMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(MESSAGE, exception.getMessage()); + response.put(ERROR_CODE, exception.getErrorCode()); + return new ExceptionBodyMessage(response); + }; + } + + public static Function variableViolationMessageException() { + return exception -> { + Map response = new HashMap<>(); + response.put(MESSAGE, exception.getMessage() + " : " + exception.getErrorMessage()); + response.put(PROCESS_INSTANCE_ID, exception.getProcessInstanceId()); + response.put(VARIABLE, exception.getVariableName()); + return new ExceptionBodyMessage(response); + }; + } +} diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java new file mode 100644 index 00000000000..083e4efe834 --- /dev/null +++ b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/RestExceptionHandler.java @@ -0,0 +1,58 @@ +/* + * 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.resource.exceptions; + +import java.util.function.Function; + +import static org.kie.kogito.resource.exceptions.ExceptionBodyMessageFunctions.defaultMessageException; + +public class RestExceptionHandler { + private final Function messageConverter; + + private final Function responseConverter; + + private Class type; + + public RestExceptionHandler(Class type, Function messageConverter, Function responseConverter) { + this.type = type; + this.messageConverter = messageConverter; + this.responseConverter = responseConverter; + } + + public Class getType() { + return type; + } + + public ExceptionBodyMessage getContent(Throwable exception) { + return messageConverter.apply(getType().cast(exception)); + } + + public RESPONSE buildResponse(ExceptionBodyMessage exceptionBodyMessage) { + return responseConverter.apply(exceptionBodyMessage); + } + + public static RestExceptionHandler newExceptionHandler(Class type, Function contentGenerator, + Function responseGenerator) { + return new RestExceptionHandler(type, contentGenerator, responseGenerator); + } + + public static RestExceptionHandler newExceptionHandler(Class type, Function responseGenerator) { + return new RestExceptionHandler(type, defaultMessageException(), responseGenerator); + } +} \ No newline at end of file diff --git a/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java b/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java index 01f244915df..bf8f28fc314 100644 --- a/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java +++ b/addons/common/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/BaseExceptionHandlerTest.java @@ -40,7 +40,7 @@ @ExtendWith(MockitoExtension.class) class BaseExceptionHandlerTest { - private BaseExceptionsHandler tested; + private AbstractExceptionsHandler tested; @Mock private Object badRequestResponse; @@ -59,29 +59,29 @@ class BaseExceptionHandlerTest { @BeforeEach void setUp() { - tested = spy(new BaseExceptionsHandler() { + tested = spy(new AbstractExceptionsHandler() { @Override - protected Object badRequest(Object body) { + protected Object badRequest(ExceptionBodyMessage body) { return badRequestResponse; } @Override - protected Object conflict(Object body) { + protected Object conflict(ExceptionBodyMessage body) { return conflictResponse; } @Override - protected Object internalError(Object body) { + protected Object internalError(ExceptionBodyMessage body) { return internalErrorResponse; } @Override - protected Object notFound(Object body) { + protected Object notFound(ExceptionBodyMessage body) { return notFoundResponse; } @Override - protected Object forbidden(Object body) { + protected Object forbidden(ExceptionBodyMessage body) { return forbiddenResponse; } }); diff --git a/api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java b/api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java new file mode 100644 index 00000000000..7bfbdf37ef0 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/handler/ExceptionHandler.java @@ -0,0 +1,25 @@ +/* + * 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.handler; + +public interface ExceptionHandler { + + void handle(Exception th); + +} diff --git a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java index 644f9fe25a7..72180a45c1e 100755 --- a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java +++ b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/ProcessHandler.java @@ -107,6 +107,8 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + public class ProcessHandler extends BaseAbstractHandler implements Handler { private static final Logger logger = LoggerFactory.getLogger(ProcessHandler.class); @@ -412,7 +414,7 @@ public static void linkConnections(RuleFlowProcess process, NodeContainer nodeCo result.setMetaData("bendpoints", connection.getBendpoints()); result.setMetaData(Metadata.UNIQUE_ID, connection.getId()); - if (source instanceof NodeImpl nodeImpl && Boolean.parseBoolean((String) process.getMetaData().get("jbpm.enable.multi.con"))) { + if (source instanceof NodeImpl nodeImpl && WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(process)) { Constraint constraint = buildConstraint(connection, nodeImpl); if (constraint != null) { nodeImpl.addConstraint(new ConnectionRef(connection.getId(), target.getId(), org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE), diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java index 0ea5dee5509..13a21788e1b 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/impl/NodeImpl.java @@ -40,6 +40,8 @@ import org.kie.api.definition.process.NodeContainer; import org.kie.api.definition.process.WorkflowElementIdentifier; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a node. */ @@ -297,7 +299,7 @@ public Connection getFrom() { if (list.size() == 1) { return list.get(0); } - if (Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { return list.get(0); } else { throw new IllegalArgumentException( @@ -317,7 +319,7 @@ public Connection getTo() { if (list.size() == 1) { return list.get(0); } - if (Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { return list.get(0); } else { throw new IllegalArgumentException( diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java index 9144d42833a..53f74b41436 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/ActionNode.java @@ -27,6 +27,8 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of an action node. * @@ -53,7 +55,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -68,7 +70,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java index ae88f439561..222f13d1c75 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EndNode.java @@ -22,6 +22,8 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of an end node. * @@ -58,7 +60,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java index 42d216f4835..0ce70b86f88 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/EventNode.java @@ -28,6 +28,8 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + public class EventNode extends ExtendedNodeImpl implements EventNodeInterface { private static final long serialVersionUID = 510l; @@ -104,7 +106,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -119,7 +121,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java index 7ffabedcda2..1a1ba697ff6 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/MilestoneNode.java @@ -24,6 +24,8 @@ import org.kie.api.definition.process.Connection; import org.kie.api.runtime.process.ProcessContext; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a milestone node. */ @@ -59,7 +61,7 @@ public void validateAddIncomingConnection(final String type, final Connection co if (!Node.CONNECTION_DEFAULT_TYPE.equals(type)) { throwValidationException(connection, "only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throwValidationException(connection, "cannot have more than one incoming connection!"); } } @@ -70,7 +72,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co if (!Node.CONNECTION_DEFAULT_TYPE.equals(type)) { throwValidationException(connection, "only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throwValidationException(connection, "cannot have more than one outgoing connection!"); } } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java index 3d129f3b228..b7d9f872073 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/RuleSetNode.java @@ -35,6 +35,7 @@ import org.kie.api.runtime.KieRuntime; import org.kie.kogito.decision.DecisionModel; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; import static org.jbpm.workflow.instance.rule.RuleType.DRL_LANG; /** @@ -105,7 +106,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -120,7 +121,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java index fe2114905c0..77c4a8e7179 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/Split.java @@ -26,6 +26,8 @@ import org.jbpm.workflow.core.impl.NodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a split node. * @@ -143,7 +145,7 @@ public void validateAddIncomingConnection(final String type, final Connection co + "] only accepts default incoming connection type!"); } - if (!getIncomingConnections(Node.CONNECTION_DEFAULT_TYPE).isEmpty() && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (!getIncomingConnections(Node.CONNECTION_DEFAULT_TYPE).isEmpty() && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java index 3743bbd1573..e6e43a2bb35 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/StartNode.java @@ -27,6 +27,8 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a start node. * @@ -92,7 +94,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co throw new IllegalArgumentException( "A start node [" + this.getUniqueId() + ", " + this.getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "A start node [" + this.getUniqueId() + ", " + this.getName() + "] cannot have more than one outgoing connection!"); } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java index 77f873ddc76..7dcad360219 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java @@ -27,6 +27,8 @@ import org.jbpm.workflow.core.Node; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a sub-flow node. * @@ -77,7 +79,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -92,7 +94,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java index e8ce9a54957..5096a47f0ee 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/TimerNode.java @@ -23,6 +23,8 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + public class TimerNode extends ExtendedNodeImpl { private static final long serialVersionUID = 510l; @@ -45,7 +47,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -60,7 +62,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java index d7cd3c06c19..5eb00999d0c 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/WorkItemNode.java @@ -28,6 +28,8 @@ import org.jbpm.workflow.core.Node; import org.kie.api.definition.process.Connection; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Default implementation of a task node. * @@ -66,7 +68,7 @@ public void validateAddIncomingConnection(final String type, final Connection co "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] only accepts default incoming connection type!"); } - if (getFrom() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getFrom() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getTo().getUniqueId() + ", " + connection.getTo().getName() + "] cannot have more than one incoming connection!"); @@ -81,7 +83,7 @@ public void validateAddOutgoingConnection(final String type, final Connection co "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] only accepts default outgoing connection type!"); } - if (getTo() != null && !Boolean.parseBoolean((String) getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (getTo() != null && !WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcess())) { throw new IllegalArgumentException( "This type of node [" + connection.getFrom().getUniqueId() + ", " + connection.getFrom().getName() + "] cannot have more than one outgoing connection!"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java new file mode 100644 index 00000000000..cf5c1bd611f --- /dev/null +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/WorkflowProcessParameters.java @@ -0,0 +1,56 @@ +/* + * 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.jbpm.workflow.instance; + +import java.util.function.Function; + +public class WorkflowProcessParameters { + + /** + * Allows activities to have multiple outgoing connections + */ + public static final WorkflowProcessParameter WORKFLOW_PARAM_MULTIPLE_CONNECTIONS = newBooleanParameter("jbpm.enable.multi.con"); + public static final WorkflowProcessParameter WORKFLOW_PARAM_TRANSACTIONS = newBooleanParameter("jbpm.transactions.enable"); + + public static WorkflowProcessParameter newStringParameter(String name) { + return new WorkflowProcessParameter(name, Function.identity()); + } + + public static WorkflowProcessParameter newBooleanParameter(String name) { + return new WorkflowProcessParameter(name, Boolean::parseBoolean); + } + + public static class WorkflowProcessParameter { + private String name; + private Function converter; + + WorkflowProcessParameter(String name, Function converter) { + this.name = name; + this.converter = converter; + } + + public String getName() { + return name; + } + + public T get(org.kie.api.definition.process.Process workflowProcess) { + return converter.apply((String) workflowProcess.getMetaData().get(name)); + } + } +} diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java index fb73d73c005..fd78e89418f 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java @@ -61,12 +61,15 @@ import org.kie.kogito.internal.process.runtime.KogitoNodeInstanceContainer; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; +import org.kie.kogito.process.ProcessInstanceExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.jbpm.ruleflow.core.Metadata.HIDDEN; import static org.jbpm.ruleflow.core.Metadata.INCOMING_CONNECTION; import static org.jbpm.ruleflow.core.Metadata.OUTGOING_CONNECTION; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS; import static org.kie.kogito.internal.process.runtime.KogitoProcessInstance.STATE_ACTIVE; /** @@ -248,10 +251,15 @@ public final void trigger(KogitoNodeInstance from, String type) { try { internalTrigger(from, type); } catch (Exception e) { - logger.debug("Node instance causing process instance error in id {}", this.getStringId(), e); - captureError(e); + if (!WORKFLOW_PARAM_TRANSACTIONS.get(getProcessInstance().getProcess())) { + logger.error("Node instance causing process instance error in id {} in a non transactional environment", this.getStringId()); + captureError(e); + return; + } else { + logger.error("Node instance causing process instance error in id {} in a transactional environment (Wrapping)", this.getStringId()); + throw new ProcessInstanceExecutionException(this.getProcessInstance().getId(), this.getNodeDefinitionId(), e.getMessage(), e); + } // stop after capturing error - return; } if (!hidden) { ((InternalProcessRuntime) kruntime.getProcessRuntime()) @@ -260,8 +268,6 @@ public final void trigger(KogitoNodeInstance from, String type) { } protected void captureError(Exception e) { - logger.error("capture error", e); - e.printStackTrace(); getProcessInstance().setErrorState(this, e); } @@ -317,7 +323,7 @@ public void triggerCompleted(String type, boolean remove) { List connections = null; if (node != null) { - if (Boolean.parseBoolean((String) getProcessInstance().getProcess().getMetaData().get("jbpm.enable.multi.con")) && !((NodeImpl) node).getConstraints().isEmpty()) { + if (WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcessInstance().getProcess()) && !((NodeImpl) node).getConstraints().isEmpty()) { int priority; connections = ((NodeImpl) node).getDefaultOutgoingConnections(); boolean found = false; diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java index 48a7276e269..e185f6d0197 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java @@ -100,6 +100,7 @@ import org.kie.kogito.process.EventDescription; import org.kie.kogito.process.NamedDataType; import org.kie.kogito.process.ProcessInstance; +import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.flexible.AdHocFragment; import org.kie.kogito.process.flexible.ItemDescription; import org.kie.kogito.process.flexible.Milestone; @@ -1316,6 +1317,15 @@ public void internalSetErrorMessage(String errorMessage) { this.errorCause = Optional.empty(); } + public void internalSetError(ProcessInstanceExecutionException e) { + this.nodeIdInError = e.getFailedNodeId(); + Throwable rootException = getRootException(e); + this.errorMessage = rootException instanceof MessageException ? rootException.getMessage() : rootException.getClass().getCanonicalName() + " - " + rootException.getMessage(); + this.errorCause = Optional.of(e); + setState(STATE_ERROR); + ((InternalProcessRuntime) getKnowledgeRuntime().getProcessRuntime()).getProcessEventSupport().fireOnError(this, null, getKnowledgeRuntime(), e); + } + @Override public Collection adHocFragments() { return Stream.of(getNodeContainer().getNodes()) diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java index 9b17022e460..4749aee86ed 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java @@ -54,6 +54,8 @@ import org.mvel2.integration.VariableResolver; import org.mvel2.integration.impl.SimpleValueResolver; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_MULTIPLE_CONNECTIONS; + /** * Runtime counterpart of a for each node. */ @@ -297,7 +299,7 @@ public void internalTrigger(KogitoNodeInstance from, String type) { ((NodeInstanceContainer) getNodeInstanceContainer()).removeNodeInstance(this); if (getForEachNode().isWaitForCompletion()) { - if (!Boolean.parseBoolean((String) getForEachNode().getProcess().getMetaData().get("jbpm.enable.multi.con"))) { + if (!WORKFLOW_PARAM_MULTIPLE_CONNECTIONS.get(getProcessInstance().getProcess())) { triggerConnection(getForEachJoinNode().getTo()); } else { List connections = getForEachJoinNode().getOutgoingConnections(Node.CONNECTION_DEFAULT_TYPE); diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java index 9c84008fc5c..7737a67acac 100644 --- a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java +++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/process/ProcessGenerationIT.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -82,6 +83,7 @@ import static org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE; import static org.jbpm.workflow.core.impl.ExtendedNodeImpl.EVENT_NODE_ENTER; import static org.jbpm.workflow.core.impl.ExtendedNodeImpl.EVENT_NODE_EXIT; +import static org.jbpm.workflow.instance.WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -108,7 +110,7 @@ public class ProcessGenerationIT extends AbstractCodegenIT { private static final Collection IGNORED_PROCESS_META = - Arrays.asList("Definitions", "BPMN.Connections", "BPMN.Associations", "ItemDefinitions"); + Arrays.asList("Definitions", "BPMN.Connections", "BPMN.Associations", "ItemDefinitions", WORKFLOW_PARAM_TRANSACTIONS.getName()); private static final Path BASE_PATH = Paths.get("src/test/resources"); static Stream processesProvider() throws IOException { @@ -405,13 +407,15 @@ private static void assertMetadata(Map expected, Map ignoredKeys == null || !ignoredKeys.contains(k)) - .count()); + Predicate precicateIgnoredKeys = Predicate.not(ignoredKeys::contains); + + List currentKeys = current.keySet().stream().filter(precicateIgnoredKeys).toList(); + List expectedKeys = expected.keySet().stream().filter(precicateIgnoredKeys).toList(); + assertThat(currentKeys).containsExactlyElementsOf(expectedKeys); + expected.keySet() .stream() - .filter(k -> ignoredKeys == null || !ignoredKeys.contains(k)) + .filter(precicateIgnoredKeys) .forEach(k -> assertThat(current).as("Metadata " + k).containsEntry(k, expected.get(k))); } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java index 14bab62579a..20c7189198a 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java @@ -48,6 +48,8 @@ import org.jbpm.compiler.xml.core.SemanticModules; import org.jbpm.process.core.impl.ProcessImpl; import org.jbpm.process.core.validation.ProcessValidatorRegistry; +import org.jbpm.workflow.core.impl.WorkflowProcessImpl; +import org.jbpm.workflow.instance.WorkflowProcessParameters; import org.kie.api.definition.process.Process; import org.kie.api.definition.process.WorkflowProcess; import org.kie.api.io.Resource; @@ -57,7 +59,9 @@ import org.kie.kogito.codegen.api.SourceFileCodegenBindEvent; import org.kie.kogito.codegen.api.context.ContextAttributesConstants; import org.kie.kogito.codegen.api.context.KogitoBuildContext; +import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; import org.kie.kogito.codegen.api.io.CollectedResource; +import org.kie.kogito.codegen.api.template.TemplatedGenerator; import org.kie.kogito.codegen.core.AbstractGenerator; import org.kie.kogito.codegen.core.DashboardGeneratedFileUtils; import org.kie.kogito.codegen.process.config.ProcessConfigGenerator; @@ -74,11 +78,13 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; +import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static org.jbpm.process.core.constants.CalendarConstants.BUSINESS_CALENDAR_PATH; +import static org.kie.kogito.codegen.process.util.CodegenUtil.isTransactionEnabled; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.buildDashboardName; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.generateOperationalDashboard; import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName; @@ -297,6 +303,12 @@ protected Collection internalGenerate() { // first we generate all the data classes from variable declarations for (WorkflowProcess workFlowProcess : processes.values()) { + // transaction is disabled by default for SW types + boolean defaultTransactionEnabled = !KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType()); + if (isTransactionEnabled(this, context(), defaultTransactionEnabled)) { + ((WorkflowProcessImpl) workFlowProcess).setMetaData(WorkflowProcessParameters.WORKFLOW_PARAM_TRANSACTIONS.getName(), "true"); + } + if (!skipModelGeneration(workFlowProcess)) { ModelClassGenerator mcg = new ModelClassGenerator(context(), workFlowProcess); processIdToModelGenerator.put(workFlowProcess.getId(), mcg); @@ -308,9 +320,10 @@ protected Collection internalGenerate() { processIdToOutputModelGenerator.put(workFlowProcess.getId(), omcg); } } - + boolean isServerless = false; // then we generate work items task inputs and outputs if any for (WorkflowProcess workFlowProcess : processes.values()) { + isServerless |= KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType()); if (KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType())) { continue; } @@ -339,6 +352,7 @@ protected Collection internalGenerate() { } // generate Process, ProcessInstance classes and the REST resource + for (ProcessExecutableModelGenerator execModelGen : processExecutableModelGenerators) { String classPrefix = sanitizeClassName(execModelGen.extractedProcessId()); KogitoWorkflowProcess workFlowProcess = execModelGen.process(); @@ -373,7 +387,7 @@ protected Collection internalGenerate() { .withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId())) .withSignals(metaData.getSignals()) .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) - .withTransaction(CodegenUtil.isTransactionEnabled(this, context())); + .withTransaction(isTransactionEnabled(this, context())); rgs.add(processResourceGenerator); } @@ -451,6 +465,17 @@ protected Collection internalGenerate() { .forEach((key, value) -> storeFile(PRODUCER_TYPE, key, value)); } + if (CodegenUtil.isTransactionEnabled(this, context()) && !isServerless) { + String template = "ExceptionHandlerTransaction"; + TemplatedGenerator generator = TemplatedGenerator.builder() + .withTemplateBasePath("/class-templates/transaction/") + .withFallbackContext(JavaKogitoBuildContext.CONTEXT_NAME) + .withTargetTypeName(template) + .build(context(), template); + CompilationUnit handler = generator.compilationUnitOrThrow(); + storeFile(MODEL_TYPE, generator.generatedFilePath(), handler.toString()); + } + if (context().hasRESTForGenerator(this)) { for (ProcessResourceGenerator resourceGenerator : rgs) { storeFile(REST_TYPE, resourceGenerator.generatedFilePath(), diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java index ecf9737e170..1e33a958615 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java @@ -22,6 +22,7 @@ import org.kie.kogito.codegen.api.Generator; import org.kie.kogito.codegen.api.context.KogitoBuildContext; +import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,9 +66,14 @@ public static String globalProperty(String propertyName) { * @see CodegenUtil#getProperty */ public static boolean isTransactionEnabled(Generator generator, KogitoBuildContext context) { - boolean propertyValue = getProperty(generator, context, TRANSACTION_ENABLED, Boolean::parseBoolean, true); - LOG.debug("trying to compute property {} for generator {} property with value {}", TRANSACTION_ENABLED, generator.name(), propertyValue); - return propertyValue; + return isTransactionEnabled(generator, context, true); + } + + public static boolean isTransactionEnabled(Generator generator, KogitoBuildContext context, boolean defaultValue) { + boolean propertyValue = getProperty(generator, context, TRANSACTION_ENABLED, Boolean::parseBoolean, defaultValue); + LOG.debug("Compute property {} for generator {} property with value {}", TRANSACTION_ENABLED, generator.name(), propertyValue); + // java implementation does not have transactions + return !JavaKogitoBuildContext.CONTEXT_NAME.equals(context.name()) && propertyValue; } /** diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java new file mode 100644 index 00000000000..3230f0d71bc --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionQuarkusTemplate.java @@ -0,0 +1,77 @@ +/* + * 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.quarkus.workflow.handler; + +import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; +import org.kie.kogito.Model; +import org.kie.kogito.handler.ExceptionHandler; +import org.kie.kogito.process.MutableProcessInstances; +import org.kie.kogito.process.ProcessInstanceExecutionException; +import org.kie.kogito.process.Processes; +import org.kie.kogito.process.impl.AbstractProcessInstance; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.uow.UnitOfWorkManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.transaction.Transactional.TxType; + +@ApplicationScoped +public class ExceptionHandlerTransaction implements ExceptionHandler { + + private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandlerTransaction.class); + + @Inject + UnitOfWorkManager unitOfWorkManager; + + @Inject + Instance processesContainer; + + @Override + @Transactional(value = TxType.REQUIRES_NEW) + public void handle(Exception th) { + if (processesContainer.isResolvable()) { + return; + } + + Processes processes = processesContainer.get(); + if (th instanceof ProcessInstanceExecutionException) { + ProcessInstanceExecutionException processInstanceExecutionException = (ProcessInstanceExecutionException) th; + LOG.info("handling exception {} by the handler {}", th, this.getClass().getName()); + UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> { + String processInstanceId = processInstanceExecutionException.getProcessInstanceId(); + processes.processByProcessInstanceId(processInstanceId).ifPresent(processDefinition -> { + processDefinition.instances().findById(processInstanceId).ifPresent(instance -> { + AbstractProcessInstance processInstance = ((AbstractProcessInstance) instance); + ((WorkflowProcessInstanceImpl) processInstance.internalGetProcessInstance()).internalSetError(processInstanceExecutionException); + ((MutableProcessInstances) processDefinition.instances()).update(processInstanceId, processInstance); + }); + + }); + + return null; + }); + } + } + +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java new file mode 100644 index 00000000000..d9996d62f16 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/transaction/ExceptionHandlerTransactionSpringTemplate.java @@ -0,0 +1,73 @@ +/* + * 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.process.handler; + +import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl; +import org.kie.kogito.Model; +import org.kie.kogito.handler.ExceptionHandler; +import org.kie.kogito.process.MutableProcessInstances; +import org.kie.kogito.process.ProcessInstanceExecutionException; +import org.kie.kogito.process.Processes; +import org.kie.kogito.process.impl.AbstractProcessInstance; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.uow.UnitOfWorkManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class ExceptionHandlerTransaction implements ExceptionHandler { + + private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandlerTransaction.class); + + @Autowired + UnitOfWorkManager unitOfWorkManager; + + @Autowired(required = false) + Processes processes; + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void handle(Exception th) { + if (processes == null) { + return; + } + if (th instanceof ProcessInstanceExecutionException) { + ProcessInstanceExecutionException processInstanceExecutionException = (ProcessInstanceExecutionException) th; + LOG.info("handling exception {} by the handler {}", th, this.getClass().getName()); + UnitOfWorkExecutor.executeInUnitOfWork(unitOfWorkManager, () -> { + String processInstanceId = processInstanceExecutionException.getProcessInstanceId(); + processes.processByProcessInstanceId(processInstanceId).ifPresent(processDefinition -> { + processDefinition.instances().findById(processInstanceId).ifPresent(instance -> { + AbstractProcessInstance processInstance = ((AbstractProcessInstance) instance); + ((WorkflowProcessInstanceImpl) processInstance.internalGetProcessInstance()).internalSetError(processInstanceExecutionException); + ((MutableProcessInstances) processDefinition.instances()).update(processInstanceId, processInstance); + }); + + }); + + return null; + }); + } + } + +} diff --git a/quarkus/addons/rest-exception-handler/pom.xml b/quarkus/addons/rest-exception-handler/pom.xml index a5419789ab6..03f28e92666 100644 --- a/quarkus/addons/rest-exception-handler/pom.xml +++ b/quarkus/addons/rest-exception-handler/pom.xml @@ -51,6 +51,10 @@ jakarta.inject-api provided + + jakarta.enterprise + jakarta.enterprise.cdi-api + org.mockito mockito-junit-jupiter diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java index 21b70e76d74..09a44058b31 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionMapper.java @@ -23,13 +23,6 @@ public abstract class BaseExceptionMapper implements ExceptionMapper { - protected ExceptionsHandler exceptionsHandler; - - protected BaseExceptionMapper() { - this.exceptionsHandler = new ExceptionsHandler(); - } - @Override - @SuppressWarnings("squid:S3038") public abstract Response toResponse(E e); } diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java index ec1bd6679dd..2d1afaf5780 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandler.java @@ -18,54 +18,60 @@ */ package org.kie.kogito.resource.exceptions; +import org.kie.kogito.handler.ExceptionHandler; + import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -public class ExceptionsHandler extends BaseExceptionsHandler { +public class ExceptionsHandler extends AbstractExceptionsHandler { + + public ExceptionsHandler(Iterable handlers) { + super(handlers); + } @Override - protected Response badRequest(R body) { + protected Response badRequest(ExceptionBodyMessage body) { return Response .status(Response.Status.BAD_REQUEST) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .entity(body) + .entity(body.getBody()) .build(); } @Override - protected Response conflict(R body) { + protected Response conflict(ExceptionBodyMessage body) { return Response .status(Response.Status.CONFLICT) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .entity(body) + .entity(body.getBody()) .build(); } @Override - protected Response internalError(R body) { + protected Response internalError(ExceptionBodyMessage body) { return Response .status(Response.Status.INTERNAL_SERVER_ERROR) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .entity(body) + .entity(body.getBody()) .build(); } @Override - protected Response notFound(R body) { + protected Response notFound(ExceptionBodyMessage body) { return Response .status(Response.Status.NOT_FOUND) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .entity(body) + .entity(body.getBody()) .build(); } @Override - protected Response forbidden(R body) { + protected Response forbidden(ExceptionBodyMessage body) { return Response .status(Response.Status.FORBIDDEN) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) - .entity(body) + .entity(body.getBody()) .build(); } } diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java new file mode 100644 index 00000000000..a207c680723 --- /dev/null +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerProducer.java @@ -0,0 +1,32 @@ +/* + * 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.resource.exceptions; + +import org.kie.kogito.handler.ExceptionHandler; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; + +public class ExceptionsHandlerProducer { + + @Produces + public ExceptionsHandler newExceptionsHandler(Instance handlers) { + return new ExceptionsHandler(handlers); + } +} diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java index 56a734a511a..e019fd12f96 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/IllegalArgumentExceptionMapper.java @@ -18,12 +18,16 @@ */ package org.kie.kogito.resource.exceptions; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class IllegalArgumentExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(IllegalArgumentException e) { return exceptionsHandler.mapException(e); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java index 82341b6eecd..7d5b5a938a7 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidLifeCyclePhaseExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class InvalidLifeCyclePhaseExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(InvalidLifeCyclePhaseException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java index ff04b15e15f..abd80e367db 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/InvalidTransitionExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.internal.process.workitem.InvalidTransitionException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class InvalidTransitionExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(InvalidTransitionException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java index 698a1155e7d..891c60fb7c0 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeInstanceNotFoundExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.NodeInstanceNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class NodeInstanceNotFoundExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(NodeInstanceNotFoundException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java index 182e1df839f..c41b0397fef 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NodeNotFoundExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.NodeNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class NodeNotFoundExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(NodeNotFoundException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java index 030783daa6e..2831677feb8 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/NotAuthorizedExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.internal.process.workitem.NotAuthorizedException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class NotAuthorizedExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(NotAuthorizedException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java index ed9bb35c967..6c54bf486e0 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceDuplicatedExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.ProcessInstanceDuplicatedException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class ProcessInstanceDuplicatedExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(ProcessInstanceDuplicatedException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java index 9e3f41ef953..3ff0d6449c5 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceExecutionExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.ProcessInstanceExecutionException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class ProcessInstanceExecutionExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(ProcessInstanceExecutionException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java index 46b511d6b35..d0e31c3fc52 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/ProcessInstanceNotFoundExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.ProcessInstanceNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class ProcessInstanceNotFoundExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(ProcessInstanceNotFoundException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java index 98710328810..be350a1d6df 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class UserTaskInstanceNotAuthorizedExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(UserTaskInstanceNotFoundException e) { return exceptionsHandler.mapException(e); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java index d74ddf1fc5c..196bd85535b 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class UserTaskInstanceNotFoundExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(UserTaskInstanceNotAuthorizedException e) { return exceptionsHandler.mapException(e); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java index 233d3cd679a..063439ead6e 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class UserTaskTransitionExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(UserTaskInstanceNotFoundException e) { return exceptionsHandler.mapException(e); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java index 85ab766ee42..fdea3253351 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/VariableViolationExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.process.VariableViolationException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class VariableViolationExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(VariableViolationException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java index 1ffb619cebb..bcd9f18ede5 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemExecutionExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.internal.process.workitem.WorkItemExecutionException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class WorkItemExecutionExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(WorkItemExecutionException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java index a0d0731dcc3..000e0f748a7 100644 --- a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/WorkItemNotFoundExceptionMapper.java @@ -20,12 +20,16 @@ import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException; +import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.Provider; @Provider public class WorkItemNotFoundExceptionMapper extends BaseExceptionMapper { + @Inject + ExceptionsHandler exceptionsHandler; + @Override public Response toResponse(WorkItemNotFoundException exception) { return exceptionsHandler.mapException(exception); diff --git a/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java b/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java index 028a91a91b5..156d8aae68c 100644 --- a/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java +++ b/quarkus/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/ExceptionsHandlerTest.java @@ -18,6 +18,8 @@ */ package org.kie.kogito.resource.exceptions; +import java.util.ArrayList; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -40,7 +42,7 @@ class ExceptionsHandlerTest { private ExceptionsHandler tested; @Mock - private Object body; + private ExceptionBodyMessage body; @Mock private RuntimeDelegate runtimeDelegate; @@ -53,7 +55,7 @@ class ExceptionsHandlerTest { @BeforeEach void setUp() { - tested = new ExceptionsHandler(); + tested = new ExceptionsHandler(new ArrayList<>()); RuntimeDelegate.setInstance(runtimeDelegate); when(runtimeDelegate.createResponseBuilder()).thenReturn(builder); when(builder.status(any(Response.StatusType.class))).thenReturn(builder); @@ -71,7 +73,7 @@ void testBadRequest() { private void assertRequest(Response.Status status) { verify(builder).header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); verify(builder).status((Response.StatusType) status); - verify(builder).entity(body); + verify(builder).entity(body.getBody()); } @Test diff --git a/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java b/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java index 6165d773ab2..29ca134938c 100644 --- a/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java +++ b/quarkus/addons/source-files/runtime/src/main/java/org/kie/kogito/addon/source/files/SourceFilesResource.java @@ -19,11 +19,10 @@ package org.kie.kogito.addon.source.files; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.Collection; +import java.util.Optional; -import org.kie.kogito.resource.exceptions.ExceptionsHandler; import org.kie.kogito.source.files.SourceFile; import org.kie.kogito.source.files.SourceFilesProvider; @@ -41,24 +40,24 @@ @Path("/management/processes/") public final class SourceFilesResource { - private static final ExceptionsHandler EXCEPTIONS_HANDLER = new ExceptionsHandler(); - SourceFilesProvider sourceFilesProvider; @GET @Path("sources") @Produces(MediaType.APPLICATION_OCTET_STREAM) - public Response getSourceFileByUri(@QueryParam("uri") String uri) { - return sourceFilesProvider.getSourceFilesByUri(uri) - .map(sourceFile -> { - try (InputStream file = new ByteArrayInputStream(sourceFile.readContents())) { - return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM) - .header("Content-Disposition", "inline; filename=\"" + java.nio.file.Path.of(sourceFile.getUri()).getFileName() + "\"") - .build(); - } catch (Exception e) { - return EXCEPTIONS_HANDLER.mapException(e); - } - }).orElseGet(() -> Response.status(Response.Status.NOT_FOUND).build()); + public Response getSourceFileByUri(@QueryParam("uri") String uri) throws Exception { + Optional sourceFile = sourceFilesProvider.getSourceFilesByUri(uri); + + if (sourceFile.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + try (InputStream file = new ByteArrayInputStream(sourceFile.get().readContents())) { + return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM) + .header("Content-Disposition", "inline; filename=\"" + java.nio.file.Path.of(sourceFile.get().getUri()).getFileName() + "\"") + .build(); + } + } @GET @@ -71,15 +70,15 @@ public Collection getSourceFilesByProcessId(@PathParam("processId") @GET @Path("{processId}/source") @Produces(MediaType.TEXT_PLAIN) - public Response getSourceFileByProcessId(@PathParam("processId") String processId) { - return sourceFilesProvider.getProcessSourceFile(processId) - .map(sourceFile -> { - try { - return Response.ok(sourceFile.readContents()).build(); - } catch (IOException e) { - return EXCEPTIONS_HANDLER.mapException(e); - } - }).orElseGet(() -> Response.status(Response.Status.NOT_FOUND).build()); + public Response getSourceFileByProcessId(@PathParam("processId") String processId) throws Exception { + Optional sourceFile = sourceFilesProvider.getProcessSourceFile(processId); + + if (sourceFile.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok(sourceFile.get().readContents()).build(); + } @Inject diff --git a/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java b/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java index 642bf95be8a..2968fe78d64 100644 --- a/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java +++ b/quarkus/addons/source-files/runtime/src/test/java/org/kie/kogito/addon/source/files/SourceFilesResourceTest.java @@ -58,14 +58,14 @@ void getSourceFilesByProcessIdTest() { } @Test - void getEmptySourceFileByProcessIdTest() { + void getEmptySourceFileByProcessIdTest() throws Exception { when(mockSourceFileProvider.getProcessSourceFile(PROCESS_ID)).thenReturn(Optional.empty()); assertThat(sourceFilesTestResource.getSourceFileByProcessId(PROCESS_ID).getStatus()).isEqualTo(Response.Status.NOT_FOUND.getStatusCode()); verify(mockSourceFileProvider).getProcessSourceFile(PROCESS_ID); } @Test - void getValidSourceFileByProcessIdTest() { + void getValidSourceFileByProcessIdTest() throws Exception { when(mockSourceFileProvider.getProcessSourceFile(PROCESS_ID)).thenReturn(Optional.of(new SourceFile("petstore.sw.json"))); assertThat(sourceFilesTestResource.getSourceFileByProcessId(PROCESS_ID).getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); verify(mockSourceFileProvider).getProcessSourceFile(PROCESS_ID); diff --git a/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java b/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java index d5d5dfa96e4..9872513141e 100644 --- a/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java +++ b/springboot/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandler.java @@ -18,6 +18,9 @@ */ package org.kie.kogito.resource.exceptions.springboot; +import java.util.List; +import java.util.Map; + import org.kie.kogito.internal.process.workitem.InvalidLifeCyclePhaseException; import org.kie.kogito.internal.process.workitem.InvalidTransitionException; import org.kie.kogito.internal.process.workitem.NotAuthorizedException; @@ -29,7 +32,9 @@ import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.ProcessInstanceNotFoundException; import org.kie.kogito.process.VariableViolationException; -import org.kie.kogito.resource.exceptions.BaseExceptionsHandler; +import org.kie.kogito.resource.exceptions.AbstractExceptionsHandler; +import org.kie.kogito.resource.exceptions.ExceptionBodyMessage; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -37,105 +42,111 @@ import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice -public class ExceptionsHandler extends BaseExceptionsHandler { +public class ExceptionsHandler extends AbstractExceptionsHandler>> { + + @Autowired + public ExceptionsHandler(List handlers) { + super(handlers); + } @Override - protected ResponseEntity badRequest(R body) { + protected ResponseEntity> badRequest(ExceptionBodyMessage body) { return ResponseEntity .badRequest() .contentType(MediaType.APPLICATION_JSON) - .body(body); + .body(body.getBody()); } @Override - protected ResponseEntity conflict(R body) { + protected ResponseEntity> conflict(ExceptionBodyMessage body) { return ResponseEntity .status(HttpStatus.CONFLICT) .contentType(MediaType.APPLICATION_JSON) - .body(body); + .body(body.getBody()); } @Override - protected ResponseEntity internalError(R body) { + protected ResponseEntity> internalError(ExceptionBodyMessage body) { return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .contentType(MediaType.APPLICATION_JSON) - .body(body); + .body(body.getBody()); } @Override - protected ResponseEntity notFound(R body) { + protected ResponseEntity> notFound(ExceptionBodyMessage body) { return ResponseEntity .status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) - .body(body); + .body(body.getBody()); } @Override - protected ResponseEntity forbidden(R body) { + protected ResponseEntity> forbidden(ExceptionBodyMessage body) { return ResponseEntity .status(HttpStatus.FORBIDDEN) .contentType(MediaType.APPLICATION_JSON) - .body(body); + .body(body.getBody()); } @ExceptionHandler(InvalidLifeCyclePhaseException.class) - public ResponseEntity toResponse(InvalidLifeCyclePhaseException exception) { + public ResponseEntity> toResponse(InvalidLifeCyclePhaseException exception) { return mapException(exception); } @ExceptionHandler(InvalidTransitionException.class) - public ResponseEntity toResponse(InvalidTransitionException exception) { + public ResponseEntity> toResponse(InvalidTransitionException exception) { return mapException(exception); } @ExceptionHandler(NodeInstanceNotFoundException.class) - public ResponseEntity toResponse(NodeInstanceNotFoundException exception) { + public ResponseEntity> toResponse(NodeInstanceNotFoundException exception) { return mapException(exception); } @ExceptionHandler(NodeNotFoundException.class) - public ResponseEntity toResponse(NodeNotFoundException exception) { + public ResponseEntity> toResponse(NodeNotFoundException exception) { return mapException(exception); } @ExceptionHandler(NotAuthorizedException.class) - public ResponseEntity toResponse(NotAuthorizedException exception) { + public ResponseEntity> toResponse(NotAuthorizedException exception) { return mapException(exception); } @ExceptionHandler(ProcessInstanceDuplicatedException.class) - public ResponseEntity toResponse(ProcessInstanceDuplicatedException exception) { + public ResponseEntity> toResponse(ProcessInstanceDuplicatedException exception) { return mapException(exception); } @ExceptionHandler(ProcessInstanceExecutionException.class) - public ResponseEntity toResponse(ProcessInstanceExecutionException exception) { + public ResponseEntity> toResponse(ProcessInstanceExecutionException exception) { return mapException(exception); } @ExceptionHandler(ProcessInstanceNotFoundException.class) - public ResponseEntity toResponse(ProcessInstanceNotFoundException exception) { + public ResponseEntity> toResponse(ProcessInstanceNotFoundException exception) { return mapException(exception); } @ExceptionHandler(WorkItemNotFoundException.class) - public ResponseEntity toResponse(WorkItemNotFoundException exception) { + public ResponseEntity> toResponse(WorkItemNotFoundException exception) { return mapException(exception); } @ExceptionHandler(WorkItemExecutionException.class) - public ResponseEntity toResponse(WorkItemExecutionException exception) { + public ResponseEntity> toResponse(WorkItemExecutionException exception) { return mapException(exception); } @ExceptionHandler(VariableViolationException.class) - public ResponseEntity toResponse(VariableViolationException exception) { + public ResponseEntity> toResponse(VariableViolationException exception) { return mapException(exception); } @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity toResponse(IllegalArgumentException exception) { + public ResponseEntity> toResponse(IllegalArgumentException exception) { return mapException(exception); } + } diff --git a/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java b/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java index 4512a30b7fc..10c82906203 100644 --- a/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java +++ b/springboot/addons/rest-exception-handler/src/test/java/org/kie/kogito/resource/exceptions/springboot/ExceptionsHandlerTest.java @@ -18,6 +18,9 @@ */ package org.kie.kogito.resource.exceptions.springboot; +import java.util.ArrayList; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,6 +32,7 @@ import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.ProcessInstanceNotFoundException; import org.kie.kogito.process.VariableViolationException; +import org.kie.kogito.resource.exceptions.ExceptionBodyMessage; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; @@ -44,45 +48,45 @@ class ExceptionsHandlerTest { private ExceptionsHandler tested; @Mock - private Object body; + private ExceptionBodyMessage body; @BeforeEach void setUp() { - tested = spy(new ExceptionsHandler()); + tested = spy(new ExceptionsHandler(new ArrayList<>())); } @Test void testBadRequest() { - ResponseEntity responseEntity = tested.badRequest(body); + ResponseEntity> responseEntity = tested.badRequest(body); assertResponse(responseEntity, HttpStatus.BAD_REQUEST); } - private void assertResponse(ResponseEntity responseEntity, HttpStatus status) { + private void assertResponse(ResponseEntity> responseEntity, HttpStatus status) { assertThat(responseEntity.getStatusCode()).isEqualTo(status); - assertThat(responseEntity.getBody()).isEqualTo(body); + assertThat(responseEntity.getBody()).isEqualTo(body.getBody()); } @Test void testConflict() { - ResponseEntity responseEntity = tested.conflict(body); + ResponseEntity> responseEntity = tested.conflict(body); assertResponse(responseEntity, HttpStatus.CONFLICT); } @Test void testIternalError() { - ResponseEntity responseEntity = tested.internalError(body); + ResponseEntity> responseEntity = tested.internalError(body); assertResponse(responseEntity, HttpStatus.INTERNAL_SERVER_ERROR); } @Test void testNotFound() { - ResponseEntity responseEntity = tested.badRequest(body); + ResponseEntity> responseEntity = tested.badRequest(body); assertResponse(responseEntity, HttpStatus.BAD_REQUEST); } @Test void testForbidden() { - ResponseEntity responseEntity = tested.forbidden(body); + ResponseEntity> responseEntity = tested.forbidden(body); assertResponse(responseEntity, HttpStatus.FORBIDDEN); } diff --git a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java index b823c16d26b..d1875057a9d 100644 --- a/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java +++ b/springboot/starters/kogito-processes-spring-boot-starter/src/main/java/org/kie/kogito/process/KogitoBeanProducer.java @@ -22,6 +22,9 @@ import org.kie.kogito.correlation.CorrelationService; import org.kie.kogito.event.correlation.DefaultCorrelationService; import org.kie.kogito.process.version.ProjectVersionProcessVersionResolver; +import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory; +import org.kie.kogito.services.uow.DefaultUnitOfWorkManager; +import org.kie.kogito.uow.UnitOfWorkManager; import org.kogito.workitem.rest.RestWorkItemHandlerUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -53,6 +56,12 @@ ProcessVersionResolver projectVersionResolver() { return new ProjectVersionProcessVersionResolver(configBean.getGav().orElseThrow(() -> new RuntimeException("Unable to use kogito.workflow.version-strategy without a project GAV"))); } + @Bean + @ConditionalOnMissingBean(UnitOfWorkManager.class) + UnitOfWorkManager unitOfWorkManager() { + return new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()); + } + @Bean @ConditionalOnMissingBean(WebClientOptions.class) WebClientOptions sslDefaultOptions() {