Skip to content

Commit

Permalink
Implement search
Browse files Browse the repository at this point in the history
  • Loading branch information
matthias-ronge committed Oct 28, 2024
1 parent c1bbea7 commit 3cf372b
Show file tree
Hide file tree
Showing 9 changed files with 715 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.kitodo.data.database.beans.BaseTemplateBean;
import org.kitodo.data.database.beans.Process;
import org.kitodo.data.database.persistence.HibernateUtil;
import org.kitodo.production.services.ServiceManager;
import org.kitodo.production.services.data.BeanQuery;

Expand Down Expand Up @@ -63,14 +64,15 @@ public HSSFWorkbook getResult() {
private List<Process> getResultsWithFilter() {
BeanQuery query = new BeanQuery(Process.class);
if (StringUtils.isNotBlank(filter)) {
query.forIdOrInTitle(filter);
query.restrictWithUserFilterString(filter);
}
if (!this.showClosedProcesses) {
query.restrictToNotCompletedProcesses();
}
if (!this.showInactiveProjects) {
query.addBooleanRestriction("project.active", Boolean.FALSE);
}
query.performIndexSearches(HibernateUtil.getSession());
return ServiceManager.getProcessService().getByQuery(query.formQueryForAll(), query.getQueryParameters());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.tuple.Pair;
import org.hibernate.Session;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.kitodo.data.database.beans.BaseBean;
import org.kitodo.data.database.beans.Role;
import org.kitodo.production.enums.ProcessState;
Expand All @@ -33,11 +36,13 @@
*/
public class BeanQuery {
private static final Pattern EXPLICIT_ID_SEARCH = Pattern.compile("id:(\\d+)");
private final String objectClass;
private final Class<? extends BaseBean> beanClass;
private final String className;
private final String varName;
private final Collection<String> extensions = new ArrayList<>();
private final Collection<String> restrictions = new ArrayList<>();
private Pair<String, String> sorting = Pair.of("id", "ASC");
private final Map<String, Pair<FilterField, String>> indexQueries = new HashMap<>();
private final Map<String, Object> parameters = new HashMap<>();

/**
Expand All @@ -47,8 +52,9 @@ public class BeanQuery {
* class of beans to search for
*/
public BeanQuery(Class<? extends BaseBean> beanClass) {
objectClass = beanClass.getSimpleName();
varName = objectClass.toLowerCase();
this.beanClass = beanClass;
className = beanClass.getSimpleName();
varName = className.toLowerCase();
}

/**
Expand Down Expand Up @@ -153,7 +159,7 @@ public void forIdOrInTitle(String searchInput) {
Matcher idSearchInput = EXPLICIT_ID_SEARCH.matcher(searchInput);
if (idSearchInput.matches()) {
Integer expectedId = Integer.valueOf(idSearchInput.group(1));
if (objectClass.equals("Task")) {
if (className.equals("Task")) {
restrictions.add(varName + ".process.id = :id");
} else {
restrictions.add(varName + ".id = :id");
Expand All @@ -171,14 +177,33 @@ public void forIdOrInTitle(String searchInput) {
}
}

/**
* Performs index searches.
*
* @param session
* session of hibernate
*/
public void performIndexSearches(Session session) {
SearchSession searchSession = Search.session(session);
for (var iterator = indexQueries.entrySet().iterator(); iterator.hasNext();) {
Entry<String, Pair<FilterField, String>> entry = iterator.next();
List<Integer> ids = searchSession.search(beanClass).select(searchSession.scope(beanClass).projection()
.field("id", Integer.class).toProjection()).where(function -> function.match().field(entry
.getValue().getLeft().getSearchField()).matching(entry.getValue().getRight())).fetchAll()
.hits();
parameters.put(entry.getKey(), ids);
iterator.remove();
}
}

/**
* Requires that the query only find objects owned by the specified client.
*
* @param sessionClientId
* client record number
*/
public void restrictToClient(int sessionClientId) {
switch (objectClass) {
switch (className) {
case "Docket":
case "Project":
case "Ruleset":
Expand All @@ -193,8 +218,8 @@ public void restrictToClient(int sessionClientId) {
restrictions.add(varName + ".process.project.client.id = :sessionClientId");
break;
default:
throw new IllegalStateException("BeanQuery.restrictToClient() not yet implemented for "
.concat(objectClass));
throw new IllegalStateException("BeanQuery.restrictToClient() not yet implemented for ".concat(
className));
}
parameters.put("sessionClientId", sessionClientId);
}
Expand All @@ -216,16 +241,16 @@ public void restrictToNotCompletedProcesses() {
* record numbers of the projects to which the hits may belong
*/
public void restrictToProjects(Collection<Integer> projectIDs) {
switch (objectClass) {
switch (className) {
case "Process":
restrictions.add(varName + ".project.id IN (:projectIDs)");
break;
case "Task":
restrictions.add(varName + ".process.project.id IN (:projectIDs)");
break;
default:
throw new IllegalStateException("BeanQuery.restrictToProjects() not yet implemented for "
.concat(objectClass));
throw new IllegalStateException("BeanQuery.restrictToProjects() not yet implemented for ".concat(
className));
}
parameters.put("projectIDs", projectIDs);
}
Expand Down Expand Up @@ -266,9 +291,33 @@ public void restrictToRoles(List<Role> roles) {
restrictions.add(restriction.toString());
}

public void restrictWithUserFilterString(String s) {
// full user filters not yet implemented -- TODO
forIdOrInTitle(s);
public void restrictWithUserFilterString(String filterString) {
int userFilterCount = 0;
for (var groupFilter : UserSpecifiedFilterParser.parse(filterString).entrySet()) {
List<String> groupFilters = new ArrayList<>();
for (UserSpecifiedFilter searchFilter : groupFilter.getValue()) {
userFilterCount++;
String parameterName = "userFilter".concat(Integer.toString(userFilterCount));
if (searchFilter instanceof DatabaseQueryPart) {
DatabaseQueryPart databaseSearchQueryPart = (DatabaseQueryPart) searchFilter;
String query = databaseSearchQueryPart.getDatabaseQuery(className, varName, parameterName);
if (query.contains(" AS ")) {
extensions.add(query);
} else {
groupFilters.add(query);
}
databaseSearchQueryPart.addParameters(parameterName, parameters);
} else {
IndexQueryPart indexQueryPart = (IndexQueryPart) searchFilter;
indexQueryPart.putQueryParameters(varName, parameterName, indexQueries, restrictions);
}
}
if (groupFilters.size() == 1) {
restrictions.add(groupFilters.get(0));
} else if (groupFilters.size() > 1) {
restrictions.add("( " + String.join(" OR ", groupFilters) + " )");
}
}
}

public void defineSorting(String sortField, SortOrder sortOrder) {
Expand Down Expand Up @@ -319,7 +368,7 @@ public String formQueryForDistinct(String field, boolean sorted) {
}

private void innerFormQuery(StringBuilder query) {
query.append("FROM ").append(objectClass).append(" AS ").append(varName);
query.append("FROM ").append(className).append(" AS ").append(varName);
for (String extension : extensions) {
query.append(" INNER JOIN ").append(extension);
}
Expand All @@ -333,6 +382,9 @@ private void innerFormQuery(StringBuilder query) {
}

public Map<String, Object> getQueryParameters() {
if (!indexQueries.isEmpty()) {
throw new IllegalStateException("index searches not yet performed");
}
return parameters;
}

Expand All @@ -342,8 +394,8 @@ private String varName(String input) {
boolean upperCase = false;
while (inputIterator.current() != CharacterIterator.DONE) {
char currentChar = inputIterator.current();
if (currentChar < '0' || (currentChar > '9' && currentChar < 'A')
|| (currentChar > 'Z' && currentChar < 'a') || currentChar > 'z') {
if (currentChar < '0' || (currentChar > '9' && currentChar < 'A') || (currentChar > 'Z'
&& currentChar < 'a') || currentChar > 'z') {
upperCase = true;
} else {
result.append(upperCase ? Character.toUpperCase(currentChar) : Character.toLowerCase(currentChar));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* (c) Kitodo. Key to digital objects e. V. <[email protected]>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/

package org.kitodo.production.services.data;

import java.util.Map;
import java.util.Objects;

/**
* A part of the filter that is resolved on the database. Searching is only
* possible by ID or by ID range.
*/
class DatabaseQueryPart implements UserSpecifiedFilter {

private static final String SECOND_PARAMETER_EXTENSION = "upTo";

private FilterField filterField;
private Integer firstId;
private Integer upToId;

/**
* Constructor, creates a new DatabaseQueryPart.
*
* @param filterField
* field to search on
* @param firstId
* first or only ID
* @param upToId
* {@code null} or last ID
*/
DatabaseQueryPart(FilterField filterField, String firstId, String upToId) {
this.filterField = filterField;
this.firstId = Integer.valueOf(firstId);
this.upToId = Objects.nonNull(upToId) ? Integer.valueOf(upToId) : null;
}

@Override
public FilterField getFilterField() {
return filterField;
}

/**
* Returns the part HQL query. If the query contains the keyword "IN", it
* must be queried using JOIN, otherwise using WHERE.
*
* @param className
* "Task" for a query on the tasks table, else for a query on the
* process table.
* @param varName
* variable name to use in the query
* @param parameterName
* parameter name to use in the query
* @return the part HQL query
*/
String getDatabaseQuery(String className, String varName, String parameterName) {
String query = Objects.equals(className, "Task") ? filterField.getTaskQuery() : filterField.getProcessQuery();
return varName + '.' + query + (upToId == null ? " = :" + parameterName
: " BETWEEN :" + parameterName + " AND :" + parameterName + SECOND_PARAMETER_EXTENSION);
}

/**
* Puts the parameters necessary for the query.
*
* @param parameterName
* parameter name used in the query
* @param parameters
* map to put the parameters into
*/
void addParameters(String parameterName, Map<String, Object> parameters) {
if (Objects.nonNull(filterField.getQueryObject())) {
parameters.put("queryObject", filterField.getQueryObject());
}
parameters.put(parameterName, firstId);
if (Objects.nonNull(upToId)) {
parameters.put(parameterName.concat(SECOND_PARAMETER_EXTENSION), upToId);
}
}

@Override
public String toString() {
return filterField + (upToId == null ? " = " + firstId : " BETWEEN " + firstId + " AND " + upToId);
}
}
Loading

0 comments on commit 3cf372b

Please sign in to comment.