Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KOGITO-9354] Python service #3079

Merged
merged 7 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -34,6 +38,25 @@ public static <T> T convert(Object value, Class<T> clazz) {
return convert(value, clazz, Object::toString);
}

/**
* Converts a string into a list of objects
*
* @param <T>
* @param value object to be converted into list
* @param clazz the item target class
* @return a collection
*/
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
public static <T> Collection<T> convertToCollection(Object value, Class<T> clazz) {
return convertToCollection(value, clazz, ",");
}

public static <T> Collection<T> convertToCollection(Object value, Class<T> clazz, String separator) {
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
if (value == null) {
return Collections.emptyList();
}
return Arrays.stream(value.toString().split(separator)).map(v -> ConversionUtils.convert(v, clazz)).collect(Collectors.toList());
}

/**
* Converts an object to an instance of the provided class
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/
package org.kie.kogito.internal.utils;

import java.util.Arrays;
import java.util.Objects;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.kie.kogito.internal.utils.ConversionUtils.concatPaths;
import static org.kie.kogito.internal.utils.ConversionUtils.convert;
import static org.kie.kogito.internal.utils.ConversionUtils.convertToCollection;
import static org.kie.kogito.internal.utils.ConversionUtils.toCamelCase;

class ConversionUtilsTest {
Expand Down Expand Up @@ -127,6 +129,10 @@ public void testConcatPaths() {
assertThat(concatPaths("http:localhost:8080/pepe", "pepa/pepi")).isEqualTo(expected);
assertThat(concatPaths("http:localhost:8080/pepe/", "pepa/pepi")).isEqualTo(expected);
assertThat(concatPaths("http:localhost:8080/pepe", "/pepa/pepi")).isEqualTo(expected);
}

@Test
public void testConvertToCollection() {
assertThat(convertToCollection("1,2,3", Integer.class)).isEqualTo(Arrays.asList(1, 2, 3));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.serverlessworkflow.api.functions.FunctionRef;

import static org.kie.kogito.serverless.workflow.SWFConstants.PYTHON;
import static org.kie.kogito.serverless.workflow.SWFConstants.PYTHON_SCRIPT;
import static org.kie.kogito.serverless.workflow.SWFConstants.SCRIPT;
import static org.kie.kogito.serverless.workflow.parser.FunctionTypeHandlerFactory.trimCustomOperation;
import static org.kie.kogito.serverless.workflow.parser.handlers.ActionNodeUtils.actionNode;
Expand All @@ -49,7 +50,7 @@ public String type() {
if (PYTHON.equalsIgnoreCase(lang)) {
return addFunctionArgs(workflow,
buildWorkItem(embeddedSubProcess, context, varInfo.getInputVar(), varInfo.getOutputVar()).name(functionDef.getName()),
functionRef).workName(PYTHON);
functionRef).workName(PYTHON_SCRIPT);
} else {
return actionNode(embeddedSubProcess, context, functionDef).action(JavaDialect.ID,
functionRef.getArguments().get(SCRIPT).asText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,22 @@
import io.serverlessworkflow.api.functions.FunctionDefinition;
import io.serverlessworkflow.api.functions.FunctionRef;

import static org.kie.kogito.serverless.workflow.SWFConstants.JAVA;
import static org.kie.kogito.serverless.workflow.SWFConstants.PYTHON;
import static org.kie.kogito.serverless.workflow.SWFConstants.PYTHON_SVC;
import static org.kie.kogito.serverless.workflow.SWFConstants.SERVICE_IMPL_KEY;
import static org.kie.kogito.serverless.workflow.SWFConstants.SERVICE_TASK_TYPE;
import static org.kie.kogito.serverless.workflow.SWFConstants.WORKITEM_INTERFACE;
import static org.kie.kogito.serverless.workflow.SWFConstants.WORKITEM_INTERFACE_IMPL;
import static org.kie.kogito.serverless.workflow.SWFConstants.WORKITEM_OPERATION;
import static org.kie.kogito.serverless.workflow.SWFConstants.WORKITEM_OPERATION_IMPL;
import static org.kie.kogito.serverless.workflow.parser.FunctionTypeHandlerFactory.trimCustomOperation;
import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.resolveFunctionMetadata;

public class ServiceTypeHandler extends WorkItemTypeHandler {

public static final String SERVICE_TYPE = "service";
public static final String SERVICE_TASK_TYPE = "Service Task";
public static final String WORKITEM_INTERFACE = "Interface";
public static final String WORKITEM_OPERATION = "Operation";
public static final String INTFC_SEPARATOR = "::";
public static final String SERVICE_IMPL_KEY = "implementation";
public static final String WORKITEM_INTERFACE_IMPL = "interfaceImplementationRef";
public static final String WORKITEM_OPERATION_IMPL = "operationImplementationRef";
private static final String WORKITEM_PARAM_TYPE = "ParameterType";

private static final String LANG_SEPARATOR = ":";
Expand Down Expand Up @@ -79,15 +82,23 @@ public class ServiceTypeHandler extends WorkItemTypeHandler {
}
if (lang == null) {
lang = resolveFunctionMetadata(
functionDef, SERVICE_IMPL_KEY, context.getContext(), String.class, "Java");
functionDef, SERVICE_IMPL_KEY, context.getContext(), String.class, JAVA);
}
switch (lang) {
case PYTHON:
node.workName(PYTHON_SVC);
break;
case JAVA:
default:
node.workName(SERVICE_TASK_TYPE);
break;
}
return node.workParameter(WORKITEM_INTERFACE, intfc)
.workParameter(WORKITEM_OPERATION, method)
.workParameter(WORKITEM_INTERFACE_IMPL, intfc)
.workParameter(WORKITEM_OPERATION_IMPL, method)
.workParameter(SERVICE_IMPL_KEY, lang)
.metaData(TaskDescriptor.KEY_WORKITEM_TYPE, SERVICE_TASK_TYPE)
.workName(SERVICE_TASK_TYPE);
.metaData(TaskDescriptor.KEY_WORKITEM_TYPE, SERVICE_TASK_TYPE);
}

@Override
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,29 @@
*/
package org.kie.kogito.serverless.workflow.executor;

