From 02b276809bbdffc526c3ceed6fad65378572aba0 Mon Sep 17 00:00:00 2001 From: BartChris Date: Thu, 27 Nov 2025 18:21:37 +0100 Subject: [PATCH 01/13] Use multiple field/value terms in ES query --- .../production/services/data/BeanQuery.java | 14 +++++-- .../services/index/IndexingService.java | 39 ++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java index 297e2ba7da2..425e138227c 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java @@ -224,11 +224,19 @@ public void forIdOrInTitle(String searchInput) { * Searches the index and inserts the IDs into the HQL query parameters. */ public void performIndexSearches() { + List> terms = new ArrayList<>(); for (var iterator = indexQueries.entrySet().iterator(); iterator.hasNext();) { Entry> entry = iterator.next(); - Collection ids = indexingService.searchIds(Process.class, entry.getValue().getLeft() - .getSearchField(), entry.getValue().getRight()); - parameters.put(entry.getKey(), ids.isEmpty() ? NO_HIT : ids); + String field = entry.getValue().getLeft().getSearchField(); + String token = entry.getValue().getRight(); + terms.add(Pair.of(field, token)); + } + Collection ids = indexingService.searchIds(Process.class, terms); + Collection finalIds = ids.isEmpty() ? NO_HIT : ids; + + for (var iterator = indexQueries.entrySet().iterator(); iterator.hasNext();) { + Entry> entry = iterator.next(); + parameters.put(entry.getKey(), finalIds); iterator.remove(); } } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 7d486da75f1..814e4f42976 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -15,7 +15,9 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.exception.DataException; @@ -123,24 +125,41 @@ public CompletionStage startIndexing(Class type, MassInde } /** - * Searches for a search term in a search field and returns the hit IDs. + * Searches for entities matching all given field/value terms and returns their IDs. * * @param beanClass * class of beans to search for - * @param searchField - * search field to search on - * @param value - * value to be found in the search field + * @param terms + * list of field/value pairs to match (AND-combined) * @return ids of the found beans */ - public Collection searchIds(Class beanClass, String searchField, String value) { + public Collection searchIds(Class beanClass, List> terms) { SearchSession searchSession = Search.session(HibernateUtil.getSession()); SearchProjection idField = searchSession.scope(beanClass).projection().field("id", Integer.class) .toProjection(); - List ids = searchSession.search(beanClass).select(idField).where(function -> function.match().field( - searchField).matching(value)).fetchAll().hits(); - logger.debug("Searching {} IDs in field \"{}\" for \"{}\": {} hits", beanClass.getSimpleName(), searchField, - value, ids.size()); + var query = searchSession.search(beanClass) + .select(idField) + .where(f -> { + var bool = f.bool(); + for (var term : terms) { + bool.must( + f.match() + .field(term.getLeft()) + .matching(term.getRight()) + ); + } + return bool; + }); + List ids = query.fetchAll().hits(); + + logger.debug( + "Searching {} IDs with terms {}: {} hits", + beanClass.getSimpleName(), + terms.stream() + .map(t -> t.getLeft() + "=\"" + t.getRight() + "\"") + .collect(Collectors.joining(", ")), + ids.size() + ); return ids; } From cbf2dd75ae4d6c6ea0fdb8650d0be09acc9f9269 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 28 Nov 2025 10:27:32 +0100 Subject: [PATCH 02/13] Fix tests --- .../production/services/command/KitodoScriptServiceIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kitodo/src/test/java/org/kitodo/production/services/command/KitodoScriptServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/command/KitodoScriptServiceIT.java index 7d5f3bf20fd..c3b7a07948d 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/command/KitodoScriptServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/command/KitodoScriptServiceIT.java @@ -554,7 +554,7 @@ public void shouldAddDataWithMultipleScripts() throws Exception { assertEquals(6, process.getSortHelperMetadata()); assertEquals(2, process.getSortHelperDocstructs()); - String script = "action:addData " + "key:" + metadataKey + " value:legal note;" + "key:" + metadataKey + " value:secondNote"; + String script = "action:addData key:" + metadataKey + " \"value:legal note\"; key:" + metadataKey + " \"value:secondNote\""; List processes = new ArrayList<>(); processes.add(process); KitodoScriptService kitodoScript = ServiceManager.getKitodoScriptService(); From 72b16720bef493a2747a1146a3da15ee03c7b3c5 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 28 Nov 2025 10:38:02 +0100 Subject: [PATCH 03/13] Fix security warning --- .../kitodo/production/services/index/IndexingService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 814e4f42976..4ed941cf7a1 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -152,12 +152,14 @@ public Collection searchIds(Class beanClass, List

ids = query.fetchAll().hits(); + String termSummary = terms.stream() + .distinct() + .map(t -> t.getLeft() + "=\"***\"") + .collect(Collectors.joining(", ")); logger.debug( "Searching {} IDs with terms {}: {} hits", beanClass.getSimpleName(), - terms.stream() - .map(t -> t.getLeft() + "=\"" + t.getRight() + "\"") - .collect(Collectors.joining(", ")), + termSummary, ids.size() ); return ids; From cbe065bfe1d8dae4801b733476be51315c16dae8 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 18:10:28 +0100 Subject: [PATCH 04/13] Reduce coupling between database and search index --- .../production/services/data/BeanQuery.java | 27 ++++++++----------- .../services/data/IndexQueryPart.java | 12 +-------- .../services/data/ProcessService.java | 25 +++++++++++++---- .../production/services/data/TaskService.java | 10 +++++-- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java index 7e0c7da6bc2..d5cb10955e5 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java @@ -225,22 +225,19 @@ public void forIdOrInTitle(String searchInput) { /** * Searches the index and inserts the IDs into the HQL query parameters. */ - public void performIndexSearches() { - List> terms = new ArrayList<>(); - for (var iterator = indexQueries.entrySet().iterator(); iterator.hasNext();) { - Entry> entry = iterator.next(); - String field = entry.getValue().getLeft().getSearchField(); - String token = entry.getValue().getRight(); + public Collection performIndexSearches() { + if (indexQueries.isEmpty()) { + return Collections.emptyList(); + } + List> terms = new ArrayList<>(); + for (var entry : indexQueries.values()) { + String field = entry.getLeft().getSearchField(); + String token = entry.getRight(); terms.add(Pair.of(field, token)); } + indexQueries.clear(); Collection ids = indexingService.searchIds(Process.class, terms); - Collection finalIds = ids.isEmpty() ? NO_HIT : ids; - - for (var iterator = indexQueries.entrySet().iterator(); iterator.hasNext();) { - Entry> entry = iterator.next(); - parameters.put(entry.getKey(), finalIds); - iterator.remove(); - } + return ids.isEmpty() ? NO_HIT : ids; } /** @@ -343,9 +340,7 @@ public void restrictWithUserFilterString(String filterString) { } } else { IndexQueryPart indexQueryPart = (IndexQueryPart) searchFilter; - indexQueryPart.putQueryParameters(varName, parameterName, (className.equals("Process") ? "id" - : "process.id"), indexQueries, indexFiltersAsAlternatives ? restrictionAlternatives - : restrictions); + indexQueryPart.putQueryParameters(varName, indexQueries); } } if (groupFilters.size() == 1) { diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java index ad849c8f20e..372e2fb980e 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java @@ -90,29 +90,19 @@ public FilterField getFilterField() { /** * Inserts the search parameters into the database query logic. * - * @param varName - * variable name of the HQL search * @param parameterName * name of the search parameter for the results - * @param idField - * field name of the process ID * @param indexQueries * puts the prepared tokens for the search queries here - * @param restrictions - * puts the HQL restrictions here */ - void putQueryParameters(String varName, String parameterName, String idField, - Map> indexQueries, - Collection restrictions) { + void putQueryParameters(String parameterName, Map> indexQueries) { if (lookfor.size() == 1) { - restrictions.add(varName + "." + idField + (operand ? " IN (:" : " NOT IN (:") + parameterName + ')'); indexQueries.put(parameterName, Pair.of(filterField, lookfor.getFirst())); } else if (lookfor.size() >= 1) { int queryCount = 0; for (String lookingFor : lookfor) { queryCount++; String uniqueParameterName = parameterName + UNIQUE_PARAMETER_EXTENSION + queryCount; - restrictions.add(varName + "." + idField + (operand ? " IN (:" : " NOT IN (:") + uniqueParameterName + ')'); indexQueries.put(uniqueParameterName, Pair.of(filterField, lookingFor)); } } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java index ec4d2287fe5..ad020f6c7c2 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java @@ -307,7 +307,10 @@ private BeanQuery createProcessQuery(Map filters, boolean showClosedP Collection projectIDs = ServiceManager.getUserService().getCurrentUser().getProjects().stream().filter( project -> showInactiveProjects || project.isActive()).map(Project::getId).collect(Collectors.toList()); query.restrictToProjects(projectIDs); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return query; } @@ -482,7 +485,10 @@ public List findByMetadata(Map metadata, boolean exactM query.restrictWithUserFilterString(metadata.entrySet().stream().map(entry -> '"' + entry.getKey() + ':' + entry .getValue() + '"').collect(Collectors.joining(" "))); query.setUnordered(); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -513,7 +519,10 @@ public List findByMetadataInAllProjects(Map metadata, b query.restrictWithUserFilterString(metadata.entrySet().stream().map(entry -> '"' + entry.getKey() + ':' + entry .getValue() + '"').collect(Collectors.joining(" "))); query.setUnordered(); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -678,7 +687,10 @@ public List findSelectedProcesses(boolean showClosedProcesses, boolean .filter(project -> showInactiveProjects || project.isActive()).map(Project::getId) .collect(Collectors.toList()); query.restrictToProjects(projectIDs); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -2545,7 +2557,10 @@ public List getProcessesForExport( query.addBooleanRestriction("project.active", Boolean.TRUE); } query.restrictToClient(sessionClientId); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } query.addInnerJoin("project proj"); query.defineSorting("id", SortOrder.ASCENDING); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java index e7bdb85919f..eb8cd469745 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java @@ -178,7 +178,10 @@ public Long countResults(Map filters, boolean onlyOwnTasks, boolean h boolean showAutomaticTasks, List taskStatus) throws DAOException { BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return count(query.formCountQuery(), query.getQueryParameters()); } @@ -259,7 +262,10 @@ public List loadData(int offset, int limit, String sortField, SortOrder so BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus); query.defineSorting(SORT_FIELD_MAPPING.get(sortField), sortOrder); - query.performIndexSearches(); + Collection queryIds = query.performIndexSearches(); + if (!queryIds.isEmpty()) { + query.addInCollectionRestriction("id", queryIds); + } return getByQuery(query.formQueryForAll(), query.getQueryParameters(), offset, limit); } From 8cc66c7172b3f42e5f19607cc68df54378b78cde Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 18:36:02 +0100 Subject: [PATCH 05/13] Fix filters in TaskService --- .../java/org/kitodo/production/services/data/TaskService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java index eb8cd469745..d2d32c60be4 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java @@ -180,7 +180,7 @@ public Long countResults(Map filters, boolean onlyOwnTasks, boolean h BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus); Collection queryIds = query.performIndexSearches(); if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); + query.addInCollectionRestriction("process.id", queryIds); } return count(query.formCountQuery(), query.getQueryParameters()); } @@ -264,7 +264,7 @@ public List loadData(int offset, int limit, String sortField, SortOrder so query.defineSorting(SORT_FIELD_MAPPING.get(sortField), sortOrder); Collection queryIds = query.performIndexSearches(); if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); + query.addInCollectionRestriction("process.id", queryIds); } return getByQuery(query.formQueryForAll(), query.getQueryParameters(), offset, limit); } From 02763dc00649e5c9ff16153fbb8f88569c9886f6 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 18:45:14 +0100 Subject: [PATCH 06/13] Autoclose Hibernate session --- .../services/index/IndexingService.java | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 7f29d4ad7af..23ea11fd04a 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -137,35 +137,37 @@ public CompletionStage startIndexing(Class type, MassInde * @return ids of the found beans */ public Collection searchIds(Class beanClass, List> terms) { - SearchSession searchSession = Search.session(HibernateUtil.getSession()); - SearchProjection idField = searchSession.scope(beanClass).projection().field("id", Integer.class) - .toProjection(); - var query = searchSession.search(beanClass) - .select(idField) - .where(f -> { - var bool = f.bool(); - for (var term : terms) { - bool.must( - f.match() - .field(term.getLeft()) - .matching(term.getRight()) - ); - } - return bool; - }); - List ids = query.fetchAll().hits(); + try (Session ormSession = HibernateUtil.getSession()) { + SearchSession searchSession = Search.session(ormSession); + SearchProjection idField = searchSession.scope(beanClass).projection().field("id", Integer.class) + .toProjection(); + var query = searchSession.search(beanClass) + .select(idField) + .where(f -> { + var bool = f.bool(); + for (var term : terms) { + bool.must( + f.match() + .field(term.getLeft()) + .matching(term.getRight()) + ); + } + return bool; + }); + List ids = query.fetchAll().hits(); - String termSummary = terms.stream() - .distinct() - .map(t -> t.getLeft() + "=\"***\"") - .collect(Collectors.joining(", ")); - logger.debug( - "Searching {} IDs with terms {}: {} hits", - beanClass.getSimpleName(), - termSummary, - ids.size() - ); - return ids; + String termSummary = terms.stream() + .distinct() + .map(t -> t.getLeft() + "=\"***\"") + .collect(Collectors.joining(", ")); + logger.debug( + "Searching {} IDs with terms {}: {} hits", + beanClass.getSimpleName(), + termSummary, + ids.size() + ); + return ids; + } } /** From 89d6fce3002849b112e208b71a7a444392684059 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 18:57:14 +0100 Subject: [PATCH 07/13] Adapt to Java21 --- .../production/services/index/IndexingService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 23ea11fd04a..59771533f2c 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.CompletionStage; -import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; @@ -156,10 +155,11 @@ public Collection searchIds(Class beanClass, List

ids = query.fetchAll().hits(); - String termSummary = terms.stream() - .distinct() - .map(t -> t.getLeft() + "=\"***\"") - .collect(Collectors.joining(", ")); + String termSummary = String.join(", ", + terms.stream() + .distinct() + .map(t -> t.getLeft() + "=\"***\"") + .toList()); logger.debug( "Searching {} IDs with terms {}: {} hits", beanClass.getSimpleName(), From 33c2512d03cc005c4559575626a237126d7b59e3 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 18:59:42 +0100 Subject: [PATCH 08/13] Adjust comment --- .../org/kitodo/production/services/data/IndexQueryPart.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java index 372e2fb980e..a6c86f83691 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java @@ -88,7 +88,7 @@ public FilterField getFilterField() { } /** - * Inserts the search parameters into the database query logic. + * Adds the prepared index search terms for this filter to the given query map. * * @param parameterName * name of the search parameter for the results From 4bd2dca689ef1cd296ac6d84840fd983efc7e399 Mon Sep 17 00:00:00 2001 From: BartChris Date: Fri, 13 Mar 2026 19:11:03 +0100 Subject: [PATCH 09/13] Further simplification --- .../production/services/data/BeanQuery.java | 7 +++--- .../services/data/IndexQueryPart.java | 23 ++++--------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java index d5cb10955e5..bc8285a5067 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -64,7 +63,7 @@ public class BeanQuery { private final List restrictionAlternatives = new ArrayList<>(); private boolean indexFiltersAsAlternatives = false; private Pair sorting; - private final Map> indexQueries = new HashMap<>(); + private final List> indexQueries = new ArrayList<>(); private final Map parameters = new HashMap<>(); /** @@ -230,7 +229,7 @@ public Collection performIndexSearches() { return Collections.emptyList(); } List> terms = new ArrayList<>(); - for (var entry : indexQueries.values()) { + for (var entry : indexQueries) { String field = entry.getLeft().getSearchField(); String token = entry.getRight(); terms.add(Pair.of(field, token)); @@ -340,7 +339,7 @@ public void restrictWithUserFilterString(String filterString) { } } else { IndexQueryPart indexQueryPart = (IndexQueryPart) searchFilter; - indexQueryPart.putQueryParameters(varName, indexQueries); + indexQueryPart.putQueryParameters(indexQueries); } } if (groupFilters.size() == 1) { diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java index a6c86f83691..d4d19beb054 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/IndexQueryPart.java @@ -13,9 +13,7 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.tuple.Pair; import org.kitodo.data.database.beans.ProcessKeywords; @@ -26,8 +24,6 @@ */ class IndexQueryPart implements UserSpecifiedFilter { - private static final String UNIQUE_PARAMETER_EXTENSION = "query"; - private static final char VALUE_SEPARATOR = 'q'; private final List lookfor = new ArrayList<>(); private final FilterField filterField; @@ -88,23 +84,14 @@ public FilterField getFilterField() { } /** - * Adds the prepared index search terms for this filter to the given query map. - * - * @param parameterName - * name of the search parameter for the results + * Adds the prepared index search terms for this filter to the list of index queries. + * * @param indexQueries * puts the prepared tokens for the search queries here */ - void putQueryParameters(String parameterName, Map> indexQueries) { - if (lookfor.size() == 1) { - indexQueries.put(parameterName, Pair.of(filterField, lookfor.getFirst())); - } else if (lookfor.size() >= 1) { - int queryCount = 0; - for (String lookingFor : lookfor) { - queryCount++; - String uniqueParameterName = parameterName + UNIQUE_PARAMETER_EXTENSION + queryCount; - indexQueries.put(uniqueParameterName, Pair.of(filterField, lookingFor)); - } + void putQueryParameters(List> indexQueries) { + for (String lookingFor : lookfor) { + indexQueries.add(Pair.of(filterField, lookingFor)); } } From 1a48eeec8a6dc1b66815cd48cbce59a19b1876e2 Mon Sep 17 00:00:00 2001 From: BartChris Date: Mon, 16 Mar 2026 11:40:05 +0100 Subject: [PATCH 10/13] Refactor variables for more clarity and use filter query --- .../production/services/index/IndexingService.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 59771533f2c..31e48c8822f 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -22,6 +22,7 @@ import org.hibernate.Session; import org.hibernate.exception.DataException; import org.hibernate.search.engine.search.projection.SearchProjection; +import org.hibernate.search.engine.search.query.SearchQuery; import org.hibernate.search.mapper.orm.Search; import org.hibernate.search.mapper.orm.massindexing.MassIndexer; import org.hibernate.search.mapper.orm.session.SearchSession; @@ -142,16 +143,16 @@ public Collection searchIds(Class beanClass, List

{ - var bool = f.bool(); - for (var term : terms) { - bool.must( - f.match() + .where(searchPredicateFactory -> { + var booleanPredicate = searchPredicateFactory.bool(); + for (Pair term : terms) { + booleanPredicate.filter( + searchPredicateFactory.match() .field(term.getLeft()) .matching(term.getRight()) ); } - return bool; + return booleanPredicate; }); List ids = query.fetchAll().hits(); From 13b126a5181bccff25112bb1856e4795a705c675 Mon Sep 17 00:00:00 2001 From: BartChris Date: Mon, 16 Mar 2026 11:45:39 +0100 Subject: [PATCH 11/13] Log search terms --- .../org/kitodo/production/services/index/IndexingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java index 31e48c8822f..118cfc4501c 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/index/IndexingService.java @@ -159,7 +159,7 @@ public Collection searchIds(Class beanClass, List

t.getLeft() + "=\"***\"") + .map(t -> t.getLeft() + "=\"" + t.getRight() + "\"") .toList()); logger.debug( "Searching {} IDs with terms {}: {} hits", From 68ee189c46adfe6e882092c9ca3ce041ce8f7b0f Mon Sep 17 00:00:00 2001 From: BartChris Date: Tue, 17 Mar 2026 18:25:46 +0100 Subject: [PATCH 12/13] Refactor for better encapsulation and clarity --- .../production/services/data/BeanQuery.java | 22 +++++++++++++--- .../services/data/ProcessService.java | 25 ++++--------------- .../production/services/data/TaskService.java | 10 ++------ 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java index bc8285a5067..9f08de80149 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java @@ -222,12 +222,28 @@ public void forIdOrInTitle(String searchInput) { } /** - * Searches the index and inserts the IDs into the HQL query parameters. + * Applies a restriction based on index search results to the given field. + * + *

If index queries were defined, this performs a search in the index and + * restricts the query to the resulting IDs. If the index search yields no hits, + * a non-matching ID set is applied to ensure the query returns no results.

+ * + *

If no index queries were defined, no restriction is added.

+ * + * @param field the entity field to restrict (e.g. "id" or "process.id") */ - public Collection performIndexSearches() { + public void applyIndexRestriction(String field) { if (indexQueries.isEmpty()) { - return Collections.emptyList(); + return; } + Collection ids = performIndexSearches(); + addInCollectionRestriction(field, ids); + } + + /** + * Searches the index and inserts the IDs into the HQL query parameters. + */ + private Collection performIndexSearches() { List> terms = new ArrayList<>(); for (var entry : indexQueries) { String field = entry.getLeft().getSearchField(); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java index ad020f6c7c2..8e0534a422e 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java @@ -307,10 +307,7 @@ private BeanQuery createProcessQuery(Map filters, boolean showClosedP Collection projectIDs = ServiceManager.getUserService().getCurrentUser().getProjects().stream().filter( project -> showInactiveProjects || project.isActive()).map(Project::getId).collect(Collectors.toList()); query.restrictToProjects(projectIDs); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); - } + query.applyIndexRestriction("id"); return query; } @@ -485,10 +482,7 @@ public List findByMetadata(Map metadata, boolean exactM query.restrictWithUserFilterString(metadata.entrySet().stream().map(entry -> '"' + entry.getKey() + ':' + entry .getValue() + '"').collect(Collectors.joining(" "))); query.setUnordered(); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); - } + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -519,10 +513,7 @@ public List findByMetadataInAllProjects(Map metadata, b query.restrictWithUserFilterString(metadata.entrySet().stream().map(entry -> '"' + entry.getKey() + ':' + entry .getValue() + '"').collect(Collectors.joining(" "))); query.setUnordered(); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); - } + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -687,10 +678,7 @@ public List findSelectedProcesses(boolean showClosedProcesses, boolean .filter(project -> showInactiveProjects || project.isActive()).map(Project::getId) .collect(Collectors.toList()); query.restrictToProjects(projectIDs); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); - } + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -2557,10 +2545,7 @@ public List getProcessesForExport( query.addBooleanRestriction("project.active", Boolean.TRUE); } query.restrictToClient(sessionClientId); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("id", queryIds); - } + query.applyIndexRestriction("id"); query.addInnerJoin("project proj"); query.defineSorting("id", SortOrder.ASCENDING); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java index d2d32c60be4..3648023596f 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/TaskService.java @@ -178,10 +178,7 @@ public Long countResults(Map filters, boolean onlyOwnTasks, boolean h boolean showAutomaticTasks, List taskStatus) throws DAOException { BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("process.id", queryIds); - } + query.applyIndexRestriction("process.id"); return count(query.formCountQuery(), query.getQueryParameters()); } @@ -262,10 +259,7 @@ public List loadData(int offset, int limit, String sortField, SortOrder so BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus); query.defineSorting(SORT_FIELD_MAPPING.get(sortField), sortOrder); - Collection queryIds = query.performIndexSearches(); - if (!queryIds.isEmpty()) { - query.addInCollectionRestriction("process.id", queryIds); - } + query.applyIndexRestriction("process.id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters(), offset, limit); } From 0164fc3b8fd33e9bf9117888337065eaefb14411 Mon Sep 17 00:00:00 2001 From: BartChris Date: Wed, 18 Mar 2026 12:10:55 +0100 Subject: [PATCH 13/13] Adapt comment --- .../java/org/kitodo/production/services/data/BeanQuery.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java index 9f08de80149..2bdcd812112 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/BeanQuery.java @@ -241,7 +241,9 @@ public void applyIndexRestriction(String field) { } /** - * Searches the index and inserts the IDs into the HQL query parameters. + * Executes all collected index query terms as a single combined index search + * and returns the matching process IDs. If no hits are found, a non-matching + * ID collection is returned by the caller. */ private Collection performIndexSearches() { List> terms = new ArrayList<>();