edu.unc.lib.dl.search.solr.service.SolrSearchService.java Source code

Java tutorial

Introduction

Here is the source code for edu.unc.lib.dl.search.solr.service.SolrSearchService.java

Source

/**
 * Copyright 2008 The University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.unc.lib.dl.search.solr.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.GroupParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import edu.unc.lib.dl.search.solr.model.AbstractHierarchicalFacet;
import edu.unc.lib.dl.search.solr.model.BriefObjectMetadata;
import edu.unc.lib.dl.acl.exception.AccessRestrictionException;
import edu.unc.lib.dl.acl.util.AccessGroupConstants;
import edu.unc.lib.dl.acl.util.AccessGroupSet;
import edu.unc.lib.dl.acl.util.GroupsThreadStore;
import edu.unc.lib.dl.search.solr.model.BriefObjectMetadataBean;
import edu.unc.lib.dl.search.solr.model.CutoffFacet;
import edu.unc.lib.dl.search.solr.model.FacetFieldFactory;
import edu.unc.lib.dl.search.solr.model.FacetFieldObject;
import edu.unc.lib.dl.search.solr.model.GroupedMetadataBean;
import edu.unc.lib.dl.search.solr.model.IdListRequest;
import edu.unc.lib.dl.search.solr.model.SearchRequest;
import edu.unc.lib.dl.search.solr.model.SearchResultResponse;
import edu.unc.lib.dl.search.solr.model.SearchState;
import edu.unc.lib.dl.search.solr.model.SimpleIdRequest;
import edu.unc.lib.dl.search.solr.util.DateFormatUtil;
import edu.unc.lib.dl.search.solr.util.FacetFieldUtil;
import edu.unc.lib.dl.search.solr.util.SearchFieldKeys;
import edu.unc.lib.dl.search.solr.util.SearchSettings;
import edu.unc.lib.dl.search.solr.util.SolrSettings;

/**
 * Performs CDR specific Solr search tasks, using solrj for connecting to the solr instance.
 * 
 * @author bbpennel
 */
public class SolrSearchService {
    private static final Logger LOG = LoggerFactory.getLogger(SolrSearchService.class);
    private SolrServer server;
    @Autowired
    protected SolrSettings solrSettings;
    @Autowired
    protected SearchSettings searchSettings;
    @Autowired
    protected FacetFieldFactory facetFieldFactory;
    protected FacetFieldUtil facetFieldUtil;

    public SolrSearchService() {
    }

    /**
     * Establish the SolrServer object according to the configuration specified in settings.
     */
    protected void initializeSolrServer() {
        server = solrSettings.getSolrServer();
    }

