All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.discovery.SolrServiceImpl Maven / Gradle / Ivy

The newest version!
/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.discovery;

import static java.util.stream.Collectors.joining;
import static org.dspace.discovery.indexobject.ItemIndexFactoryImpl.STATUS_FIELD;
import static org.dspace.discovery.indexobject.ItemIndexFactoryImpl.STATUS_FIELD_PREDB;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import jakarta.mail.MessagingException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.json.BucketBasedJsonFacet;
import org.apache.solr.client.solrj.response.json.NestableJsonFacet;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.HighlightParams;
import org.apache.solr.common.params.MoreLikeThisParams;
import org.apache.solr.common.params.SpellingParams;
import org.apache.solr.common.util.NamedList;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper;
import org.dspace.discovery.configuration.DiscoveryConfiguration;
import org.dspace.discovery.configuration.DiscoveryConfigurationParameters;
import org.dspace.discovery.configuration.DiscoveryMoreLikeThisConfiguration;
import org.dspace.discovery.configuration.DiscoverySearchFilterFacet;
import org.dspace.discovery.configuration.DiscoverySortConfiguration;
import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.discovery.indexobject.IndexableCommunity;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.discovery.indexobject.factory.IndexFactory;
import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * SolrIndexer contains the methods that index Items and their metadata,
 * collections, communities, etc. It is meant to either be invoked from the
 * command line (see dspace/bin/index-all) or via the indexContent() methods
 * within DSpace.
 * 

* The Administrator can choose to run SolrIndexer in a cron that repeats * regularly, a failed attempt to index from the UI will be "caught" up on in * that cron. * * The SolrServiceImpl is registered as a Service in the ServiceManager via * a Spring configuration file located under * classpath://spring/spring-dspace-applicationContext.xml * * Its configuration is Autowired by the ApplicationContext * * @author Kevin Van de Velde (kevin at atmire dot com) * @author Mark Diggory (markd at atmire dot com) * @author Ben Bosman (ben at atmire dot com) */ @Service public class SolrServiceImpl implements SearchService, IndexingService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class); // Suffix of the solr field used to index the facet/filter so that the facet search can search all word in a // facet by indexing "each word to end of value' partial value public static final String SOLR_FIELD_SUFFIX_FACET_PREFIXES = "_prefix"; @Autowired protected ContentServiceFactory contentServiceFactory; @Autowired protected GroupService groupService; @Autowired protected IndexObjectFactoryFactory indexObjectServiceFactory; @Autowired protected SolrSearchCore solrSearchCore; @Autowired protected ConfigurationService configurationService; protected SolrServiceImpl() { } /** * If the handle for the "dso" already exists in the index, and the "dso" * has a lastModified timestamp that is newer than the document in the index * then it is updated, otherwise a new document is added. * * @param context Users Context * @param dso DSpace Object (Item, Collection or Community * @throws SQLException if error */ @Override public void indexContent(Context context, IndexableObject dso) throws SQLException { indexContent(context, dso, false); } /** * If the handle for the "dso" already exists in the index, and the "dso" * has a lastModified timestamp that is newer than the document in the index * then it is updated, otherwise a new document is added. * * @param context Users Context * @param indexableObject The object we want to index * @param force Force update even if not stale. */ @Override public void indexContent(Context context, IndexableObject indexableObject, boolean force) { try { final IndexFactory indexableObjectFactory = indexObjectServiceFactory. getIndexableObjectFactory(indexableObject); if (force || requiresIndexing(indexableObject.getUniqueIndexID(), indexableObject.getLastModified())) { update(context, indexableObjectFactory, indexableObject); log.info(LogHelper.getHeader(context, "indexed_object", indexableObject.getUniqueIndexID())); } } catch (IOException | SQLException | SolrServerException | SearchServiceException e) { log.error(e.getMessage(), e); } } protected void update(Context context, IndexFactory indexableObjectService, IndexableObject indexableObject) throws IOException, SQLException, SolrServerException { final SolrInputDocument solrInputDocument = indexableObjectService.buildDocument(context, indexableObject); indexableObjectService.writeDocument(context, indexableObject, solrInputDocument); } /** * Update the given indexable object using a given service * @param context The DSpace Context * @param indexableObjectService The service to index the object with * @param indexableObject The object to index * @param preDB Add a "preDB" status to the document */ protected void update(Context context, IndexFactory indexableObjectService, IndexableObject indexableObject, boolean preDB) throws IOException, SQLException, SolrServerException { if (preDB) { final SolrInputDocument solrInputDocument = indexableObjectService.buildNewDocument(context, indexableObject); indexableObjectService.writeDocument(context, indexableObject, solrInputDocument); } else { update(context, indexableObjectService, indexableObject); } } /** * unIndex removes an Item, Collection, or Community * * @param context The relevant DSpace Context. * @param dso DSpace Object, can be Community, Item, or Collection * @throws SQLException if database error * @throws IOException if IO error */ @Override public void unIndexContent(Context context, IndexableObject dso) throws SQLException, IOException { unIndexContent(context, dso, false); } /** * unIndex removes an Item, Collection, or Community * * @param context The relevant DSpace Context. * @param indexableObject The object to be indexed * @param commit if true force an immediate commit on SOLR * @throws SQLException if database error * @throws IOException if IO error */ @Override public void unIndexContent(Context context, IndexableObject indexableObject, boolean commit) throws SQLException, IOException { try { if (indexableObject == null) { return; } String uniqueID = indexableObject.getUniqueIndexID(); log.info("Try to delete uniqueID:" + uniqueID); indexObjectServiceFactory.getIndexableObjectFactory(indexableObject).delete(indexableObject); if (commit) { solrSearchCore.getSolr().commit(); } } catch (IOException | SolrServerException exception) { log.error(exception.getMessage(), exception); emailException(exception); } } /** * Unindex a Document in the Lucene index. * * @param context the dspace context * @param searchUniqueID the search uniqueID of the document to be deleted * @throws IOException if IO error */ @Override public void unIndexContent(Context context, String searchUniqueID) throws IOException { unIndexContent(context, searchUniqueID, false); } /** * Unindex a Document in the Lucene Index. * * @param context the dspace context * @param searchUniqueID the search uniqueID of the document to be deleted * @param commit commit the update immediately. * @throws IOException if IO error */ @Override public void unIndexContent(Context context, String searchUniqueID, boolean commit) throws IOException { try { if (solrSearchCore.getSolr() != null) { IndexFactory index = indexObjectServiceFactory.getIndexableObjectFactory(searchUniqueID); if (index != null) { index.delete(searchUniqueID); } else { log.warn("Object not found in Solr index: " + searchUniqueID); } if (commit) { solrSearchCore.getSolr().commit(); } } } catch (SolrServerException e) { log.error(e.getMessage(), e); } } /** * reIndexContent removes something from the index, then re-indexes it * * @param context context object * @param dso object to re-index * @throws java.sql.SQLException passed through. * @throws java.io.IOException passed through. */ @Override public void reIndexContent(Context context, IndexableObject dso) throws SQLException, IOException { try { indexContent(context, dso); } catch (SQLException exception) { log.error(exception.getMessage(), exception); emailException(exception); } } /** * create full index - wiping old index * * @param c context to use * @throws java.sql.SQLException passed through. * @throws java.io.IOException passed through. */ @Override public void createIndex(Context c) throws SQLException, IOException { /* Reindex all content preemptively. */ updateIndex(c, true); } /** * Iterates over all Items, Collections and Communities. And updates them in * the index. Uses decaching to control memory footprint. Uses indexContent * and isStale to check state of item in index. * * @param context the dspace context */ @Override public void updateIndex(Context context) { updateIndex(context, false); } /** * Iterates over all Items, Collections and Communities. And updates them in * the index. Uses decaching to control memory footprint. Uses indexContent * and isStale to check state of item in index. *

