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-8330] ForEach and subprocess #2715

Merged
merged 9 commits into from
Jan 5, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@
package org.kie.kogito.internal.process.event;

import java.util.Collection;
import java.util.Objects;

public interface KogitoObjectListenerAware {

default void fireEvent(String propertyName, Object oldValue, Object newValue, Runnable updater) {
if (newValue instanceof KogitoObjectListenerAware) {
((KogitoObjectListenerAware) newValue).addKogitoObjectListener(new KogitoObjectListenerAwareListener(this, propertyName));
if (!Objects.equals(oldValue, newValue)) {
if (newValue instanceof KogitoObjectListenerAware) {
((KogitoObjectListenerAware) newValue).addKogitoObjectListener(new KogitoObjectListenerAwareListener(this, propertyName));
}
listeners().forEach(l -> l.beforeValueChanged(this, propertyName, oldValue, newValue));
updater.run();
listeners().forEach(l -> l.afterValueChanged(this, propertyName, oldValue, newValue));
}
listeners().forEach(l -> l.beforeValueChanged(this, propertyName, oldValue, newValue));
updater.run();
listeners().forEach(l -> l.afterValueChanged(this, propertyName, oldValue, newValue));
}

void addKogitoObjectListener(KogitoObjectListener listener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public void visitNode(String factoryField, SubProcessNode node, BlockStmt body,
.forEach(t -> t.setName(subProcessModelClassName));

retValueExpression.findFirst(MethodDeclaration.class, m -> m.getNameAsString().equals("bind"))
.ifPresent(m -> m.setBody(bind(node, subProcessModel)));
.ifPresent(m -> m.setBody(bind(subProcessModel)));
retValueExpression.findFirst(MethodDeclaration.class, m -> m.getNameAsString().equals("createInstance"))
.ifPresent(m -> m.setBody(createInstance(node, metadata)));
retValueExpression.findFirst(MethodDeclaration.class, m -> m.getNameAsString().equals("unbind"))
Expand All @@ -113,7 +113,7 @@ public void visitNode(String factoryField, SubProcessNode node, BlockStmt body,
body.addStatement(getDoneMethod(getNodeId(node)));
}

private BlockStmt bind(SubProcessNode subProcessNode, ModelMetaData subProcessModel) {
private BlockStmt bind(ModelMetaData subProcessModel) {
BlockStmt actionBody = new BlockStmt();
actionBody.addStatement(subProcessModel.newInstance("model"));

Expand All @@ -135,23 +135,7 @@ private BlockStmt bind(SubProcessNode subProcessNode, ModelMetaData subProcessMo
AssignExpr inputs = new AssignExpr(expr, processInputsExpr, AssignExpr.Operator.ASSIGN);
actionBody.addStatement(inputs);

// do the actual assignments
for (DataDefinition inputDefinition : subProcessNode.getIoSpecification().getDataInput().values()) {
// remove multiinstance data. It does not belong to this model it is just for calculations with
// data associations
String collectionInput = (String) subProcessNode.getMetaData().get("MICollectionInput");
if (collectionInput != null && collectionInput.equals(inputDefinition.getLabel())) {
continue;
}
DataDefinition multiInstance = subProcessNode.getMultiInstanceSpecification().getInputDataItem();
if (multiInstance != null && multiInstance.getLabel().equals(inputDefinition.getLabel())) {
continue;
}

Expression getValueExpr = new MethodCallExpr(new NameExpr("inputs"), "get", nodeList(new StringLiteralExpr(inputDefinition.getLabel())));
actionBody.addStatement(subProcessModel.callSetter("model", inputDefinition.getLabel(), getValueExpr));
}

actionBody.addStatement(subProcessModel.callUpdateFromMap("model", "inputs"));
actionBody.addStatement(new ReturnStmt(new NameExpr("model")));
return actionBody;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ public MethodCallExpr callSetter(String targetVar, String destField, String valu
return callSetter(targetVar, destField, new NameExpr(value));
}

public MethodCallExpr callUpdateFromMap(String targetVar, String mapVar) {
return new MethodCallExpr(new NameExpr(targetVar), "update").addArgument(new NameExpr(mapVar));
}

public MethodCallExpr callSetter(String targetVar, String destField, Expression value) {
String name = variableScope.getTypes().get(destField).getSanitizedName();
String type = variableScope.getType(destField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CastExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.DoubleLiteralExpr;
Expand Down Expand Up @@ -137,9 +138,10 @@ private static Expression convertExpression(Object object) {
}
if (objectClass != null) {
// will generate TypeConverterRegistry.get().forType("JsonNode.class").apply("{\"dog\":\"perro\"}"))
return new MethodCallExpr(new MethodCallExpr(new MethodCallExpr(new TypeExpr(StaticJavaParser.parseClassOrInterfaceType(TypeConverterRegistry.class.getName())), "get"), "forType",
NodeList.nodeList(new StringLiteralExpr(objectClass.getName()))), "apply",
NodeList.nodeList(new StringLiteralExpr().setString(TypeConverterRegistry.get().forTypeReverse(object).apply((object)))));
return new CastExpr(StaticJavaParser.parseClassOrInterfaceType(object.getClass().getName()),
new MethodCallExpr(new MethodCallExpr(new MethodCallExpr(new TypeExpr(StaticJavaParser.parseClassOrInterfaceType(TypeConverterRegistry.class.getName())), "get"), "forType",
NodeList.nodeList(new StringLiteralExpr(objectClass.getName()))), "apply",
NodeList.nodeList(new StringLiteralExpr().setString(TypeConverterRegistry.get().forTypeReverse(object).apply((object))))));
} else {
return new StringLiteralExpr().setString(object.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.process.core.datatype.impl.coverter;

import java.io.IOException;

import org.jbpm.process.core.datatype.DataType;
import org.jbpm.process.core.datatype.DataTypeResolver;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

public class DataTypeDeserializer extends JsonDeserializer<DataType> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class DataTypeDeserializer extends JsonDeserializer<DataType> {
final class DataTypeDeserializer extends JsonDeserializer<DataType> {

Copy link
Contributor Author

@fjtirado fjtirado Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, why changing public to final?
I agree most classes that are public probably should not be public, but in these cases, these serializers might be extended (Im not saying they will, but that we are not 100% sure is not the case, we are not particularly interested on preventing inheritance)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it's fine to leave it as public.
I thought it was an implementation detail. That's why I suggested reducing visibility.


@Override
public DataType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return DataTypeResolver.fromType(p.getValueAsString(), Thread.currentThread().getContextClassLoader());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.process.core.datatype.impl.coverter;

import java.io.IOException;

import org.jbpm.process.core.datatype.DataType;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class DataTypeSerializer extends JsonSerializer<DataType> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class DataTypeSerializer extends JsonSerializer<DataType> {
final class DataTypeSerializer extends JsonSerializer<DataType> {


@Override
public void serialize(DataType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.getStringType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.process.core.datatype.impl.coverter;

import java.util.function.Function;

import org.kie.kogito.jackson.utils.ObjectMapperFactory;

import com.fasterxml.jackson.core.JsonProcessingException;

public class JacksonConverter<T> implements Function<String, T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class JacksonConverter<T> implements Function<String, T> {
final class JacksonConverter<T> implements Function<String, T> {


private Class<T> clazz;
fjtirado marked this conversation as resolved.
Show resolved Hide resolved

public JacksonConverter(Class<T> clazz) {
this.clazz = clazz;
}

@Override
public T apply(String t) {
try {
return ObjectMapperFactory.get().readValue(t, clazz);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.process.core.datatype.impl.coverter;

import java.util.function.Function;

import org.kie.kogito.jackson.utils.ObjectMapperFactory;

import com.fasterxml.jackson.core.JsonProcessingException;

public class JacksonUnconverter<T> implements Function<T, String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class JacksonUnconverter<T> implements Function<T, String> {
final class JacksonUnconverter<T> implements Function<T, String> {


@Override
public String apply(T t) {
try {
return ObjectMapperFactory.get().writeValueAsString(t);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
*/
package org.jbpm.process.core.datatype.impl.coverter;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.jbpm.process.core.context.variable.Variable;
import org.jbpm.process.core.datatype.DataType;
import org.kie.kogito.jackson.utils.JsonNodeConverter;
import org.kie.kogito.jackson.utils.ObjectMapperFactory;
import org.kie.kogito.jackson.utils.StringConverter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class TypeConverterRegistry {

Expand All @@ -34,9 +38,20 @@ public class TypeConverterRegistry {
private Function<String, String> defaultConverter = new NoOpTypeConverter();

private TypeConverterRegistry() {
converters.put("java.util.Date", new DateTypeConverter());
converters.put(Date.class.getName(), new DateTypeConverter());
converters.put(JsonNode.class.getName(), new JsonNodeConverter(ObjectMapperFactory::listenerAware));
unconverters.put(JsonNode.class.getName(), new StringConverter());
addJacksonPair(Variable.class, Map.class);
}

private void addJacksonPair(Class<?>... classes) {
SimpleModule module = new SimpleModule();
module.addSerializer(DataType.class, new DataTypeSerializer()).addDeserializer(DataType.class, new DataTypeDeserializer());
ObjectMapperFactory.get().registerModule(module);
for (Class<?> clazz : classes) {
converters.put(clazz.getName(), new JacksonConverter<>(clazz));
unconverters.put(clazz.getName(), new JacksonUnconverter<>());
}
}

public boolean isRegistered(String type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.jbpm.process.instance.context.variable;

import java.util.List;
import java.util.Objects;

import org.jbpm.process.instance.InternalProcessRuntime;
import org.jbpm.process.instance.ProcessInstance;
Expand Down Expand Up @@ -59,4 +60,23 @@ public void beforeValueChanged(Object container, String property, Object oldValu
private KogitoProcessEventSupport getProcessEventSupport() {
return ((InternalProcessRuntime) processInstance.getKnowledgeRuntime().getProcessRuntime()).getProcessEventSupport();
}

@Override
public int hashCode() {
return Objects.hash(processInstance, tags, variableIdPrefix, variableInstanceIdPrefix);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VariableScopeListener other = (VariableScopeListener) obj;
return Objects.equals(processInstance, other.processInstance) && Objects.equals(tags, other.tags)
&& Objects.equals(variableIdPrefix, other.variableIdPrefix)
&& Objects.equals(variableInstanceIdPrefix, other.variableInstanceIdPrefix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import org.jbpm.ruleflow.core.factory.AbstractCompositeNodeFactory;
import org.jbpm.ruleflow.core.factory.CompositeContextNodeFactory;
import org.jbpm.ruleflow.core.factory.NodeFactory;
import org.jbpm.ruleflow.core.factory.SubProcessNodeFactory;
import org.jbpm.ruleflow.core.factory.TimerNodeFactory;
import org.kie.kogito.serverless.workflow.parser.FunctionNamespaceFactory;
import org.kie.kogito.serverless.workflow.parser.FunctionTypeHandlerFactory;
import org.kie.kogito.serverless.workflow.parser.ParserContext;
import org.kie.kogito.serverless.workflow.parser.VariableInfo;
import org.kie.kogito.serverless.workflow.utils.JsonNodeContext;

import io.serverlessworkflow.api.Workflow;
import io.serverlessworkflow.api.actions.Action;
Expand Down Expand Up @@ -120,10 +122,12 @@ private TimerNodeFactory<?> createTimerNode(RuleFlowNodeContainerFactory<?, ?> f
SubFlowRef subFlowRef,
String inputVar,
String outputVar) {
return subprocessNode(
SubProcessNodeFactory<?> subProcessNode = subprocessNode(
factory.subProcessNode(parserContext.newId()).name(subFlowRef.getWorkflowId()).processId(subFlowRef.getWorkflowId()).waitForCompletion(true),
inputVar,
outputVar);
JsonNodeContext.getEvalVariables(factory.getNode()).forEach(v -> subProcessNode.inMapping(v.getName(), v.getName()));
return subProcessNode;
}

private NodeFactory<?, ?> getActionNode(RuleFlowNodeContainerFactory<?, ?> embeddedSubProcess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ protected MakeNodeResult makeNode(RuleFlowNodeContainerFactory<?, ?> factory) {
factory.forEachNode(parserContext.newId()).sequential(false).waitForCompletion(true).expressionLanguage(workflow.getExpressionLang()).collectionExpression(state.getInputCollection())
.outputVariable(outputVarName, new ObjectDataType())
.metaData(Metadata.VARIABLE, ServerlessWorkflowParser.DEFAULT_WORKFLOW_VAR);
handleActions(result, state.getActions(), outputVarName, false);
if (state.getIterationParam() != null) {
result.variable(state.getIterationParam(), new ObjectDataType());
}
if (state.getOutputCollection() != null) {
result.completionAction(new CollectorActionSupplier(workflow.getExpressionLang(), state.getOutputCollection(), DEFAULT_WORKFLOW_VAR, ForEachNodeInstance.TEMP_OUTPUT_VAR));
}
handleActions(result, state.getActions(), outputVarName, false);
return new MakeNodeResult(result);
}

Expand Down
Loading