    /**
     * Retrieves the Solr tuple representing the object identified by id.
     * 
     * @param id
     *           identifier (uuid) of the object to retrieve.
     * @param userAccessGroups
     * @return
     */
    public BriefObjectMetadataBean getObjectById(SimpleIdRequest idRequest) {
        LOG.debug("In getObjectbyID");

        QueryResponse queryResponse = null;
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder();
        query.append(solrSettings.getFieldName(SearchFieldKeys.ID.name())).append(':')
                .append(SolrSettings.sanitize(idRequest.getId()));
        try {
            // Add access restrictions to query
            addAccessRestrictions(query, idRequest.getAccessGroups());
            /*
             * if (idRequest.getAccessTypeFilter() != null) { addAccessRestrictions(query, idRequest.getAccessGroups(),
             * idRequest.getAccessTypeFilter()); }
             */
        } catch (AccessRestrictionException e) {
            // If the user doesn't have any access groups, they don't have access to anything, return null.
            LOG.error("Error while attempting to add access restrictions to object " + idRequest.getId(), e);
            return null;
        }

        // Restrict the result fields if set
        if (idRequest.getResultFields() != null) {
            for (String field : idRequest.getResultFields()) {
                solrQuery.addField(solrSettings.getFieldName(field));
            }
        }

        solrQuery.setQuery(query.toString());
        solrQuery.setRows(1);

        LOG.debug("getObjectById query: " + solrQuery.toString());
        try {
            queryResponse = server.query(solrQuery);
        } catch (SolrServerException e) {
            LOG.error("Error retrieving Solr object request: " + e);
            return null;
        }

        List<BriefObjectMetadataBean> results = queryResponse.getBeans(BriefObjectMetadataBean.class);
        if (results != null && results.size() > 0) {
            return results.get(0);
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public List<BriefObjectMetadata> getObjectsById(IdListRequest listRequest) {
        QueryResponse queryResponse = null;
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder("*:* ");

        try {
            // Add access restrictions to query
            addAccessRestrictions(query, listRequest.getAccessGroups());
        } catch (AccessRestrictionException e) {
            // If the user doesn't have any access groups, they don't have access to anything, return null.
            LOG.error("Error while attempting to add access restrictions to object " + listRequest.getId(), e);
            return null;
        }

        query.append(" AND (");

        boolean first = true;
        for (String id : listRequest.getIds()) {
            if (first)
                first = false;
            else
                query.append(" OR ");
            query.append(solrSettings.getFieldName(SearchFieldKeys.ID.name())).append(':')
                    .append(SolrSettings.sanitize(id));
        }

        query.append(")");

        // Restrict the result fields if set
        if (listRequest.getResultFields() != null) {
            for (String field : listRequest.getResultFields()) {
                solrQuery.addField(solrSettings.getFieldName(field));
            }
        }

        solrQuery.setQuery(query.toString());
        solrQuery.setRows(listRequest.getIds().size());

        LOG.debug("getObjectsById query: " + solrQuery.toString());
        try {
            queryResponse = server.query(solrQuery);
        } catch (SolrServerException e) {
            LOG.error("Error retrieving Solr object request: " + e);
            return null;
        }

        List<?> results = queryResponse.getBeans(BriefObjectMetadataBean.class);
        return (List<BriefObjectMetadata>) results;
    }

    /**
     * Retrieves search results as a SearchResultResponse. Will not return the solr query use for the request.
     * 
     * @param searchRequest
     * @return
     */
    public SearchResultResponse getSearchResults(SearchRequest searchRequest) {
        return getSearchResults(searchRequest, false);
    }

    /**
     * Generates a solr query from the search state specified in searchRequest and executes it, returning a result set of
     * BriefObjectMetadataBeans and, optionally, the SolrQuery generated for this request.
     * 
     * @param searchRequest
     * @param returnQuery
     * @return
     */
    public SearchResultResponse getSearchResults(SearchRequest searchRequest, boolean returnQuery) {
        LOG.debug("In getSearchResults: " + searchRequest.getSearchState());

        SolrQuery solrQuery = this.generateSearch(searchRequest);

        LOG.debug("getSearchResults query: " + solrQuery);
        try {
            SearchResultResponse resultResponse = executeSearch(solrQuery, searchRequest.getSearchState(),
                    searchRequest.isRetrieveFacets(), returnQuery);
            // Add in the correct rollup representatives when they are missing, if we are rolling up on the rollup id
            if (searchRequest.getSearchState().getRollup() != null && searchRequest.getSearchState().getRollup()
                    && searchRequest.getSearchState().getRollupField() == null) {
                for (BriefObjectMetadata item : resultResponse.getResultList()) {
                    if (item.getId() != null && !item.getId().equals(item.getRollup())) {
                        BriefObjectMetadataBean representative = this.getObjectById(
                                new SimpleIdRequest(item.getRollup(), searchRequest.getAccessGroups()));
                        if (representative != null) {
                            GroupedMetadataBean grouped = (GroupedMetadataBean) item;
                            grouped.getItems().add(representative);
                            grouped.setRepresentative(representative);
                        }
                    }
                }
            }

            return resultResponse;
        } catch (SolrServerException e) {
            LOG.error("Error retrieving Solr search result request", e);
        }
        return null;
    }

    /**
     * Adds access restrictions to the provided query string buffer. If there are no access groups in the provided group
     * set, then an AccessRestrictionException is thrown as it is invalid for a user to have no permissions. If the user
     * is an admin, then do not restrict access
     * 
     * @param query
     *           string buffer containing the query to append access groups to.
     * @param accessGroups
     *           set of access groups to append to the query
     * @return The original query restricted to results available to the provided access groups
     * @throws AccessRestrictionException
     *            thrown if no groups are provided.
     */
    protected StringBuilder addAccessRestrictions(StringBuilder query) throws AccessRestrictionException {
        return this.addAccessRestrictions(query, null);
    }

    protected StringBuilder addAccessRestrictions(StringBuilder query, AccessGroupSet accessGroups)
            throws AccessRestrictionException {
        return this.addAccessRestrictions(query, accessGroups, searchSettings.getAllowPatronAccess());
    }

    protected StringBuilder addAccessRestrictions(StringBuilder query, AccessGroupSet accessGroups,
            boolean allowPatronAccess) throws AccessRestrictionException {
        if (accessGroups == null || accessGroups.size() == 0) {
            accessGroups = GroupsThreadStore.getGroups();
            if (accessGroups == null || accessGroups.size() == 0)
                throw new AccessRestrictionException("No access groups were provided.");
        }
        if (!accessGroups.contains(AccessGroupConstants.ADMIN_GROUP)) {
            String joinedGroups = accessGroups.joinAccessGroups(" OR ", null, true);
            if (allowPatronAccess) {
                query.append(" AND (").append("readGroup:(").append(joinedGroups).append(')')
                        .append(" OR adminGroup:(").append(joinedGroups).append("))");
            } else {
                query.append(" AND adminGroup:(").append(joinedGroups).append(')');
            }
        }
        return query;
    }

    /**
     * Attempts to retrieve the hierarchical facet from the facet field corresponding to fieldKey that matches the value
     * of searchValue. Retrieves and populates all tiers leading up to the tier number given in searchValue.
     * 
     * @param fieldKey
     *           Key of the facet field to search for the facet within.
     * @param searchValue
     *           Value to find a matching facet for, should be formatted <tier>,<value>
     * @param accessGroups
     * @return
     */
    public FacetFieldObject getHierarchicalFacet(AbstractHierarchicalFacet facet, AccessGroupSet accessGroups) {
        QueryResponse queryResponse = null;
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder();
        query.append("[* TO *]");

        try {
            // Add access restrictions to query
            addAccessRestrictions(query, accessGroups);
        } catch (AccessRestrictionException e) {
            // If the user doesn't have any access groups, they don't have access to anything, return null.
            LOG.error(e.getMessage());
            return null;
        }
        solrQuery.setQuery(query.toString());
        solrQuery.setRows(0);

        solrQuery.setFacet(true);
        solrQuery.setFacetMinCount(1);

        String solrFieldName = solrSettings.getFieldName(facet.getFieldName());

        solrQuery.addFacetField(solrFieldName);
        solrQuery.setFacetPrefix(solrFieldName, facet.getSearchValue());

        LOG.debug("getHierarchicalFacet query: " + solrQuery.toString());
        try {
            queryResponse = server.query(solrQuery);
        } catch (SolrServerException e) {
            LOG.error("Error retrieving Solr object request: " + e);
            return null;
        }
        FacetField facetField = queryResponse.getFacetField(solrFieldName);
        if (facetField.getValueCount() == 0)
            return null;
        return facetFieldFactory.createFacetFieldObject(facet.getFieldName(),
                queryResponse.getFacetField(solrFieldName));
    }

    /**
     * Gets the ancestor path facet for the provided pid, given the access groups provided.
     * 
     * @param pid
     * @param accessGroups
     * @return
     */
    public CutoffFacet getAncestorPath(String pid, AccessGroupSet accessGroups) {
        List<String> resultFields = new ArrayList<String>();
        resultFields.add(SearchFieldKeys.ANCESTOR_PATH.name());

        SimpleIdRequest idRequest = new SimpleIdRequest(pid, resultFields, accessGroups);

        BriefObjectMetadataBean rootNode = null;
        try {
            rootNode = getObjectById(idRequest);
        } catch (Exception e) {
            LOG.error("Error while retrieving Solr entry for ", e);
        }
        if (rootNode == null)
            return null;
        return rootNode.getAncestorPathFacet();
    }

    public Date getTimestamp(String pid, AccessGroupSet accessGroups) {
        QueryResponse queryResponse = null;
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder();
        query.append(solrSettings.getFieldName(SearchFieldKeys.ID.name())).append(':')
                .append(SolrSettings.sanitize(pid));
        try {
            // Add access restrictions to query
            addAccessRestrictions(query, accessGroups);
        } catch (AccessRestrictionException e) {
            // If the user doesn't have any access groups, they don't have access to anything, return null.
            LOG.error("Error while attempting to add access restrictions to object " + pid, e);
            return null;
        }

        solrQuery.addField(solrSettings.getFieldName(SearchFieldKeys.TIMESTAMP.name()));

        solrQuery.setQuery(query.toString());
        solrQuery.setRows(1);

        LOG.debug("query: " + solrQuery.toString());
        try {
            queryResponse = server.query(solrQuery);
        } catch (SolrServerException e) {
            LOG.error("Error retrieving Solr object request: " + e);
            return null;
        }

        if (queryResponse.getResults().getNumFound() == 0)
            return null;
        return (Date) queryResponse.getResults().get(0).getFieldValue("timestamp");
    }

    public BriefObjectMetadata addSelectedContainer(String containerPid, SearchState searchState,
            boolean applyCutoffs) {
        BriefObjectMetadata selectedContainer = getObjectById(new SimpleIdRequest(containerPid));
        if (selectedContainer == null)
            return null;
        CutoffFacet selectedPath = selectedContainer.getPath();
        if (applyCutoffs)
            selectedPath.setCutoff(selectedPath.getHighestTier() + 1);
        searchState.getFacets().put(SearchFieldKeys.ANCESTOR_PATH.name(), selectedPath);
        return selectedContainer;
    }

    /**
     * Wrapper method for executing a solr query.
     * 
     * @param query
     * @return
     * @throws SolrServerException
     */
    protected QueryResponse executeQuery(SolrQuery query) throws SolrServerException {
        return server.query(query);
    }

    /**
     * Constructs a SolrQuery object from the search state specified within a SearchRequest object. The request may
     * optionally request to retrieve facet results in addition to search results.
     * 
     * @param searchRequest
     * @param isRetrieveFacetsRequest
     * @return
     */
    protected SolrQuery generateSearch(SearchRequest searchRequest) {
        SearchState searchState = (SearchState) searchRequest.getSearchState();
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder termQuery = new StringBuilder();

        // Generate search term query string
        addSearchFields(searchState, termQuery);

        // Add range Fields to the query
        addRangeFields(searchState, termQuery);

        // No query terms given, make it an everything query
        StringBuilder query = new StringBuilder();
        if (termQuery.length() == 0) {
            query.append("*:* ");
        } else {
            query.append('(').append(termQuery).append(')');
        }

        // Add access restrictions to query
        try {
            addAccessRestrictions(query, searchRequest.getAccessGroups());
        } catch (AccessRestrictionException e) {
            // If the user doesn't have any access groups, they don't have access to anything, return null.
            LOG.debug("User had no access groups", e);
            return null;
        }

        // Add query
        solrQuery.setQuery(query.toString());

        if (searchState.getResultFields() != null) {
            for (String field : searchState.getResultFields()) {
                String solrFieldName = solrSettings.getFieldName(field);
                if (solrFieldName != null)
                    solrQuery.addField(solrFieldName);
            }
        }

        if (searchState.getRollup() != null && searchState.getRollup()) {
            solrQuery.set(GroupParams.GROUP, true);
            if (searchState.getRollupField() == null)
                solrQuery.set(GroupParams.GROUP_FIELD, solrSettings.getFieldName(SearchFieldKeys.ROLLUP_ID.name()));
            else
                solrQuery.set(GroupParams.GROUP_FIELD, solrSettings.getFieldName(searchState.getRollupField()));

            solrQuery.set(GroupParams.GROUP_TOTAL_COUNT, true);
            if (searchState.getFacetsToRetrieve() != null && searchState.getFacetsToRetrieve().size() > 0) {
                solrQuery.set(GroupParams.GROUP_FACET, true);
            }
        }

        // Add sort parameters
        List<SearchSettings.SortField> sortFields = searchSettings.sortTypes.get(searchState.getSortType());
        if (sortFields != null) {
            for (int i = 0; i < sortFields.size(); i++) {
                SearchSettings.SortField sortField = sortFields.get(i);
                SolrQuery.ORDER sortOrder = SolrQuery.ORDER.valueOf(sortField.getSortOrder());
                if (!searchState.getSortNormalOrder())
                    sortOrder = sortOrder.reverse();
                solrQuery.addSort(solrSettings.getFieldName(sortField.getFieldName()), sortOrder);
            }
        }

        // Set requested resource types
        String resourceTypeFilter = this.getResourceTypeFilter(searchState.getResourceTypes());
        if (resourceTypeFilter != null) {
            solrQuery.addFilterQuery(resourceTypeFilter);
        }

        // Turn on faceting
        if (searchRequest.isRetrieveFacets()) {
            solrQuery.setFacet(true);
            solrQuery.setFacetMinCount(1);
            if (searchState.getBaseFacetLimit() != null)
                solrQuery.setFacetLimit(searchState.getBaseFacetLimit());

            if (searchState.getFacetsToRetrieve() != null) {
                // Add facet fields
                for (String facetName : searchState.getFacetsToRetrieve()) {
                    String facetField = solrSettings.getFieldName(facetName);
                    if (facetField != null)
                        solrQuery.addFacetField(solrSettings.getFieldName(facetName));
                }
            }
        }

        // Override the base facet limit if overrides are given.
        if (searchState.getFacetLimits() != null) {
            for (Entry<String, Integer> facetLimit : searchState.getFacetLimits().entrySet()) {
                solrQuery.add("f." + solrSettings.getFieldName(facetLimit.getKey()) + ".facet.limit",
                        facetLimit.getValue().toString());
            }
        }

        // Add facet limits
        Map<String, Object> facets = searchState.getFacets();
        if (facets != null) {
            Iterator<Entry<String, Object>> facetIt = facets.entrySet().iterator();
            while (facetIt.hasNext()) {
                Entry<String, Object> facetEntry = facetIt.next();

                if (facetEntry.getValue() instanceof String) {
                    LOG.debug("Adding facet " + facetEntry.getKey() + " as a String");
                    // Add Normal facets
                    solrQuery.addFilterQuery(solrSettings.getFieldName(facetEntry.getKey()) + ":\""
                            + SolrSettings.sanitize((String) facetEntry.getValue()) + "\"");
                } else {
                    LOG.debug("Adding facet " + facetEntry.getKey() + " as a "
                            + facetEntry.getValue().getClass().getName());
                    facetFieldUtil.addToSolrQuery(facetEntry.getValue(), solrQuery);
                }
            }
        }

        // Scope hierarchical facet results to the highest tier selected within the facet tree
        if (searchRequest.isRetrieveFacets() && searchRequest.isApplyCutoffs()
                && searchState.getFacetsToRetrieve() != null) {
            Set<String> facetsQueried = searchState.getFacets().keySet();
            // Apply closing cutoff to all cutoff facets that are being retrieved but not being queried for
            for (String fieldKey : searchState.getFacetsToRetrieve()) {
                if (!facetsQueried.contains(fieldKey)) {
                    facetFieldUtil.addDefaultFacetPivot(fieldKey, solrQuery);
                }
            }

            // Add individual facet field sorts if they are present.
            if (searchState.getFacetSorts() != null) {
                for (Entry<String, String> facetSort : searchState.getFacetSorts().entrySet()) {
                    solrQuery.add("f." + solrSettings.getFieldName(facetSort.getKey()) + ".facet.sort",
                            facetSort.getValue());
                }
            }
        }

        // Set Navigation options
        if (searchState.getStartRow() != null)
            solrQuery.setStart(searchState.getStartRow());
        if (searchState.getRowsPerPage() != null)
            solrQuery.setRows(searchState.getRowsPerPage());

        return solrQuery;
    }

    /**
     * Add search fields from a search state to the given termQuery
     * 
     * @param searchState
     * @param termQuery
     */
    private void addSearchFields(SearchState searchState, StringBuilder termQuery) {
        // Generate search term query string
        String searchType = null;
        Map<String, String> searchFields = searchState.getSearchFields();
        if (searchFields != null) {
            Iterator<String> searchTypeIt = searchFields.keySet().iterator();
            while (searchTypeIt.hasNext()) {
                searchType = searchTypeIt.next();
                String fieldValue = searchState.getSearchFields().get(searchType);
                // Special "field exists" keyword
                if ("*".equals(fieldValue)) {
                    if (termQuery.length() > 0)
                        termQuery.append(" AND ");
                    termQuery.append(solrSettings.getFieldName(searchType)).append(":*");
                    continue;
                }
                List<String> searchFragments = SolrSettings.getSearchTermFragments(fieldValue);
                if (searchFragments != null && searchFragments.size() > 0) {
                    if (termQuery.length() > 0)
                        termQuery.append(" AND ");
                    LOG.debug("{} : {}", searchType, searchFragments);
                    termQuery.append(solrSettings.getFieldName(searchType)).append(':').append('(');
                    boolean firstTerm = true;
                    for (String searchFragment : searchFragments) {
                        if (firstTerm)
                            firstTerm = false;
                        else
                            termQuery.append(' ').append(searchState.getSearchTermOperator()).append(' ');
                        termQuery.append(searchFragment);
                    }
                    termQuery.append(')');
                }
            }
        }
    }

    private void addRangeFields(SearchState searchState, StringBuilder termQuery) {
        Map<String, SearchState.RangePair> rangeFields = searchState.getRangeFields();
        if (rangeFields != null) {
            Iterator<Map.Entry<String, SearchState.RangePair>> rangeTermIt = rangeFields.entrySet().iterator();
            while (rangeTermIt.hasNext()) {
                Map.Entry<String, SearchState.RangePair> rangeTerm = rangeTermIt.next();
                if (rangeTerm != null && !(rangeTerm.getValue().getLeftHand() == null
                        && rangeTerm.getValue().getRightHand() == null)) {

                    if (termQuery.length() > 0)
                        termQuery.append(" AND ");

                    termQuery.append(solrSettings.getFieldName(rangeTerm.getKey())).append(":[");

                    if (rangeTerm.getValue().getLeftHand() == null
                            || rangeTerm.getValue().getLeftHand().length() == 0) {
                        termQuery.append('*');
                    } else {
                        if (searchSettings.dateSearchableFields.contains(rangeTerm.getKey())) {
                            try {
                                termQuery.append(DateFormatUtil.getFormattedDate(rangeTerm.getValue().getLeftHand(),
                                        true, false));
                            } catch (NumberFormatException e) {
                                termQuery.append('*');
                            }
                        } else {
                            termQuery.append(SolrSettings.sanitize(rangeTerm.getValue().getLeftHand()));
                        }
                    }
                    termQuery.append(" TO ");
                    if (rangeTerm.getValue().getRightHand() == null
                            || rangeTerm.getValue().getRightHand().length() == 0) {
                        termQuery.append('*');
                    } else {
                        if (searchSettings.dateSearchableFields.contains(rangeTerm.getKey())) {
                            try {
                                termQuery.append(DateFormatUtil
                                        .getFormattedDate(rangeTerm.getValue().getRightHand(), true, true));
                            } catch (NumberFormatException e) {
                                termQuery.append('*');
                            }
                        } else {
                            termQuery.append(SolrSettings.sanitize(rangeTerm.getValue().getRightHand()));
                        }
                    }
                    termQuery.append("] ");
                }
            }
        }
    }

    /**
     * Executes a SolrQuery based off of a search state and stores the results as BriefObjectMetadataBeans.
     * 
     * @param query
     *           the solr query to be executed
     * @param searchState
     *           the search state used to generate this SolrQuery
     * @param isRetrieveFacetsRequest
     *           indicates if facet results hould be returned
     * @param returnQuery
     *           indicates whether to return the solr query object as part of the response.
     * @return
     * @throws SolrServerException
     */
    @SuppressWarnings("unchecked")
    protected SearchResultResponse executeSearch(SolrQuery query, SearchState searchState,
            boolean isRetrieveFacetsRequest, boolean returnQuery) throws SolrServerException {
        QueryResponse queryResponse = server.query(query);

        GroupResponse groupResponse = queryResponse.getGroupResponse();
        SearchResultResponse response = new SearchResultResponse();
        if (groupResponse != null) {
            List<BriefObjectMetadata> groupResults = new ArrayList<BriefObjectMetadata>();
            for (GroupCommand groupCmd : groupResponse.getValues()) {
                // response.setResultCount(groupCmd.getMatches());
                response.setResultCount(groupCmd.getNGroups());
                for (Group group : groupCmd.getValues()) {
                    GroupedMetadataBean grouped = new GroupedMetadataBean(group.getGroupValue(),
                            this.server.getBinder().getBeans(BriefObjectMetadataBean.class, group.getResult()),
                            group.getResult().getNumFound());
                    groupResults.add(grouped);
                }
            }
            response.setResultList(groupResults);

        } else {
            List<?> results = queryResponse.getBeans(BriefObjectMetadataBean.class);
            response.setResultList((List<BriefObjectMetadata>) results);
            // Store the number of results
            response.setResultCount(queryResponse.getResults().getNumFound());
        }

        if (isRetrieveFacetsRequest) {
            // Store facet results
            response.setFacetFields(facetFieldFactory.createFacetFieldList(queryResponse.getFacetFields()));
            // Add empty entries for any empty facets, then sort the list
            if (response.getFacetFields() != null) {
                if (searchState.getFacetsToRetrieve() != null
                        && searchState.getFacetsToRetrieve().size() != response.getFacetFields().size()) {
                    facetFieldFactory.addMissingFacetFieldObjects(response.getFacetFields(),
                            searchState.getFacetsToRetrieve());
                }
            }
        } else {
            response.setFacetFields(null);
        }

        // Set search state that generated this result
        response.setSearchState(searchState);

        // Add the query to the result if it was requested
        if (returnQuery) {
            response.setGeneratedQuery(query);
        }
        return response;
    }

    /**
     * Generates a solr query style string to add a resource type filter for the given list of resources types.
     * 
     * @param resourceTypes
     * @return
     */
    protected String getResourceTypeFilter(List<String> resourceTypes) {
        if (resourceTypes == null || resourceTypes.size() == 0)
            return null;

        StringBuilder sb = new StringBuilder();
        boolean firstType = true;
        String resourceTypeLabel = solrSettings.getFieldName(SearchFieldKeys.RESOURCE_TYPE.name());
        Iterator<String> resourceTypeIt = resourceTypes.iterator();
        while (resourceTypeIt.hasNext()) {
            if (firstType)
                firstType = false;
            else
                sb.append(" OR ");
            sb.append(resourceTypeLabel).append(':').append(resourceTypeIt.next()).append(' ');
        }
        return sb.toString();
    }

    /**
     * Returns the value of a single field from the object identified by pid.
     * 
     * @param pid
     * @param field
     * @return The value of the specified field or null if it wasn't found.
     */
    public Object getField(String pid, String field) throws SolrServerException {
        QueryResponse queryResponse = null;
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder();
        query.append("id:").append(SolrSettings.sanitize(pid));
        solrQuery.setQuery(query.toString());
        solrQuery.addField(field);

        queryResponse = server.query(solrQuery);
        if (queryResponse.getResults().getNumFound() > 0) {
            return queryResponse.getResults().get(0).getFieldValue(field);
        }
        return null;
    }

    /**
     * Returns a combined set of distinct field values for one or more fields, limited by the set of access groups
     * provided
     * 
     * @param fields
     *           Solr field names to retrieve distinct values for
     * @param maxValuesPerField
     *           Max number of distinct values to retrieve for each field
     * @param accessGroups
     * @return
     * @throws AccessRestrictionException
     * @throws SolrServerException
     */
    public java.util.Collection<String> getDistinctFieldValues(String[] fields, int maxValuesPerField,
            AccessGroupSet accessGroups) throws AccessRestrictionException, SolrServerException {
        SolrQuery solrQuery = new SolrQuery();
        StringBuilder query = new StringBuilder("*:*");
        addAccessRestrictions(query, accessGroups);
        solrQuery.setQuery(query.toString());
        solrQuery.setFacet(true);
        for (String facetField : fields)
            solrQuery.addFacetField(facetField);
        solrQuery.setFacetLimit(maxValuesPerField);
        solrQuery.setFacetSort("index");

        QueryResponse queryResponse = server.query(solrQuery);
        // Determine initial capacity for the result list
        int numberValues = 0;
        for (FacetField facet : queryResponse.getFacetFields())
            numberValues += facet.getValueCount();

        java.util.Collection<String> fieldValues = new java.util.HashSet<String>(numberValues);
        for (FacetField facet : queryResponse.getFacetFields())
            for (Count count : facet.getValues())
                fieldValues.add(count.getName());

        return fieldValues;
    }

    public SolrSettings getSolrSettings() {
        return solrSettings;
    }

    public void setSolrSettings(SolrSettings solrSettings) {
        this.solrSettings = solrSettings;
    }

    public SearchSettings getSearchSettings() {
        return searchSettings;
    }

    public void setSearchSettings(SearchSettings searchSettings) {
        this.searchSettings = searchSettings;
    }

    public void setFacetFieldFactory(FacetFieldFactory facetFieldFactory) {
        this.facetFieldFactory = facetFieldFactory;
    }

    public void setFacetFieldUtil(FacetFieldUtil facetFieldUtil) {
        this.facetFieldUtil = facetFieldUtil;
    }
}