diff --git a/README.md b/README.md index 03f31734..7b89ee4c 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,22 @@ To activate either mode please navigate to the frontend folder via `cd frontend` Web mode: The default user is `admin` and the default password is `admin`. -### Generating JWT keys (web mode only) +## Generating JWT keys (web mode only) To generate the keys necessary for authentication via JWT please run the following script once: ``` ./backend/generate-keys.sh ``` +## Database and Fuzzy Match +ProA uses fuzzy match so that the BPMN labels can be matched with some degree of tolerance, e.g. ignoring typos. +To this end, postgres levenshtein function is used, which needs to be activated by ececuting the following: + +```CREATE EXTENSION fuzzystrmatch;``` + +Furthermore, in Azure before executing the above statement, the extension needs to be activated via: DB >> Settings >> Server parameters >> azure.extensions >> fuzzystrmatch; + + ## The Entire Application In order to build an uber jar, which also contains the frontend, run the following in the root: diff --git a/backend/src/main/java/de/envite/proa/repository/datastore/DataStoreDao.java b/backend/src/main/java/de/envite/proa/repository/datastore/DataStoreDao.java index 86e21d06..badb3fed 100644 --- a/backend/src/main/java/de/envite/proa/repository/datastore/DataStoreDao.java +++ b/backend/src/main/java/de/envite/proa/repository/datastore/DataStoreDao.java @@ -2,16 +2,21 @@ import de.envite.proa.repository.tables.DataStoreTable; import de.envite.proa.repository.tables.ProjectVersionTable; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import org.eclipse.microprofile.config.inject.ConfigProperty; import java.util.List; @ApplicationScoped public class DataStoreDao { + @ConfigProperty(name = "quarkus.datasource.db-kind") + String dbKind; + private EntityManager em; @Inject @@ -34,11 +39,28 @@ public List getDataStores(ProjectVersionTable projectVersionTabl @Transactional public DataStoreTable getDataStoreForLabel(String label, ProjectVersionTable projectVersionTable) { - return em // - .createQuery("SELECT d FROM DataStoreTable d WHERE d.label = :label AND d.project = :project", - DataStoreTable.class) - .setParameter("label", label)// - .setParameter("project", projectVersionTable)// - .getSingleResult(); + + String searchLabel = SearchLabelBuilder.buildSearchLabel(label); + + if ("postgresql".equals(dbKind)) { + return em.createQuery( + "SELECT d FROM DataStoreTable d " + + "WHERE d.project = :project " + + "AND ( d.searchLabel = :searchLabel " + + "OR function('levenshtein', d.searchLabel, :searchLabel) <= 4 )", + DataStoreTable.class) + .setParameter("searchLabel", searchLabel)// + .setParameter("project", projectVersionTable)// + .getSingleResult(); + } else { + return em.createQuery( + "SELECT d FROM DataStoreTable d " + + "WHERE d.project = :project " + + "AND d.searchLabel = :searchLabel ", + DataStoreTable.class) + .setParameter("searchLabel", searchLabel)// + .setParameter("project", projectVersionTable)// + .getSingleResult(); + } } } diff --git a/backend/src/main/java/de/envite/proa/repository/processmodel/CallActivityDao.java b/backend/src/main/java/de/envite/proa/repository/processmodel/CallActivityDao.java index dc239d6b..e8a42f14 100644 --- a/backend/src/main/java/de/envite/proa/repository/processmodel/CallActivityDao.java +++ b/backend/src/main/java/de/envite/proa/repository/processmodel/CallActivityDao.java @@ -3,16 +3,21 @@ import de.envite.proa.repository.tables.CallActivityTable; import de.envite.proa.repository.tables.ProcessModelTable; import de.envite.proa.repository.tables.ProjectVersionTable; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import org.eclipse.microprofile.config.inject.ConfigProperty; import java.util.List; @RequestScoped public class CallActivityDao { + @ConfigProperty(name = "quarkus.datasource.db-kind") + String dbKind; + private final EntityManager em; @Inject @@ -26,13 +31,30 @@ public void persist(CallActivityTable table) { } @Transactional - public List getCallActivitiesForName(String name, ProjectVersionTable project) { - return em // - .createQuery("SELECT c FROM CallActivityTable c WHERE c.label = :label AND c.project = :project", - CallActivityTable.class) - .setParameter("label", name)// - .setParameter("project", project)// - .getResultList(); + public List getCallActivitiesForName(String label, ProjectVersionTable projectVersionTable) { + + String searchLabel = SearchLabelBuilder.buildSearchLabel(label); + + if ("postgresql".equals(dbKind)) { + return em.createQuery( + "SELECT c FROM CallActivityTable c " + + "WHERE c.project = :project " + + "AND ( c.searchLabel = :searchLabel " + + "OR function('levenshtein', c.searchLabel, :searchLabel) <= 4 )", + CallActivityTable.class) + .setParameter("searchLabel", searchLabel)// + .setParameter("project", projectVersionTable)// + .getResultList(); + } else { + return em.createQuery( + "SELECT c FROM CallActivityTable c " + + "WHERE c.project = :project " + + "AND c.searchLabel = :searchLabel ", + CallActivityTable.class) + .setParameter("searchLabel", searchLabel)// + .setParameter("project", projectVersionTable)// + .getResultList(); + } } @Transactional diff --git a/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessEventDao.java b/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessEventDao.java index b1631c71..de1a34f6 100644 --- a/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessEventDao.java +++ b/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessEventDao.java @@ -1,19 +1,24 @@ package de.envite.proa.repository.processmodel; +import java.util.List; + import de.envite.proa.entities.process.EventType; import de.envite.proa.repository.tables.ProcessEventTable; import de.envite.proa.repository.tables.ProcessModelTable; import de.envite.proa.repository.tables.ProjectVersionTable; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; - -import java.util.List; +import org.eclipse.microprofile.config.inject.ConfigProperty; @RequestScoped public class ProcessEventDao { + @ConfigProperty(name = "quarkus.datasource.db-kind") + String dbKind; + private EntityManager em; @Inject @@ -25,14 +30,32 @@ public ProcessEventDao(EntityManager em) { public List getEventsForLabelAndType(String label, EventType eventType, ProjectVersionTable projectVersionTable) { - return em // - .createQuery( - "SELECT e FROM ProcessEventTable e WHERE e.label = :label AND e.eventType=:eventType AND e.project=:project", - ProcessEventTable.class) - .setParameter("label", label)// - .setParameter("eventType", eventType)// - .setParameter("project", projectVersionTable)// - .getResultList(); + String searchLabel = SearchLabelBuilder.buildSearchLabel(label); + + if ("postgresql".equals(dbKind)) { + return em.createQuery( + "SELECT e FROM ProcessEventTable e " + + "WHERE e.eventType = :eventType " + + "AND e.project = :project " + + "AND ( e.searchLabel = :searchLabel " + + "OR function('levenshtein', e.searchLabel, :searchLabel) <= 4 )", + ProcessEventTable.class) + .setParameter("searchLabel", searchLabel) + .setParameter("eventType", eventType) + .setParameter("project", projectVersionTable) + .getResultList(); + } else { + return em.createQuery( + "SELECT e FROM ProcessEventTable e " + + "WHERE e.eventType = :eventType " + + "AND e.project = :project " + + "AND e.searchLabel = :searchLabel ", + ProcessEventTable.class) + .setParameter("searchLabel", searchLabel) + .setParameter("eventType", eventType) + .setParameter("project", projectVersionTable) + .getResultList(); + } } @Transactional diff --git a/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessModelDao.java b/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessModelDao.java index aaee00ed..bc6ec113 100644 --- a/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessModelDao.java +++ b/backend/src/main/java/de/envite/proa/repository/processmodel/ProcessModelDao.java @@ -3,6 +3,7 @@ import de.envite.proa.entities.process.ProcessType; import de.envite.proa.repository.tables.ProcessModelTable; import de.envite.proa.repository.tables.ProjectVersionTable; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityGraph; @@ -13,8 +14,13 @@ import java.util.List; import java.util.Map; +import org.eclipse.microprofile.config.inject.ConfigProperty; + @RequestScoped public class ProcessModelDao { + + @ConfigProperty(name = "quarkus.datasource.db-kind") + private String dbKind; private EntityManager em; @@ -52,16 +58,35 @@ public List getProcessModelsWithoutCollaborationsAndWithEvent @Transactional public List getProcessModelsForName(String name, ProjectVersionTable projectVersionTable) { - return em - .createQuery( - "SELECT p " + - "FROM ProcessModelTable p " + - "WHERE p.name = :name " + - "AND p.project = :project", - ProcessModelTable.class) - .setParameter("name", name) - .setParameter("project", projectVersionTable) - .getResultList(); + + if ("postgresql".equals(dbKind)) { + + String searchLabel = SearchLabelBuilder.buildSearchLabel(name); + + return em + .createQuery( + "SELECT p " + + "FROM ProcessModelTable p " + + "WHERE ( p.name = :name " + + "OR function('levenshtein', p.searchLabel, :searchLabel) <= 4 )" + + "AND p.project = :project", + ProcessModelTable.class) + .setParameter("name", name) + .setParameter("searchLabel", searchLabel) + .setParameter("project", projectVersionTable) + .getResultList(); + }else { + return em + .createQuery( + "SELECT p " + + "FROM ProcessModelTable p " + + "WHERE p.name = :name " + + "AND p.project = :project", + ProcessModelTable.class) + .setParameter("name", name) + .setParameter("project", projectVersionTable) + .getResultList(); + } } @Transactional diff --git a/backend/src/main/java/de/envite/proa/repository/tables/CallActivityTable.java b/backend/src/main/java/de/envite/proa/repository/tables/CallActivityTable.java index c8dc2ca1..c4adce4b 100644 --- a/backend/src/main/java/de/envite/proa/repository/tables/CallActivityTable.java +++ b/backend/src/main/java/de/envite/proa/repository/tables/CallActivityTable.java @@ -1,5 +1,6 @@ package de.envite.proa.repository.tables; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.persistence.*; import lombok.Data; @@ -15,9 +16,17 @@ public class CallActivityTable { private String label; + private String searchLabel; + @ManyToOne(fetch = FetchType.LAZY) private ProcessModelTable processModel; @ManyToOne(fetch = FetchType.LAZY) private ProjectVersionTable project; + + @PrePersist + @PreUpdate + private void generateSearchLabel() { + this.searchLabel = SearchLabelBuilder.buildSearchLabel(this.label); + } } \ No newline at end of file diff --git a/backend/src/main/java/de/envite/proa/repository/tables/DataStoreTable.java b/backend/src/main/java/de/envite/proa/repository/tables/DataStoreTable.java index e83a38a8..77c38b68 100644 --- a/backend/src/main/java/de/envite/proa/repository/tables/DataStoreTable.java +++ b/backend/src/main/java/de/envite/proa/repository/tables/DataStoreTable.java @@ -1,5 +1,6 @@ package de.envite.proa.repository.tables; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.persistence.*; import lombok.Data; @@ -13,6 +14,14 @@ public class DataStoreTable { private String label; + private String searchLabel; + @ManyToOne(fetch = FetchType.LAZY) private ProjectVersionTable project; + + @PrePersist + @PreUpdate + private void generateSearchLabel() { + this.searchLabel = SearchLabelBuilder.buildSearchLabel(this.label); + } } \ No newline at end of file diff --git a/backend/src/main/java/de/envite/proa/repository/tables/ProcessEventTable.java b/backend/src/main/java/de/envite/proa/repository/tables/ProcessEventTable.java index 96ece719..920f3962 100644 --- a/backend/src/main/java/de/envite/proa/repository/tables/ProcessEventTable.java +++ b/backend/src/main/java/de/envite/proa/repository/tables/ProcessEventTable.java @@ -1,6 +1,7 @@ package de.envite.proa.repository.tables; import de.envite.proa.entities.process.EventType; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.persistence.*; import lombok.Data; @@ -16,6 +17,8 @@ public class ProcessEventTable { private String label; + private String searchLabel; + @Enumerated(EnumType.STRING) private EventType eventType; @@ -23,5 +26,11 @@ public class ProcessEventTable { private ProcessModelTable processModel; @ManyToOne(fetch = FetchType.LAZY) - private ProjectVersionTable project; + private ProjectVersionTable project; + + @PrePersist + @PreUpdate + private void generateSearchLabel() { + this.searchLabel = SearchLabelBuilder.buildSearchLabel(this.label); + } } \ No newline at end of file diff --git a/backend/src/main/java/de/envite/proa/repository/tables/ProcessModelTable.java b/backend/src/main/java/de/envite/proa/repository/tables/ProcessModelTable.java index 34727c9e..387b3631 100644 --- a/backend/src/main/java/de/envite/proa/repository/tables/ProcessModelTable.java +++ b/backend/src/main/java/de/envite/proa/repository/tables/ProcessModelTable.java @@ -1,6 +1,7 @@ package de.envite.proa.repository.tables; import de.envite.proa.entities.process.ProcessType; +import de.envite.proa.util.SearchLabelBuilder; import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; @@ -56,6 +57,8 @@ public ProcessModelTable(Long id) { @EqualsAndHashCode.Include private String name; + + private String searchLabel; @EqualsAndHashCode.Include private String bpmnProcessId; @@ -77,7 +80,7 @@ public ProcessModelTable(Long id) { @Column @EqualsAndHashCode.Include private String description; - + @Column @EqualsAndHashCode.Include private LocalDateTime createdAt; @@ -99,4 +102,10 @@ public ProcessModelTable(Long id) { @EqualsAndHashCode.Include private ProcessType processType; + + @PrePersist + @PreUpdate + private void generateSearchLabel() { + this.searchLabel = SearchLabelBuilder.buildSearchLabel(this.name); + } } \ No newline at end of file diff --git a/backend/src/main/java/de/envite/proa/util/SearchLabelBuilder.java b/backend/src/main/java/de/envite/proa/util/SearchLabelBuilder.java new file mode 100644 index 00000000..d17e30e1 --- /dev/null +++ b/backend/src/main/java/de/envite/proa/util/SearchLabelBuilder.java @@ -0,0 +1,18 @@ +package de.envite.proa.util; + +import java.util.Arrays; + +public class SearchLabelBuilder { + public static String buildSearchLabel(String label) { + if (label == null) + return null; + String cleanedLabel = label + .toLowerCase() + .replaceAll("[^a-zA-Z0-9\\s]", " ") + .trim() + .replaceAll("\\s+", " "); + String[] words = cleanedLabel.split("\\s+"); + Arrays.sort(words); + return String.join("", words); + } +}