This is an automated email from the ASF dual-hosted git repository.
pefernan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git
The following commit(s) were added to refs/heads/main by this push:
new 45a28b66aef [kie-issues#7150] Support java.lang.Object as a process
variable type in BPM Processes (#7151)
45a28b66aef is described below
commit 45a28b66aefde8ebaf23a1a1fec76c11e53144d1
Author: Pere Fernández <[email protected]>
AuthorDate: Mon Oct 5 13:44:12 2026 +0200
[kie-issues#7150] Support java.lang.Object as a process variable type in
BPM Processes (#7151)
* [kie-issues#7150] Support java.lang.Object as a process variable type in
BPM Processes
* - improving testing
* - fix
* - adding `ObjectVariableProcess.bpmn2` to rat exclude
* - removing empty object from nested pojos
---
.rat-excludes | 1 +
.../kogito/codegen/json/JsonSchemaGenerator.java | 23 +++-
.../persistence/proto/AbstractProtoGenerator.java | 8 +-
.../proto/ReflectionProtoGenerator.java | 1 +
.../org/kie/kogito/codegen/data/ObjectHolder.java | 33 +++++
.../kogito/codegen/data/ObjectProcessModel.java | 53 ++++++++
.../codegen/json/JsonSchemaGeneratorTest.java | 89 ++++++++++++++
.../kogito/codegen/process/ProcessCodegenTest.java | 14 +++
.../proto/AbstractProtoGeneratorTest.java | 75 ++++++++----
.../objectvariable/ObjectVariableProcess.bpmn2 | 122 +++++++++++++++++++
.../org/jbpm/compiler/canonical/ModelMetaData.java | 10 ++
.../compiler/canonical/WorkItemModelMetaData.java | 26 +++-
.../flow/serialization/JavaSerializationUtils.java | 68 +++++++++++
.../ProtobufObjectMarshallerStrategy.java | 35 +-----
.../SerializableProtostreamBaseMarshaller.java | 29 ++---
.../serialization/JavaSerializationUtilsTest.java | 135 +++++++++++++++++++++
.../workflow/deployment/JandexProtoGenerator.java | 1 +
.../workflow/deployment/JandexTestUtils.java | 4 +
18 files changed, 648 insertions(+), 79 deletions(-)
diff --git a/.rat-excludes b/.rat-excludes
index f7bb5614773..e8d8ad5827a 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -2354,6 +2354,7 @@
kogito-codegen-modules/kogito-codegen-predictions/src/test/resources/prediction/
kogito-codegen-modules/kogito-codegen-predictions/src/test/resources/prediction/test_multiplemodels.pmml
kogito-codegen-modules/kogito-codegen-predictions/src/test/resources/prediction/test_regression.pmml
kogito-codegen-modules/kogito-codegen-predictions/src/test/resources/prediction/test_scorecard.pmml
+kogito-codegen-modules/kogito-codegen-processes/src/test/resources/objectvariable/ObjectVariableProcess.bpmn2
kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/META-INF/kogito.addon
kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
kogito-codegen-modules/kogito-codegen-sample/kogito-codegen-sample-generator/src/test/resources/sampleFile1.txt
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/json/JsonSchemaGenerator.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/json/JsonSchemaGenerator.java
index e101099eaf3..9b075485226 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/json/JsonSchemaGenerator.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/json/JsonSchemaGenerator.java
@@ -112,7 +112,7 @@ public class JsonSchemaGenerator {
builder.forTypesInGeneral()
.withStringFormatResolver(target ->
target.getSimpleTypeDescription().equals("Date") ? "date-time" : null);
builder.forFields()
- .withIgnoreCheck(JsonSchemaGenerator::checkFields)
+ .withIgnoreCheck(JsonSchemaGenerator::shouldIgnoreField)
.withCustomDefinitionProvider(this::getInputOutput);
SchemaGenerator generator = new SchemaGenerator(builder.build());
ObjectWriter writer = new ObjectMapper().writer();
@@ -164,9 +164,24 @@ public class JsonSchemaGenerator {
throw new RuntimeException("Cannot create the schema name. Class must
be have UserTask or ProcessInput annotation");
}
- private static boolean checkFields(FieldScope fieldScope) {
- return
(fieldScope.getDeclaringType().getErasedType().isAnnotationPresent(UserTask.class)
&& fieldScope.getAnnotation(UserTaskParam.class) == null)
- ||
(fieldScope.getDeclaringType().getErasedType().isAnnotationPresent(ProcessInput.class)
&& fieldScope.getAnnotation(VariableInfo.class) == null);
+ private static boolean shouldIgnoreField(FieldScope fieldScope) {
+ Class<?> declaringType = fieldScope.getDeclaringType().getErasedType();
+
+ if (declaringType.isAnnotationPresent(UserTask.class) &&
fieldScope.getAnnotation(UserTaskParam.class) == null
+ || declaringType.isAnnotationPresent(ProcessInput.class) &&
fieldScope.getAnnotation(VariableInfo.class) == null) {
+ return true;
+ }
+
+ if (Object.class.equals(fieldScope.getDeclaredType().getErasedType()))
{
+ logger.warn("Field '{}' in '{}' is declared as java.lang.Object
and will be excluded from the " +
+ "generated JSON schema. The UI cannot render a form widget
for an untyped field. " +
+ "Use a concrete type to enable form rendering.",
+ fieldScope.getDeclaredName(),
+ declaringType.getSimpleName());
+ return true;
+ }
+
+ return false;
}
}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGenerator.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGenerator.java
index 10576e43d51..f507f640361 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGenerator.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGenerator.java
@@ -270,11 +270,17 @@ public abstract class AbstractProtoGenerator<T>
implements ProtoGenerator {
if (cls.isInterface()) {
return null;
}
+ if (Object.class.getCanonicalName().equals(type)) {
+ LOGGER.warn("A field is declared as java.lang.Object. " +
+ "It will be persisted using Java serialization. " +
+ "Make sure the stored value implements
java.io.Serializable.");
+ return KOGITO_SERIALIZABLE;
+ }
boolean assignable = Serializable.class.isAssignableFrom(cls);
if (assignable) {
return KOGITO_SERIALIZABLE;
} else {
- throw new IllegalArgumentException(format("Java type %s is
no supported by Kogito persistence, please consider using a class that extends
java.io.Serializable", type));
+ throw new IllegalArgumentException(format("Java type %s is
not supported by Kogito persistence, please consider using a class that extends
java.io.Serializable", type));
}
} catch (ClassNotFoundException e) {
return null;
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/ReflectionProtoGenerator.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/ReflectionProtoGenerator.java
index 05f3e7f57a9..8473365a2ae 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/ReflectionProtoGenerator.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/persistence/proto/ReflectionProtoGenerator.java
@@ -87,6 +87,7 @@ public class ReflectionProtoGenerator extends
AbstractProtoGenerator<Class<?>> {
String fieldTypeString = pd.getPropertyType().getCanonicalName();
Class<?> fieldType = pd.getPropertyType();
+ LOGGER.debug("Resolving proto type for field '{}' of type '{}' in
'{}'", pd.getName(), fieldTypeString, clazz.getName());
String protoType;
if (pd.getPropertyType().isArray() &&
!pd.getPropertyType().getComponentType().isPrimitive()) {
fieldTypeString = ARRAY;
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectHolder.java
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectHolder.java
new file mode 100644
index 00000000000..8acaec88179
--- /dev/null
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectHolder.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.kogito.codegen.data;
+
+public class ObjectHolder {
+
+ private Object value;
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectProcessModel.java
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectProcessModel.java
new file mode 100644
index 00000000000..8703b5539ec
--- /dev/null
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/data/ObjectProcessModel.java
@@ -0,0 +1,53 @@
+/*
+ * 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.codegen.data;
+
+import org.kie.kogito.MapInput;
+import org.kie.kogito.MapInputId;
+import org.kie.kogito.MapOutput;
+import org.kie.kogito.Model;
+
+import jakarta.validation.Valid;
+
[email protected](value = "kogito-codegen", reference =
"objectProcess", name = "ObjectProcess", hidden = false)
+public class ObjectProcessModel implements Model, MapInput, MapInputId,
MapOutput {
+
+ private String id;
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ @org.kie.kogito.codegen.VariableInfo(tags = "")
+ @com.fasterxml.jackson.annotation.JsonProperty(value = "value")
+ @jakarta.validation.Valid()
+ private Object value;
+
+ public @Valid() Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/json/JsonSchemaGeneratorTest.java
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/json/JsonSchemaGeneratorTest.java
index 9d72d78744d..82629c37037 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/json/JsonSchemaGeneratorTest.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/json/JsonSchemaGeneratorTest.java
@@ -46,6 +46,7 @@ import com.github.victools.jsonschema.generator.SchemaVersion;
import static org.assertj.core.api.Assertions.assertThat;
import static
org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+import static
org.kie.kogito.codegen.json.JsonSchemaGenerator.DEFAULT_SCHEMA_VERSION;
public class JsonSchemaGeneratorTest {
@@ -152,6 +153,43 @@ public class JsonSchemaGeneratorTest {
private Person person;
}
+ @ProcessInput(processName = "objectVarProcess")
+ private static class ProcessInputModelWithObjectVar {
+
+ @VariableInfo
+ private String name;
+
+ @VariableInfo
+ private Object data;
+ }
+
+ @UserTask(taskName = "objectVarTask", processName = "org.jbpm.test")
+ private static class UserTaskWithObjectVar {
+
+ @UserTaskParam(UserTaskParam.ParamType.INPUT)
+ private String name;
+
+ @UserTaskParam(UserTaskParam.ParamType.INPUT)
+ private Object payload;
+ }
+
+ private static class HolderWithObjectField {
+
+ private String label;
+
+ private Object nested;
+ }
+
+ @ProcessInput(processName = "nestedObjectVarProcess")
+ private static class ProcessInputModelWithNestedObjectVar {
+
+ @VariableInfo
+ private String name;
+
+ @VariableInfo
+ private HolderWithObjectField holder;
+ }
+
private static class Person {
@SuppressWarnings("unused")
@@ -415,4 +453,55 @@ public class JsonSchemaGeneratorTest {
private String resolveDefinitionsProperty(SchemaVersion schemaVersion) {
return SchemaVersion.DRAFT_2019_09.equals(schemaVersion) ? "$defs" :
"definitions";
}
+
+ @Test
+ public void testObjectTypedFieldIsExcludedFromSchema() throws IOException {
+ Collection<GeneratedFile> files = new JsonSchemaGenerator.ClassBuilder(
+
Stream.of(ProcessInputModelWithObjectVar.class)).build().generate();
+ assertThat(files).hasSize(1);
+
+ ObjectReader reader = new ObjectMapper().reader();
+ JsonNode node = reader.readTree(files.iterator().next().contents());
+
+ JsonNode properties = node.get("properties");
+ assertThat(properties).isNotNull();
+ assertThat(properties.has("name")).isTrue();
+ assertThat(properties.has("data")).isFalse();
+ }
+
+ @Test
+ public void testObjectTypedUserTaskParamIsExcludedFromSchema() throws
IOException {
+ Collection<GeneratedFile> files = new JsonSchemaGenerator.ClassBuilder(
+ Stream.of(UserTaskWithObjectVar.class)).build().generate();
+ assertThat(files).hasSize(1);
+
+ ObjectReader reader = new ObjectMapper().reader();
+ JsonNode properties =
reader.readTree(files.iterator().next().contents()).get("properties");
+ assertThat(properties).isNotNull();
+ assertThat(properties.has("name")).isTrue();
+ assertThat(properties.has("payload")).isFalse();
+ }
+
+ @Test
+ public void testNestedObjectTypedFieldIsExcludedFromSchema() throws
IOException {
+ Collection<GeneratedFile> files = new JsonSchemaGenerator.ClassBuilder(
+
Stream.of(ProcessInputModelWithNestedObjectVar.class)).build().generate();
+ assertThat(files).hasSize(1);
+
+ ObjectReader reader = new ObjectMapper().reader();
+ JsonNode node = reader.readTree(files.iterator().next().contents());
+
+ // top-level: holder present, name present
+ JsonNode properties = node.get("properties");
+ assertThat(properties).isNotNull();
+ assertThat(properties.has("name")).isTrue();
+ assertThat(properties.has("holder")).isTrue();
+
+ // nested: label present, but nested Object field excluded
+ String definitionsKey =
resolveDefinitionsProperty(DEFAULT_SCHEMA_VERSION);
+ JsonNode holderDef =
node.get(definitionsKey).get("HolderWithObjectField");
+ assertThat(holderDef).isNotNull();
+ assertThat(holderDef.get("properties").has("label")).isTrue();
+ assertThat(holderDef.get("properties").has("nested")).isFalse();
+ }
}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessCodegenTest.java
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessCodegenTest.java
index d24d83d5690..c9970338878 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessCodegenTest.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessCodegenTest.java
@@ -51,6 +51,8 @@ class ProcessCodegenTest {
private static final Path MESSAGE_USERTASK_SOURCE_FULL_SOURCE =
BASE_PATH.resolve(MESSAGE_USERTASK_SOURCE);
private static final String REST_HYPHEN_PARAMS_SOURCE =
"workitem/RestServiceHyphenParams.bpmn2";
private static final Path REST_HYPHEN_PARAMS_FULL_PATH =
BASE_PATH.resolve(REST_HYPHEN_PARAMS_SOURCE);
+ private static final String OBJECT_VARIABLE_SOURCE =
"objectvariable/ObjectVariableProcess.bpmn2";
+ private static final Path OBJECT_VARIABLE_SOURCE_FULL_PATH =
BASE_PATH.resolve(OBJECT_VARIABLE_SOURCE);
@ParameterizedTest
@MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#contextBuilders")
@@ -195,4 +197,16 @@ class ProcessCodegenTest {
.as("Hyphenated identifier QUERY_max-size must not appear in
generated code")
.doesNotContain("QUERY_max-size");
}
+
+ @ParameterizedTest
+
@MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#contextBuilders")
+ public void
objectVariableProcessGeneratesWithoutError(KogitoBuildContext.Builder
contextBuilder) {
+ KogitoBuildContext context = contextBuilder.build();
+ ProcessCodegen codeGenerator = ProcessCodegen.ofCollectedResources(
+ context,
+ CollectedResourceProducer.fromFiles(BASE_PATH,
OBJECT_VARIABLE_SOURCE_FULL_PATH.toFile()));
+
+ Collection<GeneratedFile> generatedFiles = codeGenerator.generate();
+ assertThat(generatedFiles).isNotEmpty();
+ }
}
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGeneratorTest.java
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGeneratorTest.java
index b5921519bcc..baf97025f95 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGeneratorTest.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/persistence/proto/AbstractProtoGeneratorTest.java
@@ -19,33 +19,11 @@
package org.kie.kogito.codegen.process.persistence.proto;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import org.drools.codegen.common.GeneratedFile;
import org.junit.jupiter.api.Test;
-import org.kie.kogito.codegen.data.Address;
-import org.kie.kogito.codegen.data.Answer;
-import org.kie.kogito.codegen.data.AnswerBroken;
-import org.kie.kogito.codegen.data.AnswerBrokenV2;
-import org.kie.kogito.codegen.data.AnswerWithAnnotations;
-import org.kie.kogito.codegen.data.GeneratedPOJO;
-import org.kie.kogito.codegen.data.Hello;
-import org.kie.kogito.codegen.data.HelloModel;
-import org.kie.kogito.codegen.data.JacksonData;
-import org.kie.kogito.codegen.data.ListWithoutType;
-import org.kie.kogito.codegen.data.Person;
-import org.kie.kogito.codegen.data.PersonSubClass;
-import org.kie.kogito.codegen.data.PersonVarInfo;
-import org.kie.kogito.codegen.data.PersonWithAddress;
-import org.kie.kogito.codegen.data.PersonWithAddresses;
-import org.kie.kogito.codegen.data.PersonWithList;
-import org.kie.kogito.codegen.data.Question;
-import org.kie.kogito.codegen.data.QuestionWithAnnotatedEnum;
-import org.kie.kogito.codegen.data.Travels;
+import org.kie.kogito.codegen.data.*;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -826,6 +804,55 @@ public abstract class AbstractProtoGeneratorTest<T> {
assertClassIsIncludedInSubclass(person, personSubClass);
}
+ @Test
+ void pojoWithObjectField() {
+ AbstractProtoGenerator<T> generator = protoGeneratorBuilder()
+
.withDataClasses(Collections.singleton(convertToType(ObjectHolder.class)))
+ .build(null);
+
+ Proto proto =
generator.protoOfDataClasses("org.kie.kogito.test.persons");
+ assertThat(proto).isNotNull();
+
+
assertThat(proto.getPackageName()).isEqualTo("org.kie.kogito.test.persons");
+ assertThat(proto.getSyntax()).isEqualTo("proto2");
+ assertThat(proto.getMessages()).hasSize(1);
+
+ ProtoMessage protoMessage = proto.getMessages().get(0);
+ assertThat(protoMessage).isNotNull();
+ assertThat(protoMessage.getName()).isEqualTo("ObjectHolder");
+
assertThat(protoMessage.getJavaPackageOption()).isEqualTo("org.kie.kogito.codegen.data");
+ assertThat(protoMessage.getFields()).hasSize(1)
+ .element(0)
+ .hasFieldOrPropertyWithValue("name", "value")
+ .hasFieldOrPropertyWithValue("type", "kogito.Serializable");
+ }
+
+ @Test
+ void modelWithObjectField() {
+ AbstractProtoGenerator<T> generator = protoGeneratorBuilder()
+
.build(Collections.singleton(convertToType(ObjectProcessModel.class)));
+
+ Proto proto = generator.generate("comment",
ProtoGenerator.INDEX_COMMENT, "org.kie.kogito.test",
+ convertToType(ObjectProcessModel.class));
+
+ assertThat(proto.getPackageName()).isEqualTo("org.kie.kogito.test");
+ assertThat(proto.getSyntax()).isEqualTo("proto2");
+ assertThat(proto.getMessages()).hasSize(1);
+
+ ProtoMessage message = proto.getMessages().get(0);
+ assertThat(message.getName()).isEqualTo("ObjectProcess");
+
assertThat(message.getJavaPackageOption()).isEqualTo("org.kie.kogito.codegen.data");
+
+ assertThat(message.getFields()).hasSize(2);
+
+ ProtoField valueField = message.getFields().stream()
+ .filter(f -> f.getName().equals("value"))
+ .findFirst()
+ .orElseThrow();
+
assertThat(valueField.getType()).isEqualTo(ProtoGenerator.KOGITO_SERIALIZABLE);
+ assertThat(valueField.getOption()).isEqualTo("[kogito_java_class =
\"java.lang.Object\"]");
+ }
+
private void assertClassIsIncludedInSubclass(ProtoMessage superClass,
ProtoMessage subClass) {
for (ProtoField field : superClass.getFields()) {
assertThat(field).isNotNull();
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/objectvariable/ObjectVariableProcess.bpmn2
b/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/objectvariable/ObjectVariableProcess.bpmn2
new file mode 100644
index 00000000000..fd2eb59bc22
--- /dev/null
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/objectvariable/ObjectVariableProcess.bpmn2
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:bpsim="http://www.bpsim.org/schemas/1.0"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:drools="http://www.jboss.org/drools"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
id="_B8_akJtRED-1FZqqQztbrw"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd
http://www. [...]
+ <bpmn2:itemDefinition id="_objItem" structureRef="Object" />
+ <bpmn2:itemDefinition id="_E7762C91-1119-41A3-BB57-B4617F5D092B"
structureRef="String" />
+ <bpmn2:itemDefinition id="_8EEFAA15-E15B-438F-AB1B-1BE5E57DE91C"
structureRef="Integer" />
+ <bpmn2:collaboration id="_9211A22D-543A-46CC-A4D7-76DF2EEB4154"
name="Default Collaboration">
+ <bpmn2:participant id="_1B027897-2CBF-4063-A335-E4F1D9E0272F" name="Pool
Participant" processRef="objectprocess" />
+ </bpmn2:collaboration>
+ <bpmn2:process id="objectprocess" drools:packageName="com.example"
drools:version="1.0" drools:adHoc="false" name="objectprocess"
isExecutable="true" processType="Public">
+ <bpmn2:property id="obj" itemSubjectRef="_objItem" name="obj" />
+ <bpmn2:property id="_96C4A35F-EC53-4CCD-A8EE-0B6874D92226" name="name"
itemSubjectRef="_E7762C91-1119-41A3-BB57-B4617F5D092B">
+ <bpmn2:extensionElements />
+ </bpmn2:property>
+ <bpmn2:property id="_AAA41D2D-8E2E-4B52-9D12-E231392389C2" name="age"
itemSubjectRef="_8EEFAA15-E15B-438F-AB1B-1BE5E57DE91C">
+ <bpmn2:extensionElements />
+ </bpmn2:property>
+ <bpmn2:property id="_6F9BAD03-F58A-4C93-B444-A3CED8A62FF3"
name="_6F9BAD03-F58A-4C93-B444-A3CED8A62FF3" />
+ <bpmn2:laneSet id="_51D81EEB-490C-4BD3-BD93-9F34354E7541" />
+ <bpmn2:startEvent id="_EC77F346-CFAA-4180-8459-396589D8020A">
+ <bpmn2:outgoing>_41686461-A732-4D76-BE81-4AE9E74A58F0</bpmn2:outgoing>
+ </bpmn2:startEvent>
+ <bpmn2:endEvent id="_CB939C91-EB0A-48FC-8FF6-B1E6534B969B">
+ <bpmn2:incoming>_F6A671ED-93F2-4CD3-93D9-8CA03BD4D3B5</bpmn2:incoming>
+ </bpmn2:endEvent>
+ <bpmn2:scriptTask id="_17F91537-B6F4-4232-AE8A-1FA6F7B8865D" name="Log"
scriptFormat="http://www.java.com/java">
+ <bpmn2:extensionElements>
+ <drools:metaData name="elementname">
+ <drools:metaValue>log</drools:metaValue>
+ </drools:metaData>
+ </bpmn2:extensionElements>
+ <bpmn2:incoming>_D9DA7439-23C8-4956-BDC7-EB219622F408</bpmn2:incoming>
+ <bpmn2:outgoing>_F6A671ED-93F2-4CD3-93D9-8CA03BD4D3B5</bpmn2:outgoing>
+ <bpmn2:script>System.out.println("Process started with object
variable " + obj);</bpmn2:script>
+ </bpmn2:scriptTask>
+ <bpmn2:scriptTask id="_D19B34E2-7536-4075-A0FD-0AF3E78F77F3"
scriptFormat="http://www.java.com/java" name="Create Person.">
+ <bpmn2:extensionElements>
+ <drools:metaData name="customAsync">
+ <drools:metaValue>false</drools:metaValue>
+ </drools:metaData>
+ <drools:metaData name="customAutoStart">
+ <drools:metaValue>false</drools:metaValue>
+ </drools:metaData>
+ </bpmn2:extensionElements>
+ <bpmn2:incoming>_41686461-A732-4D76-BE81-4AE9E74A58F0</bpmn2:incoming>
+ <bpmn2:outgoing>_D9DA7439-23C8-4956-BDC7-EB219622F408</bpmn2:outgoing>
+ <bpmn2:script>System.out.println("creating person with values:
" + name + ":" + age);
+
+obj = new org.kie.kogito.codegen.data.Person(name, age);
+
+kcontext.setVariable("obj", obj);</bpmn2:script>
+ </bpmn2:scriptTask>
+ <bpmn2:sequenceFlow id="_41686461-A732-4D76-BE81-4AE9E74A58F0"
sourceRef="_EC77F346-CFAA-4180-8459-396589D8020A"
targetRef="_D19B34E2-7536-4075-A0FD-0AF3E78F77F3" />
+ <bpmn2:sequenceFlow id="_D9DA7439-23C8-4956-BDC7-EB219622F408"
sourceRef="_D19B34E2-7536-4075-A0FD-0AF3E78F77F3"
targetRef="_17F91537-B6F4-4232-AE8A-1FA6F7B8865D" />
+ <bpmn2:sequenceFlow id="_F6A671ED-93F2-4CD3-93D9-8CA03BD4D3B5"
sourceRef="_17F91537-B6F4-4232-AE8A-1FA6F7B8865D"
targetRef="_CB939C91-EB0A-48FC-8FF6-B1E6534B969B" />
+ </bpmn2:process>
+ <bpmndi:BPMNDiagram id="_E146D427-7409-455D-8E60-61AF7CFD8C80">
+ <bpmndi:BPMNPlane bpmnElement="objectprocess"
id="_D756861C-0374-4F19-831B-6DE0C1DD61AE">
+ <bpmndi:BPMNShape id="shape__EC77F346-CFAA-4180-8459-396589D8020A"
bpmnElement="_EC77F346-CFAA-4180-8459-396589D8020A">
+ <dc:Bounds height="56" width="56" x="20" y="180" />
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="shape__17F91537-B6F4-4232-AE8A-1FA6F7B8865D"
bpmnElement="_17F91537-B6F4-4232-AE8A-1FA6F7B8865D">
+ <dc:Bounds height="102" width="154" x="320" y="160" />
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="shape__CB939C91-EB0A-48FC-8FF6-B1E6534B969B"
bpmnElement="_CB939C91-EB0A-48FC-8FF6-B1E6534B969B">
+ <dc:Bounds height="56" width="56" x="540" y="180" />
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNShape id="_46C3831B-B77D-454D-B63B-434025DEC207"
bpmnElement="_D19B34E2-7536-4075-A0FD-0AF3E78F77F3">
+ <dc:Bounds x="100" y="160" width="180" height="100" />
+ </bpmndi:BPMNShape>
+ <bpmndi:BPMNEdge
id="_3F83D37B-DE4C-4A19-A667-2744A8824990-AUTO-SOURCE-AUTO-TARGET"
bpmnElement="_41686461-A732-4D76-BE81-4AE9E74A58F0"
sourceElement="shape__EC77F346-CFAA-4180-8459-396589D8020A"
targetElement="_46C3831B-B77D-454D-B63B-434025DEC207">
+ <di:waypoint x="48" y="208" />
+ <di:waypoint x="190" y="210" />
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge id="_449D042B-8497-4FE8-8D9B-E7A038DB0E18"
bpmnElement="_D9DA7439-23C8-4956-BDC7-EB219622F408"
sourceElement="_46C3831B-B77D-454D-B63B-434025DEC207"
targetElement="shape__17F91537-B6F4-4232-AE8A-1FA6F7B8865D">
+ <di:waypoint x="190" y="210" />
+ <di:waypoint x="320" y="211" />
+ </bpmndi:BPMNEdge>
+ <bpmndi:BPMNEdge id="_92AC4430-EFC8-4289-8D56-5B7443DFA6ED"
bpmnElement="_F6A671ED-93F2-4CD3-93D9-8CA03BD4D3B5"
sourceElement="shape__17F91537-B6F4-4232-AE8A-1FA6F7B8865D"
targetElement="shape__CB939C91-EB0A-48FC-8FF6-B1E6534B969B">
+ <di:waypoint x="397" y="211" />
+ <di:waypoint x="540" y="208" />
+ </bpmndi:BPMNEdge>
+ </bpmndi:BPMNPlane>
+ </bpmndi:BPMNDiagram>
+ <bpmn2:relationship type="BPSimData">
+ <bpmn2:extensionElements>
+ <bpsim:BPSimData>
+ <bpsim:Scenario id="default" name="Simulationscenario">
+ <bpsim:ScenarioParameters />
+ <bpsim:ElementParameters
elementRef="_EC77F346-CFAA-4180-8459-396589D8020A">
+ <bpsim:TimeParameters>
+ <bpsim:ProcessingTime>
+ <bpsim:NormalDistribution mean="0" standardDeviation="0" />
+ </bpsim:ProcessingTime>
+ </bpsim:TimeParameters>
+ </bpsim:ElementParameters>
+ <bpsim:ElementParameters
elementRef="_17F91537-B6F4-4232-AE8A-1FA6F7B8865D">
+ <bpsim:TimeParameters>
+ <bpsim:ProcessingTime>
+ <bpsim:NormalDistribution mean="0" standardDeviation="0" />
+ </bpsim:ProcessingTime>
+ </bpsim:TimeParameters>
+ <bpsim:ResourceParameters>
+ <bpsim:Availability>
+ <bpsim:FloatingParameter value="0" />
+ </bpsim:Availability>
+ <bpsim:Quantity>
+ <bpsim:FloatingParameter value="0" />
+ </bpsim:Quantity>
+ </bpsim:ResourceParameters>
+ <bpsim:CostParameters>
+ <bpsim:UnitCost>
+ <bpsim:FloatingParameter value="0" />
+ </bpsim:UnitCost>
+ </bpsim:CostParameters>
+ </bpsim:ElementParameters>
+ </bpsim:Scenario>
+ </bpsim:BPSimData>
+ </bpmn2:extensionElements>
+ <bpmn2:source>_B8_akJtRED-1FZqqQztbrw</bpmn2:source>
+ <bpmn2:target>_B8_akJtRED-1FZqqQztbrw</bpmn2:target>
+ </bpmn2:relationship>
+</bpmn2:definitions>
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
index cdfad7af803..1432ac379ad 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
@@ -32,6 +32,8 @@ import org.kie.kogito.codegen.Generated;
import org.kie.kogito.codegen.VariableInfo;
import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess;
import org.kie.kogito.internal.utils.KogitoTags;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.javaparser.ast.CompilationUnit;
@@ -67,6 +69,8 @@ import static
org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName;
public class ModelMetaData {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ModelMetaData.class);
+
private final String processId;
private final String packageName;
private final String modelClassSimpleName;
@@ -223,6 +227,12 @@ public class ModelMetaData {
String vtype = variable.getValue().getType().getStringType();
String sanitizedName = variable.getValue().getSanitizedName();
+ if (Object.class.getCanonicalName().equals(vtype)) {
+ LOGGER.warn("Process variable '{}' is declared as
java.lang.Object. " +
+ "Object type variables are persisted using Java
serialization. " +
+ "Make sure the stored value implements
java.io.Serializable.", varName);
+ }
+
FieldDeclaration fd = declareField(sanitizedName, vtype);
modelClass.addMember(fd);
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
index d422bcf4174..c37d1e869b9 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java
@@ -34,6 +34,8 @@ import
org.kie.api.definition.process.WorkflowElementIdentifier;
import org.kie.kogito.UserTask;
import org.kie.kogito.UserTaskParam;
import org.kie.kogito.UserTaskParam.ParamType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
@@ -72,6 +74,8 @@ import static
org.kie.kogito.internal.utils.ConversionUtils.sanitizeJavaName;
public class WorkItemModelMetaData {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(WorkItemModelMetaData.class);
+
private static final String TASK_INTPUT_CLASS_SUFFIX = "TaskInput";
private static final String TASK_OUTTPUT_CLASS_SUFFIX = "TaskOutput";
private static final String TASK_MODEL_CLASS_SUFFIX = "TaskModel";
@@ -206,10 +210,17 @@ public class WorkItemModelMetaData {
}
}
+ String variableType = variable.getType().getStringType();
+ if (Object.class.getCanonicalName().equals(variableType)) {
+ LOGGER.warn("User task input variable '{}' is declared as
java.lang.Object. " +
+ "Object type variables are persisted using Java
serialization. " +
+ "Make sure the stored value implements
java.io.Serializable.", entry.getKey());
+ }
+
String fieldName = sanitizeJavaName(entry.getKey());
FieldDeclaration fd = new FieldDeclaration().addVariable(
new VariableDeclarator()
- .setType(variable.getType().getStringType())
+ .setType(variableType)
.setName(fieldName))
.addModifier(Modifier.Keyword.PRIVATE);
@@ -223,7 +234,7 @@ public class WorkItemModelMetaData {
// from static method body — sanitised name for the identifier,
original key for the runtime map lookup
FieldAccessExpr field = new FieldAccessExpr(item, fieldName);
- ClassOrInterfaceType type =
parseClassOrInterfaceType(variable.getType().getStringType());
+ ClassOrInterfaceType type =
parseClassOrInterfaceType(variableType);
staticFromMap.addStatement(new AssignExpr(field, new CastExpr(
type,
new MethodCallExpr(
@@ -320,10 +331,17 @@ public class WorkItemModelMetaData {
}
}
+ String variableType = variable.getType().getStringType();
+ if (Object.class.getCanonicalName().equals(variableType)) {
+ LOGGER.warn("User task output variable '{}' is declared as
java.lang.Object. " +
+ "Object type variables are persisted using Java
serialization. " +
+ "Make sure the stored value implements
java.io.Serializable.", entry.getKey());
+ }
+
String fieldName = sanitizeJavaName(entry.getKey());
FieldDeclaration fd = new FieldDeclaration().addVariable(
new VariableDeclarator()
- .setType(variable.getType().getStringType())
+ .setType(variableType)
.setName(fieldName))
.addModifier(Modifier.Keyword.PRIVATE);
modelClass.addMember(fd);
@@ -340,7 +358,7 @@ public class WorkItemModelMetaData {
// fromMap method body — original key for params.get(), sanitised
name for field assignment
fromMapBody.addStatement(new AssignExpr(new
FieldAccessExpr(fromMapReturn, fieldName),
- new CastExpr(new ClassOrInterfaceType(null,
variable.getType().getStringType()),
+ new CastExpr(new ClassOrInterfaceType(null, variableType),
new MethodCallExpr(params, "get").addArgument(new
StringLiteralExpr(entry.getKey()))),
Operator.ASSIGN));
diff --git
a/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/JavaSerializationUtils.java
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/JavaSerializationUtils.java
new file mode 100644
index 00000000000..fddfcc20b8f
--- /dev/null
+++
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/JavaSerializationUtils.java
@@ -0,0 +1,68 @@
+/*
+ * 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.flow.serialization;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+
+public final class JavaSerializationUtils {
+
+ private JavaSerializationUtils() {
+ }
+
+ /**
+ * Serializes {@code value} to a byte array using Java serialization.
+ * <p>
+ * ObjectOutputStream must be closed before calling toByteArray(): close()
flushes the final
+ * block header; without it the byte array is truncated and causes
EOFException on deserialization.
+ */
+ public static byte[] serialize(Object value) throws IOException {
+ try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+ try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
+ out.writeObject(value);
+ }
+ return stream.toByteArray();
+ }
+ }
+
+ /**
+ * Deserializes {@code data} using Java serialization, resolving classes
through the thread
+ * context classloader first so that application classes are found in
environments like Quarkus
+ * where the bootstrap classloader does not see deployment classes.
+ */
+ public static Object deserialize(byte[] data) throws IOException,
ClassNotFoundException {
+ try (InputStream is = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(is) {
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws
IOException, ClassNotFoundException {
+ try {
+ return Class.forName(desc.getName(), false,
Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException ex) {
+ return super.resolveClass(desc);
+ }
+ }
+ }) {
+ return ois.readObject();
+ }
+ }
+}
diff --git
a/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufObjectMarshallerStrategy.java
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufObjectMarshallerStrategy.java
index 791625afc0f..42a63b4e64d 100644
---
a/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufObjectMarshallerStrategy.java
+++
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufObjectMarshallerStrategy.java
@@ -18,21 +18,15 @@
*/
package org.jbpm.flow.serialization.impl.marshallers;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.ObjectStreamClass;
+import org.jbpm.flow.serialization.JavaSerializationUtils;
import org.jbpm.flow.serialization.ObjectMarshallerStrategy;
import org.jbpm.flow.serialization.ProcessInstanceMarshallerException;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
-import com.google.protobuf.InvalidProtocolBufferException;
public class ProtobufObjectMarshallerStrategy implements
ObjectMarshallerStrategy {
@@ -53,9 +47,9 @@ public class ProtobufObjectMarshallerStrategy implements
ObjectMarshallerStrateg
@Override
public Any marshall(Object unmarshalled) {
- try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(stream)) {
- out.writeObject(unmarshalled);
- return
Any.pack(BytesValue.of(ByteString.copyFrom(stream.toByteArray())));
+ try {
+ byte[] bytes = JavaSerializationUtils.serialize(unmarshalled);
+ return Any.pack(BytesValue.of(ByteString.copyFrom(bytes)));
} catch (IOException e) {
throw new ProcessInstanceMarshallerException("Not possible to
unmarshall value: " + unmarshalled, e);
}
@@ -68,26 +62,9 @@ public class ProtobufObjectMarshallerStrategy implements
ObjectMarshallerStrateg
if (ByteString.EMPTY.equals(storedValue.getValue())) {
return null;
}
- return readObject(storedValue.getValue().toByteArray());
- } catch (InvalidProtocolBufferException e1) {
- throw new ProcessInstanceMarshallerException("Unexpected error
during protobuf object unmarshalling", e1);
- }
- }
-
- private Object readObject(byte[] data) {
- try (InputStream is = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(is) {
- @Override
- protected Class<?> resolveClass(ObjectStreamClass desc) throws
IOException, ClassNotFoundException {
- try {
- return Class.forName(desc.getName(), false,
Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException ex) {
- return super.resolveClass(desc);
- }
- }
- }) {
- return ois.readObject();
+ return
JavaSerializationUtils.deserialize(storedValue.getValue().toByteArray());
} catch (IOException | ClassNotFoundException e) {
- throw new ProcessInstanceMarshallerException("Unexpected error
while trying to read object", e);
+ throw new ProcessInstanceMarshallerException("Unexpected error
during protobuf object unmarshalling", e);
}
}
diff --git
a/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/marshaller/SerializableProtostreamBaseMarshaller.java
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/marshaller/SerializableProtostreamBaseMarshaller.java
index c97b94e9448..aa8c0cbd36b 100644
---
a/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/marshaller/SerializableProtostreamBaseMarshaller.java
+++
b/kogito-jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/marshaller/SerializableProtostreamBaseMarshaller.java
@@ -18,15 +18,11 @@
*/
package org.jbpm.flow.serialization.marshaller;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.infinispan.protostream.MessageMarshaller;
+import org.jbpm.flow.serialization.JavaSerializationUtils;
import org.jbpm.flow.serialization.ProcessInstanceMarshallerException;
public class SerializableProtostreamBaseMarshaller implements
MessageMarshaller<Serializable> {
@@ -43,25 +39,24 @@ public class SerializableProtostreamBaseMarshaller
implements MessageMarshaller<
@Override
public Serializable readFrom(ProtoStreamReader reader) throws IOException {
- return (Serializable) readObject(reader.readBytes("data"));
+ byte[] data = reader.readBytes("data");
+ if (data == null || data.length == 0) {
+ return null;
+ }
+ try {
+ return (Serializable) JavaSerializationUtils.deserialize(data);
+ } catch (ClassNotFoundException e) {
+ throw new ProcessInstanceMarshallerException("Unexpected error
while trying to unmarshall object", e);
+ }
}
@Override
public void writeTo(ProtoStreamWriter writer, Serializable serializable)
throws IOException {
- try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(stream)) {
- out.writeObject(serializable);
- writer.writeBytes("data", stream.toByteArray());
+ try {
+ writer.writeBytes("data",
JavaSerializationUtils.serialize(serializable));
} catch (IOException e) {
throw new ProcessInstanceMarshallerException("Not possible to
marshall value: " + serializable, e);
}
}
- private Object readObject(byte[] data) {
- try (InputStream is = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(is)) {
- return ois.readObject();
- } catch (IOException | ClassNotFoundException e) {
- throw new ProcessInstanceMarshallerException("Unexpected error
while trying to unmarshall object", e);
- }
- }
-
}
diff --git
a/kogito-jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/JavaSerializationUtilsTest.java
b/kogito-jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/JavaSerializationUtilsTest.java
new file mode 100644
index 00000000000..7973fcf177d
--- /dev/null
+++
b/kogito-jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/JavaSerializationUtilsTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.flow.serialization;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class JavaSerializationUtilsTest {
+
+ // -- test data ----------------------------------------------------------
+
+ static class Person implements Serializable {
+ private static final long serialVersionUID = 1L;
+ final String name;
+ final int age;
+
+ Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Person))
+ return false;
+ Person p = (Person) o;
+ return age == p.age && Objects.equals(name, p.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, age);
+ }
+ }
+
+ static class NotSerializable {
+ // intentionally does not implement Serializable
+ }
+
+ // -- tests --------------------------------------------------------------
+
+ @Test
+ void roundTripString() throws Exception {
+ String original = "hello world";
+ byte[] bytes = JavaSerializationUtils.serialize(original);
+ assertThat(bytes).isNotEmpty();
+
assertThat(JavaSerializationUtils.deserialize(bytes)).isEqualTo(original);
+ }
+
+ @Test
+ void roundTripCustomObject() throws Exception {
+ Person original = new Person("Alice", 30);
+ byte[] bytes = JavaSerializationUtils.serialize(original);
+ assertThat(bytes).isNotEmpty();
+
assertThat(JavaSerializationUtils.deserialize(bytes)).isEqualTo(original);
+ }
+
+ @Test
+ void roundTripNull() throws Exception {
+ byte[] bytes = JavaSerializationUtils.serialize(null);
+ assertThat(bytes).isNotEmpty(); // OOS writes a null token, not empty
bytes
+ assertThat(JavaSerializationUtils.deserialize(bytes)).isNull();
+ }
+
+ @Test
+ void roundTripCollection() throws Exception {
+ List<String> original = List.of("a", "b", "c");
+ byte[] bytes = JavaSerializationUtils.serialize(original);
+
assertThat(JavaSerializationUtils.deserialize(bytes)).isEqualTo(original);
+ }
+
+ @Test
+ void serializeBytesAreCompleteAndDeserializable() throws Exception {
+ // Regression: toByteArray() called before OOS.close() produced
truncated bytes
+ // causing EOFException on deserialization. Verify bytes are always
fully flushed.
+ Person original = new Person("Bob", 42);
+ byte[] bytes = JavaSerializationUtils.serialize(original);
+ // A valid Java serialization stream starts with the magic bytes 0xACED
+ assertThat(bytes[0]).isEqualTo((byte) 0xAC);
+ assertThat(bytes[1]).isEqualTo((byte) 0xED);
+ // And can be deserialized without EOFException
+
assertThat(JavaSerializationUtils.deserialize(bytes)).isEqualTo(original);
+ }
+
+ @Test
+ void serializeNonSerializableThrows() {
+ assertThatThrownBy(() -> JavaSerializationUtils.serialize(new
NotSerializable()))
+ .isInstanceOf(IOException.class);
+ }
+
+ @Test
+ void deserializeCorruptBytesThrows() {
+ byte[] garbage = new byte[] { 0x00, 0x01, 0x02, 0x03 };
+ assertThatThrownBy(() -> JavaSerializationUtils.deserialize(garbage))
+ .isInstanceOf(IOException.class);
+ }
+
+ @Test
+ void deserializeUsesThreadContextClassLoader() throws Exception {
+ // Verify that deserialize resolves classes through the thread-context
classloader.
+ // We swap in a classloader that can see Person, confirm round-trip
still works.
+ ClassLoader original = Thread.currentThread().getContextClassLoader();
+ try {
+
Thread.currentThread().setContextClassLoader(Person.class.getClassLoader());
+ Person person = new Person("Carol", 25);
+ byte[] bytes = JavaSerializationUtils.serialize(person);
+
assertThat(JavaSerializationUtils.deserialize(bytes)).isEqualTo(person);
+ } finally {
+ Thread.currentThread().setContextClassLoader(original);
+ }
+ }
+}
diff --git
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/main/java/org/kie/kogito/quarkus/workflow/deployment/JandexProtoGenerator.java
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/main/java/org/kie/kogito/quarkus/workflow/deployment/JandexProtoGenerator.java
index 9aabb4dc569..46ce5f2b616 100644
---
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/main/java/org/kie/kogito/quarkus/workflow/deployment/JandexProtoGenerator.java
+++
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/main/java/org/kie/kogito/quarkus/workflow/deployment/JandexProtoGenerator.java
@@ -137,6 +137,7 @@ public class JandexProtoGenerator extends
AbstractProtoGenerator<ClassInfo> {
String fieldTypeString = pd.type().name().toString();
DotName fieldType = pd.type().name();
+ LOGGER.debug("Resolving proto type for field '{}' of type '{}' in
'{}'", pd.name(), fieldTypeString, clazz.name());
String protoType;
if (isArray(pd)) {
fieldTypeString = ARRAY;
diff --git
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/test/java/org/kie/kogito/quarkus/workflow/deployment/JandexTestUtils.java
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/test/java/org/kie/kogito/quarkus/workflow/deployment/JandexTestUtils.java
index 2201a3d51be..82611c52138 100644
---
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/test/java/org/kie/kogito/quarkus/workflow/deployment/JandexTestUtils.java
+++
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common-deployment/src/test/java/org/kie/kogito/quarkus/workflow/deployment/JandexTestUtils.java
@@ -41,6 +41,8 @@ import org.kie.kogito.codegen.data.HelloModel;
import org.kie.kogito.codegen.data.JacksonData;
import org.kie.kogito.codegen.data.ListWithoutType;
import org.kie.kogito.codegen.data.NotEmptyConstructor;
+import org.kie.kogito.codegen.data.ObjectHolder;
+import org.kie.kogito.codegen.data.ObjectProcessModel;
import org.kie.kogito.codegen.data.Person;
import org.kie.kogito.codegen.data.PersonSubClass;
import org.kie.kogito.codegen.data.PersonVarInfo;
@@ -82,6 +84,8 @@ public final class JandexTestUtils {
PersonSubClass.class,
JacksonData.class,
ListWithoutType.class,
+ ObjectHolder.class,
+ ObjectProcessModel.class,
PersonWithBooleanObject.class,
PersonWithBooleanGetAccessor.class);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]