* At first it may appear counterintuitive to have an IndexWriter/Reader * opened and closed on each DSO. But this allows the UI processes to step * in and attain a lock and write to the index even if other processes/jvms * are running a reindex. * * @param context the dspace context * @param force whether or not to force the reindexing */ @Override public void updateIndex(Context context, boolean force) { updateIndex(context, force, null); } @Override public void updateIndex(Context context, boolean force, String type) { try { final List indexableObjectServices = indexObjectServiceFactory. getIndexFactories(); for (IndexFactory indexableObjectService : indexableObjectServices) { if (type == null || StringUtils.equals(indexableObjectService.getType(), type)) { final Iterator indexableObjects = indexableObjectService.findAll(context); while (indexableObjects.hasNext()) { final IndexableObject indexableObject = indexableObjects.next(); indexContent(context, indexableObject, force); context.uncacheEntity(indexableObject.getIndexedObject()); } } } if (solrSearchCore.getSolr() != null) { solrSearchCore.getSolr().commit(); } } catch (IOException | SQLException | SolrServerException e) { log.error(e.getMessage(), e); } } /** * Removes all documents from the Lucene index */ public void deleteIndex() { try { final List indexableObjectServices = indexObjectServiceFactory. getIndexFactories(); for (IndexFactory indexableObjectService : indexableObjectServices) { indexableObjectService.deleteAll(); } } catch (IOException | SolrServerException e) { log.error("Error cleaning discovery index: " + e.getMessage(), e); } } /** * Iterates over all documents in the Lucene index and verifies they are in * database, if not, they are removed. * * @throws IOException IO exception * @throws SQLException sql exception * @throws SearchServiceException occurs when something went wrong with querying the solr server */ @Override public void cleanIndex() throws IOException, SQLException, SearchServiceException { Context context = new Context(); context.turnOffAuthorisationSystem(); try { if (solrSearchCore.getSolr() == null) { return; } // First, we'll just get a count of the total results SolrQuery countQuery = new SolrQuery("*:*"); countQuery.setRows(0); // don't actually request any data // Get the total amount of results QueryResponse totalResponse = solrSearchCore.getSolr().query(countQuery, solrSearchCore.REQUEST_METHOD); long total = totalResponse.getResults().getNumFound(); int start = 0; int batch = 100; // Now get actual Solr Documents in batches SolrQuery query = new SolrQuery(); query.setFields(SearchUtils.RESOURCE_UNIQUE_ID, SearchUtils.RESOURCE_ID_FIELD, SearchUtils.RESOURCE_TYPE_FIELD); query.addSort(SearchUtils.RESOURCE_UNIQUE_ID, SolrQuery.ORDER.asc); query.setQuery("*:*"); query.setRows(batch); // Keep looping until we hit the total number of Solr docs while (start < total) { query.setStart(start); QueryResponse rsp = solrSearchCore.getSolr().query(query, solrSearchCore.REQUEST_METHOD); SolrDocumentList docs = rsp.getResults(); for (SolrDocument doc : docs) { String uniqueID = (String) doc.getFieldValue(SearchUtils.RESOURCE_UNIQUE_ID); IndexableObject o = findIndexableObject(context, doc); if (o == null) { log.info("Deleting: " + uniqueID); /* * Use IndexWriter to delete, its easier to manage * write.lock */ unIndexContent(context, uniqueID); } else { log.debug("Keeping: " + o.getUniqueIndexID()); } } start += batch; } } catch (IOException | SQLException | SolrServerException e) { log.error("Error cleaning discovery index: " + e.getMessage(), e); } finally { context.abort(); } } /** * Maintenance to keep a SOLR index efficient. * Note: This might take a long time. */ @Override public void optimize() { try { if (solrSearchCore.getSolr() == null) { return; } long start = Instant.now().toEpochMilli(); System.out.println("SOLR Search Optimize -- Process Started:" + start); solrSearchCore.getSolr().optimize(); long finish = Instant.now().toEpochMilli(); System.out.println("SOLR Search Optimize -- Process Finished:" + finish); System.out.println("SOLR Search Optimize -- Total time taken:" + (finish - start) + " (ms)."); } catch (SolrServerException | IOException e) { System.err.println(e.getMessage()); } } @Override public void buildSpellCheck() throws SearchServiceException, IOException { try { if (solrSearchCore.getSolr() == null) { return; } SolrQuery solrQuery = new SolrQuery(); solrQuery.set("spellcheck", true); solrQuery.set(SpellingParams.SPELLCHECK_BUILD, true); solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); } catch (SolrServerException e) { //Make sure to also log the exception since this command is usually run from a crontab. log.error(e, e); throw new SearchServiceException(e); } } @Override public void atomicUpdate(Context context, String uniqueIndexId, String field, Map fieldModifier) throws SolrServerException, IOException { SolrInputDocument solrInputDocument = new SolrInputDocument(); solrInputDocument.addField(SearchUtils.RESOURCE_UNIQUE_ID, uniqueIndexId); solrInputDocument.addField(field, fieldModifier); solrSearchCore.getSolr().add(solrInputDocument); } // ////////////////////////////////// // Private // ////////////////////////////////// protected void emailException(Exception exception) { // Also email an alert, system admin may need to check for stale lock try { String recipient = configurationService.getProperty("alert.recipient"); if (StringUtils.isNotBlank(recipient)) { Email email = Email .getEmail(I18nUtil.getEmailFilename( Locale.getDefault(), "internal_error")); email.addRecipient(recipient); email.addArgument(configurationService.getProperty("dspace.ui.url")); email.addArgument(Instant.now()); String stackTrace; if (exception != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); exception.printStackTrace(pw); pw.flush(); stackTrace = sw.toString(); } else { stackTrace = "No exception"; } email.addArgument(stackTrace); email.send(); } } catch (IOException | MessagingException e) { // Not much we can do here! log.warn("Unable to send email alert", e); } } /** * Is stale checks the lastModified time stamp in the database and the index * to determine if the index is stale. * * @param uniqueId the unique identifier of the object that we want to index * @param lastModified the last modified date of the DSpace object * @return a boolean indicating if the dso should be re indexed again * @throws SQLException sql exception * @throws IOException io exception * @throws SearchServiceException if something went wrong with querying the solr server */ protected boolean requiresIndexing(String uniqueId, Instant lastModified) throws SQLException, IOException, SearchServiceException { // Check if we even have a last modified date if (lastModified == null) { return true; } boolean reindexItem = false; boolean inIndex = false; SolrQuery query = new SolrQuery(); query.setQuery(SearchUtils.RESOURCE_UNIQUE_ID + ":" + uniqueId); // Specify that we ONLY want the LAST_INDEXED_FIELD returned in the field list (fl) query.setFields(SearchUtils.LAST_INDEXED_FIELD); QueryResponse rsp; try { if (solrSearchCore.getSolr() == null) { return false; } rsp = solrSearchCore.getSolr().query(query, solrSearchCore.REQUEST_METHOD); } catch (SolrServerException e) { throw new SearchServiceException(e.getMessage(), e); } for (SolrDocument doc : rsp.getResults()) { inIndex = true; Object value = doc.getFieldValue(SearchUtils.LAST_INDEXED_FIELD); // If it's a java.util.Date, convert to an Instant if (value instanceof java.util.Date) { value = ((java.util.Date) value).toInstant(); } if (value instanceof Instant lastIndexed) { if (lastIndexed.isBefore(lastModified)) { reindexItem = true; } } } return reindexItem || !inIndex; } @Override public String createLocationQueryForAdministrableItems(Context context) throws SQLException { StringBuilder locationQuery = new StringBuilder(); if (context.getCurrentUser() != null) { List groupList = EPersonServiceFactory.getInstance().getGroupService() .allMemberGroups(context, context.getCurrentUser()); List communitiesPolicies = AuthorizeServiceFactory.getInstance().getResourcePolicyService() .find(context, context.getCurrentUser(), groupList, Constants.ADMIN, Constants.COMMUNITY); List collectionsPolicies = AuthorizeServiceFactory.getInstance().getResourcePolicyService() .find(context, context.getCurrentUser(), groupList, Constants.ADMIN, Constants.COLLECTION); List allCollections = new ArrayList<>(); for (ResourcePolicy rp : collectionsPolicies) { Collection collection = ContentServiceFactory.getInstance().getCollectionService() .find(context, rp.getdSpaceObject().getID()); allCollections.add(collection); } if (CollectionUtils.isNotEmpty(communitiesPolicies) || CollectionUtils.isNotEmpty(allCollections)) { locationQuery.append("location:( "); for (int i = 0; i < communitiesPolicies.size(); i++) { ResourcePolicy rp = communitiesPolicies.get(i); Community community = ContentServiceFactory.getInstance().getCommunityService() .find(context, rp.getdSpaceObject().getID()); locationQuery.append("m").append(community.getID()); if (i != (communitiesPolicies.size() - 1)) { locationQuery.append(" OR "); } allCollections.addAll(ContentServiceFactory.getInstance().getCommunityService() .getAllCollections(context, community)); } Iterator collIter = allCollections.iterator(); if (communitiesPolicies.size() > 0 && allCollections.size() > 0) { locationQuery.append(" OR "); } while (collIter.hasNext()) { locationQuery.append("l").append(collIter.next().getID()); if (collIter.hasNext()) { locationQuery.append(" OR "); } } locationQuery.append(")"); } else { log.warn("We have a collection or community admin with ID: " + context.getCurrentUser().getID() + " without any administrable collection or community!"); } } return locationQuery.toString(); } public String locationToName(Context context, String field, String value) throws SQLException { if ("location.comm".equals(field) || "location.coll".equals(field)) { int type = ("location.comm").equals(field) ? Constants.COMMUNITY : Constants.COLLECTION; DSpaceObject commColl = null; if (StringUtils.isNotBlank(value)) { commColl = contentServiceFactory.getDSpaceObjectService(type).find(context, UUID.fromString(value)); } if (commColl != null) { return commColl.getName(); } } return value; } //========== SearchService implementation @Override public DiscoverResult search(Context context, IndexableObject dso, DiscoverQuery discoveryQuery) throws SearchServiceException { if (dso != null) { if (dso instanceof IndexableCommunity) { discoveryQuery.addFilterQueries("location:m" + dso.getID()); } else if (dso instanceof IndexableCollection) { discoveryQuery.addFilterQueries("location:l" + dso.getID()); } else if (dso instanceof IndexableItem) { discoveryQuery.addFilterQueries(SearchUtils.RESOURCE_UNIQUE_ID + ":" + dso. getUniqueIndexID()); } } return search(context, discoveryQuery); } @Override public Iterator iteratorSearch(Context context, IndexableObject dso, DiscoverQuery query) throws SearchServiceException { return new SearchIterator(context, dso, query); } @Override public DiscoverResult search(Context context, DiscoverQuery discoveryQuery) throws SearchServiceException { try { if (solrSearchCore.getSolr() == null) { return new DiscoverResult(); } return retrieveResult(context, discoveryQuery); } catch (Exception e) { throw new org.dspace.discovery.SearchServiceException(e.getMessage(), e); } } /** * This class implements an iterator over items that is specifically used to iterate over search results */ private class SearchIterator implements Iterator { private Context context; private DiscoverQuery discoverQuery; private DiscoverResult discoverResult; private IndexableObject dso; private int absoluteCursor; private int relativeCursor; private int pagesize; SearchIterator(Context context, DiscoverQuery discoverQuery) throws SearchServiceException { this.context = context; this.discoverQuery = discoverQuery; this.absoluteCursor = discoverQuery.getStart(); initialise(); } SearchIterator(Context context, IndexableObject dso, DiscoverQuery discoverQuery) throws SearchServiceException { this.context = context; this.dso = dso; this.discoverQuery = discoverQuery; initialise(); } private void initialise() throws SearchServiceException { this.relativeCursor = 0; if (discoverQuery.getMaxResults() != -1) { pagesize = discoverQuery.getMaxResults(); } else { pagesize = 10; } discoverQuery.setMaxResults(pagesize); this.discoverResult = search(context, dso, discoverQuery); } @Override public boolean hasNext() { return absoluteCursor < discoverResult.getTotalSearchResults(); } @Override public Item next() { //paginate getting results from the discoverquery. if (relativeCursor == pagesize) { // get a new page of results when the last element of the previous page has been read int offset = absoluteCursor; // reset the position counter for getting element relativecursor on a page relativeCursor = 0; discoverQuery.setStart(offset); try { discoverResult = search(context, dso, discoverQuery); } catch (SearchServiceException e) { log.error("error while getting search results", e); } } // get the element at position relativecursor on a page IndexableObject res = discoverResult.getIndexableObjects().get(relativeCursor); relativeCursor++; absoluteCursor++; return (Item) res.getIndexedObject(); } } protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQuery) throws SearchServiceException { SolrQuery solrQuery = new SolrQuery(); String query = "*:*"; if (discoveryQuery.getQuery() != null) { query = discoveryQuery.getQuery(); } solrQuery.setQuery(query); // Add any search fields to our query. This is the limited list // of fields that will be returned in the solr result for (String fieldName : discoveryQuery.getSearchFields()) { solrQuery.addField(fieldName); } // Also ensure a few key obj identifier fields are returned with every query solrQuery.addField(SearchUtils.RESOURCE_TYPE_FIELD); solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD); solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID); solrQuery.addField(STATUS_FIELD); if (discoveryQuery.isSpellCheck()) { solrQuery.setParam(SpellingParams.SPELLCHECK_Q, query); solrQuery.setParam(SpellingParams.SPELLCHECK_COLLATE, Boolean.TRUE); solrQuery.setParam("spellcheck", Boolean.TRUE); } for (int i = 0; i < discoveryQuery.getFilterQueries().size(); i++) { String filterQuery = discoveryQuery.getFilterQueries().get(i); solrQuery.addFilterQuery(filterQuery); } if (discoveryQuery.getDSpaceObjectFilters() != null) { solrQuery.addFilterQuery( discoveryQuery.getDSpaceObjectFilters() .stream() .map(filter -> SearchUtils.RESOURCE_TYPE_FIELD + ":" + filter) .collect(joining(" OR ")) ); } for (int i = 0; i < discoveryQuery.getFieldPresentQueries().size(); i++) { String filterQuery = discoveryQuery.getFieldPresentQueries().get(i); solrQuery.addFilterQuery(filterQuery + ":[* TO *]"); } if (discoveryQuery.getStart() != -1) { solrQuery.setStart(discoveryQuery.getStart()); } if (discoveryQuery.getMaxResults() != -1) { solrQuery.setRows(discoveryQuery.getMaxResults()); } if (discoveryQuery.getSortField() != null) { SolrQuery.ORDER order = SolrQuery.ORDER.asc; if (discoveryQuery.getSortOrder().equals(DiscoverQuery.SORT_ORDER.desc)) { order = SolrQuery.ORDER.desc; } solrQuery.addSort(discoveryQuery.getSortField(), order); } for (String property : discoveryQuery.getProperties().keySet()) { List values = discoveryQuery.getProperties().get(property); solrQuery.add(property, values.toArray(new String[values.size()])); } List facetFields = discoveryQuery.getFacetFields(); if (0 < facetFields.size()) { //Only add facet information if there are any facets for (DiscoverFacetField facetFieldConfig : facetFields) { String field = transformFacetField(facetFieldConfig, facetFieldConfig.getField(), false); if (facetFieldConfig.getPrefix() != null) { field = transformPrefixFacetField(facetFieldConfig, facetFieldConfig.getField(), false); } solrQuery.addFacetField(field); // Setting the facet limit in this fashion ensures that each facet can have its own max solrQuery .add("f." + field + "." + FacetParams.FACET_LIMIT, String.valueOf(facetFieldConfig.getLimit())); String facetSort; if (DiscoveryConfigurationParameters.SORT.COUNT.equals(facetFieldConfig.getSortOrder())) { facetSort = FacetParams.FACET_SORT_COUNT; } else { facetSort = FacetParams.FACET_SORT_INDEX; } solrQuery.add("f." + field + "." + FacetParams.FACET_SORT, facetSort); if (facetFieldConfig.getOffset() != -1) { solrQuery.setParam("f." + field + "." + FacetParams.FACET_OFFSET, String.valueOf(facetFieldConfig.getOffset())); } if (facetFieldConfig.getPrefix() != null) { solrQuery.setFacetPrefix(field, facetFieldConfig.getPrefix()); } } } List facetQueries = discoveryQuery.getFacetQueries(); for (String facetQuery : facetQueries) { solrQuery.addFacetQuery(facetQuery); } if (discoveryQuery.getFacetMinCount() != -1) { solrQuery.setFacetMinCount(discoveryQuery.getFacetMinCount()); } if (CollectionUtils.isNotEmpty(facetFields) || CollectionUtils.isNotEmpty(facetQueries)) { solrQuery.setParam(FacetParams.FACET_OFFSET, String.valueOf(discoveryQuery.getFacetOffset())); } if (0 < discoveryQuery.getHitHighlightingFields().size()) { solrQuery.setHighlight(true); solrQuery.add(HighlightParams.USE_PHRASE_HIGHLIGHTER, Boolean.TRUE.toString()); for (DiscoverHitHighlightingField highlightingField : discoveryQuery.getHitHighlightingFields()) { solrQuery.addHighlightField(highlightingField.getField() + "_hl"); solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.FRAGSIZE, String.valueOf(highlightingField.getMaxChars())); solrQuery.add("f." + highlightingField.getField() + "_hl." + HighlightParams.SNIPPETS, String.valueOf(highlightingField.getMaxSnippets())); } } //Add any configured search plugins ! List solrServiceSearchPlugins = DSpaceServicesFactory.getInstance() .getServiceManager().getServicesByType(SolrServiceSearchPlugin.class); for (SolrServiceSearchPlugin searchPlugin : solrServiceSearchPlugins) { searchPlugin.additionalSearchParameters(context, discoveryQuery, solrQuery); } return solrQuery; } protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) throws SQLException, SolrServerException, IOException, SearchServiceException { // we use valid and executeLimit to decide if the solr query need to be re-run if we found some stale objects boolean valid = false; int executionCount = 0; DiscoverResult result = null; SolrQuery solrQuery = resolveToSolrQuery(context, query); // how many re-run of the query are allowed other than the first run int maxAttempts = configurationService.getIntProperty("discovery.removestale.attempts", 3); do { executionCount++; result = new DiscoverResult(); // if we found stale objects we can decide to skip execution of the remaining code to improve performance boolean skipLoadingResponse = false; // use zombieDocs to collect stale found objects List zombieDocs = new ArrayList<>(); QueryResponse solrQueryResponse = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); if (solrQueryResponse != null) { result.setSearchTime(solrQueryResponse.getQTime()); result.setStart(query.getStart()); result.setMaxResults(query.getMaxResults()); result.setTotalSearchResults(solrQueryResponse.getResults().getNumFound()); List searchFields = query.getSearchFields(); for (SolrDocument doc : solrQueryResponse.getResults()) { IndexableObject indexableObject = findIndexableObject(context, doc); if (indexableObject != null) { result.addIndexableObject(indexableObject); } else { // log has warn because we try to fix the issue log.warn(LogHelper.getHeader(context, "Stale entry found in Discovery index," + " as we could not find the DSpace object it refers to. ", "Unique identifier: " + doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID))); // Enables solr to remove documents related to items not on database anymore (Stale) // if maxAttemps is greater than 0 cleanup the index on each step if (maxAttempts >= 0) { Object statusObj = doc.getFirstValue(STATUS_FIELD); if (!(statusObj instanceof String && statusObj.equals(STATUS_FIELD_PREDB))) { zombieDocs.add((String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID)); // avoid to process the response except if we are in the last allowed execution. // When maxAttempts is 0 this will be just the first and last run as the // executionCount is increased at the start of the loop it will be equals to 1 skipLoadingResponse = maxAttempts + 1 != executionCount; } } continue; } if (!skipLoadingResponse) { DiscoverResult.SearchDocument resultDoc = new DiscoverResult.SearchDocument(); // Add information about our search fields for (String field : searchFields) { List valuesAsString = new ArrayList<>(); Optional.ofNullable(doc.getFieldValues(field)) .ifPresent(l -> l.forEach(o -> valuesAsString.add(String.valueOf(o)))); resultDoc.addSearchField(field, valuesAsString.toArray(new String[valuesAsString.size()])); } result.addSearchDocument(indexableObject, resultDoc); if (solrQueryResponse.getHighlighting() != null) { Map> highlightedFields = solrQueryResponse.getHighlighting().get( indexableObject.getUniqueIndexID()); if (MapUtils.isNotEmpty(highlightedFields)) { //We need to remove all the "_hl" appendix strings from our keys Map> resultMap = new HashMap<>(); for (String key : highlightedFields.keySet()) { resultMap.put(key.substring(0, key.lastIndexOf("_hl")), highlightedFields.get(key)); } result.addHighlightedResult(indexableObject, new DiscoverResult.IndexableObjectHighlightResult(indexableObject, resultMap)); } } } } //Resolve our facet field values resolveFacetFields(context, query, result, skipLoadingResponse, solrQueryResponse); //Add total entries count for metadata browsing resolveEntriesCount(result, solrQueryResponse); } // If any stale entries are found in the current page of results, // we remove those stale entries and rerun the same query again. // Otherwise, the query is valid and the results are returned. if (!zombieDocs.isEmpty()) { log.info("Cleaning " + zombieDocs.size() + " stale objects from Discovery Index"); log.info("ZombieDocs "); zombieDocs.forEach(log::info); solrSearchCore.getSolr().deleteById(zombieDocs); solrSearchCore.getSolr().commit(); } else { valid = true; } } while (!valid && executionCount <= maxAttempts); if (!valid && executionCount == maxAttempts) { String message = "The Discovery (Solr) index has a large number of stale entries," + " and we could not complete this request. Please reindex all content" + " to remove these stale entries (e.g. dspace index-discovery -f)."; log.fatal(message); throw new RuntimeException(message); } return result; } /** * Stores the total count of entries for metadata index browsing. The count is calculated by the * json.facet parameter with the following value: * *


     * {
     *     "entries_count": {
     *         "type": "terms",
     *         "field": "facetNameField_filter",
     *         "limit": 0,
     *         "prefix": "prefix_value",
     *         "numBuckets": true
     *     }
     * }
     * 
* * This value is returned in the facets field of the Solr response. * * @param result DiscoverResult object where the total entries count will be stored * @param solrQueryResponse QueryResponse object containing the solr response */ private void resolveEntriesCount(DiscoverResult result, QueryResponse solrQueryResponse) { NestableJsonFacet response = solrQueryResponse.getJsonFacetingResponse(); if (response != null) { BucketBasedJsonFacet facet = response.getBucketBasedFacets("entries_count"); if (facet != null) { result.setTotalEntries(facet.getNumBucketsCount()); } } } private void resolveFacetFields(Context context, DiscoverQuery query, DiscoverResult result, boolean skipLoadingResponse, QueryResponse solrQueryResponse) throws SQLException { List facetFields = solrQueryResponse.getFacetFields(); if (!skipLoadingResponse) { if (facetFields != null) { for (int i = 0; i < facetFields.size(); i++) { FacetField facetField = facetFields.get(i); DiscoverFacetField facetFieldConfig = query.getFacetFields().get(i); List facetValues = facetField.getValues(); if (facetValues != null) { if (facetFieldConfig.getType() .equals(DiscoveryConfigurationParameters.TYPE_DATE) && facetFieldConfig .getSortOrder().equals(DiscoveryConfigurationParameters.SORT.VALUE)) { //If we have a date & are sorting by value, ensure that the results are flipped for a // proper result Collections.reverse(facetValues); } for (FacetField.Count facetValue : facetValues) { String displayedValue = transformDisplayedValue(context, facetField.getName(), facetValue.getName()); String field = transformFacetField(facetFieldConfig, facetField.getName(), true); String authorityValue = transformAuthorityValue(context, facetField.getName(), facetValue.getName()); String sortValue = transformSortValue(context, facetField.getName(), facetValue.getName()); String filterValue = displayedValue; if (StringUtils.isNotBlank(authorityValue)) { filterValue = authorityValue; } result.addFacetResult( field, new DiscoverResult.FacetResult(filterValue, displayedValue, authorityValue, sortValue, facetValue.getCount(), facetFieldConfig.getType())); } } } } if (solrQueryResponse.getFacetQuery() != null && !skipLoadingResponse) { // just retrieve the facets in the order they where requested! // also for the date we ask it in proper (reverse) order // At the moment facet queries are only used for dates LinkedHashMap sortedFacetQueries = new LinkedHashMap<>( solrQueryResponse.getFacetQuery()); for (String facetQuery : sortedFacetQueries.keySet()) { //TODO: do not assume this, people may want to use it for other ends, use a regex to make sure //We have a facet query, the values looks something like: //dateissued.year:[1990 TO 2000] AND -2000 //Prepare the string from {facet.field.name}:[startyear TO endyear] to startyear - endyear String facetField = facetQuery.substring(0, facetQuery.indexOf(":")); String name = ""; String filter = ""; if (facetQuery.indexOf('[') > -1 && facetQuery.lastIndexOf(']') > -1) { name = facetQuery.substring(facetQuery.indexOf('[') + 1); name = name.substring(0, name.lastIndexOf(']')).replaceAll("TO", "-"); filter = facetQuery.substring(facetQuery.indexOf('[')); filter = filter.substring(0, filter.lastIndexOf(']') + 1); } Integer count = sortedFacetQueries.get(facetQuery); //No need to show empty years if (0 < count) { result.addFacetResult(facetField, new DiscoverResult.FacetResult(filter, name, null, name, count, DiscoveryConfigurationParameters .TYPE_DATE)); } } } if (solrQueryResponse.getSpellCheckResponse() != null && !skipLoadingResponse) { String recommendedQuery = solrQueryResponse.getSpellCheckResponse().getCollatedResult(); if (StringUtils.isNotBlank(recommendedQuery)) { result.setSpellCheckQuery(recommendedQuery); } } } } /** * Find the indexable object by type and UUID * * @param context * The relevant DSpace Context. * @param doc * the solr document, the following fields MUST be present RESOURCE_TYPE_FIELD, RESOURCE_ID_FIELD and * HANDLE_FIELD * @return an IndexableObject * @throws SQLException * An exception that provides information on a database access error or other errors. */ protected IndexableObject findIndexableObject(Context context, SolrDocument doc) throws SQLException { String type = (String) doc.getFirstValue(SearchUtils.RESOURCE_TYPE_FIELD); String id = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD); final IndexFactory indexableObjectService = indexObjectServiceFactory. getIndexFactoryByType(type); Optional indexableObject = indexableObjectService.findIndexableObject(context, id); if (!indexableObject.isPresent()) { log.warn("Not able to retrieve object RESOURCE_ID:" + id + " - RESOURCE_TYPE_ID:" + type); } return indexableObject.orElse(null); } public List search(Context context, String query, int offset, int max, String... filterquery) { return search(context, query, null, true, offset, max, filterquery); } @Override public List search(Context context, String query, String orderfield, boolean ascending, int offset, int max, String... filterquery) { try { if (solrSearchCore.getSolr() == null) { return Collections.emptyList(); } SolrQuery solrQuery = new SolrQuery(); solrQuery.setQuery(query); //Only return obj identifier fields in result doc solrQuery.setFields(SearchUtils.RESOURCE_ID_FIELD, SearchUtils.RESOURCE_TYPE_FIELD); solrQuery.setStart(offset); solrQuery.setRows(max); if (orderfield != null) { solrQuery.addSort(orderfield, ascending ? SolrQuery.ORDER.asc : SolrQuery.ORDER.desc); } if (filterquery != null) { solrQuery.addFilterQuery(filterquery); } QueryResponse rsp = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); SolrDocumentList docs = rsp.getResults(); Iterator iter = docs.iterator(); List result = new ArrayList<>(); while (iter.hasNext()) { SolrDocument doc = (SolrDocument) iter.next(); IndexableObject o = findIndexableObject(context, doc); if (o != null) { result.add(o); } } return result; } catch (IOException | SQLException | SolrServerException e) { // Any acception that we get ignore it. // We do NOT want any crashed to shown by the user log.error(LogHelper.getHeader(context, "Error while querying solr", "Query: " + query), e); return new ArrayList<>(0); } } @Override public DiscoverFilterQuery toFilterQuery(Context context, String field, String operator, String value, DiscoveryConfiguration config) throws SQLException { DiscoverFilterQuery result = new DiscoverFilterQuery(); StringBuilder filterQuery = new StringBuilder(); if (StringUtils.isNotBlank(field) && StringUtils.isNotBlank(value)) { filterQuery.append(field); if (operator.endsWith("equals")) { final boolean isStandardField = Optional.ofNullable(config) .flatMap(c -> Optional.ofNullable(c.getSidebarFacet(field))) .map(facet -> facet.getType().equals(DiscoveryConfigurationParameters.TYPE_STANDARD)) .orElse(false); if (!isStandardField) { filterQuery.append("_keyword"); } } else if (operator.endsWith("authority")) { filterQuery.append("_authority"); } if (operator.startsWith("not")) { filterQuery.insert(0, "-"); } filterQuery.append(":"); if ("equals".equals(operator) || "notequals".equals(operator)) { //DO NOT ESCAPE RANGE QUERIES ! if (!value.matches("\\[.*TO.*\\]")) { value = ClientUtils.escapeQueryChars(value); filterQuery.append(value); } else { if (value.matches("\\[\\d{1,4} TO \\d{1,4}\\]")) { int minRange = Integer.parseInt(value.substring(1, value.length() - 1).split(" TO ")[0]); int maxRange = Integer.parseInt(value.substring(1, value.length() - 1).split(" TO ")[1]); value = "[" + String.format("%04d", minRange) + " TO " + String.format("%04d", maxRange) + "]"; } filterQuery.append(value); } } else { //DO NOT ESCAPE RANGE QUERIES ! if (!value.matches("\\[.*TO.*\\]")) { value = ClientUtils.escapeQueryChars(value); filterQuery.append("\"").append(value).append("\""); } else { filterQuery.append(value); } } result.setDisplayedValue(transformDisplayedValue(context, field, value)); } result.setFilterQuery(filterQuery.toString()); return result; } @Override public List getRelatedItems(Context context, Item item, DiscoveryMoreLikeThisConfiguration mltConfig) { List results = new ArrayList<>(); try { SolrQuery solrQuery = new SolrQuery(); //Set the query to handle since this is unique solrQuery.setQuery(SearchUtils.RESOURCE_UNIQUE_ID + ": " + new IndexableItem(item).getUniqueIndexID()); //Only return obj identifier fields in result doc solrQuery.setFields(SearchUtils.RESOURCE_TYPE_FIELD, SearchUtils.RESOURCE_ID_FIELD); //Add the more like this parameters ! solrQuery.setParam(MoreLikeThisParams.MLT, true); //Add a comma separated list of the similar fields @SuppressWarnings("unchecked") java.util.Collection similarityMetadataFields = CollectionUtils .collect(mltConfig.getSimilarityMetadataFields(), new Transformer() { @Override public Object transform(Object input) { //Add the mlt appendix ! return input + "_mlt"; } }); solrQuery.setParam(MoreLikeThisParams.SIMILARITY_FIELDS, StringUtils.join(similarityMetadataFields, ',')); solrQuery.setParam(MoreLikeThisParams.MIN_TERM_FREQ, String.valueOf(mltConfig.getMinTermFrequency())); solrQuery.setParam(MoreLikeThisParams.DOC_COUNT, String.valueOf(mltConfig.getMax())); solrQuery.setParam(MoreLikeThisParams.MIN_WORD_LEN, String.valueOf(mltConfig.getMinWordLength())); if (solrSearchCore.getSolr() == null) { return Collections.emptyList(); } QueryResponse rsp = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD); NamedList mltResults = (NamedList) rsp.getResponse().get("moreLikeThis"); if (mltResults != null && mltResults.get(item.getType() + "-" + item.getID()) != null) { SolrDocumentList relatedDocs = (SolrDocumentList) mltResults.get(item.getType() + "-" + item.getID()); for (Object relatedDoc : relatedDocs) { SolrDocument relatedDocument = (SolrDocument) relatedDoc; IndexableObject relatedItem = findIndexableObject(context, relatedDocument); if (relatedItem instanceof IndexableItem) { results.add(((IndexableItem) relatedItem).getIndexedObject()); } } } } catch (IOException | SQLException | SolrServerException e) { log.error(LogHelper.getHeader(context, "Error while retrieving related items", "Handle: " + item.getHandle()), e); } return results; } @Override public String toSortFieldIndex(String metadataField, String type) { if (StringUtils.equalsIgnoreCase(DiscoverySortConfiguration.SCORE, metadataField)) { return DiscoverySortConfiguration.SCORE; } else if (StringUtils.equals(type, DiscoveryConfigurationParameters.TYPE_DATE)) { return metadataField + "_dt"; } else { return metadataField + "_sort"; } } /** * Gets the solr field that contains the facet value split on each word break to the end, so can be searched * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} * Only applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular * facet filter field */ protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) { if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT) || facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { if (removePostfix) { return field.substring(0, field.lastIndexOf(SOLR_FIELD_SUFFIX_FACET_PREFIXES)); } else { return field + SOLR_FIELD_SUFFIX_FACET_PREFIXES; } } else { return this.transformFacetField(facetFieldConfig, field, removePostfix); } } protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) { if (field.contains(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { return this.transformPrefixFacetField(facetFieldConfig, field, removePostfix); } if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { if (removePostfix) { return field.substring(0, field.lastIndexOf("_filter")); } else { return field + "_filter"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { if (removePostfix) { return field.substring(0, field.lastIndexOf(".year")); } else { return field + ".year"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_AC)) { if (removePostfix) { return field.substring(0, field.lastIndexOf("_ac")); } else { return field + "_ac"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { if (removePostfix) { return StringUtils.substringBeforeLast(field, "_tax_"); } else { //Only display top level filters ! return field + "_tax_0_filter"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_AUTHORITY)) { if (removePostfix) { return field.substring(0, field.lastIndexOf("_acid")); } else { return field + "_acid"; } } else { return field; } } protected String transformDisplayedValue(Context context, String field, String value) throws SQLException { if (value == null) { return null; } if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); if (separator == null) { separator = SearchUtils.FILTER_SEPARATOR; } //Escape any regex chars separator = java.util.regex.Pattern.quote(separator); String[] fqParts = value.split(separator); StringBuilder valueBuffer = new StringBuilder(); int start = fqParts.length / 2; for (int i = start; i < fqParts.length; i++) { String[] split = fqParts[i].split(SearchUtils.AUTHORITY_SEPARATOR, 2); valueBuffer.append(split[0]); } value = valueBuffer.toString(); } else if (value.matches("\\((.*?)\\)")) { //The brackets where added for better solr results, remove the first & last one value = value.substring(1, value.length() - 1); } return value; } protected String transformAuthorityValue(Context context, String field, String value) throws SQLException { if (value == null) { return null; } if (field.equals("location.comm") || field.equals("location.coll")) { return value; } if (field.endsWith("_filter") || field.endsWith("_ac") || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); if (separator == null) { separator = SearchUtils.FILTER_SEPARATOR; } //Escape any regex chars separator = java.util.regex.Pattern.quote(separator); String[] fqParts = value.split(separator); StringBuilder authorityBuffer = new StringBuilder(); int start = fqParts.length / 2; for (int i = start; i < fqParts.length; i++) { String[] split = fqParts[i].split(SearchUtils.AUTHORITY_SEPARATOR, 2); if (split.length == 2) { authorityBuffer.append(split[1]); } } if (authorityBuffer.length() > 0) { return authorityBuffer.toString(); } } return null; } protected String transformSortValue(Context context, String field, String value) throws SQLException { if (value == null) { return null; } if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") || field.endsWith("_acid")) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); if (separator == null) { separator = SearchUtils.FILTER_SEPARATOR; } //Escape any regex chars separator = java.util.regex.Pattern.quote(separator); String[] fqParts = value.split(separator); StringBuilder valueBuffer = new StringBuilder(); int end = fqParts.length / 2; for (int i = 0; i < end; i++) { valueBuffer.append(fqParts[i]); } value = valueBuffer.toString(); } else if (value.matches("\\((.*?)\\)")) { //The brackets where added for better solr results, remove the first & last one value = value.substring(1, value.length() - 1); } return value; } @Override public void indexContent(Context context, IndexableObject dso, boolean force, boolean commit) throws SearchServiceException, SQLException { indexContent(context, dso, force); if (commit) { commit(); } } @Override public void indexContent(Context context, IndexableObject indexableObject, boolean force, boolean commit, boolean preDb) throws SearchServiceException, SQLException { if (preDb) { try { final IndexFactory indexableObjectFactory = indexObjectServiceFactory. getIndexableObjectFactory(indexableObject); if (force || requiresIndexing(indexableObject.getUniqueIndexID(), indexableObject.getLastModified())) { update(context, indexableObjectFactory, indexableObject, true); log.info(LogHelper.getHeader(context, "indexed_object", indexableObject.getUniqueIndexID())); } } catch (IOException | SQLException | SolrServerException | SearchServiceException e) { log.error(e.getMessage(), e); } } else { indexContent(context, indexableObject, force); } if (commit) { commit(); } } @Override public void commit() throws SearchServiceException { try { if (solrSearchCore.getSolr() != null) { solrSearchCore.getSolr().commit(); } } catch (IOException | SolrServerException e) { throw new SearchServiceException(e.getMessage(), e); } } @Override public String escapeQueryChars(String query) { // Use Solr's built in query escape tool // WARNING: You should only escape characters from user entered queries, // otherwise you may accidentally BREAK field-based queries (which often // rely on special characters to separate the field from the query value) return ClientUtils.escapeQueryChars(query); } @Override public FacetYearRange getFacetYearRange(Context context, IndexableObject scope, DiscoverySearchFilterFacet facet, List filterQueries, DiscoverQuery parentQuery) throws SearchServiceException { FacetYearRange result = new FacetYearRange(facet); result.calculateRange(context, filterQueries, scope, this, parentQuery); return result; } @Override public String calculateExtremeValue(Context context, String valueField, String sortField, DiscoverQuery.SORT_ORDER sortOrder) throws SearchServiceException { DiscoverQuery maxQuery = new DiscoverQuery(); maxQuery.setMaxResults(1); //Set our query to anything that has this value maxQuery.addFieldPresentQueries(valueField); //Set sorting so our last value will appear on top maxQuery.setSortField(sortField, sortOrder); maxQuery.addSearchField(valueField); DiscoverResult maxResult = this.search(context,maxQuery); if (0 < maxResult.getIndexableObjects().size()) { List searchDocuments = maxResult .getSearchDocument(maxResult.getIndexableObjects().get(0)); if (0 < searchDocuments.size() && 0 < searchDocuments.get(0).getSearchFieldValues (valueField).size()) { return searchDocuments.get(0).getSearchFieldValues(valueField).get(0); } } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy