Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,7 +63,7 @@ public class BeanQuery {
private final List<String> restrictionAlternatives = new ArrayList<>();
private boolean indexFiltersAsAlternatives = false;
private Pair<String, String> sorting;
private final Map<String, Pair<FilterField, String>> indexQueries = new HashMap<>();
private final List<Pair<FilterField, String>> indexQueries = new ArrayList<>();
private final Map<String, Object> parameters = new HashMap<>();

/**
Expand Down Expand Up @@ -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.
*
* <p>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.</p>
*
* <p>If no index queries were defined, no restriction is added.</p>
*
* @param field the entity field to restrict (e.g. "id" or "process.id")
*/
public void applyIndexRestriction(String field) {
if (indexQueries.isEmpty()) {
return;
}
Collection<Integer> 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<String, Pair<FilterField, String>> entry = iterator.next();
Collection<Integer> 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<Integer> performIndexSearches() {
List<Pair<String,String>> 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<Integer> ids = indexingService.searchIds(Process.class, terms);
return ids.isEmpty() ? NO_HIT : ids;
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> lookfor = new ArrayList<>();
private final FilterField filterField;
Expand Down Expand Up @@ -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<String, Pair<FilterField, String>> indexQueries,
Collection<String> 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<Pair<FilterField, String>> indexQueries) {
for (String lookingFor : lookfor) {
indexQueries.add(Pair.of(filterField, lookingFor));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ private BeanQuery createProcessQuery(Map<?, String> filters, boolean showClosedP
Collection<Integer> 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;
}

Expand Down Expand Up @@ -472,7 +472,7 @@ public List<Process> findByMetadata(Map<String, String> 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());
}

Expand Down Expand Up @@ -503,7 +503,7 @@ public List<Process> findByMetadataInAllProjects(Map<String, String> 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());
}

Expand Down Expand Up @@ -668,7 +668,7 @@ public List<Process> 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());
}

Expand Down Expand Up @@ -2563,7 +2563,7 @@ private BeanQuery createExportQuery(
}

query.restrictToClient(sessionClientId);
query.performIndexSearches();
query.applyIndexRestriction("id");
query.addInnerJoin("project proj");
query.defineSorting("id", SortOrder.ASCENDING);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public Long countResults(Map<?, String> filters, boolean onlyOwnTasks, boolean h
boolean showAutomaticTasks, List<TaskStatus> taskStatus) throws DAOException {

BeanQuery query = formBeanQuery(filters, onlyOwnTasks, hideCorrectionTasks, showAutomaticTasks, taskStatus);
query.performIndexSearches();
query.applyIndexRestriction("process.id");
return count(query.formCountQuery(), query.getQueryParameters());
}

Expand Down Expand Up @@ -258,7 +258,7 @@ public List<Task> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -136,25 +138,45 @@ public CompletionStage<?> startIndexing(Class<? extends BaseBean> 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<Integer> searchIds(Class<? extends BaseBean> beanClass, String searchField, String value) {
public Collection<Integer> searchIds(Class<? extends BaseBean> beanClass, List<Pair<String, String>> terms) {
try (Session ormSession = HibernateUtil.getSession()) {
SearchSession searchSession = Search.session(ormSession);
SearchProjection<Integer> idField = searchSession.scope(beanClass).projection().field("id", Integer.class)
.toProjection();
List<Integer> 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<String, String> term : terms) {
booleanPredicate.filter(
searchPredicateFactory.match()
.field(term.getLeft())
.matching(term.getRight())
);
}
return booleanPredicate;
});
List<Integer> 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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Process> processes = new ArrayList<>();
processes.add(process);
KitodoScriptService kitodoScript = ServiceManager.getKitodoScriptService();
Expand Down
Loading