diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/SearchRestClient.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/SearchRestClient.java index 27d908ab697..4893747dc03 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/SearchRestClient.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/SearchRestClient.java @@ -14,6 +14,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -25,6 +27,9 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Request; @@ -36,6 +41,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.sort.SortBuilder; import org.kitodo.data.elasticsearch.KitodoRestClient; import org.kitodo.data.elasticsearch.exceptions.CustomResponseException; @@ -193,6 +199,42 @@ SearchHits getDocument(String type, QueryBuilder query, SortBuilder sort, Intege } } + /** + * Retrieves a map of document IDs to their corresponding base type for the given list of IDs. + * + * @param type the type of documents being requested, used to determine the index. + * @param ids the list of document IDs to search for. + * @return a map where each key is a document ID and the value is the corresponding base type of the document. + */ + public Map fetchIdToBaseTypeMap(String type, List ids) throws CustomResponseException, DataException { + Map idToBaseTypeMap = new HashMap<>(); + + try { + // Create a MultiGetRequest to fetch multiple documents with only baseType field + MultiGetRequest multiGetRequest = new MultiGetRequest(); + for (Integer id : ids) { + MultiGetRequest.Item item = new MultiGetRequest.Item(this.indexBase + "_" + type, String.valueOf(id)); + // Only fetch baseType field + item.fetchSourceContext(new FetchSourceContext(true, new String[]{"baseType"}, null)); + multiGetRequest.add(item); + } + MultiGetResponse multiGetResponse = highLevelClient.mget(multiGetRequest, RequestOptions.DEFAULT); + for (MultiGetItemResponse itemResponse : multiGetResponse.getResponses()) { + if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) { + String baseType = (String) itemResponse.getResponse().getSourceAsMap().get("baseType"); + Integer id = Integer.parseInt(itemResponse.getResponse().getId()); + idToBaseTypeMap.put(id, baseType); + } + } + } catch (ResponseException e) { + handleResponseException(e); + } catch (IOException | NumberFormatException e) { + throw new DataException(e); + } + + return idToBaseTypeMap; + } + private String performRequest(String type, HttpEntity entity, String httpMethod, String urlRequest) throws CustomResponseException, DataException { String output = ""; diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/Searcher.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/Searcher.java index 002f93b5efc..c3e99897a31 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/Searcher.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/elasticsearch/search/Searcher.java @@ -104,6 +104,19 @@ public Aggregations aggregateDocuments(QueryBuilder query, AggregationBuilder ag return restClient.aggregateDocuments(this.type, query, aggregation); } + /** + * Retrieves a mapping of document IDs to their corresponding base type for the given list of IDs. + * Delegates to the `SearchRestClient`. + * + * @param ids + * the list of document IDs to search for. + * @return a map where each key is a document ID and the value is the corresponding base type of the document. + */ + public Map fetchIdToBaseTypeMap(List ids) throws CustomResponseException, DataException { + SearchRestClient restClient = initiateRestClient(); + return restClient.fetchIdToBaseTypeMap(this.type,ids); + } + /** * Find document by id. * diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java index 74887942d25..0c55b5d313f 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/dataeditor/StructurePanel.java @@ -558,10 +558,29 @@ private DefaultTreeNode buildStructureTree() { invisibleRootNode.setExpanded(true); invisibleRootNode.setType(STRUCTURE_NODE_TYPE); addParentLinksRecursive(dataEditor.getProcess(), invisibleRootNode); - buildStructureTreeRecursively(structure, invisibleRootNode); + List processIds = getAllLinkedProcessIds(structure); + Map processTypeMap = processIds.isEmpty() ? Collections.emptyMap() : fetchProcessTypes(processIds); + Map viewCache = new HashMap<>(); + buildStructureTreeRecursively(structure, invisibleRootNode, processTypeMap, viewCache); return invisibleRootNode; } + private List getAllLinkedProcessIds(LogicalDivision structure) { + return structure.getAllChildren().stream() + .filter(division -> division.getLink() != null) + .map(division -> ServiceManager.getProcessService().processIdFromUri(division.getLink().getUri())) + .collect(Collectors.toList()); + } + + private Map fetchProcessTypes(List processIds) { + try { + return ServiceManager.getProcessService().getIdBaseTypeMap(processIds); + } catch (DataException e) { + Helper.setErrorMessage("metadataReadError", e.getMessage(), logger, e); + return Collections.emptyMap(); + } + } + /** * Constructs a page range string by combining the labels of the first and last view * of the provided logical division. @@ -582,14 +601,19 @@ private String buildPageRangeFromLogicalDivision(LogicalDivision structure) { /** * Build a StructureTreeNode for a logical division, which is then visualized in the logical structure tree. * - * @param structure the logical division - * @return the StructureTreeNode instance + * @param structure the logical division for which the tree node is being constructed + * @param idTypeMap the mapping of process id to basetype + * @param viewCache a cache for storing and retrieving already processed StructuralElementViews + * @return the constructed {@link StructureTreeNode} instance representing the given logical division */ - private StructureTreeNode buildStructureTreeNode(LogicalDivision structure) { + private StructureTreeNode buildStructureTreeNode(LogicalDivision structure, Map idTypeMap, + Map viewCache) { StructureTreeNode node; if (Objects.isNull(structure.getLink())) { - StructuralElementViewInterface divisionView = dataEditor.getRulesetManagement().getStructuralElementView( - structure.getType(), dataEditor.getAcquisitionStage(), dataEditor.getPriorityList()); + StructuralElementViewInterface divisionView = viewCache.computeIfAbsent(structure.getType(), key -> + dataEditor.getRulesetManagement().getStructuralElementView( + key, dataEditor.getAcquisitionStage(), dataEditor.getPriorityList()) + ); String label = divisionView.getLabel(); String pageRange = buildPageRangeFromLogicalDivision(structure); boolean undefined = divisionView.isUndefined() && Objects.nonNull(structure.getType()); @@ -597,18 +621,15 @@ private StructureTreeNode buildStructureTreeNode(LogicalDivision structure) { } else { node = new StructureTreeNode(structure.getLink().getUri().toString(), null, true, true, structure); for (Process child : dataEditor.getCurrentChildren()) { - try { - String type = ServiceManager.getProcessService().getBaseType(child.getId()); - if (child.getId() == ServiceManager.getProcessService() - .processIdFromUri(structure.getLink().getUri())) { - StructuralElementViewInterface view = dataEditor.getRulesetManagement().getStructuralElementView( - type, dataEditor.getAcquisitionStage(), dataEditor.getPriorityList()); - node = new StructureTreeNode("[" + child.getId() + "] " + view.getLabel() + " - " - + child.getTitle(), null, view.isUndefined(), true, structure); - } - } catch (DataException e) { - Helper.setErrorMessage("metadataReadError", e.getMessage(), logger, e); - node = new StructureTreeNode(child.getTitle(), null, true, true, child); + if (child.getId() == ServiceManager.getProcessService().processIdFromUri(structure.getLink().getUri())) { + String type = idTypeMap.get(child.getId()); + // Retrieve the view from cache if it exists, otherwise compute and cache it + StructuralElementViewInterface view = viewCache.computeIfAbsent(type, key -> + dataEditor.getRulesetManagement().getStructuralElementView( + key, dataEditor.getAcquisitionStage(), dataEditor.getPriorityList()) + ); + node = new StructureTreeNode("[" + child.getId() + "] " + view.getLabel() + " - " + + child.getTitle(), null, view.isUndefined(), true, structure); } } } @@ -620,10 +641,13 @@ private StructureTreeNode buildStructureTreeNode(LogicalDivision structure) { * * @param structure the current logical structure * @param result the current corresponding primefaces tree node + * @param processTypeMap the mapping of process id to basetype + * @param viewCache a cache for storing and retrieving already processed StructuralElementViews * @return a collection of views that contains all views of the full sub-tree */ - private Collection buildStructureTreeRecursively(LogicalDivision structure, TreeNode result) { - StructureTreeNode node = buildStructureTreeNode(structure); + private Collection buildStructureTreeRecursively(LogicalDivision structure, TreeNode result, Map processTypeMap, Map viewCache) { + StructureTreeNode node = buildStructureTreeNode(structure, processTypeMap, viewCache); /* * Creating the tree node by handing over the parent node automatically * appends it to the parent as a child. That’s the logic of the JSF @@ -637,7 +661,7 @@ private Collection buildStructureTreeRecursively(LogicalDivision structure Set viewsShowingOnAChild = new HashSet<>(); if (!this.logicalStructureTreeContainsMedia()) { for (LogicalDivision child : structure.getChildren()) { - viewsShowingOnAChild.addAll(buildStructureTreeRecursively(child, parent)); + viewsShowingOnAChild.addAll(buildStructureTreeRecursively(child, parent, processTypeMap, viewCache)); } } else { // iterate through children and views ordered by the ORDER attribute @@ -645,7 +669,8 @@ private Collection buildStructureTreeRecursively(LogicalDivision structure for (Pair pair : merged) { if (Objects.nonNull(pair.getRight())) { // add child and their views - viewsShowingOnAChild.addAll(buildStructureTreeRecursively(pair.getRight(), parent)); + viewsShowingOnAChild.addAll(buildStructureTreeRecursively(pair.getRight(), parent, + processTypeMap, viewCache)); } else if (!viewsShowingOnAChild.contains(pair.getLeft())) { // add views of current logical division as leaf nodes DefaultTreeNode viewNode = addTreeNode(buildViewLabel(pair.getLeft()), false, false, pair.getLeft(), parent); 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 b813448d4cb..8207cc87632 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 @@ -1730,6 +1730,17 @@ public String getBaseType(int processId) throws DataException { return ""; } + /** + * Retrieves a mapping of process IDs to their corresponding base types. + * + * @param processIds + * list of document IDs to retrieve and process. + * @return a map where the keys are document IDs and the values are their associated base types + */ + public Map getIdBaseTypeMap(List processIds) throws DataException { + return fetchIdToBaseTypeMap(processIds); + } + /** * Filter for correction / solution messages. * diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchService.java index 0758af802dc..23a9a09bf00 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/base/SearchService.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -458,6 +459,21 @@ public S findById(Integer id, boolean related) throws DataException { } } + /** + * Retrieves a mapping of document IDs to their corresponding base types for the given list of IDs. + * + * @param ids + * list of document IDs to retrieve and process. + * @return a map where the keys are document IDs and the values are their associated base types. + */ + public Map fetchIdToBaseTypeMap(List ids) throws DataException { + try { + return searcher.fetchIdToBaseTypeMap(ids); + } catch (CustomResponseException e) { + throw new DataException(e); + } + } + /** * Find list of DTO objects by query. * diff --git a/Kitodo/src/test/java/org/kitodo/production/forms/dataeditor/StructurePanelTest.java b/Kitodo/src/test/java/org/kitodo/production/forms/dataeditor/StructurePanelTest.java index 5f896815f2a..be7879557bc 100644 --- a/Kitodo/src/test/java/org/kitodo/production/forms/dataeditor/StructurePanelTest.java +++ b/Kitodo/src/test/java/org/kitodo/production/forms/dataeditor/StructurePanelTest.java @@ -17,14 +17,18 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.kitodo.DummyRulesetManagement; +import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface; import org.kitodo.api.dataformat.LogicalDivision; import org.kitodo.api.dataformat.mets.LinkedMetsResource; import org.kitodo.data.database.beans.Process; import org.kitodo.data.database.beans.Template; import org.kitodo.data.database.beans.Workflow; +import org.kitodo.production.services.ServiceManager; import org.primefaces.model.DefaultTreeNode; import org.primefaces.model.TreeNode; @@ -47,12 +51,15 @@ public void testBuildStructureTreeRecursively() throws Exception { LinkedMetsResource link = new LinkedMetsResource(); link.setUri(URI.create("database://?process.id=42")); structure.setLink(link); + Map processTypeMap = new HashMap<>(); + Map viewCache = new HashMap<>(); + processTypeMap.put(ServiceManager.getProcessService().processIdFromUri(link.getUri()), "Monograph"); TreeNode result = new DefaultTreeNode(); Method buildStructureTreeRecursively = StructurePanel.class.getDeclaredMethod("buildStructureTreeRecursively", - LogicalDivision.class, TreeNode.class); + LogicalDivision.class, TreeNode.class, Map.class, Map.class); buildStructureTreeRecursively.setAccessible(true); - buildStructureTreeRecursively.invoke(underTest, structure, result); + buildStructureTreeRecursively.invoke(underTest, structure, result, processTypeMap, viewCache); assertTrue(((StructureTreeNode) result.getChildren().get(0).getData()).isLinked()); }