From 891401f7b7aa46ab77952dbe6001e352c7b72c99 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:09:51 +0100 Subject: [PATCH] [Fix #2169] Allow filtering over processInstances.definition and definition.metadata (#2191) * [Fix_#2169] Supporting query over ProcessInstance.definition * [Fix_#2169] Fixing broken tests * Revert "[Fix_#2169] Fixing broken tests" This reverts commit bede4279f566d7e0d56db838c4e99a5f2601400f. * [Fix #2169] Alternative approach * [Fix #2169] Metadata query IT * [Fix #2169] Annotation query IT * [Fix #2169] Simplifying Flyway script * [Fix #2169] Walters comments --- .../service/ProcessDefinitionHelper.java | 6 ++- .../graphql/AbstractGraphQLSchemaManager.java | 2 +- .../src/main/resources/basic.schema.graphqls | 7 ++++ .../kogito/index/model/ProcessDefinition.java | 6 +-- .../kie/kogito/index/test/QueryTestUtils.java | 11 +++++ .../AbstractProcessDefinitionQueryIT.java | 10 +---- .../ProcessDefinitionMarshaller.java | 6 ++- .../ProcessDefinitionMarshallerTest.java | 14 ++++--- .../mapper/ProcessDefinitionEntityMapper.java | 15 +++++++ .../mapper/ProcessInstanceEntityMapper.java | 7 ++++ .../jpa/model/ProcessDefinitionEntity.java | 20 ++++----- .../jpa/model/ProcessInstanceEntity.java | 15 +++++++ .../ProcessDefinitionEntityStorage.java | 3 ++ .../storage/ProcessInstanceEntityStorage.java | 1 + .../ansi/V1.45.1.0__metadata_as_column.sql | 39 +++++++++++++++++ .../model/ProcessDefinitionEntity.java | 6 +-- .../postgresql/PostgresqlJsonHelper.java | 29 ++++++++++--- ...tgresqlProcessDefinitionEntityStorage.java | 42 +++++++++++++++++++ .../V1.45.1.0__metadata_as_jsonb.sql | 27 ++++++++++++ .../query/ProcessDefinitionEntityQueryIT.java | 31 ++++++++++++++ .../query/ProcessInstanceEntityQueryIT.java | 23 ++++++++++ 21 files changed, 280 insertions(+), 40 deletions(-) create mode 100644 data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.1.0__metadata_as_column.sql create mode 100644 data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessDefinitionEntityStorage.java create mode 100644 data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.1.0__metadata_as_jsonb.sql diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/ProcessDefinitionHelper.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/ProcessDefinitionHelper.java index 96e3b9c4ce..0a5b64bbe5 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/ProcessDefinitionHelper.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/ProcessDefinitionHelper.java @@ -63,7 +63,7 @@ public static ProcessDefinition merge(ProcessDefinition instance, ProcessDefinit instance.setEndpoint(doMerge(data.getEndpoint(), instance.getEndpoint())); instance.setDescription(doMerge(data.getDescription(), instance.getDescription())); instance.setAnnotations(doMerge(data.getAnnotations(), instance.getAnnotations())); - instance.setMetadata(CommonUtils.mergeMap(toStringMap(data.getMetadata()), instance.getMetadata())); + instance.setMetadata(CommonUtils.mergeMap(toObjectMap(data.getMetadata()), instance.getMetadata())); instance.setNodes(doMerge(nodeDefinitions(data), instance.getNodes())); instance.setSource(doMerge(data.getSource(), instance.getSource())); return instance; @@ -95,6 +95,10 @@ private static T doMerge(T incoming, T current) { return current; } + private static Map toObjectMap(Map map) { + return map == null ? null : Collections.unmodifiableMap(map); + } + private static Map toStringMap(Map input) { if (input == null) { return null; 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 142f2c02a4..a3872eff87 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 @@ -149,7 +149,7 @@ public String getProcessInstanceServiceUrl(DataFetchingEnvironment env) { public ProcessDefinition getProcessDefinition(DataFetchingEnvironment env) { ProcessInstance source = env.getSource(); - return cacheService.getProcessDefinitionStorage().get(new ProcessDefinitionKey(source.getProcessId(), source.getVersion())); + return source.getDefinition(); } protected String getServiceUrl(String endpoint, String processId) { diff --git a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls index 0f9ca76289..358454c2a9 100644 --- a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls +++ b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls @@ -64,6 +64,12 @@ input ProcessDefinitionArgument { id: StringArgument name: StringArgument version: StringArgument + annotations: StringArrayArgument + roles: StringArrayArgument + serviceUrl: StringArgument + description: StringArgument + type: StringArgument + metadata: JSON } type ProcessInstance { @@ -194,6 +200,7 @@ input ProcessInstanceArgument { businessKey: StringArgument createdBy: StringArgument updatedBy: StringArgument + definition: ProcessDefinitionArgument } input ProcessInstanceErrorArgument { diff --git a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/ProcessDefinition.java b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/ProcessDefinition.java index e6e2450ea0..7476448eed 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/ProcessDefinition.java +++ b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/ProcessDefinition.java @@ -35,7 +35,7 @@ public class ProcessDefinition { private String source; private String description; private Set annotations; - private Map metadata; + private Map metadata; private List nodes; public String getId() { @@ -126,11 +126,11 @@ public void setAnnotations(Set annotations) { this.annotations = annotations; } - public Map getMetadata() { + public Map getMetadata() { return metadata; } - public void setMetadata(Map metadata) { + public void setMetadata(Map metadata) { this.metadata = metadata; } diff --git a/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/QueryTestUtils.java b/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/QueryTestUtils.java index 5e8f305dc8..1feb8e5827 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/QueryTestUtils.java +++ b/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/QueryTestUtils.java @@ -21,6 +21,9 @@ import java.util.List; import java.util.function.BiConsumer; +import org.assertj.core.groups.Tuple; +import org.kie.kogito.index.model.ProcessDefinitionKey; + import com.fasterxml.jackson.databind.node.ObjectNode; import static org.assertj.core.api.Assertions.assertThat; @@ -55,4 +58,12 @@ public static BiConsumer, String[]> assertNotId() { return (instances, ids) -> assertThat(instances).extracting("id").doesNotContainAnyElementsOf(List.of(ids)); } + public static BiConsumer, ProcessDefinitionKey[]> assertWithKey() { + return (instances, ids) -> assertThat(instances).hasSize(ids == null ? 0 : ids.length).extracting("id", "version").map(Tuple::toArray) + .map(objs -> new ProcessDefinitionKey((String) objs[0], (String) objs[1])).containsExactly(ids); + } + + public static BiConsumer, ProcessDefinitionKey[]> assertNoKey() { + return (instances, ids) -> assertThat(instances).isEmpty(); + } } diff --git a/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/query/AbstractProcessDefinitionQueryIT.java b/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/query/AbstractProcessDefinitionQueryIT.java index a6e61fc01f..f66412a9d5 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/query/AbstractProcessDefinitionQueryIT.java +++ b/data-index/data-index-storage/data-index-storage-api/src/test/java/org/kie/kogito/index/test/query/AbstractProcessDefinitionQueryIT.java @@ -18,11 +18,8 @@ */ package org.kie.kogito.index.test.query; -import java.util.List; import java.util.Set; -import java.util.function.BiConsumer; -import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.Test; import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessDefinitionKey; @@ -33,7 +30,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.kogito.index.test.QueryTestUtils.assertWithKey; import static org.kie.kogito.persistence.api.query.QueryFilterFactory.contains; import static org.kie.kogito.persistence.api.query.QueryFilterFactory.containsAll; import static org.kie.kogito.persistence.api.query.QueryFilterFactory.containsAny; @@ -71,9 +68,4 @@ void testProcessDefinitionQuery() { null, pdv2Key, pdv1Key); } - public static BiConsumer, ProcessDefinitionKey[]> assertWithKey() { - return (instances, ids) -> assertThat(instances).hasSize(ids == null ? 0 : ids.length).extracting("id", "version").map(Tuple::toArray) - .map(objs -> new ProcessDefinitionKey((String) objs[0], (String) objs[1])).containsExactly(ids); - } - } diff --git a/data-index/data-index-storage/data-index-storage-infinispan/src/main/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshaller.java b/data-index/data-index-storage/data-index-storage-infinispan/src/main/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshaller.java index 720fdf1e02..a4f3eeb6ff 100644 --- a/data-index/data-index-storage/data-index-storage-infinispan/src/main/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshaller.java +++ b/data-index/data-index-storage/data-index-storage-infinispan/src/main/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshaller.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -61,7 +62,7 @@ public ProcessDefinition readFrom(ProtoStreamReader reader) throws IOException { pd.setName(reader.readString(NAME)); pd.setDescription(reader.readString(DESCRIPTION)); pd.setAnnotations(reader.readCollection(ANNOTATIONS, new HashSet<>(), String.class)); - pd.setMetadata(buildMetadata(reader)); + pd.setMetadata(Collections.unmodifiableMap(buildMetadata(reader))); pd.setRoles(reader.readCollection(ROLES, new HashSet<>(), String.class)); pd.setAddons(reader.readCollection(ADDONS, new HashSet<>(), String.class)); pd.setType(reader.readString(TYPE)); @@ -97,7 +98,8 @@ public void writeTo(ProtoStreamWriter writer, ProcessDefinition pd) throws IOExc private static Set buildMetadata(ProcessDefinition pd) { return Optional.ofNullable(pd.getMetadata()) .map(Map::entrySet) - .map(entries -> entries.stream().map(e -> new Entry(e.getKey(), e.getValue())).collect(Collectors.toSet())) + .map(entries -> entries.stream().filter(e -> e.getValue() != null) + .map(e -> new Entry(e.getKey(), e.getValue().toString())).collect(Collectors.toSet())) .orElse(null); } diff --git a/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshallerTest.java b/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshallerTest.java index e8f08da5fb..e614a32a2d 100644 --- a/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshallerTest.java +++ b/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/ProcessDefinitionMarshallerTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.infinispan.protostream.MessageMarshaller; import org.junit.jupiter.api.Test; @@ -29,7 +30,6 @@ import org.mockito.InOrder; import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.kie.kogito.index.infinispan.protostream.ProcessDefinitionMarshaller.ADDONS; import static org.kie.kogito.index.infinispan.protostream.ProcessDefinitionMarshaller.ANNOTATIONS; @@ -48,6 +48,9 @@ class ProcessDefinitionMarshallerTest { + private static final String metaKey = "key1"; + private final String metaValue = "value1"; + @Test void testReadFrom() throws IOException { MessageMarshaller.ProtoStreamReader reader = mock(MessageMarshaller.ProtoStreamReader.class); @@ -56,7 +59,7 @@ void testReadFrom() throws IOException { when(reader.readString(NAME)).thenReturn("processName"); when(reader.readString(DESCRIPTION)).thenReturn("descr"); when(reader.readCollection(eq(ANNOTATIONS), any(), eq(String.class))).thenReturn(new HashSet<>(singleton("tag1"))); - when(reader.readCollection(eq(METADATA), any(), eq(Entry.class))).thenReturn(new HashSet<>(singleton(new Entry("key1", "value1")))); + when(reader.readCollection(eq(METADATA), any(), eq(Entry.class))).thenReturn(new HashSet<>(singleton(new Entry(metaKey, metaValue)))); when(reader.readCollection(eq(ROLES), any(), eq(String.class))).thenReturn(new HashSet<>(singleton("admin"))); when(reader.readCollection(eq(ADDONS), any(), eq(String.class))).thenReturn(new HashSet<>(singleton("process-management"))); when(reader.readString(TYPE)).thenReturn("processType"); @@ -71,7 +74,7 @@ void testReadFrom() throws IOException { .hasFieldOrPropertyWithValue(NAME, "processName") .hasFieldOrPropertyWithValue(DESCRIPTION, "descr") .hasFieldOrPropertyWithValue(ANNOTATIONS, singleton("tag1")) - .hasFieldOrPropertyWithValue(METADATA, Map.of("key1", "value1")) + .hasFieldOrPropertyWithValue(METADATA, Map.of(metaKey, metaValue)) .hasFieldOrPropertyWithValue(ROLES, singleton("admin")) .hasFieldOrPropertyWithValue(ADDONS, singleton("process-management")) .hasFieldOrPropertyWithValue(TYPE, "processType"); @@ -90,13 +93,14 @@ void testReadFrom() throws IOException { @Test void testWriteTo() throws IOException { + ProcessDefinition pd = new ProcessDefinition(); pd.setId("processId"); pd.setVersion("1.0"); pd.setName("processName"); pd.setDescription("descr"); pd.setAnnotations(singleton("tag1")); - pd.setMetadata(Map.of("key1", "value1")); + pd.setMetadata(Map.of(metaKey, metaValue)); pd.setRoles(singleton("admin")); pd.setAddons(singleton("process-management")); pd.setType("processType"); @@ -112,7 +116,7 @@ void testWriteTo() throws IOException { inOrder.verify(writer).writeString(NAME, pd.getName()); inOrder.verify(writer).writeString(DESCRIPTION, pd.getDescription()); inOrder.verify(writer).writeCollection(ANNOTATIONS, pd.getAnnotations(), String.class); - inOrder.verify(writer).writeCollection(METADATA, pd.getMetadata().entrySet().stream().map(e -> new Entry(e.getKey(), e.getValue())).collect(toSet()), Entry.class); + inOrder.verify(writer).writeCollection(METADATA, Set.of(new Entry(metaKey, metaValue)), Entry.class); inOrder.verify(writer).writeCollection(ROLES, pd.getRoles(), String.class); inOrder.verify(writer).writeCollection(ADDONS, pd.getAddons(), String.class); inOrder.verify(writer).writeString(TYPE, pd.getType()); diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessDefinitionEntityMapper.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessDefinitionEntityMapper.java index 1cf04b7ab9..6549f9f993 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessDefinitionEntityMapper.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessDefinitionEntityMapper.java @@ -18,13 +18,19 @@ */ package org.kie.kogito.index.jpa.mapper; +import java.util.Map; + import org.kie.kogito.index.jpa.model.ProcessDefinitionEntity; import org.kie.kogito.index.model.ProcessDefinition; +import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.mapstruct.AfterMapping; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + @Mapper(componentModel = "cdi", suppressTimestampInGenerated = true) public interface ProcessDefinitionEntityMapper { @@ -41,6 +47,15 @@ default String map(byte[] value) { return value == null ? null : new String(value); } + default ObjectNode map(Map model) { + JsonNode entity = JsonObjectUtils.fromValue(model); + return entity == null || !entity.isObject() ? null : (ObjectNode) entity; + } + + default Map map(ObjectNode entity) { + return (Map) JsonObjectUtils.convertValue(entity, Map.class); + } + @AfterMapping default void afterMapping(@MappingTarget ProcessDefinitionEntity entity) { if (entity.getNodes() != null) { diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessInstanceEntityMapper.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessInstanceEntityMapper.java index f241c4d857..55b1cb471e 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessInstanceEntityMapper.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/mapper/ProcessInstanceEntityMapper.java @@ -19,13 +19,16 @@ package org.kie.kogito.index.jpa.mapper; import org.kie.kogito.index.jpa.model.MilestoneEntity; +import org.kie.kogito.index.jpa.model.ProcessDefinitionEntity; import org.kie.kogito.index.jpa.model.ProcessInstanceEntity; import org.kie.kogito.index.model.Milestone; +import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessInstance; import org.mapstruct.AfterMapping; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; @Mapper(componentModel = "cdi", suppressTimestampInGenerated = true) public interface ProcessInstanceEntityMapper { @@ -40,6 +43,10 @@ public interface ProcessInstanceEntityMapper { @InheritInverseConfiguration ProcessInstance mapToModel(ProcessInstanceEntity pi); + default ProcessDefinition mapToDefinition(ProcessDefinitionEntity entity) { + return Mappers.getMapper(ProcessDefinitionEntityMapper.class).mapToModel(entity); + } + @AfterMapping default void afterMapping(@MappingTarget ProcessInstanceEntity entity) { entity.getNodes().forEach(n -> n.setProcessInstance(entity)); diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessDefinitionEntity.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessDefinitionEntity.java index a7158f31bb..2b70331c1a 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessDefinitionEntity.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessDefinitionEntity.java @@ -19,22 +19,24 @@ package org.kie.kogito.index.jpa.model; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import org.kie.kogito.index.model.ProcessDefinitionKey; +import org.kie.kogito.persistence.postgresql.hibernate.JsonBinaryConverter; + +import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; -import jakarta.persistence.MapKeyColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @@ -74,13 +76,9 @@ public class ProcessDefinitionEntity extends AbstractEntity { @JoinColumn(name = "process_version", referencedColumnName = "version") }, foreignKey = @ForeignKey(name = "fk_definitions_annotations")) @Column(name = "annotation") private Set annotations; - @ElementCollection - @CollectionTable(name = "definitions_metadata", joinColumns = { - @JoinColumn(name = "process_id", referencedColumnName = "id"), @JoinColumn(name = "process_version", referencedColumnName = "version") }, - foreignKey = @ForeignKey(name = "fk_definitions_metadata")) - @MapKeyColumn(name = "name") - @Column(name = "meta_value") - private Map metadata; + @Convert(converter = JsonBinaryConverter.class) + @Column(columnDefinition = "jsonb") + private ObjectNode metadata; @Override public String getId() { @@ -171,11 +169,11 @@ public void setAnnotations(Set annotations) { this.annotations = annotations; } - public Map getMetadata() { + public ObjectNode getMetadata() { return metadata; } - public void setMetadata(Map metadata) { + public void setMetadata(ObjectNode metadata) { this.metadata = metadata; } diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessInstanceEntity.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessInstanceEntity.java index a3a0b0ceb3..73644f03b1 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessInstanceEntity.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/ProcessInstanceEntity.java @@ -23,6 +23,8 @@ import java.util.Objects; import java.util.Set; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; import org.kie.kogito.persistence.postgresql.hibernate.JsonBinaryConverter; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -34,9 +36,12 @@ import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @@ -85,6 +90,12 @@ public class ProcessInstanceEntity extends AbstractEntity { @Embedded private ProcessInstanceErrorEntity error; + @ManyToOne(targetEntity = ProcessDefinitionEntity.class, fetch = FetchType.LAZY) + @JoinColumns({ @JoinColumn(name = "processId", referencedColumnName = "id", insertable = false, updatable = false), + @JoinColumn(name = "version", referencedColumnName = "version", insertable = false, updatable = false) }) + @NotFound(action = NotFoundAction.IGNORE) + private ProcessDefinitionEntity definition; + @Override public String getId() { return id; @@ -279,6 +290,10 @@ public int hashCode() { return Objects.hash(id); } + public ProcessDefinitionEntity getDefinition() { + return definition; + } + @Override public String toString() { return "ProcessInstanceEntity{" + diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessDefinitionEntityStorage.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessDefinitionEntityStorage.java index e22670c665..9b64ef09b1 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessDefinitionEntityStorage.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessDefinitionEntityStorage.java @@ -24,11 +24,14 @@ import org.kie.kogito.index.model.ProcessDefinition; import org.kie.kogito.index.model.ProcessDefinitionKey; +import io.quarkus.arc.DefaultBean; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @ApplicationScoped +@DefaultBean public class ProcessDefinitionEntityStorage extends AbstractStorage { protected ProcessDefinitionEntityStorage() { diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java index 17a3c82904..b9fe816cb6 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java @@ -113,6 +113,7 @@ private ProcessInstanceEntity findOrInit(ProcessInstanceDataEvent event) { return repository.findByIdOptional(event.getKogitoProcessInstanceId()).orElseGet(() -> { ProcessInstanceEntity pi = new ProcessInstanceEntity(); pi.setProcessId(event.getKogitoProcessId()); + pi.setVersion(event.getKogitoProcessInstanceVersion()); pi.setId(event.getKogitoProcessInstanceId()); pi.setLastUpdate(toZonedDateTime(event.getTime())); pi.setNodes(new ArrayList<>()); diff --git a/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.1.0__metadata_as_column.sql b/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.1.0__metadata_as_column.sql new file mode 100644 index 0000000000..9b8e2304ff --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.1.0__metadata_as_column.sql @@ -0,0 +1,39 @@ +/* + * 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. + */ + +ALTER TABLE definitions ADD COLUMN metadata varchar(max); + +/* Migration for postgresql and sql server */ +/* +UPDATE definitions SET metadata = v +FROM (SELECT process_id, process_version, string_agg(name || ':' || meta_value, ',') as v FROM definitions_metadata group by process_id, process_version) as grouped_metadata +WHERE id = grouped_metadata.process_id and version = grouped_metadata.process_version; +*/ + + +/* Migration for h2 and oracle*/ +/* +UPDATE definitions SET metadata = v +FROM (SELECT process_id, process_version, listagg(name || ':' || meta_value, ',') as v FROM definitions_metadata group by process_id, process_version) as grouped_metadata +WHERE id = grouped_metadata.process_id and version = grouped_metadata.process_version; +*/ + + +/* Since migration is disable, do not delete not longer used table*/ +/* DROP TABLE definitions_metadata;*/ diff --git a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/ProcessDefinitionEntity.java b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/ProcessDefinitionEntity.java index b009835ade..01176239e0 100644 --- a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/ProcessDefinitionEntity.java +++ b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/ProcessDefinitionEntity.java @@ -40,7 +40,7 @@ public class ProcessDefinitionEntity { private Set annotations; - private Map metadata; + private Map metadata; private Set roles; @@ -148,11 +148,11 @@ public void setAnnotations(Set annotations) { this.annotations = annotations; } - public Map getMetadata() { + public Map getMetadata() { return metadata; } - public void setMetadata(Map metadata) { + public void setMetadata(Map metadata) { this.metadata = metadata; } diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java index 3cb1f347d8..d8bb74a7bb 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlJsonHelper.java @@ -18,14 +18,18 @@ */ package org.kie.kogito.index.postgresql; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.kie.kogito.persistence.api.query.AttributeFilter; +import jakarta.persistence.PersistenceException; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; @@ -101,11 +105,26 @@ private static Expression buildObjectExpression(CriteriaBuilder builder, Object private static Expression buildPathExpression(CriteriaBuilder builder, Root root, String attributeName, boolean isStr) { String[] attributes = attributeName.split("\\."); - Expression[] arguments = new Expression[attributes.length]; - arguments[0] = root.get(attributes[0]); - for (int i = 1; i < attributes.length; i++) { - arguments[i] = builder.literal(attributes[i]); + + Collection arguments = new ArrayList<>(); + if (attributes.length == 1) + return root.get(attributeName); + int startIndex; + // Check if the first attribute is a join, if it is, assume next attribute is the json property (not sure thats necessarily correct but it will work) + try { + Join join = root.join(attributes[0]); + arguments.add(join.get(attributes[1])); + startIndex = 2; + } catch (PersistenceException ex) { + // If not, the first attribute is the json one, + arguments.add(root.get(attributes[0])); + startIndex = 1; + } + + for (int i = startIndex; i < attributes.length; i++) { + arguments.add(builder.literal(attributes[i])); } - return isStr ? builder.function("jsonb_extract_path_text", String.class, arguments) : builder.function("jsonb_extract_path", Object.class, arguments); + return isStr ? builder.function("jsonb_extract_path_text", String.class, arguments.toArray(new Expression[arguments.size()])) + : builder.function("jsonb_extract_path", Object.class, arguments.toArray(new Expression[arguments.size()])); } } diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessDefinitionEntityStorage.java b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessDefinitionEntityStorage.java new file mode 100644 index 0000000000..03b25e9329 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/java/org/kie/kogito/index/postgresql/PostgresqlProcessDefinitionEntityStorage.java @@ -0,0 +1,42 @@ +/* + * 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.postgresql; + +import org.kie.kogito.index.jpa.mapper.ProcessDefinitionEntityMapper; +import org.kie.kogito.index.jpa.model.ProcessDefinitionEntityRepository; +import org.kie.kogito.index.jpa.storage.ProcessDefinitionEntityStorage; +import org.kie.kogito.index.model.ProcessDefinition; +import org.kie.kogito.persistence.api.query.Query; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class PostgresqlProcessDefinitionEntityStorage extends ProcessDefinitionEntityStorage { + + @Inject + public PostgresqlProcessDefinitionEntityStorage(ProcessDefinitionEntityRepository repository, ProcessDefinitionEntityMapper mapper) { + super(repository, mapper); + } + + @Override + public Query query() { + return new PostgresqlJsonJPAQuery<>(repository, mapToModel, entityClass); + } +} diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.1.0__metadata_as_jsonb.sql b/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.1.0__metadata_as_jsonb.sql new file mode 100644 index 0000000000..df50d93017 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.1.0__metadata_as_jsonb.sql @@ -0,0 +1,27 @@ +/* + * 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. + */ + +ALTER TABLE definitions ADD COLUMN metadata jsonb; + +WITH grouped_metadata as (SELECT process_id, process_version, json_object_agg(name, meta_value) as v FROM definitions_metadata group by process_id, process_version) +UPDATE definitions SET metadata = v +FROM grouped_metadata +WHERE id = grouped_metadata.process_id and version = grouped_metadata.process_version; + +DROP TABLE definitions_metadata; diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessDefinitionEntityQueryIT.java b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessDefinitionEntityQueryIT.java index 0f304623be..9b6a70aa14 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessDefinitionEntityQueryIT.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessDefinitionEntityQueryIT.java @@ -18,13 +18,44 @@ */ package org.kie.kogito.index.postgresql.query; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; import org.kie.kogito.index.jpa.query.AbstractProcessDefinitionEntityQueryIT; +import org.kie.kogito.index.model.ProcessDefinition; +import org.kie.kogito.index.model.ProcessDefinitionKey; +import org.kie.kogito.index.test.TestUtils; +import org.kie.kogito.persistence.api.Storage; import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import static java.util.Collections.singletonList; +import static org.kie.kogito.index.json.JsonUtils.jsonFilter; +import static org.kie.kogito.index.test.QueryTestUtils.assertNoKey; +import static org.kie.kogito.index.test.QueryTestUtils.assertWithKey; +import static org.kie.kogito.persistence.api.query.QueryFilterFactory.contains; +import static org.kie.kogito.persistence.api.query.QueryFilterFactory.equalTo; + @QuarkusTest @QuarkusTestResource(PostgreSqlQuarkusTestResource.class) class ProcessDefinitionEntityQueryIT extends AbstractProcessDefinitionEntityQueryIT { + + @Test + void testMetadata() { + final String processId = "persons"; + final String version = "1.0"; + ProcessDefinitionKey key = new ProcessDefinitionKey(processId, version); + ProcessDefinition definitionEvent = TestUtils.createProcessDefinition(processId, version, Set.of()); + definitionEvent.setMetadata(Map.of("name", "Javierito", "hobbies", List.of("community", "first"))); + Storage storage = getStorage(); + storage.put(key, definitionEvent); + queryAndAssert(assertWithKey(), storage, singletonList(jsonFilter(equalTo("metadata.name", "Javierito"))), null, null, null, key); + queryAndAssert(assertNoKey(), storage, singletonList(jsonFilter(equalTo("metadata.name", "Fulanito"))), null, null, null, key); + queryAndAssert(assertWithKey(), storage, singletonList(jsonFilter(contains("metadata.hobbies", "community"))), null, null, null, key); + queryAndAssert(assertNoKey(), storage, singletonList(jsonFilter(contains("metadata.hobbies", "commercial"))), null, null, null, key); + } } diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java index 9eb7611e74..fe2a332379 100644 --- a/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/test/java/org/kie/kogito/index/postgresql/query/ProcessInstanceEntityQueryIT.java @@ -19,11 +19,16 @@ package org.kie.kogito.index.postgresql.query; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.Test; import org.kie.kogito.event.process.ProcessInstanceVariableDataEvent; import org.kie.kogito.index.jpa.query.AbstractProcessInstanceEntityQueryIT; +import org.kie.kogito.index.jpa.storage.ProcessDefinitionEntityStorage; +import org.kie.kogito.index.model.ProcessDefinition; +import org.kie.kogito.index.model.ProcessDefinitionKey; import org.kie.kogito.index.storage.ProcessInstanceStorage; import org.kie.kogito.index.test.TestUtils; import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource; @@ -31,6 +36,8 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; + import static java.util.Collections.singletonList; import static org.kie.kogito.index.json.JsonUtils.jsonFilter; import static org.kie.kogito.index.test.QueryTestUtils.assertNotId; @@ -41,6 +48,9 @@ @QuarkusTestResource(PostgreSqlQuarkusTestResource.class) class ProcessInstanceEntityQueryIT extends AbstractProcessInstanceEntityQueryIT { + @Inject + ProcessDefinitionEntityStorage definitionStorage; + @Test void testProcessInstanceVariables() { String processId = "travels"; @@ -48,6 +58,13 @@ void testProcessInstanceVariables() { ProcessInstanceStorage storage = getStorage(); ProcessInstanceVariableDataEvent variableEvent = TestUtils.createProcessInstanceVariableEvent(processInstanceId, processId, "John", 28, false, List.of("Super", "Astonishing", "TheRealThing")); + final String version = "1.0"; + ProcessDefinitionKey key = new ProcessDefinitionKey(processId, version); + ProcessDefinition definitionEvent = TestUtils.createProcessDefinition(processId, version, Set.of()); + definitionEvent.setAnnotations(Set.of("Javierito", "Another")); + definitionEvent.setMetadata(Map.of("name", "Javierito", "hobbies", List.of("community", "first"))); + variableEvent.setKogitoProcessInstanceVersion(version); + definitionStorage.put(key, definitionEvent); storage.indexVariable(variableEvent); queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(equalTo("variables.traveller.name", "John"))), null, null, null, processInstanceId); @@ -123,5 +140,11 @@ void testProcessInstanceVariables() { processInstanceId); queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(containsAll("variables.traveller.aliases", List.of("Super", "TheDummyThing")))), null, null, null, processInstanceId); + queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(equalTo("definition.metadata.name", "Javierito"))), null, null, null, processInstanceId); + queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(equalTo("definition.metadata.name", "Fulanito"))), null, null, null, processInstanceId); + queryAndAssert(assertWithId(), storage, singletonList(jsonFilter(contains("definition.metadata.hobbies", "community"))), null, null, null, processInstanceId); + queryAndAssert(assertNotId(), storage, singletonList(jsonFilter(contains("definition.metadata.hobbies", "commercial"))), null, null, null, processInstanceId); + queryAndAssert(assertWithId(), storage, singletonList(contains("definition.annotations", "Javierito")), null, null, null, processInstanceId); + queryAndAssert(assertNotId(), storage, singletonList(contains("definition.annotations", "Fulanito")), null, null, null, processInstanceId); } }