package com.constellio.app.ui.pages.search; import com.constellio.app.api.extensions.taxonomies.UserSearchEvent; import com.constellio.app.entities.schemasDisplay.MetadataDisplayConfig; import com.constellio.app.modules.rm.model.labelTemplate.LabelTemplate; import com.constellio.app.modules.rm.reports.builders.search.stats.StatsReportParameters; import com.constellio.app.modules.rm.reports.factories.labels.ExampleReportParameters; import com.constellio.app.modules.rm.wrappers.ContainerRecord; import com.constellio.app.modules.rm.wrappers.Document; import com.constellio.app.modules.rm.wrappers.Folder; import com.constellio.app.services.factories.ConstellioFactories; import com.constellio.app.services.schemasDisplay.SchemasDisplayManager; import com.constellio.app.ui.application.ConstellioUI; import com.constellio.app.ui.entities.FacetVO; import com.constellio.app.ui.entities.MetadataSchemaVO; import com.constellio.app.ui.entities.MetadataVO; import com.constellio.app.ui.entities.RecordVO; import com.constellio.app.ui.framework.builders.MetadataSchemaToVOBuilder; import com.constellio.app.ui.framework.builders.MetadataToVOBuilder; import com.constellio.app.ui.framework.builders.RecordToVOBuilder; import com.constellio.app.ui.framework.components.NewReportPresenter; import com.constellio.app.ui.framework.components.breadcrumb.BaseBreadcrumbTrail; import com.constellio.app.ui.framework.data.SearchResultVODataProvider; import com.constellio.app.ui.framework.reports.NewReportWriterFactory; import com.constellio.app.ui.pages.base.BasePresenter; import com.constellio.app.ui.pages.base.SessionContext; import com.constellio.app.ui.pages.base.UIContext; import com.constellio.data.dao.dto.records.FacetValue; import com.constellio.data.utils.KeySetMap; import com.constellio.data.utils.TimeProvider; import com.constellio.model.entities.Taxonomy; import com.constellio.model.entities.enums.SearchSortType; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.wrappers.Facet; import com.constellio.model.entities.records.wrappers.SavedSearch; import com.constellio.model.entities.records.wrappers.User; import com.constellio.model.entities.records.wrappers.structure.FacetType; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchemaType; import com.constellio.model.entities.schemas.MetadataValueType; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.records.RecordServicesRuntimeException; import com.constellio.model.services.records.SchemasRecordsServices; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.search.SPEQueryResponse; import com.constellio.model.services.search.SearchBoostManager; import com.constellio.model.services.search.StatusFilter; 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.LogicalSearchQueryFacetFilters; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; import com.constellio.model.services.search.zipContents.ZipContentsService; import com.constellio.model.services.search.zipContents.ZipContentsService.NoContentToZipRuntimeException; import com.vaadin.server.StreamResource.StreamSource; import org.apache.commons.lang3.StringUtils; import org.joda.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.Map.Entry; import static com.constellio.app.ui.i18n.i18n.$; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; import static java.util.Arrays.asList; public abstract class SearchPresenter<T extends SearchView> extends BasePresenter<T> implements NewReportPresenter { private static final String ZIP_CONTENT_RESOURCE = "zipContentsFolder"; public enum SortOrder {ASCENDING, DESCENDING} private static Logger LOGGER = LoggerFactory.getLogger(SearchPresenter.class); protected Map<String, String[]> extraSolrParams = new HashMap<>(); KeySetMap<String, String> facetSelections = new KeySetMap<>(); Map<String, Boolean> facetStatus = new HashMap<>(); List<String> suggestions; String sortCriterion; SortOrder sortOrder; String resultsViewMode; String collection; transient SchemasDisplayManager schemasDisplayManager; transient SearchPresenterService service; boolean highlighter = true; int selectedPageLength; public int getSelectedPageLength() { return selectedPageLength; } public void setSelectedPageLength(int selectedPageLength) { this.selectedPageLength = selectedPageLength; } public SearchPresenter(T view) { super(view); init(view.getConstellioFactories(), view.getSessionContext()); initSortParameters(); } private void initSortParameters() { SearchSortType searchSortType = modelLayerFactory.getSystemConfigs().getSearchSortType(); switch (searchSortType) { case RELEVENCE: sortOrder = SortOrder.DESCENDING; this.sortCriterion = null; break; case PATH_ASC: this.sortCriterion = Schemas.PATH.getCode(); this.sortOrder = SortOrder.ASCENDING; break; case PATH_DES: this.sortCriterion = Schemas.PATH.getCode(); this.sortOrder = SortOrder.DESCENDING; break; case ID_ASC: this.sortCriterion = Schemas.IDENTIFIER.getCode(); this.sortOrder = SortOrder.ASCENDING; break; case ID_DES: this.sortCriterion = Schemas.IDENTIFIER.getCode(); this.sortOrder = SortOrder.DESCENDING; break; case CREATION_DATE_ASC: this.sortCriterion = Schemas.CREATED_ON.getCode(); this.sortOrder = SortOrder.ASCENDING; break; case CREATION_DATE_DES: this.sortCriterion = Schemas.CREATED_ON.getCode(); this.sortOrder = SortOrder.DESCENDING; break; case MODIFICATION_DATE_ASC: this.sortCriterion = Schemas.MODIFIED_ON.getCode(); this.sortOrder = SortOrder.ASCENDING; break; case MODIFICATION_DATE_DES: this.sortCriterion = Schemas.MODIFIED_ON.getCode(); this.sortOrder = SortOrder.DESCENDING; break; default: throw new RuntimeException("Unsupported type " + searchSortType); } } public void setExtraSolrParams(Map<String, String[]> extraSolrParams) { this.extraSolrParams = extraSolrParams; } public Map<String, String[]> getExtraSolrParams() { return extraSolrParams; } private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); ConstellioFactories constellioFactories = ConstellioFactories.getInstance(); SessionContext sessionContext = ConstellioUI.getCurrentSessionContext(); init(constellioFactories, sessionContext); } private void init(ConstellioFactories constellioFactories, SessionContext sessionContext) { collection = sessionContext.getCurrentCollection(); service = new SearchPresenterService(collection, constellioFactories.getModelLayerFactory()); schemasDisplayManager = constellioFactories.getAppLayerFactory().getMetadataSchemasDisplayManager(); } public void resetFacetAndOrder() { resetFacetSelection(); //TODO initSortParameters(); //sortOrder = SortOrder.ASCENDING; } public String getResultsViewMode() { return resultsViewMode; } public abstract Record getTemporarySearchRecord(); public abstract SearchPresenter<T> forRequestParameters(String params); public abstract boolean mustDisplayResults(); public abstract int getPageNumber(); public abstract void setPageNumber(int pageNumber); public List<FacetVO> getFacets() { return service.getFacets(getSearchQuery(), facetStatus); } public String getSortCriterion() { return sortCriterion; } public SortOrder getSortOrder() { return sortOrder; } public String getUserSearchExpression() { return null; } public boolean mustDisplaySuggestions() { if (searchServices().getResultsCount(getSearchQuery()) != 0) { return false; } SPEQueryResponse suggestionsResponse = searchServices() .query(getSearchQuery().setNumberOfRows(0).setSpellcheck(true)); if (suggestionsResponse.isCorrectlySpelt()) { return false; } suggestions = suggestionsResponse.getSpellCheckerSuggestions(); return !suggestions.isEmpty(); } public List<String> getSuggestions() { return suggestions; } public SearchResultVODataProvider getSearchResults() { return new SearchResultVODataProvider(new RecordToVOBuilder(), appLayerFactory, view.getSessionContext()) { @Override protected LogicalSearchQuery getQuery() { LogicalSearchQuery query = getSearchQuery().setHighlighting(highlighter).setOverridedQueryParams(extraSolrParams); if (sortCriterion == null) { if (StringUtils.isNotBlank(getUserSearchExpression())) { query.setFieldBoosts(searchBoostManager().getAllSearchBoostsByMetadataType(view.getCollection())); query.setQueryBoosts(searchBoostManager().getAllSearchBoostsByQueryType(view.getCollection())); } return query; } Metadata metadata = getMetadata(sortCriterion); return sortOrder == SortOrder.ASCENDING ? query.sortAsc(metadata) : query.sortDesc(metadata); } boolean hasExtensionsBeenNotified = false; @Override protected void onQuery(LogicalSearchQuery query, SPEQueryResponse response) { if (!hasExtensionsBeenNotified) { hasExtensionsBeenNotified = true; SavedSearch search = new SavedSearch(recordServices().newRecordWithSchema(schema(SavedSearch.DEFAULT_SCHEMA)), types()) .setUser(getCurrentUser().getId()) .setSortField(sortCriterion) .setSortOrder(SavedSearch.SortOrder.valueOf(sortOrder.name())) .setSelectedFacets(facetSelections.getNestedMap()) .setTemporary(false); search = prepareSavedSearch(search); LocalDateTime queryDateTime = TimeProvider.getLocalDateTime(); String username = view.getSessionContext().getCurrentUser().getUsername(); String language = view.getSessionContext().getCurrentLocale().getLanguage(); UserSearchEvent param = new UserSearchEvent(response, query, search, queryDateTime, language, username); appLayerFactory.getExtensions().forCollection(view.getSessionContext().getCurrentCollection()) .notifyNewUserSearch(param); } } }; } private List<MetadataSchemaVO> getSchemas() { MetadataSchemaToVOBuilder builder = new MetadataSchemaToVOBuilder(); return asList( builder.build(schema(Folder.DEFAULT_SCHEMA), RecordVO.VIEW_MODE.TABLE, view.getSessionContext()), builder.build(schema(Folder.DEFAULT_SCHEMA), RecordVO.VIEW_MODE.TABLE, view.getSessionContext()), builder.build(schema(Folder.DEFAULT_SCHEMA), RecordVO.VIEW_MODE.TABLE, view.getSessionContext())); } // TODO RecordVODataProvider for search results public void facetValueSelected(String facetId, String facetValue) { facetSelections.get(facetId).add(facetValue); view.refreshSearchResultsAndFacets(); } public void facetValueDeselected(String facetId, String facetValue) { facetSelections.get(facetId).remove(facetValue); view.refreshSearchResultsAndFacets(); } public void facetDeselected(String facetId) { facetSelections.get(facetId).clear(); view.refreshSearchResultsAndFacets(); } public void facetOpened(String facetId) { facetStatus.put(facetId, true); } public void facetClosed(String facetId) { facetStatus.put(facetId, false); } public KeySetMap<String, String> getFacetSelections() { return facetSelections; } public void setFacetSelections(Map<String, Set<String>> facetSelections) { this.facetSelections.putAll(facetSelections); } public void sortCriterionSelected(String sortCriterion, SortOrder sortOrder) { this.sortCriterion = sortCriterion; this.sortOrder = sortOrder; view.refreshSearchResults(true); } @Override public List<String> getSupportedReports() { List<String> supportedReports = new ArrayList<>(); if (view.computeStatistics()) { supportedReports.add("Reports.FolderLinearMeasureStats"); } return supportedReports; } @Override public NewReportWriterFactory getReport(String report) { switch (report) { case "Reports.fakeReport": return getRmReportBuilderFactories().exampleBuilderFactory.getValue(); case "Reports.FolderLinearMeasureStats": return getRmReportBuilderFactories().statsBuilderFactory.getValue(); } throw new UnknownReportRuntimeException("BUG: Unknown report " + report); } public String getZippedContentsFilename() { return $("SearchView.contentZip"); } public StreamSource getZippedContents() { return new StreamSource() { @Override public InputStream getStream() { File folder = modelLayerFactory.getDataLayerFactory().getIOServicesFactory().newFileService() .newTemporaryFolder(ZIP_CONTENT_RESOURCE); File file = new File(folder, getZippedContentsFilename()); try { new ZipContentsService(modelLayerFactory, collection) .zipContentsOfRecords(view.getSelectedRecordIds(), file); return new FileInputStream(file); } catch (NoContentToZipRuntimeException e) { LOGGER.error("Error while zipping", e); view.showErrorMessage($("SearchView.noContentInSelectedRecords")); return null; } catch (Exception e) { LOGGER.error("Error while zipping", e); view.showErrorMessage($("SearchView.zipContentsError")); return null; } } }; } public String getSortCriterionValueAmong(List<MetadataVO> sortableMetadata) { if (this.sortCriterion == null) { return null; } if (!this.sortCriterion.startsWith("global_")) { return this.sortCriterion; } else { String localCode = new SchemaUtils().getLocalCodeFromMetadataCode(this.sortCriterion); for (MetadataVO metadata : sortableMetadata) { if (metadata.getLocalCode().equals(localCode)) { return metadata.getCode(); } } } return this.sortCriterion; } public abstract void suggestionSelected(String suggestion); public abstract List<MetadataVO> getMetadataAllowedInSort(); public abstract boolean isPreferAnalyzedFields(); protected abstract LogicalSearchCondition getSearchCondition(); protected LogicalSearchQuery getSearchQuery() { LogicalSearchQuery query = new LogicalSearchQuery(getSearchCondition()) .setOverridedQueryParams(extraSolrParams) .setFreeTextQuery(getUserSearchExpression()) .filteredWithUser(getCurrentUser()) .filteredByStatus(StatusFilter.ACTIVES) .setPreferAnalyzedFields(isPreferAnalyzedFields()); // query.setReturnedMetadatas(ReturnedMetadatasFilter.onlyFields( // schemasDisplayManager.getReturnedFieldsForSearch(collection))); query.setReturnedMetadatas(ReturnedMetadatasFilter.allExceptContentAndLargeText()); SchemasRecordsServices schemas = new SchemasRecordsServices(collection, modelLayerFactory); LogicalSearchQueryFacetFilters filters = query.getFacetFilters(); filters.clear(); for (Entry<String, Set<String>> selection : facetSelections.getMapEntries()) { try { Facet facet = schemas.getFacet(selection.getKey()); if (!selection.getValue().isEmpty()) { if (facet.getFacetType() == FacetType.FIELD) { filters.selectedFieldFacetValues(facet.getFieldDataStoreCode(), selection.getValue()); } else if (facet.getFacetType() == FacetType.QUERY) { filters.selectedQueryFacetValues(facet.getId(), selection.getValue()); } } } catch (RecordServicesRuntimeException.NoSuchRecordWithId id) { LOGGER.warn("Facet '" + id + "' has been deleted"); } } return query; } public void setHighlighter(boolean highlighter) { this.highlighter = highlighter; } protected void resetFacetSelection() { facetSelections.clear(); initSortParameters(); } protected SavedSearch getSavedSearch(String id) { Record record = recordServices().getDocumentById(id); return new SavedSearch(record, types()); } Metadata getMetadata(String code) { if (code.startsWith("global_")) { return Schemas.getGlobalMetadata(code); } SchemaUtils utils = new SchemaUtils(); String schemaCode = utils.getSchemaCode(code); return schema(schemaCode).getMetadata(utils.getLocalCode(code, schemaCode)); } protected List<MetadataVO> getMetadataAllowedInAdvancedSearch(String schemaTypeCode) { MetadataToVOBuilder builder = new MetadataToVOBuilder(); MetadataSchemaType schemaType = schemaType(schemaTypeCode); List<FacetValue> schema_s = modelLayerFactory.newSearchServices().query(new LogicalSearchQuery() .setCondition(from(schemaType).returnAll()).addFieldFacet("schema_s").filteredWithUser(getCurrentUser())).getFieldFacetValues("schema_s"); Set<String> metadataLocalCodes = new HashSet<>(); if(schema_s != null) { for(FacetValue facetValue: schema_s) { if(facetValue.getQuantity() > 0) { String schema = facetValue.getValue(); metadataLocalCodes.addAll(types().getSchema(schema).getMetadatas().toLocalCodesList()); } } } List<MetadataVO> result = new ArrayList<>(); for (Metadata metadata : schemaType.getAllMetadatas()) { if(!schemaType.hasSecurity() || metadataLocalCodes.contains(metadata.getLocalCode())) { MetadataDisplayConfig config = schemasDisplayManager().getMetadata(view.getCollection(), metadata.getCode()); if (config.isVisibleInAdvancedSearch() && metadata.isEnabled() && isMetadataVisibleForUser(metadata, getCurrentUser())) { result.add(builder.build(metadata, view.getSessionContext())); } } } return result; } private boolean isMetadataVisibleForUser(Metadata metadata, User currentUser) { if(MetadataValueType.REFERENCE.equals(metadata.getType())) { String referencedSchemaType = metadata.getAllowedReferences().getTypeWithAllowedSchemas(); Taxonomy taxonomy = appLayerFactory.getModelLayerFactory().getTaxonomiesManager().getTaxonomyFor(collection, referencedSchemaType); if(taxonomy != null) { List<String> taxonomyGroupIds = taxonomy.getGroupIds(); List<String> taxonomyUserIds = taxonomy.getUserIds(); List<String> userGroups = currentUser.getUserGroups(); for(String group: taxonomyGroupIds) { for(String userGroup: userGroups) { if(userGroup.equals(group)) { return true; } } } return (taxonomyGroupIds.isEmpty() && taxonomyUserIds.isEmpty()) || taxonomyUserIds.contains(currentUser.getId()); } else { return true; } } return true; } protected MetadataVO getMetadataVO(String metadataCode) { return presenterService().getMetadataVO(metadataCode, view.getSessionContext()); } protected List<MetadataVO> getMetadataAllowedInSort(String schemaTypeCode) { MetadataSchemaType schemaType = schemaType(schemaTypeCode); return getMetadataAllowedInSort(schemaType); } protected List<MetadataVO> getMetadataAllowedInSort(MetadataSchemaType schemaType) { MetadataToVOBuilder builder = new MetadataToVOBuilder(); List<MetadataVO> result = new ArrayList<>(); for (Metadata metadata : schemaType.getAllMetadatas()) { if (metadata.isSortable()) { result.add(builder.build(metadata, view.getSessionContext())); } } return result; } protected List<LabelTemplate> getTemplates() { return appLayerFactory.getLabelTemplateManager().listTemplates(null); } protected boolean saveSearch(String title, boolean publicAccess) { SavedSearch search = new SavedSearch(recordServices().newRecordWithSchema(schema(SavedSearch.DEFAULT_SCHEMA)), types()) .setTitle(title) .setUser(getCurrentUser().getId()) .setPublic(publicAccess) .setSortField(sortCriterion) .setSortOrder(SavedSearch.SortOrder.valueOf(sortOrder.name())) .setSelectedFacets(facetSelections.getNestedMap()) .setTemporary(false); try { recordServices().add(prepareSavedSearch(search)); } catch (RecordServicesException e) { view.showErrorMessage($("SearchView.errorSavingSearch")); return false; } view.showMessage($("SearchView.searchSaved")); return true; } protected abstract SavedSearch saveTemporarySearch(boolean refreshPage); protected SavedSearch prepareSavedSearch(SavedSearch search) { return search; } protected void updateUIContext(SavedSearch savedSearch) { String searchId = savedSearch.getId(); boolean advancedSearch = StringUtils.isNotBlank(savedSearch.getSchemaFilter()); UIContext uiContext = view.getUIContext(); uiContext.setAttribute(BaseBreadcrumbTrail.SEARCH_ID, searchId); uiContext.setAttribute(BaseBreadcrumbTrail.ADVANCED_SEARCH, advancedSearch); uiContext.clearAttribute(BaseBreadcrumbTrail.TAXONOMY_CODE); } protected SearchBoostManager searchBoostManager() { return modelLayerFactory.getSearchBoostManager(); } @Override public Object getReportParameters(String report) { switch (report) { case "Reports.fakeReport": return new ExampleReportParameters(view.getSelectedRecordIds()); case "Reports.FolderLinearMeasureStats": return new StatsReportParameters(view.getCollection(), appLayerFactory, getSearchQuery()); } throw new UnknownReportRuntimeException("BUG: Unknown report " + report); } protected void addToSelectionButtonClicked() { SessionContext sessionContext = view.getSessionContext(); List<String> selectedSearchResultRecordIds = view.getSelectedRecordIds(); boolean someElementsNotAdded = false; for (String selectedRecordId : selectedSearchResultRecordIds) { Record record = modelLayerFactory.newRecordServices().getDocumentById(selectedRecordId); if (asList(Folder.SCHEMA_TYPE, Document.SCHEMA_TYPE, ContainerRecord.SCHEMA_TYPE).contains(record.getTypeCode())) { sessionContext.addSelectedRecordId(selectedRecordId, record == null ? null : record.getTypeCode()); } else { someElementsNotAdded = true; } } if (someElementsNotAdded) { view.showErrorMessage($("ConstellioHeader.selection.cannotAddRecords")); } } }