package com.constellio.model.services.taxonomies; import static com.constellio.data.utils.LangUtils.isTrueOrNull; import static com.constellio.model.entities.schemas.Schemas.LINKABLE; import static com.constellio.model.entities.schemas.Schemas.PATH_PARTS; import static com.constellio.model.entities.schemas.Schemas.VISIBLE_IN_TREES; import static com.constellio.model.services.schemas.SchemaUtils.getSchemaTypeCode; import static com.constellio.model.services.search.StatusFilter.ACTIVES; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.allConditions; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasIn; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasInCollectionOf; import static com.constellio.model.services.search.query.logical.valueCondition.ConditionTemplateFactory.schemaTypeIsIn; import static com.constellio.model.services.search.query.logical.valueCondition.ConditionTemplateFactory.schemaTypeIsNotIn; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.childConceptsQuery; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.childrenCondition; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.directChildOf; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.fromTypeIn; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.notDirectChildOf; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.recordInHierarchyOf; import static com.constellio.model.services.taxonomies.ConceptNodesTaxonomySearchServices.visibleInTrees; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import com.constellio.model.entities.CorePermissions; import com.constellio.model.entities.Taxonomy; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.wrappers.User; import com.constellio.model.entities.schemas.MetadataSchemaType; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.entities.security.Role; import com.constellio.model.entities.security.global.AuthorizationDetails; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.records.RecordServicesRuntimeException; import com.constellio.model.services.records.RecordUtils; import com.constellio.model.services.records.utils.RecordCodeComparator; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.search.SPEQueryResponse; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.ReturnedMetadatasFilter; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; import com.constellio.model.services.taxonomies.TaxonomiesSearchServicesRuntimeException.TaxonomiesSearchServicesRuntimeException_CannotFilterNonPrincipalConceptWithWriteOrDeleteAccess; public class TaxonomiesSearchServices { private static final String CHILDREN_QUERY = "children"; private static final boolean NOT_LINKABLE = false; SearchServices searchServices; TaxonomiesManager taxonomiesManager; MetadataSchemasManager metadataSchemasManager; RecordServices recordServices; SchemaUtils schemaUtils = new SchemaUtils(); ConceptNodesTaxonomySearchServices conceptNodesTaxonomySearchServices; public TaxonomiesSearchServices(ModelLayerFactory modelLayerFactory) { this.searchServices = modelLayerFactory.newSearchServices(); this.taxonomiesManager = modelLayerFactory.getTaxonomiesManager(); this.metadataSchemasManager = modelLayerFactory.getMetadataSchemasManager(); this.recordServices = modelLayerFactory.newRecordServices(); this.conceptNodesTaxonomySearchServices = new ConceptNodesTaxonomySearchServices(searchServices, taxonomiesManager, metadataSchemasManager); } public List<TaxonomySearchRecord> getVisibleRootConcept(User user, String collection, String taxonomyCode, TaxonomiesSearchOptions options) { return getVisibleRootConceptResponse(user, collection, taxonomyCode, options, null).getRecords(); } public List<TaxonomySearchRecord> getVisibleChildConcept(User user, String taxonomyCode, Record record, TaxonomiesSearchOptions options) { return getVisibleChildConceptResponse(user, taxonomyCode, record, options).getRecords(); } public boolean findNonTaxonomyRecordsInStructure(Record record, TaxonomiesSearchOptions options) { String schemaType = new SchemaUtils().getSchemaTypeCode(record.getSchemaCode()); GetChildrenContext context = new GetChildrenContext(User.GOD, record, options, null); LogicalSearchCondition condition = findVisibleNonTaxonomyRecordsInStructure(context, false); return searchServices.hasResults(new LogicalSearchQuery(condition).filteredByStatus(options.getIncludeStatus())); } public List<TaxonomySearchRecord> getLinkableRootConcept(User user, String collection, String taxonomyCode, String selectedType, TaxonomiesSearchOptions options) { return getLinkableRootConceptResponse(user, collection, taxonomyCode, selectedType, options).getRecords(); } public LinkableTaxonomySearchResponse getLinkableRootConceptResponse(User user, String collection, String usingTaxonomyCode, String selectedType, TaxonomiesSearchOptions options) { return getLinkableConceptResponse(user, collection, usingTaxonomyCode, selectedType, null, options); } public List<TaxonomySearchRecord> getLinkableChildConcept(User user, Record record, String usingTaxonomy, String selectedType, TaxonomiesSearchOptions options) { return getLinkableChildConceptResponse(user, record, usingTaxonomy, selectedType, options).getRecords(); } public LinkableTaxonomySearchResponse getLinkableChildConceptResponse(User user, Record inRecord, String usingTaxonomy, String selectedType, TaxonomiesSearchOptions options) { return getLinkableConceptResponse(user, inRecord.getCollection(), usingTaxonomy, selectedType, inRecord, options); } public List<TaxonomySearchRecord> getVisibleChildConcept(User user, Record record, TaxonomiesSearchOptions options) { Taxonomy taxonomy = taxonomiesManager.getTaxonomyOf(record); String taxonomyCode = null; if (taxonomy == null) { taxonomyCode = taxonomiesManager.getPrincipalTaxonomy(record.getCollection()).getCode(); } else { taxonomyCode = taxonomy.getCode(); } GetChildrenContext ctx = new GetChildrenContext(user, record, options, null); return getVisibleChildrenRecords(ctx).getRecords(); } private class GetChildrenContext { User user; Record record; TaxonomiesSearchOptions options; MetadataSchemaType forSelectionOfSchemaType; Taxonomy taxonomy; public GetChildrenContext(User user, Record record, TaxonomiesSearchOptions options, MetadataSchemaType forSelectionOfSchemaType) { this.user = user; this.record = record; this.options = options; this.forSelectionOfSchemaType = forSelectionOfSchemaType; this.taxonomy = getTaxonomyForNavigation(record); } public GetChildrenContext(User user, Record record, TaxonomiesSearchOptions options, MetadataSchemaType forSelectionOfSchemaType, Taxonomy taxonomy) { this.user = user; this.record = record; this.options = options; this.forSelectionOfSchemaType = forSelectionOfSchemaType; this.taxonomy = taxonomy; } public boolean hasRequiredAccessOn(Record record) { return user.hasRequiredAccess(options.getRequiredAccess()).on(record); } public boolean isConceptOfNavigatedTaxonomy(Record childRecordLeadingToSecuredRecord) { return taxonomy.getSchemaTypes().contains(childRecordLeadingToSecuredRecord.getTypeCode()); } public String getCollection() { return record == null ? taxonomy.getCollection() : record.getCollection(); } public LogicalSearchQuery newQueryWithUserFilter(LogicalSearchCondition condition) { LogicalSearchQuery logicalSearchQuery; if (user != null) { logicalSearchQuery = new LogicalSearchQuery(condition).filteredWithUser(user, options.getRequiredAccess()); } else { logicalSearchQuery = new LogicalSearchQuery(condition); } return logicalSearchQuery; } public boolean isHiddenInvisibleInTree() { return forSelectionOfSchemaType == null ? true : !options.isShowInvisibleRecordsInLinkingMode(); } public boolean isSelectingAConcept() { return forSelectionOfSchemaType != null && taxonomy.getSchemaTypes().contains(forSelectionOfSchemaType.getCode()); } public LogicalSearchCondition applyLinkableConceptsCondition(LogicalSearchCondition condition) { if (options.getFilter() != null && options.getFilter().getLinkableConceptsCondition() != null) { return allConditions(condition, options.getFilter().getLinkableConceptsCondition()); } else { return condition; } } } private ReturnedMetadatasFilter returnedMetadatasForRecordsIn(GetChildrenContext context) { return conceptNodesTaxonomySearchServices.returnedMetadatasForRecordsIn(context.getCollection(), context.options); } private LinkableTaxonomySearchResponse getVisibleChildrenRecords(GetChildrenContext ctx) { GetConceptRecordsWithVisibleRecordsResponse conceptsResponse; if (ctx.options.getFastContinueInfos() == null || !ctx.options.getFastContinueInfos().isFinishedConceptsIteration()) { conceptsResponse = getConceptRecordsWithVisibleRecords(ctx); } else { //Since we know that all concepts has been checked and the quantity of returned concepts, we place nulls // in the list instead of records. Since these values are outside the returned range, it cause no problem conceptsResponse = createConceptsResponseWithNullValues(ctx); } List<Record> records = new ArrayList<>(); List<Record> childrenWithoutAccessToInclude = new ArrayList<>(); int realRecordsStart = 0; SPEQueryResponse nonTaxonomyRecordsResponse = null; if (ctx.isSelectingAConcept()) { nonTaxonomyRecordsResponse = new SPEQueryResponse(new ArrayList<Record>(), new HashMap<Record, Map<Record, Double>>()); } else { childrenWithoutAccessToInclude.addAll(getChildrenRecordsWithoutRequiredAccessLeadingToRecordWithAccess(ctx)); int realRecordsRows; if (ctx.options.getFastContinueInfos() == null) { realRecordsStart = 0; realRecordsRows = ctx.options.getStartRow() + ctx.options.getRows() - conceptsResponse.records.size() + childrenWithoutAccessToInclude.size(); } else { if (ctx.options.getFastContinueInfos().isFinishedConceptsIteration()) { realRecordsStart = ctx.options.getFastContinueInfos().lastReturnRecordIndex; } else { realRecordsStart = 0; } realRecordsRows = ctx.options.getRows(); if (ctx.options.getFastContinueInfos().isFinishedConceptsIteration()) { int qtyRecords = ctx.options.getFastContinueInfos().getLastReturnRecordIndex() + ctx.options.getFastContinueInfos().getShownRecordsWithVisibleChildren().size(); for (int i = 0; i < qtyRecords; i++) { records.add(null); } } } List<Record> nonNullRecords = new ArrayList<>(); realRecordsRows = Math.max(0, realRecordsRows); nonTaxonomyRecordsResponse = getNonTaxonomyRecords(ctx, childrenWithoutAccessToInclude, realRecordsStart, realRecordsRows); nonNullRecords.addAll(nonTaxonomyRecordsResponse.getRecords()); for (Record childWithoutAccessToInclude : childrenWithoutAccessToInclude) { if (ctx.options.getFastContinueInfos() == null || !ctx.options.getFastContinueInfos() .getShownRecordsWithVisibleChildren().contains(childWithoutAccessToInclude.getId())) { nonNullRecords.add(childWithoutAccessToInclude); } } Collections.sort(nonNullRecords, new RecordCodeComparator(ctx.taxonomy.getSchemaTypes())); records.addAll(nonNullRecords); } return regroupChildren(ctx, conceptsResponse, childrenWithoutAccessToInclude, nonTaxonomyRecordsResponse, records, realRecordsStart); } private GetConceptRecordsWithVisibleRecordsResponse createConceptsResponseWithNullValues(GetChildrenContext ctx) { GetConceptRecordsWithVisibleRecordsResponse conceptsResponse; conceptsResponse = new GetConceptRecordsWithVisibleRecordsResponse(); conceptsResponse.finishedIteratingOverRecords = true; conceptsResponse.records = new ArrayList<>(); int qtyConcepts = ctx.options.getStartRow() - ctx.options.getFastContinueInfos().getLastReturnRecordIndex() - ctx.options.getFastContinueInfos().getShownRecordsWithVisibleChildren().size(); for (int i = 0; i < qtyConcepts; i++) { conceptsResponse.records.add(null); } return conceptsResponse; } private LinkableTaxonomySearchResponse regroupChildren( GetChildrenContext ctx, GetConceptRecordsWithVisibleRecordsResponse conceptsResponse, List<Record> childrenWithoutAccessToInclude, SPEQueryResponse nonTaxonomyRecordsResponse, List<Record> records, int recordsStartIndex) { Set<String> childrenWithoutAccessToIncludeRecordIds = new RecordUtils().toIdSet(childrenWithoutAccessToInclude); List<TaxonomySearchRecord> concepts = conceptsResponse.records; Set<String> typesParentOfOtherTypes = metadataSchemasManager.getSchemaTypes(ctx.getCollection()) .getTypeParentOfOtherTypes(); List<TaxonomySearchRecord> returnedRecords = new ArrayList<>(); SPEQueryResponse facetResponse = queryFindingWhichRecordsHasChildren(ctx, concepts.size(), records); List<String> placedChildrenWithoutAccessToIncludeRecordIds = new ArrayList<>(); if (ctx.options.getFastContinueInfos() != null) { placedChildrenWithoutAccessToIncludeRecordIds .addAll(ctx.options.getFastContinueInfos().getShownRecordsWithVisibleChildren()); } int lastRow = recordsStartIndex; for (int i = ctx.options.getStartRow(); i < ctx.options.getEndRow() && i - concepts.size() < records.size(); i++) { if (i < concepts.size()) { returnedRecords.add(concepts.get(i)); } else { int nonTaxonomyIndex = i - concepts.size(); Record returnedRecord = records.get(nonTaxonomyIndex); boolean hasChildren; if (facetResponse == null) { hasChildren = typesParentOfOtherTypes.contains(returnedRecord.getTypeCode()); } else { hasChildren = facetResponse.hasQueryFacetResults(facetQueryFor(ctx.taxonomy, returnedRecord)); } boolean linkable = ctx.hasRequiredAccessOn(returnedRecord) && ctx.forSelectionOfSchemaType != null && ctx.forSelectionOfSchemaType.getCode().equals(returnedRecord.getTypeCode()); Record record = records.get(nonTaxonomyIndex); returnedRecords.add(new TaxonomySearchRecord(record, linkable, hasChildren)); if (childrenWithoutAccessToIncludeRecordIds.contains(record.getId())) { placedChildrenWithoutAccessToIncludeRecordIds.add(record.getId()); } else { recordsStartIndex++; } } } FastContinueInfos infos; if (conceptsResponse.finishedIteratingOverRecords) { infos = new FastContinueInfos(true, recordsStartIndex, placedChildrenWithoutAccessToIncludeRecordIds); } else { infos = new FastContinueInfos(false, conceptsResponse.continueAtPosition, new ArrayList<String>()); } long numfound; numfound = nonTaxonomyRecordsResponse.getNumFound() + childrenWithoutAccessToInclude.size() + concepts.size(); return new LinkableTaxonomySearchResponse(numfound, infos, returnedRecords); } private SPEQueryResponse getNonTaxonomyRecords(GetChildrenContext ctx, List<Record> childrenWithoutAccessToInclude, int realStart, int realRows) { LogicalSearchCondition condition; if (ctx.forSelectionOfSchemaType == null || ctx.forSelectionOfSchemaType.getAllReferencesToTaxonomySchemas(asList(ctx.taxonomy)).isEmpty()) { condition = fromAllSchemasInCollectionOf(ctx.record) .where(directChildOf(ctx.record)).andWhere(visibleInTrees) .andWhere(schemaTypeIsNotIn(ctx.taxonomy.getSchemaTypes())); } else { condition = from(ctx.forSelectionOfSchemaType).where(directChildOf(ctx.record)); if (!ctx.options.isShowInvisibleRecordsInLinkingMode()) { condition = condition.andWhere(VISIBLE_IN_TREES).isTrueOrNull(); } } LogicalSearchQuery query = newQuery(condition, ctx.options) .setStartRow(realStart).setNumberOfRows(realRows) .filteredWithUser(ctx.user, ctx.options.getRequiredAccess()); return searchServices.query(query); } private Taxonomy getTaxonomyForNavigation(Record record) { Taxonomy taxonomy = taxonomiesManager.getTaxonomyOf(record); if (taxonomy == null) { taxonomy = taxonomiesManager.getPrincipalTaxonomy(record.getCollection()); } return taxonomy; } private SPEQueryResponse queryFindingWhichRecordsHasChildren(GetChildrenContext context, int visibleConceptsSize, List<Record> records) { SPEQueryResponse facetResponse = null; if (context.options.isHasChildrenFlagCalculated()) { LogicalSearchCondition queryCondition; if (context.forSelectionOfSchemaType == null) { queryCondition = fromAllSchemasIn(context.getCollection()) .where(recordInHierarchyOf(context.record)) .andWhere(notDirectChildOf(context.record)) .andWhere(visibleInTrees) .andWhere(schemaTypeIsNotIn(context.taxonomy.getSchemaTypes())); } else { queryCondition = from(context.forSelectionOfSchemaType) .where(recordInHierarchyOf(context.record)) .andWhere(notDirectChildOf(context.record)) .andWhere(Schemas.LINKABLE).isTrueOrNull(); if (!context.options.isShowInvisibleRecordsInLinkingMode()) { queryCondition = queryCondition.andWhere(visibleInTrees); } } LogicalSearchQuery facetQuery = context.newQueryWithUserFilter(queryCondition) .filteredByStatus(ACTIVES).setStartRow(0).setNumberOfRows(0); int facetCounts = 0; for (int i = visibleConceptsSize; i - visibleConceptsSize < records.size() && facetCounts < context.options.getRows(); i++) { int nonTaxonomyIndex = i - visibleConceptsSize; Record record = records.get(nonTaxonomyIndex); if (record != null) { facetQuery.addQueryFacet("hasChildren", facetQueryFor(context.taxonomy, record)); facetCounts++; } } if (facetCounts > 0) { facetResponse = searchServices.query(facetQuery); } } return facetResponse; } private static class GetConceptRecordsWithVisibleRecordsResponse { List<TaxonomySearchRecord> records; boolean finishedIteratingOverRecords; int continueAtPosition; } private GetConceptRecordsWithVisibleRecordsResponse getConceptRecordsWithVisibleRecords(GetChildrenContext context) { GetConceptRecordsWithVisibleRecordsResponse methodResponse = new GetConceptRecordsWithVisibleRecordsResponse(); MetadataSchemaTypes types = metadataSchemasManager.getSchemaTypes(context.getCollection()); LogicalSearchQuery mainQuery = childConceptsQuery(context.record, context.taxonomy, context.options, types); Iterator<List<Record>> iterator; int lastIteratedRecordIndex = 0; FastContinueInfos continueInfos = context.options.getFastContinueInfos(); methodResponse.records = new ArrayList<>(); if (continueInfos != null) { lastIteratedRecordIndex = continueInfos.lastReturnRecordIndex; for (int i = 0; i < context.options.getStartRow(); i++) { methodResponse.records.add(null); } int batchSize = context.options.getRows() * 2; iterator = searchServices.recordsIteratorKeepingOrder(mainQuery, batchSize, continueInfos.getLastReturnRecordIndex()) .inBatches(); } else { iterator = searchServices.recordsIteratorKeepingOrder(mainQuery, 50).inBatches(); } int consumed = 0; while (methodResponse.records.size() < context.options.getEndRow() && iterator.hasNext()) { List<Record> batch = iterator.next(); consumed += batch.size(); LogicalSearchQuery facetQuery; if (context.isSelectingAConcept()) { LogicalSearchCondition condition = fromAllSchemasIn(context.taxonomy.getCollection()) .where(PATH_PARTS).isEqualTo(context.record.getId()) .andWhere(schemaTypeIsIn(context.taxonomy.getSchemaTypes())); boolean selectingAConceptNoMatterTheLinkableStatus = context.isSelectingAConcept() && context.options.isAlwaysReturnTaxonomyConceptsWithReadAccess(); if (!selectingAConceptNoMatterTheLinkableStatus) { condition = condition.andWhere(Schemas.LINKABLE).isTrueOrNull(); } condition = context.applyLinkableConceptsCondition(condition); facetQuery = newQueryForFacets(condition, null, context.options); for (Record record : batch) { facetQuery.addQueryFacet(CHILDREN_QUERY, "id:" + record.getId()); } } else { LogicalSearchCondition condition = findVisibleNonTaxonomyRecordsInStructure( context, context.isHiddenInvisibleInTree()); facetQuery = newQueryForFacets(condition, context); } facetQuery.addQueryFacets(CHILDREN_QUERY, facetQueriesFor(context.taxonomy, batch)); SPEQueryResponse response = searchServices.query(facetQuery); for (Record child : batch) { boolean hasChildren = response.getQueryFacetCount(facetQueryFor(context.taxonomy, child)) > 0; boolean linkable; if (context.isSelectingAConcept()) { linkable = response.hasQueryFacetResults("id:" + child.getId()); } else { linkable = NOT_LINKABLE; } if (hasChildren || linkable) { if (methodResponse.records.size() < context.options.getEndRow()) { lastIteratedRecordIndex++; } methodResponse.records.add(new TaxonomySearchRecord(child, linkable, hasChildren)); } else if (context.options.isAlwaysReturnTaxonomyConceptsWithReadAccess()) { if (!taxonomiesManager.isTypeInPrincipalTaxonomy(context.getCollection(), child.getTypeCode()) || context.hasRequiredAccessOn(child)) { if (methodResponse.records.size() < context.options.getEndRow()) { lastIteratedRecordIndex++; } methodResponse.records.add(new TaxonomySearchRecord(child, linkable, false)); } } } } methodResponse.finishedIteratingOverRecords = !iterator.hasNext() && methodResponse.records.size() <= context.options.getEndRow(); methodResponse.continueAtPosition = lastIteratedRecordIndex; return methodResponse; } /** * @param context The call context * @return all children for which the user has no access, but are ancestor of a record for which he has access */ private List<Record> getChildrenRecordsWithoutRequiredAccessLeadingToRecordWithAccess(GetChildrenContext context) { List<Record> records = new ArrayList<>(); Set<String> returnedRecordIds = new HashSet<>(); for (String authorizationDetailsId : context.user.getAllUserAuthorizations()) { AuthorizationDetails authorizationDetails = context.user.getAuthorizationDetail(authorizationDetailsId); if (authorizationDetails.getRoles().contains(context.options.getRequiredAccess())) { Record securedRecord = recordServices.getDocumentById(authorizationDetails.getTarget()); String schemaType = getSchemaTypeCode(securedRecord.getSchemaCode()); boolean recordExpectedToBeVisibleInTree; if (context.forSelectionOfSchemaType == null) { recordExpectedToBeVisibleInTree = !context.taxonomy.getSchemaTypes().contains(schemaType) && !Boolean.TRUE.equals(securedRecord.get(Schemas.LOGICALLY_DELETED_STATUS)) && !Boolean.FALSE.equals(securedRecord.get(VISIBLE_IN_TREES)); } else { recordExpectedToBeVisibleInTree = context.forSelectionOfSchemaType.getCode().equals(schemaType) && !Boolean.TRUE.equals(securedRecord.get(Schemas.LOGICALLY_DELETED_STATUS)) && (context.options.isShowInvisibleRecordsInLinkingMode() || !Boolean.FALSE.equals(securedRecord.get(VISIBLE_IN_TREES))); } if (recordExpectedToBeVisibleInTree && securedRecord.getList(PATH_PARTS).contains(context.record.getId()) && !securedRecord.getList(PATH_PARTS).contains("_LAST_" + context.record.getId())) { Record childRecordLeadingToSecuredRecord = null; for (String aPath : securedRecord.<String>getList(Schemas.PATH)) { int index = aPath.indexOf("/" + context.record.getId() + "/"); if (index != -1) { String pathAfterCurrentRecord = aPath.substring(index + context.record.getId().length() + 2); String childLeadingToSecuredRecordId = StringUtils.substringBefore(pathAfterCurrentRecord, "/"); try { childRecordLeadingToSecuredRecord = recordServices.getDocumentById(childLeadingToSecuredRecordId); } catch (RecordServicesRuntimeException.NoSuchRecordWithId e) { e.printStackTrace(); } } } if (childRecordLeadingToSecuredRecord != null && !context.hasRequiredAccessOn(childRecordLeadingToSecuredRecord) && !context.isConceptOfNavigatedTaxonomy(childRecordLeadingToSecuredRecord) && !returnedRecordIds.contains(childRecordLeadingToSecuredRecord.getId())) { returnedRecordIds.add(childRecordLeadingToSecuredRecord.getId()); records.add(childRecordLeadingToSecuredRecord); } } } } return records; } public LinkableTaxonomySearchResponse getVisibleChildConceptResponse(User user, String taxonomyCode, Record record, TaxonomiesSearchOptions options) { GetChildrenContext ctx = new GetChildrenContext(user, record, options, null); return getVisibleChildrenRecords(ctx); } public LinkableTaxonomySearchResponse getVisibleRootConceptResponse(User user, String collection, String taxonomyCode, TaxonomiesSearchOptions options, String forSelectionOfSchemaType) { LogicalSearchQuery mainQuery = conceptNodesTaxonomySearchServices.getRootConceptsQuery(collection, taxonomyCode, options); // SearchResponseIterator<Record> rootIterator = searchServices.recordsIteratorKeepingOrder( // mainQuery.setNumberOfRows(100000).setStartRow(0), 50); Taxonomy taxonomy = taxonomiesManager.getEnabledTaxonomyWithCode(collection, taxonomyCode); boolean selectingAConcept = forSelectionOfSchemaType != null && taxonomy.getSchemaTypes().contains(forSelectionOfSchemaType); Iterator<List<Record>> iterator; List<TaxonomySearchRecord> visibleRecords = new ArrayList<>(); int lastIteratedRecordIndex = 0; FastContinueInfos continueInfos = options.getFastContinueInfos(); if (continueInfos != null) { lastIteratedRecordIndex = continueInfos.lastReturnRecordIndex; for (int i = 0; i < options.getStartRow(); i++) { visibleRecords.add(null); } iterator = searchServices.recordsIteratorKeepingOrder(mainQuery, 100, continueInfos.lastReturnRecordIndex) .inBatches(); } else { iterator = searchServices.recordsIteratorKeepingOrder(mainQuery, 100).inBatches(); } int consumed = 0; while (visibleRecords.size() < options.getEndRow() + 1 && iterator.hasNext()) { LogicalSearchQuery facetQuery; if (selectingAConcept) { LogicalSearchCondition condition = fromTypeIn(taxonomy) .where(VISIBLE_IN_TREES).isTrueOrNull() .andWhere(LINKABLE).isTrueOrNull(); if (options.getFilter() != null && options.getFilter().getLinkableConceptsCondition() != null) { condition = allConditions(condition, options.getFilter().getLinkableConceptsCondition()); } facetQuery = newQueryForFacets(condition, null, options); } else if (options.isAlwaysReturnTaxonomyConceptsWithReadAccess()) { LogicalSearchCondition condition = fromAllSchemasIn(taxonomy.getCollection()).where(VISIBLE_IN_TREES) .isTrueOrNull(); facetQuery = newQueryForFacets(condition, user, options); } else { LogicalSearchCondition condition = findVisibleNonTaxonomyRecordsInStructure(taxonomy, true); facetQuery = newQueryForFacets(condition, user, options); } List<Record> batch = iterator.next(); for (Record child : batch) { facetQuery.addQueryFacet(CHILDREN_QUERY, facetQueryFor(taxonomy, child)); if (selectingAConcept) { facetQuery.addQueryFacet(CHILDREN_QUERY, "id:" + child.getId()); } } SPEQueryResponse response = searchServices.query(facetQuery); for (Record child : batch) { consumed++; if (visibleRecords.size() < options.getEndRow()) { lastIteratedRecordIndex++; } Taxonomy taxonomyOfRecord = taxonomiesManager.getTaxonomyOf(child); boolean showEvenIfNoChildren = options.isAlwaysReturnTaxonomyConceptsWithReadAccess() && !taxonomyOfRecord.hasSameCode(taxonomiesManager.getPrincipalTaxonomy(collection)); boolean hasChildren = response.getQueryFacetCount(facetQueryFor(taxonomy, child)) > 0; boolean linkable = NOT_LINKABLE; if (selectingAConcept) { linkable = response.hasQueryFacetResults("id:" + child.getId()); } if (showEvenIfNoChildren || hasChildren || linkable) { visibleRecords.add(new TaxonomySearchRecord(child, linkable, hasChildren)); } } } int numFound = visibleRecords.size(); int toIndex = Math.min(visibleRecords.size(), options.getStartRow() + options.getRows()); List<TaxonomySearchRecord> returnedRecords = visibleRecords.subList(options.getStartRow(), toIndex); boolean finishedConceptsIteration = !iterator.hasNext(); FastContinueInfos infos = new FastContinueInfos(finishedConceptsIteration, lastIteratedRecordIndex, new ArrayList<String>()); return new LinkableTaxonomySearchResponse(numFound, infos, returnedRecords); } private LinkableTaxonomySearchResponse getLinkableConceptsForSelectionOfAPrincipalTaxonomyConceptBasedOnAuthorizations( User user, Taxonomy usingTaxonomy, Record inRecord, TaxonomiesSearchOptions originalOptions) { SPEQueryResponse mainQueryResponse; TaxonomiesSearchOptions options = new TaxonomiesSearchOptions(originalOptions); options.setRows(10000); options.setStartRow(0); if (inRecord == null) { mainQueryResponse = conceptNodesTaxonomySearchServices.getRootConceptResponse( usingTaxonomy.getCollection(), usingTaxonomy.getCode(), options); } else { mainQueryResponse = conceptNodesTaxonomySearchServices.getChildNodesResponse(inRecord, options); } List<Record> children = mainQueryResponse.getRecords(); Taxonomy taxonomy = taxonomiesManager.getEnabledTaxonomyWithCode(usingTaxonomy.getCollection(), usingTaxonomy.getCode()); LogicalSearchCondition condition = fromAllSchemasIn(taxonomy.getCollection()) .where(schemaTypeIsIn(taxonomy.getSchemaTypes())); LogicalSearchQuery query = new LogicalSearchQuery(condition) .filteredWithUser(user, options.getRequiredAccess()) .filteredByStatus(options.getIncludeStatus()) .sortAsc(Schemas.CODE).sortAsc(Schemas.TITLE) .setReturnedMetadatas( conceptNodesTaxonomySearchServices.returnedMetadatasForRecordsIn(usingTaxonomy.getCollection(), options)); for (Record child : children) { query.addQueryFacet("childrens", facetQueryFor(usingTaxonomy, child)); } SPEQueryResponse response = searchServices.query(query); List<String> responseRecordIds = new RecordUtils().toIdList(response.getRecords()); List<TaxonomySearchRecord> resultVisible = new ArrayList<>(); for (Record child : children) { boolean hasVisibleChildren = response.getQueryFacetCount(facetQueryFor(taxonomy, child)) > 0; boolean readAuthorizationsOnConcept = responseRecordIds.contains(child.getId()); boolean conceptIsLinkable = isTrueOrNull(child.get(Schemas.LINKABLE)); if (hasVisibleChildren || (readAuthorizationsOnConcept && conceptIsLinkable)) { resultVisible.add(new TaxonomySearchRecord(child, readAuthorizationsOnConcept && conceptIsLinkable, hasVisibleChildren)); } } int from = originalOptions.getStartRow(); int to = originalOptions.getEndRow(); if (resultVisible.size() < to) { to = resultVisible.size(); } return new LinkableTaxonomySearchResponse(resultVisible.size(), resultVisible.subList(from, to)); } private LinkableTaxonomySearchResponse getLinkableConceptsForSelectionOfATaxonomyConcept(User user, Taxonomy taxonomy, MetadataSchemaType selectedType, Record inRecord, TaxonomiesSearchOptions options) { options = options.cloneAddingReturnedField(Schemas.LINKABLE).cloneAddingReturnedField(Schemas.DESCRIPTION_STRING) .cloneAddingReturnedField(Schemas.DESCRIPTION_TEXT); SPEQueryResponse mainQueryResponse; if (inRecord == null) { mainQueryResponse = conceptNodesTaxonomySearchServices.getRootConceptResponse( taxonomy.getCollection(), taxonomy.getCode(), options); } else { mainQueryResponse = query(childrenCondition(taxonomy, inRecord), options); } LogicalSearchCondition condition = fromTypeIn(taxonomy).where(VISIBLE_IN_TREES).isTrueOrNull(); LogicalSearchQuery hasChildrenQuery = newQueryForFacets(condition, User.GOD, options); for (Record child : mainQueryResponse.getRecords()) { hasChildrenQuery.addQueryFacet(CHILDREN_QUERY, facetQueryFor(taxonomy, child)); } SPEQueryResponse response = searchServices.query(hasChildrenQuery); List<TaxonomySearchRecord> records = new ArrayList<>(); for (Record rootConcept : mainQueryResponse.getRecords()) { boolean sameType = rootConcept.getSchemaCode().startsWith(selectedType.getCode()); boolean linkable = isTrueOrNull(rootConcept.get(Schemas.LINKABLE)); boolean hasChildren = response.getQueryFacetCount(facetQueryFor(taxonomy, rootConcept)) > 0; records.add(new TaxonomySearchRecord(rootConcept, sameType && linkable, hasChildren)); } return new LinkableTaxonomySearchResponse(mainQueryResponse.getNumFound(), records); } private LinkableTaxonomySearchResponse getLinkableConceptsForSelectionOfARecordUsingNonPrincipalTaxonomy( GetChildrenContext ctx) { if (ctx.record != null) { return getVisibleChildrenRecords(ctx); } else { LogicalSearchQuery mainQuery = conceptNodesTaxonomySearchServices.getRootConceptsQuery( ctx.getCollection(), ctx.taxonomy.getCode(), ctx.options); mainQuery.filteredByStatus(ctx.options.getIncludeStatus()) .sortAsc(Schemas.CODE).sortAsc(Schemas.TITLE) .setReturnedMetadatas(returnedMetadatasForRecordsIn(ctx)); Iterator<List<Record>> iterator; List<TaxonomySearchRecord> visibleRecords = new ArrayList<>(); int lastIteratedRecordIndex = 0; FastContinueInfos continueInfos = ctx.options.getFastContinueInfos(); if (continueInfos != null) { lastIteratedRecordIndex = continueInfos.lastReturnRecordIndex; for (int i = 0; i < ctx.options.getStartRow(); i++) { visibleRecords.add(null); } iterator = searchServices.recordsIteratorKeepingOrder(mainQuery.setStartRow(0), 25, continueInfos.lastReturnRecordIndex).inBatches(); } else { iterator = searchServices.recordsIteratorKeepingOrder(mainQuery.setStartRow(0), 25).inBatches(); } Taxonomy principalTaxonomy = taxonomiesManager.getPrincipalTaxonomy(ctx.getCollection()); while (visibleRecords.size() < ctx.options.getEndRow() + 1 && iterator.hasNext()) { List<Record> batch = iterator.next(); boolean navigatingUsingPrincipalTaxonomy = principalTaxonomy != null && principalTaxonomy.getCode().equals(ctx.taxonomy.getCode()); List<String> schemaTypes = new ArrayList<>(); schemaTypes.add(ctx.forSelectionOfSchemaType.getCode()); if (ctx.options.isAlwaysReturnTaxonomyConceptsWithReadAccess() && navigatingUsingPrincipalTaxonomy) { schemaTypes.addAll(ctx.taxonomy.getSchemaTypes()); } LogicalSearchCondition condition = from(schemaTypes, ctx.getCollection()).returnAll(); if (!ctx.options.isShowInvisibleRecordsInLinkingMode()) { condition = condition.andWhere(VISIBLE_IN_TREES).isTrueOrNull(); } LogicalSearchQuery facetQuery = newQueryForFacets(condition, ctx.user, ctx.options); for (Record child : batch) { facetQuery.addQueryFacet(CHILDREN_QUERY, facetQueryFor(ctx.taxonomy, child)); } SPEQueryResponse response = searchServices.query(facetQuery); for (Record child : batch) { if (visibleRecords.size() < ctx.options.getEndRow()) { lastIteratedRecordIndex++; } String schemaType = getSchemaTypeCode(child.getSchemaCode()); boolean hasVisibleChildren = response.getQueryFacetCount(facetQueryFor(ctx.taxonomy, child)) > 0; Taxonomy taxonomy = taxonomiesManager.getTaxonomyOf(child); boolean visibleEvenIfEmpty = false; if (taxonomy != null && ctx.options.isAlwaysReturnTaxonomyConceptsWithReadAccess()) { if (principalTaxonomy != null && taxonomy.getCode().equals(principalTaxonomy.getCode())) { visibleEvenIfEmpty = ctx.hasRequiredAccessOn(child); } else { visibleEvenIfEmpty = true; } } if (schemaType.equals(ctx.forSelectionOfSchemaType.getCode())) { boolean hasAccess = ctx.user.hasRequiredAccess(ctx.options.getRequiredAccess()).on(child); if (hasAccess || hasVisibleChildren || visibleEvenIfEmpty) { visibleRecords.add(new TaxonomySearchRecord(child, hasAccess, hasVisibleChildren)); } } else if (hasVisibleChildren || visibleEvenIfEmpty) { visibleRecords.add(new TaxonomySearchRecord(child, false, hasVisibleChildren)); } } } int numFound = visibleRecords.size(); int toIndex = Math.min(visibleRecords.size(), ctx.options.getEndRow()); List<TaxonomySearchRecord> returnedRecords = visibleRecords.subList(ctx.options.getStartRow(), toIndex); boolean finishedConceptsIteration = !iterator.hasNext(); FastContinueInfos infos = new FastContinueInfos(finishedConceptsIteration, lastIteratedRecordIndex, new ArrayList<String>()); return new LinkableTaxonomySearchResponse(numFound, infos, returnedRecords); } } private LinkableTaxonomySearchResponse getLinkableConceptResponse(User user, String collection, String usingTaxonomyCode, String selectedTypeCode, Record inRecord, TaxonomiesSearchOptions options) { long start = new Date().getTime(); MetadataSchemaType selectedType = metadataSchemasManager.getSchemaTypes(collection).getSchemaType(selectedTypeCode); Taxonomy usingTaxonomy = taxonomiesManager.getEnabledTaxonomyWithCode(collection, usingTaxonomyCode); Taxonomy principalTaxonomy = taxonomiesManager.getPrincipalTaxonomy(collection); LinkableTaxonomySearchResponse response; if (principalTaxonomy.getSchemaTypes().contains(selectedType.getCode())) { //selecting a record of the principal taxonomy if (user == User.GOD || user.hasCollectionAccess(options.getRequiredAccess()) || user .has(CorePermissions.MANAGE_SECURITY).globally()) { //No security, the whole tree is visible response = getLinkableConceptsForSelectionOfATaxonomyConcept(user, usingTaxonomy, selectedType, inRecord, options); } else { //Security, only authorized concepts are visible (and their parents which are not selectable) response = getLinkableConceptsForSelectionOfAPrincipalTaxonomyConceptBasedOnAuthorizations( user, usingTaxonomy, inRecord, options); } } else if (usingTaxonomy.getSchemaTypes().contains(selectedType.getCode())) { //selecting a record of a non-principal taxonomy if (Role.WRITE.equals(options.getRequiredAccess()) || Role.DELETE.equals(options.getRequiredAccess())) { throw new TaxonomiesSearchServicesRuntimeException_CannotFilterNonPrincipalConceptWithWriteOrDeleteAccess(); } if (inRecord == null) { response = getVisibleRootConceptResponse(user, collection, usingTaxonomyCode, options, selectedTypeCode); } else { GetChildrenContext ctx = new GetChildrenContext(user, inRecord, options, selectedType, usingTaxonomy); response = getVisibleChildrenRecords(ctx); } } else { //selecting a non-taxonomy record using a taxonomy GetChildrenContext ctx = new GetChildrenContext(user, inRecord, options, selectedType, usingTaxonomy); response = getLinkableConceptsForSelectionOfARecordUsingNonPrincipalTaxonomy(ctx); } long duration = new Date().getTime() - start; return response.withQTime(duration); } LogicalSearchCondition findVisibleNonTaxonomyRecordsInStructure(GetChildrenContext context, boolean onlyVisibleInTrees) { LogicalSearchCondition condition = fromAllSchemasIn(context.taxonomy.getCollection()) .where(PATH_PARTS).isEqualTo(context.record.getId()) .andWhere(schemaTypeIsNotIn(context.taxonomy.getSchemaTypes())); if (onlyVisibleInTrees) { condition = condition.andWhere(VISIBLE_IN_TREES).isTrueOrNull(); } return condition; } LogicalSearchCondition findVisibleNonTaxonomyRecordsInStructure(Taxonomy taxonomy, boolean onlyVisibleInTrees) { LogicalSearchCondition condition = fromAllSchemasIn(taxonomy.getCollection()) .where(schemaTypeIsNotIn(taxonomy.getSchemaTypes())); if (onlyVisibleInTrees) { condition = condition.andWhere(VISIBLE_IN_TREES).isTrueOrNull(); } return condition; } private LogicalSearchQuery newQueryForFacets(LogicalSearchCondition condition, GetChildrenContext context) { return newQueryForFacets(condition, context.user, context.options); } private LogicalSearchQuery newQueryForFacets(LogicalSearchCondition condition, User user, TaxonomiesSearchOptions options) { LogicalSearchQuery query = new LogicalSearchQuery(condition) .filteredByStatus(options.getIncludeStatus()) .setNumberOfRows(0) .setReturnedMetadatas(ReturnedMetadatasFilter.idVersionSchema()); if (user != null) { query.filteredWithUser(user, options.getRequiredAccess()); } return query; } private LogicalSearchQuery newQuery(LogicalSearchCondition condition, TaxonomiesSearchOptions options) { return new LogicalSearchQuery(condition) .filteredByStatus(options.getIncludeStatus()) .setStartRow(options.getStartRow()) .setNumberOfRows(options.getRows()) .setReturnedMetadatas( conceptNodesTaxonomySearchServices.returnedMetadatasForRecordsIn(condition.getCollection(), options)) .sortAsc(Schemas.CODE).sortAsc(Schemas.TITLE); } private SPEQueryResponse query(LogicalSearchCondition condition, TaxonomiesSearchOptions options) { return searchServices.query(newQuery(condition, options)); } private static class TaxonomySearchRecordsComparator implements Comparator<TaxonomySearchRecord> { @Override public int compare(TaxonomySearchRecord o1, TaxonomySearchRecord o2) { return RecordCodeComparator.compareRecords(o1.getRecord(), o2.getRecord()); } } private List<String> facetQueriesFor(Taxonomy taxonomy, List<Record> records) { List<String> queries = new ArrayList<>(); for (Record record : records) { queries.add(facetQueryFor(taxonomy, record)); } return queries; } private String facetQueryFor(Taxonomy taxonomy, Record record) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("pathParts_ss:"); stringBuilder.append(record.getId()); return stringBuilder.toString(); } private enum TreeNavigationPurpose {SHOW_RECORDS_WITH_ACCESS, SET_METADATA} }