import org.kie.kogito.serverless.workflow.python.PythonWorkItemHandler;
import org.kie.kogito.serverless.workflow.python.PythonScriptWorkItemHandler;
import org.kie.kogito.serverless.workflow.python.PythonServiceWorkItemHandler;

public class StaticPythonScriptRegister implements StaticApplicationRegister {

private PythonWorkItemHandler wih;
private PythonScriptWorkItemHandler scriptWIH;
private PythonServiceWorkItemHandler svcWIH;

@Override
public void register(StaticWorkflowApplication application) {
wih = new PythonWorkItemHandler();
application.registerHandler(wih);
scriptWIH = new PythonScriptWorkItemHandler();
application.registerHandler(scriptWIH);
svcWIH = new PythonServiceWorkItemHandler();
application.registerHandler(svcWIH);
}

@Override
public void close() {
if (wih != null) {
wih.close();
if (scriptWIH != null) {
scriptWIH.close();
}
if (svcWIH != null) {
svcWIH.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
import org.junit.jupiter.api.Test;
import org.kie.kogito.serverless.workflow.fluent.ActionBuilder.ScriptType;

import com.fasterxml.jackson.databind.node.TextNode;

import io.serverlessworkflow.api.Workflow;

import static org.assertj.core.api.Assertions.assertThat;
import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call;
import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.script;
import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.*;
import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.python;
import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation;
import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.jsonObject;
import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow;

Expand All @@ -48,4 +52,22 @@ void testPythonWithArgs() {
assertThat(application.execute(workflow, Map.of("x", 2)).getWorkflowdata().get("result").asInt()).isEqualTo(4);
}
}

@Test
void testPythonService() {
try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) {
Workflow workflow = workflow("Factorial").start(operation().action(call(python("factorial", "math", "factorial"), new TextNode(".x")).outputFilter(".result")))
.end().build();
assertThat(application.execute(workflow, Map.of("x", 5)).getWorkflowdata().get("result").asInt()).isEqualTo(120);
}
}

@Test
void testNotStandardPythonService() {
try (StaticWorkflowApplication application = StaticWorkflowApplication.create(Map.of("sonata.python.searchPath", "./src/test/resources/"))) {
Workflow workflow = workflow("Factorial").start(operation().action(call(python("factorial", "custom", "factorial"), new TextNode(".x")).outputFilter(".result")))
.end().build();
assertThat(application.execute(workflow, Map.of("x", 5)).getWorkflowdata().get("result").asInt()).isEqualTo(120);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def factorial(x):
result = 1
if x > 1:
for i in range(2,x+1):
result = result*i
return result
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,36 @@
*/
package org.kie.kogito.serverless.workflow.executor;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jbpm.compiler.canonical.ReflectionUtils;
import org.kie.kogito.serverless.workflow.ServiceWorkItemHandler;

import static org.kie.kogito.serverless.workflow.SWFConstants.SERVICE_TASK_TYPE;

public class StaticServiceWorkItemHandler extends ServiceWorkItemHandler {

@Override
protected Object invoke(String className, String methodName, Object... parameters) throws ReflectiveOperationException {
ClassLoader cls = Thread.currentThread().getContextClassLoader();
Class<?> clazz = cls.loadClass(className);
return ReflectionUtils.getMethod(cls, clazz, methodName, Stream.of(parameters).map(Object::getClass).map(Class::getName).collect(Collectors.toList())).invoke(getInstance(clazz), parameters);
protected Object invoke(String className, String methodName, Object parameters) {
try {
ClassLoader cls = Thread.currentThread().getContextClassLoader();
Class<?> clazz = cls.loadClass(className);
Object[] args = parameters instanceof Map ? ((Map<String, Object>) parameters).values().toArray() : new Object[] { parameters };
return ReflectionUtils.getMethod(cls, clazz, methodName, Stream.of(args).map(Object::getClass).map(Class::getName).collect(Collectors.toList())).invoke(getInstance(clazz),
args);
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}

protected Object getInstance(Class<?> clazz) throws ReflectiveOperationException {
return clazz.getConstructor().newInstance();
}

@Override
public String getName() {
return SERVICE_TASK_TYPE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Objects;
import java.util.function.Function;

import org.kie.kogito.serverless.workflow.SWFConstants;
import org.kie.kogito.serverless.workflow.actions.WorkflowLogLevel;
import org.kie.kogito.serverless.workflow.functions.FunctionDefinitionEx;
import org.kie.kogito.serverless.workflow.parser.types.ServiceTypeHandler;
Expand Down Expand Up @@ -59,13 +60,21 @@ public static FunctionBuilder expr(String name, String expression) {
return new FunctionBuilder(new FunctionDefinition(name).withType(Type.EXPRESSION).withOperation(expression));
}

public static <T, V> FunctionBuilder java(String name, Function<T, V> function) {
return new FunctionBuilder(new FunctionDefinitionEx<T, V>(name).withFunction(function).withType(Type.CUSTOM).withOperation("java"));
public static <T, V> FunctionBuilder java(String funcName, Function<T, V> function) {
return new FunctionBuilder(new FunctionDefinitionEx<T, V>(funcName).withFunction(function).withType(Type.CUSTOM).withOperation("java"));
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
}

public static FunctionBuilder java(String name, String className, String methodName) {
public static FunctionBuilder java(String funcName, String className, String methodName) {
return service(funcName, SWFConstants.JAVA, className, methodName);
}

public static FunctionBuilder python(String funcName, String moduleName, String methodName) {
return service(funcName, SWFConstants.PYTHON, moduleName, methodName);
}

private static FunctionBuilder service(String name, String langName, String moduleName, String methodName) {
return new FunctionBuilder(new FunctionDefinition(name).withType(Type.CUSTOM)
.withOperation(ServiceTypeHandler.SERVICE_TYPE + CUSTOM_TYPE_SEPARATOR + className + ServiceTypeHandler.INTFC_SEPARATOR + methodName));
.withOperation(ServiceTypeHandler.SERVICE_TYPE + CUSTOM_TYPE_SEPARATOR + langName + CUSTOM_TYPE_SEPARATOR + moduleName + ServiceTypeHandler.INTFC_SEPARATOR + methodName));
}

public static FunctionBuilder log(String name, WorkflowLogLevel level) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class PythonContextResolver implements KogitoProcessContextResolverExtens

@Override
public Map<String, Function<KogitoProcessContext, Object>> getKogitoProcessContextResolver() {
return Map.of(SWFConstants.PYTHON, k -> new FunctionJsonNode(PythonWorkItemHandler::getValue));
return Map.of(SWFConstants.PYTHON, k -> new FunctionJsonNode(PythonWorkItemHandlerUtils::getValue));
}

}
Loading