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 b227c5624a5..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 @@ -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<>(); /** @@ -223,16 +222,39 @@ 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 void applyIndexRestriction(String field) { + if (indexQueries.isEmpty()) { + return; + } + Collection ids = performIndexSearches(); + addInCollectionRestriction(field, ids); + } + + /** + * 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. */ - public void performIndexSearches() { - 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); - iterator.remove(); + private Collection performIndexSearches() { + List> terms = new ArrayList<>(); + for (var entry : indexQueries) { + String field = entry.getLeft().getSearchField(); + String token = entry.getRight(); + terms.add(Pair.of(field, token)); } + indexQueries.clear(); + Collection ids = indexingService.searchIds(Process.class, terms); + return ids.isEmpty() ? NO_HIT : ids; } /** @@ -335,9 +357,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(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..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,33 +84,14 @@ 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 + * 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 - * @param restrictions - * puts the HQL restrictions here */ - void putQueryParameters(String varName, String parameterName, String idField, - Map> indexQueries, - Collection restrictions) { - 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)); - } + void putQueryParameters(List> indexQueries) { + for (String lookingFor : lookfor) { + indexQueries.add(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 657cd454a50..3b3b84b6946 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 @@ -297,7 +297,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); - query.performIndexSearches(); + query.applyIndexRestriction("id"); return query; } @@ -472,7 +472,7 @@ 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(); + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -503,7 +503,7 @@ public List findByMetadataInAllProjects(Map metadata, b query.restrictWithUserFilterString(metadata.entrySet().stream().map(entry -> '"' + entry.getKey() + ':' + entry .getValue() + '"').collect(Collectors.joining(" "))); query.setUnordered(); - query.performIndexSearches(); + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -668,7 +668,7 @@ public List findSelectedProcesses(boolean showClosedProcesses, boolean .filter(project -> showInactiveProjects || project.isActive()).map(Project::getId) .collect(Collectors.toList()); query.restrictToProjects(projectIDs); - query.performIndexSearches(); + query.applyIndexRestriction("id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters()); } @@ -2563,7 +2563,7 @@ private BeanQuery createExportQuery( } query.restrictToClient(sessionClientId); - query.performIndexSearches(); + 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 5e6db03f53d..60baf35dad2 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 @@ -177,7 +177,7 @@ 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(); + query.applyIndexRestriction("process.id"); return count(query.formCountQuery(), query.getQueryParameters()); } @@ -258,7 +258,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); - query.performIndexSearches(); + query.applyIndexRestriction("process.id"); return getByQuery(query.formQueryForAll(), query.getQueryParameters(), offset, limit); } 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 3a56bb82109..a6c2c99f5c7 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 @@ -16,11 +16,13 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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; @@ -136,25 +138,45 @@ 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) { try (Session ormSession = HibernateUtil.getSession()) { SearchSession searchSession = Search.session(ormSession); 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(searchPredicateFactory -> { + var booleanPredicate = searchPredicateFactory.bool(); + for (Pair term : terms) { + booleanPredicate.filter( + searchPredicateFactory.match() + .field(term.getLeft()) + .matching(term.getRight()) + ); + } + return booleanPredicate; + }); + List ids = query.fetchAll().hits(); + + String termSummary = String.join(", ", + terms.stream() + .distinct() + .map(t -> t.getLeft() + "=\"" + t.getRight() + "\"") + .toList()); + logger.debug( + "Searching {} IDs with terms {}: {} hits", + beanClass.getSimpleName(), + termSummary, + ids.size() + ); return ids; } } 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 5d394a3ea0c..78b0a3eb49c 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();