diff --git a/.rat-excludes b/.rat-excludes index 6473bd705a..7ed9b60ce9 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -42,6 +42,8 @@ application.properties basic.schema.graphqls # data-index/data-index-service/data-index-service-common/src/main/resources/domain.schema.graphqls domain.schema.graphqls +# /data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/mutation.schema.graphqls +mutation.schema.graphqls # data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-infinispan/integration-tests-process/src/main/resources/hello.bpmn hello.bpmn # data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-infinispan/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg @@ -97,4 +99,4 @@ application.properties # jobs-service/kogito-addons-jobs-service/kogito-addons-quarkus-jobs-service-embedded/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource org.eclipse.microprofile.config.spi.ConfigSource # trusty/trusty-service/trusty-service-common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker -org.mockito.plugins.MockMaker \ No newline at end of file +org.mockito.plugins.MockMaker diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/CommonUtils.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/CommonUtils.java index 3bc4b724db..47e70d8f7b 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/CommonUtils.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/CommonUtils.java @@ -18,15 +18,56 @@ */ package org.kie.kogito.index; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; + public class CommonUtils { public static final int ERROR_STATE = 5; private static final Set finalStates = Set.of("Completed", "Aborted"); + private static final Logger logger = LoggerFactory.getLogger(CommonUtils.class); public static boolean isTaskCompleted(String status) { return finalStates.contains(status); } + public static String getServiceUrl(String endpoint, String processId) { + logger.debug("Process endpoint {}", endpoint); + if (endpoint == null) { + return null; + } + if (endpoint.startsWith("/")) { + logger.warn("Process '{}' endpoint '{}', does not contain full URL, please review the kogito.service.url system property to point the public URL for this runtime.", + processId, endpoint); + } + String context = getContext(processId); + logger.debug("Process context {}", context); + if (context.equals(endpoint) || endpoint.equals("/" + context)) { + return null; + } else { + return endpoint.contains("/" + context) ? endpoint.substring(0, endpoint.lastIndexOf("/" + context)) : null; + } + } + + public static TypeDefinitionRegistry loadSchemaDefinitionFile(String fileName) { + SchemaParser schemaParser = new SchemaParser(); + try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); + InputStreamReader reader = new InputStreamReader(stream)) { + return schemaParser.parse(reader); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String getContext(String processId) { + return processId != null && processId.contains(".") ? processId.substring(processId.lastIndexOf('.') + 1) : processId; + } } diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/ExecuteArgs.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/ExecuteArgs.java new file mode 100644 index 0000000000..1e8c3a3656 --- /dev/null +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/ExecuteArgs.java @@ -0,0 +1,61 @@ +/* + * 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.index.api; + +import com.fasterxml.jackson.databind.JsonNode; + +public record ExecuteArgs(JsonNode input, String businessKey, String referenceId) { + + public static ExecuteArgs of(JsonNode input) { + return builder().withInput(input).build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private JsonNode input; + private String businessKey; + private String referenceId; + + private Builder() { + } + + public Builder withInput(JsonNode input) { + this.input = input; + return this; + } + + public Builder withBusinessKey(String businessKey) { + this.businessKey = businessKey; + return this; + } + + public Builder withReferenceId(String referenceId) { + this.referenceId = referenceId; + return this; + } + + public ExecuteArgs build() { + return new ExecuteArgs(input, businessKey, referenceId); + } + } +} diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeClient.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeClient.java index 50b94365ee..c6e42dd9d4 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeClient.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeClient.java @@ -24,11 +24,14 @@ import org.kie.kogito.index.model.Job; import org.kie.kogito.index.model.Node; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.UserTaskInstance; public interface KogitoRuntimeClient { + CompletableFuture executeProcessIntance(ProcessDefinition definition, ExecuteArgs args); + CompletableFuture abortProcessInstance(String serviceURL, ProcessInstance processInstance); CompletableFuture retryProcessInstance(String serviceURL, ProcessInstance processInstance); diff --git a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/AbstractGraphQLSchemaManager.java b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/AbstractGraphQLSchemaManager.java index 6dd81262b1..142f2c02a4 100644 --- a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/AbstractGraphQLSchemaManager.java +++ b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/AbstractGraphQLSchemaManager.java @@ -18,16 +18,17 @@ */ package org.kie.kogito.index.graphql; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.kie.kogito.index.CommonUtils; import org.kie.kogito.index.api.KogitoRuntimeClient; import org.kie.kogito.index.graphql.query.GraphQLQueryOrderByParser; import org.kie.kogito.index.graphql.query.GraphQLQueryParserRegistry; @@ -44,13 +45,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLNamedType; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; -import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; +import graphql.schema.idl.TypeRuntimeWiring.Builder; import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; @@ -82,8 +84,12 @@ public abstract class AbstractGraphQLSchemaManager implements GraphQLSchemaManag private GraphQLSchema schema; + private Collection mutations; + @PostConstruct public void setup() { + mutations = ServiceLoader.load(GraphQLMutationsProvider.class).stream().map(Provider::get).collect(Collectors.toList()); + schema = createSchema(); GraphQLQueryParserRegistry.get().registerParsers( (GraphQLInputObjectType) schema.getType("ProcessDefinitionArgument"), @@ -92,14 +98,19 @@ public void setup() { (GraphQLInputObjectType) schema.getType("JobArgument")); } + protected final void loadAdditionalMutations(Builder builder) { + Map>> mutationMap = mutations.stream().map(m -> m.mutations(this)).flatMap(map -> map.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2)); + LOGGER.info("Custom mutations are {}", mutationMap); + mutationMap.forEach(builder::dataFetcher); + } + + protected final void loadAdditionalMutations(TypeDefinitionRegistry typeRegistry) { + mutations.stream().map(GraphQLMutationsProvider::registry).forEach(typeRegistry::merge); + } + protected TypeDefinitionRegistry loadSchemaDefinitionFile(String fileName) { - SchemaParser schemaParser = new SchemaParser(); - try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); - InputStreamReader reader = new InputStreamReader(stream)) { - return schemaParser.parse(reader); - } catch (IOException e) { - throw new RuntimeException(e); - } + return CommonUtils.loadSchemaDefinitionFile(fileName); } public abstract GraphQLSchema createSchema(); @@ -142,25 +153,7 @@ public ProcessDefinition getProcessDefinition(DataFetchingEnvironment env) { } protected String getServiceUrl(String endpoint, String processId) { - LOGGER.debug("Process endpoint {}", endpoint); - if (endpoint == null) { - return null; - } - if (endpoint.startsWith("/")) { - LOGGER.warn("Process '{}' endpoint '{}', does not contain full URL, please review the kogito.service.url system property to point the public URL for this runtime.", - processId, endpoint); - } - String context = getContext(processId); - LOGGER.debug("Process context {}", context); - if (context.equals(endpoint) || endpoint.equals("/" + context)) { - return null; - } else { - return endpoint.contains("/" + context) ? endpoint.substring(0, endpoint.lastIndexOf("/" + context)) : null; - } - } - - private String getContext(String processId) { - return processId != null && processId.contains(".") ? processId.substring(processId.lastIndexOf('.') + 1) : processId; + return CommonUtils.getServiceUrl(endpoint, processId); } protected Collection getChildProcessInstancesValues(DataFetchingEnvironment env) { diff --git a/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/GraphQLMutationsProvider.java b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/GraphQLMutationsProvider.java new file mode 100644 index 0000000000..6457ee47f4 --- /dev/null +++ b/data-index/data-index-graphql/src/main/java/org/kie/kogito/index/graphql/GraphQLMutationsProvider.java @@ -0,0 +1,31 @@ +/* + * 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.index.graphql; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import graphql.schema.DataFetcher; +import graphql.schema.idl.TypeDefinitionRegistry; + +public interface GraphQLMutationsProvider { + Map>> mutations(AbstractGraphQLSchemaManager schemaManager); + + TypeDefinitionRegistry registry(); +} diff --git a/data-index/data-index-mutations/data-index-shared-output-mutation/pom.xml b/data-index/data-index-mutations/data-index-shared-output-mutation/pom.xml new file mode 100644 index 0000000000..9f73334975 --- /dev/null +++ b/data-index/data-index-mutations/data-index-shared-output-mutation/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.kie.kogito + data-index-mutations + 999-SNAPSHOT + + data-index-shared-output-mutation + + + org.kie.kogito + data-index-graphql + + + + org.kie.kogito.index.service.mutations + + \ No newline at end of file diff --git a/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/java/org/kie/kogito/index/mutations/OutputGraphQLMutationProvider.java b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/java/org/kie/kogito/index/mutations/OutputGraphQLMutationProvider.java new file mode 100644 index 0000000000..6532ae43a4 --- /dev/null +++ b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/java/org/kie/kogito/index/mutations/OutputGraphQLMutationProvider.java @@ -0,0 +1,87 @@ +/* + * 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.index.mutations; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.kie.kogito.index.CommonUtils; +import org.kie.kogito.index.api.ExecuteArgs; +import org.kie.kogito.index.graphql.AbstractGraphQLSchemaManager; +import org.kie.kogito.index.graphql.GraphQLMutationsProvider; +import org.kie.kogito.index.model.ProcessDefinition; +import org.kie.kogito.index.model.ProcessDefinitionKey; +import org.kie.kogito.index.model.ProcessInstance; +import org.kie.kogito.index.storage.DataIndexStorageService; +import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.jackson.utils.MergeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.idl.TypeDefinitionRegistry; + +public class OutputGraphQLMutationProvider implements GraphQLMutationsProvider { + + private static Logger logger = LoggerFactory.getLogger(OutputGraphQLMutationProvider.class); + private static final String COMPLETED_INSTANCE_ID = "completedInstanceId"; + + @Override + public Map>> mutations(AbstractGraphQLSchemaManager schemaManager) { + return Map.of("ExecuteAfter", env -> sharedOutput(schemaManager, env)); + } + + private CompletableFuture sharedOutput(AbstractGraphQLSchemaManager schemaManager, DataFetchingEnvironment env) { + DataIndexStorageService cacheService = schemaManager.getCacheService(); + ProcessDefinitionKey key = new ProcessDefinitionKey(mandatoryArgument(env, "processId"), mandatoryArgument(env, "processVersion")); + ProcessDefinition processDefinition = cacheService.getProcessDefinitionStorage().get(key); + if (processDefinition == null) { + throw new IllegalArgumentException(key + "does not correspond to any existing process definition"); + } + JsonNode input = JsonObjectUtils.fromValue(env.getArgument("input")); + String completedInstanceId = env.getArgument(COMPLETED_INSTANCE_ID); + if (completedInstanceId != null) { + ProcessInstance processInstance = cacheService.getProcessInstanceStorage().get(completedInstanceId); + if (processInstance != null) { + input = MergeUtils.merge(input, processInstance.getVariables()); + } else { + logger.warn("Completed Instance Id {} cannot be found, using user input as it is", completedInstanceId); + } + } else { + logger.warn("Missing " + COMPLETED_INSTANCE_ID + " parameter, using user input as it is"); + } + return schemaManager.getDataIndexApiExecutor().executeProcessIntance(processDefinition, ExecuteArgs.of(input)); + } + + private static T mandatoryArgument(DataFetchingEnvironment env, String name) { + T result = env.getArgument(name); + if (result == null) { + throw new IllegalArgumentException("Missing " + name + " mandatory parameter"); + } + return result; + } + + @Override + public TypeDefinitionRegistry registry() { + return CommonUtils.loadSchemaDefinitionFile("mutation.schema.graphqls"); + } +} diff --git a/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/META-INF/services/org.kie.kogito.index.graphql.GraphQLMutationsProvider b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/META-INF/services/org.kie.kogito.index.graphql.GraphQLMutationsProvider new file mode 100644 index 0000000000..7800b9085c --- /dev/null +++ b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/META-INF/services/org.kie.kogito.index.graphql.GraphQLMutationsProvider @@ -0,0 +1,20 @@ +# +# 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. +# + +org.kie.kogito.index.mutations.OutputGraphQLMutationProvider \ No newline at end of file diff --git a/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/mutation.schema.graphqls b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/mutation.schema.graphqls new file mode 100644 index 0000000000..120b44dc52 --- /dev/null +++ b/data-index/data-index-mutations/data-index-shared-output-mutation/src/main/resources/mutation.schema.graphqls @@ -0,0 +1,3 @@ +extend type Mutation { + ExecuteAfter(completedInstanceId: String, processId: String, processVersion: String, input: JSON): String +} diff --git a/data-index/data-index-mutations/pom.xml b/data-index/data-index-mutations/pom.xml new file mode 100644 index 0000000000..4e7eed80c1 --- /dev/null +++ b/data-index/data-index-mutations/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.kie.kogito + data-index + 999-SNAPSHOT + + data-index-mutations + pom + + data-index-shared-output-mutation + + \ No newline at end of file diff --git a/data-index/data-index-service/data-index-service-common/pom.xml b/data-index/data-index-service/data-index-service-common/pom.xml index 4abb945f17..7b8d09c534 100644 --- a/data-index/data-index-service/data-index-service-common/pom.xml +++ b/data-index/data-index-service/data-index-service-common/pom.xml @@ -91,6 +91,10 @@ io.quarkus quarkus-rest-client-reactive-jackson + + org.kie.kogito + data-index-shared-output-mutation + io.quarkus quarkus-smallrye-graphql-client diff --git a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java index badbe77b24..500d7ad0d9 100644 --- a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java +++ b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java @@ -22,9 +22,12 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.kie.kogito.index.CommonUtils; +import org.kie.kogito.index.api.ExecuteArgs; import org.kie.kogito.index.api.KogitoRuntimeClient; import org.kie.kogito.index.api.KogitoRuntimeCommonClient; import org.kie.kogito.index.model.Node; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.DataIndexServiceException; @@ -72,6 +75,17 @@ class KogitoRuntimeClientImpl extends KogitoRuntimeCommonClient implements Kogit private static final Logger LOGGER = LoggerFactory.getLogger(KogitoRuntimeClientImpl.class); + @Override + public CompletableFuture executeProcessIntance(ProcessDefinition definition, ExecuteArgs args) { + CompletableFuture future = new CompletableFuture<>(); + HttpRequest request = getWebClient(CommonUtils.getServiceUrl(definition.getEndpoint(), definition.getId())).post("/" + definition.getId()); + if (args.businessKey() != null) { + request.addQueryParam("businessKey", args.businessKey()); + } + request.sendJson(args.input(), res -> asyncHttpResponseTreatment(res, future, "START ProcessInstance of type " + definition.getId())); + return future; + } + @Override public CompletableFuture abortProcessInstance(String serviceURL, ProcessInstance processInstance) { String requestURI = format(ABORT_PROCESS_INSTANCE_PATH, processInstance.getProcessId(), processInstance.getId()); @@ -282,5 +296,4 @@ protected void send(String logMessage, Class type, CompletableFuture future, Asy future.completeExceptionally(new DataIndexServiceException(getErrorMessage(logMessage, res.result()), res.cause())); } } - } diff --git a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java index 2acaf539ab..cadff87c60 100644 --- a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java +++ b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/graphql/GraphQLSchemaManagerImpl.java @@ -74,6 +74,7 @@ public GraphQLSchema createSchema() { TypeDefinitionRegistry typeDefinitionRegistry = new TypeDefinitionRegistry(); typeDefinitionRegistry.merge(loadSchemaDefinitionFile("basic.schema.graphqls")); typeDefinitionRegistry.merge(loadSchemaDefinitionFile("domain.schema.graphqls")); + loadAdditionalMutations(typeDefinitionRegistry); RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .scalar(ExtendedScalars.Json) @@ -101,6 +102,7 @@ public GraphQLSchema createSchema() { builder.dataFetcher("UserTaskInstanceCommentDelete", this::deleteUserTaskComment); builder.dataFetcher("UserTaskInstanceAttachmentUpdate", this::updateUserTaskAttachment); builder.dataFetcher("UserTaskInstanceAttachmentDelete", this::deleteUserTaskAttachment); + loadAdditionalMutations(builder); return builder; }) .type("ProcessDefinition", builder -> { diff --git a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java index 28acb2caf3..9d61de4fe6 100644 --- a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java +++ b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java @@ -29,17 +29,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.kie.kogito.index.api.ExecuteArgs; import org.kie.kogito.index.api.KogitoRuntimeCommonClient; import org.kie.kogito.index.model.Job; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.DataIndexServiceException; import org.kie.kogito.index.test.TestUtils; +import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.usertask.model.CommentInfo; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import com.fasterxml.jackson.databind.JsonNode; + import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.SecurityIdentity; import io.vertx.core.AsyncResult; @@ -117,6 +122,20 @@ public void setup() { client.setIdentity(identityMock); } + @Test + public void textExecuteAfter() { + when(webClientMock.post(anyString())).thenReturn(httpRequestMock); + final String processId = "infra"; + ProcessDefinition definition = TestUtils.getProcessDefinition(processId); + definition.setEndpoint(SERVICE_URL + "/" + processId); + JsonNode body = ObjectMapperFactory.get().createObjectNode().put("name", "javierito"); + client.executeProcessIntance(definition, ExecuteArgs.of(body)); + verify(webClientMock).post("/" + processId); + ArgumentCaptor jsonCaptor = ArgumentCaptor.forClass(Object.class); + verify(httpRequestMock).sendJson(jsonCaptor.capture(), any()); + assertThat(jsonCaptor.getValue()).isEqualTo(body); + } + @Test public void testAbortProcessInstance() { setupIdentityMock(); diff --git a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/graphql/query/AbstractGraphQLRuntimesQueriesIT.java b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/graphql/query/AbstractGraphQLRuntimesQueriesIT.java index 04df4542e4..6f9915a1eb 100644 --- a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/graphql/query/AbstractGraphQLRuntimesQueriesIT.java +++ b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/graphql/query/AbstractGraphQLRuntimesQueriesIT.java @@ -27,17 +27,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.kie.kogito.event.process.ProcessDefinitionDataEvent; import org.kie.kogito.event.process.ProcessInstanceStateDataEvent; +import org.kie.kogito.event.process.ProcessInstanceVariableDataEvent; +import org.kie.kogito.event.process.ProcessInstanceVariableEventBody; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentEventBody; import org.kie.kogito.event.usertask.UserTaskInstanceCommentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceCommentEventBody; import org.kie.kogito.event.usertask.UserTaskInstanceStateDataEvent; +import org.kie.kogito.index.api.ExecuteArgs; import org.kie.kogito.index.api.KogitoRuntimeClient; import org.kie.kogito.index.event.KogitoJobCloudEvent; import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.AbstractIndexingIT; import org.kie.kogito.index.service.graphql.GraphQLSchemaManagerImpl; +import org.kie.kogito.index.test.TestUtils; +import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.persistence.protobuf.ProtobufService; import org.mockito.ArgumentCaptor; import org.mockito.junit.jupiter.MockitoExtension; @@ -96,6 +102,35 @@ void testProcessInstanceAbort() { eq(getProcessInstance(processId, processInstanceId, 1, null, null))); } + @Test + void testProcessExecuteInstance() { + String assesmentInstanceId = UUID.randomUUID().toString(); + ProcessInstanceStateDataEvent assesmentEvent = getProcessCloudEvent(processId, assesmentInstanceId, ACTIVE, null, null, null, "currentUser"); + indexProcessCloudEvent(assesmentEvent); + final String assesmentVarName = "assesmentVar"; + final String assesmentVarValue = "assesmentValue"; + final String infraVarName = "clientVar"; + final String infraVarValue = "clientValue"; + ProcessInstanceVariableDataEvent variableEvent = new ProcessInstanceVariableDataEvent(); + variableEvent.setKogitoProcessId(processId); + variableEvent.setKogitoProcessInstanceId(assesmentInstanceId); + variableEvent.setData(ProcessInstanceVariableEventBody.create().processId(processId).processInstanceId(assesmentInstanceId) + .variableName(assesmentVarName).variableValue(assesmentVarValue).build()); + indexProcessCloudEvent(variableEvent); + final String infraProcessId = "infra"; + ProcessDefinitionDataEvent definitionEvent = TestUtils.getProcessDefinitionDataEvent(infraProcessId); + indexProcessCloudEvent(definitionEvent); + checkOkResponse("{ \"query\" : \"mutation{ ExecuteAfter ( " + fragment("completedInstanceId", assesmentInstanceId) + "," + fragment("processId", infraProcessId) + + "," + fragment("processVersion", TestUtils.PROCESS_VERSION) + "," + "input: {" + fragment(infraVarName, infraVarValue) + "})}\"}"); + verify(dataIndexApiClient).executeProcessIntance(TestUtils.getProcessDefinition(infraProcessId), + ExecuteArgs.of(ObjectMapperFactory.get().createObjectNode().put(assesmentVarName, assesmentVarValue) + .put(infraVarName, infraVarValue))); + } + + private String fragment(String name, String value) { + return name + ": \\\"" + value + "\\\""; + } + @Test void testProcessInstanceRetry() { String processInstanceId = UUID.randomUUID().toString(); diff --git a/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java b/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java index 600d52824e..ad42f25eee 100644 --- a/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java +++ b/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java @@ -59,6 +59,7 @@ import org.kie.kogito.index.model.Milestone; import org.kie.kogito.index.model.MilestoneStatus; import org.kie.kogito.index.model.NodeInstance; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.ProcessInstanceError; import org.kie.kogito.index.model.ProcessInstanceState; @@ -121,6 +122,13 @@ public static ProcessDefinitionDataEvent getProcessDefinitionDataEvent(String pr return new ProcessDefinitionDataEvent(body); } + public static ProcessDefinition getProcessDefinition(String processId) { + ProcessDefinition def = new ProcessDefinition(); + def.setId(processId); + def.setVersion(TestUtils.PROCESS_VERSION); + return def; + } + public static ProcessInstanceStateDataEvent getProcessCloudEvent(String processId, String processInstanceId, ProcessInstanceState status, String rootProcessInstanceId, String rootProcessId, String parentProcessInstanceId, String identity) { diff --git a/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/main/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImpl.java b/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/main/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImpl.java index 1f9b64d91d..f681f39d18 100644 --- a/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/main/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImpl.java +++ b/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/main/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImpl.java @@ -29,13 +29,17 @@ import org.eclipse.microprofile.context.ManagedExecutor; import org.kie.kogito.Application; +import org.kie.kogito.Model; +import org.kie.kogito.index.api.ExecuteArgs; import org.kie.kogito.index.api.KogitoRuntimeClient; import org.kie.kogito.index.api.KogitoRuntimeCommonClient; import org.kie.kogito.index.model.Node; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.DataIndexServiceException; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; +import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.kie.kogito.process.Process; import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.Processes; @@ -273,4 +277,18 @@ private String executeOnProcessInstance(String processId, String processInstance } }); } + + @Override + public CompletableFuture executeProcessIntance(ProcessDefinition definition, ExecuteArgs args) { + Process process = processes != null ? processes.processById(definition.getId()) : null; + if (process == null) { + throw new DataIndexServiceException(String.format("Unable to find Process with id %s to perform the operation requested", definition.getId())); + } + Model m = (Model) process.createModel(); + m.update(JsonObjectUtils.convertValue(args.input(), Map.class)); + org.kie.kogito.process.ProcessInstance pi = process.createInstance(m); + pi.start(); + return CompletableFuture.completedFuture( + String.format(SUCCESSFULLY_OPERATION_MESSAGE, "Started Process Instance with id: " + pi.id())); + } } diff --git a/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/test/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImplTest.java b/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/test/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImplTest.java index 6392cd4941..7830460a87 100644 --- a/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/test/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImplTest.java +++ b/data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-common/runtime/src/test/java/org/kie/kogito/index/addon/api/KogitoAddonRuntimeClientImplTest.java @@ -19,16 +19,21 @@ package org.kie.kogito.index.addon.api; import java.nio.Buffer; +import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.kie.kogito.Application; +import org.kie.kogito.Model; +import org.kie.kogito.index.api.ExecuteArgs; import org.kie.kogito.index.api.KogitoRuntimeCommonClient; import org.kie.kogito.index.model.Job; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.test.TestUtils; +import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.process.ProcessError; import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.ProcessInstances; @@ -42,6 +47,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import com.fasterxml.jackson.databind.JsonNode; + import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.SecurityIdentity; import io.vertx.core.AsyncResult; @@ -123,6 +130,9 @@ public class KogitoAddonRuntimeClientImplTest { @Mock Instance applicationInstance; + @Mock + Model model; + @Mock private Application application; @@ -133,6 +143,8 @@ public void setup() { lenient().when(processesInstance.isResolvable()).thenReturn(true); lenient().when(processesInstance.get()).thenReturn(processes); lenient().when(processes.processById(anyString())).thenReturn(process); + lenient().when(process.createModel()).thenReturn(model); + lenient().when(process.createInstance(model)).thenReturn(processInstance); lenient().when(process.instances()).thenReturn(instances); lenient().when(instances.findById(anyString())).thenReturn(Optional.of(processInstance)); lenient().when(processInstance.error()).thenReturn(Optional.of(error)); @@ -173,6 +185,16 @@ private ProcessError mockProcessInstanceStatusActiveOnError() { }).when(error); } + @Test + void testExecuteAfterSuccess() { + ProcessDefinition definition = new ProcessDefinition(); + definition.setId("infra"); + JsonNode body = ObjectMapperFactory.get().createObjectNode().put("name", "javierito"); + client.executeProcessIntance(definition, ExecuteArgs.of(body)); + verify(model).update(Map.of("name", "javierito")); + verify(processInstance).start(); + } + @Test void testAbortProcessInstanceSuccess() { ProcessInstance pI = createProcessInstance(PROCESS_INSTANCE_ID, ACTIVE); diff --git a/data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-common/runtime/src/main/java/org/kie/kogito/index/addon/graphql/GraphQLAddonSchemaManagerImpl.java b/data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-common/runtime/src/main/java/org/kie/kogito/index/addon/graphql/GraphQLAddonSchemaManagerImpl.java index d9c853bbd8..ca8a07eb32 100644 --- a/data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-common/runtime/src/main/java/org/kie/kogito/index/addon/graphql/GraphQLAddonSchemaManagerImpl.java +++ b/data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-common/runtime/src/main/java/org/kie/kogito/index/addon/graphql/GraphQLAddonSchemaManagerImpl.java @@ -34,6 +34,7 @@ public class GraphQLAddonSchemaManagerImpl extends AbstractGraphQLSchemaManager public GraphQLSchema createSchema() { TypeDefinitionRegistry typeDefinitionRegistry = new TypeDefinitionRegistry(); typeDefinitionRegistry.merge(loadSchemaDefinitionFile("basic.schema.graphqls")); + loadAdditionalMutations(typeDefinitionRegistry); RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type("Query", builder -> { @@ -53,6 +54,7 @@ public GraphQLSchema createSchema() { builder.dataFetcher("NodeInstanceCancel", this::cancelNodeInstance); builder.dataFetcher("JobCancel", this::cancelJob); builder.dataFetcher("JobReschedule", this::rescheduleJob); + loadAdditionalMutations(builder); return builder; }) .type("ProcessDefinition", builder -> { diff --git a/data-index/pom.xml b/data-index/pom.xml index 4b2801bbd9..0de3d898b3 100644 --- a/data-index/pom.xml +++ b/data-index/pom.xml @@ -42,6 +42,7 @@ data-index-service kogito-addons-quarkus-data-index-persistence kogito-addons-quarkus-data-index + data-index-mutations diff --git a/kogito-apps-bom/pom.xml b/kogito-apps-bom/pom.xml index 774cd46967..e28ce31371 100644 --- a/kogito-apps-bom/pom.xml +++ b/kogito-apps-bom/pom.xml @@ -250,6 +250,11 @@ data-index-service ${project.version} + + org.kie.kogito + data-index-shared-output-mutation + ${project.version} + org.kie.kogito data-index-storage-api