/**
* 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.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import edu.unc.lib.dl.search.solr.exception.InvalidFacetException;
import edu.unc.lib.dl.search.solr.model.CaseInsensitiveFacet;
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.GenericFacet;
import edu.unc.lib.dl.search.solr.model.MultivaluedHierarchicalFacet;
import edu.unc.lib.dl.search.solr.model.SearchState;
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;
/**
* Factory which generates SearchState objects.
* @author bbpennel
*/
public class SearchStateFactory {
private static final Logger log = LoggerFactory.getLogger(SearchStateFactory.class);
private SearchSettings searchSettings;
@Autowired
private FacetFieldFactory facetFieldFactory;
@Autowired
private FacetFieldUtil facetFieldUtil;
public SearchStateFactory(){
}
/**
* Creates and returns a SearchState object representing the default search state
* for a blank search.
* @return
*/
public SearchState createSearchState(){
SearchState searchState = new SearchState();
searchState.setBaseFacetLimit(searchSettings.facetsPerGroup);
searchState.setResourceTypes(searchSettings.defaultResourceTypes);
searchState.setSearchTermOperator(searchSettings.defaultOperator);
searchState.setRowsPerPage(searchSettings.defaultPerPage);
searchState.setFacetsToRetrieve(new ArrayList<String>(searchSettings.searchFacetNames));
searchState.setStartRow(0);
searchState.setSortType("default");
searchState.setSortNormalOrder(true);
return searchState;
}
/**
* Creates and returns a SearchState object starting from the default options for a
* collection browse search, and then populating it with the search state.
* from the http request.
* @param request
* @return SearchState object containing the search state for a collection browse
*/
public SearchState createCollectionBrowseSearchState(Map<String,String[]> request){
SearchState searchState = createSearchState();
searchState.setRowsPerPage(searchSettings.defaultCollectionsPerPage);
searchState.setResourceTypes(new ArrayList<String>(searchSettings.defaultCollectionResourceTypes));
searchState.setFacetsToRetrieve(new ArrayList<String>(searchSettings.collectionBrowseFacetNames));
CutoffFacet depthFacet = new CutoffFacet(SearchFieldKeys.ANCESTOR_PATH.name(), "1,*");
depthFacet.setCutoff(2);
searchState.getFacets().put(SearchFieldKeys.ANCESTOR_PATH.name(), depthFacet);
populateSearchState(searchState, request);
return searchState;
}
/**
* Creates and returns a SearchState object starting from the default options for a
* normal search, and then populating it with the search state.
* from the http request.
* @param request
* @return SearchState object containing the search state
*/
public SearchState createSearchState(Map<String,String[]> request){
SearchState searchState = createSearchState();
populateSearchState(searchState, request);
return searchState;
}
/**
* Returns a search state object for an advanced search request.
* @param request
* @return
*/
public SearchState createSearchStateAdvancedSearch(Map<String,String[]> request){
SearchState searchState = createSearchState();
populateSearchStateAdvancedSearch(searchState, request);
return searchState;
}
/**
* Returns a search state for a result set of only identifiers.
* @return
*/
public SearchState createIDSearchState(){
SearchState searchState = new SearchState();
List<String> resultFields = new ArrayList<String>();
resultFields.add(SearchFieldKeys.ID.name());
searchState.setResultFields(resultFields);
searchState.setSearchTermOperator(searchSettings.defaultOperator);
searchState.setRowsPerPage(searchSettings.defaultPerPage);
searchState.setFacetsToRetrieve(null);
searchState.setStartRow(0);
return searchState;
}
/**
* Returns a search state for a result set of titles and identifiers.
* @return
*/
public SearchState createTitleListSearchState(){
SearchState searchState = createIDSearchState();
searchState.getResultFields().add(SearchFieldKeys.TITLE.name());
return searchState;
}
/**
* Returns a search state for results listing the containers within a hierarchy.
* @return
*/
public SearchState createHierarchyListSearchState(){
SearchState searchState = createIDSearchState();
searchState.setResultFields(new ArrayList<String>(searchSettings.resultFields.get("structure")));
List<String> containerTypes = new ArrayList<String>();
containerTypes.add(searchSettings.resourceTypeCollection);
containerTypes.add(searchSettings.resourceTypeFolder);
searchState.setResourceTypes(containerTypes);
searchState.setSortType("title");
searchState.setSortNormalOrder(true);
return searchState;
}
public SearchState createStructureBrowseSearchState(){
SearchState searchState = new SearchState();
searchState.setResultFields(new ArrayList<String>(searchSettings.resultFields.get("structure")));
searchState.setResourceTypes(searchSettings.defaultResourceTypes);
searchState.setSearchTermOperator(searchSettings.defaultOperator);
searchState.setRowsPerPage(0);
searchState.setStartRow(0);
searchState.setSortType("title");
searchState.setSortNormalOrder(true);
return searchState;
}
public SearchState createStructureBrowseSearchState(Map<String,String[]> request){
SearchState searchState = createStructureBrowseSearchState();
populateSearchState(searchState, request);
return searchState;
}
/**
* Returns a search state representing the default navigation search state for a hierarchical
* structure browse request.
* @return
*/
public SearchState createHierarchicalBrowseSearchState(){
SearchState searchState = new SearchState();
searchState.setResultFields(new ArrayList<String>(searchSettings.resultFields.get("structure")));
searchState.setBaseFacetLimit(searchSettings.facetsPerGroup);
searchState.setResourceTypes(searchSettings.defaultResourceTypes);
searchState.setSearchTermOperator(searchSettings.defaultOperator);
searchState.setRowsPerPage(searchSettings.defaultPerPage);
searchState.setStartRow(0);
searchState.setSortType("collection");
searchState.setSortNormalOrder(true);
searchState.setFacetsToRetrieve(new ArrayList<String>(searchSettings.facetNamesStructureBrowse));
return searchState;
}
/**
* Returns a search state representing the navigation search state for a hierarchical structure
* browse request with the users previously existing search state overlayed.
* @param request
* @return
*/
public SearchState createHierarchicalBrowseSearchState(Map<String,String[]> request){
SearchState searchState = createHierarchicalBrowseSearchState();
populateSearchState(searchState, request);
return searchState;
}
/**
* Returns a search state usable for looking up all facet values for the facet field
* specified. A base value may be given for the facet being queried, for use in
* querying specific tiers in a hierarchical facet.
* @param facetField
* @param baseValue
* @return
*/
public SearchState createFacetSearchState(String facetField, String facetSort, int maxResults){
SearchState searchState = new SearchState();
searchState.setResourceTypes(searchSettings.defaultResourceTypes);
searchState.setRowsPerPage(0);
searchState.setStartRow(0);
ArrayList<String> facetList = new ArrayList<String>();
facetList.add(facetField);
searchState.setFacetsToRetrieve(facetList);
if (facetSort != null){
HashMap<String,String> facetSorts = new HashMap<String,String>();
facetSorts.put(facetField, facetSort);
searchState.setFacetSorts(facetSorts);
}
searchState.setBaseFacetLimit(maxResults);
return searchState;
}
public SearchState createFacetSearchState(String facetField, int maxResults){
return createFacetSearchState(facetField, null, maxResults);
}
private String getParameter(Map<String,String[]> request, String key){
String[] value = request.get(key);
if (value != null)
return value[0];
return null;
}
private void populateQueryableFields(SearchState searchState, Map<String,String[]> request){
Map<String,String> searchFields = searchState.getSearchFields();
Map<String,SearchState.RangePair> rangeFields = searchState.getRangeFields();
Map<String,Object> facetFields = searchState.getFacets();
Iterator<Entry<String, String[]>> paramIt = request.entrySet().iterator();
while (paramIt.hasNext()) {
Entry<String, String[]> param = paramIt.next();
if (param.getValue().length == 0)
continue;
String key = searchSettings.searchFieldKey(param.getKey());
if (key == null)
continue;
String value;
try {
value = URLDecoder.decode(param.getValue()[0], "UTF-8");
} catch (UnsupportedEncodingException e) {
continue;
}
if (searchSettings.searchableFields.contains(key)) {
searchFields.put(key, value);
} else if (searchSettings.facetNames.contains(key)) {
try {
GenericFacet facet = this.facetFieldFactory.createFacet(key, value);
facetFields.put(facet.getFieldName(), facet);
} catch (InvalidFacetException e) {
log.debug("Invalid facet " + key + " with value " + value, e);
}
} else if (searchSettings.rangeSearchableFields.contains(key)) {
try {
rangeFields.put(key, new SearchState.RangePair(value));
} catch (ArrayIndexOutOfBoundsException e){
//An invalid range was specified, throw away the term pair
}
}
}
}
/**
* Populates the attributes of the given SearchState object with search state
* parameters retrieved from the request mapping.
* @param searchState SearchState object to populate
* @param request
* @return SearchState object containing all the parameters representing the current
* search state in the request.
*/
private void populateSearchState(SearchState searchState, Map<String,String[]> request){
populateQueryableFields(searchState, request);
//retrieve facet limits
String parameter = getParameter(request, searchSettings.searchStateParam("FACET_LIMIT_FIELDS"));
if (parameter != null){
String parameterArray[] = parameter.split("\\|");
for (String parameterPair: parameterArray){
String parameterPairArray[] = parameterPair.split(":", 2);
if (parameterPairArray.length > 1){
try {
facetFieldUtil.setFacetLimit(searchSettings.searchFieldKey(parameterPairArray[0]),
Integer.parseInt(parameterPairArray[1]), searchState);
} catch (Exception e) {
log.warn("Failed to add facet limit {} to field {}", new Object[] { parameterPairArray[0],
parameterPairArray[1] }, e);
}
}
}
}
//Set the base facet limit if one is provided
parameter = getParameter(request, searchSettings.searchStateParam("BASE_FACET_LIMIT"));
if (parameter != null){
try {
searchState.setBaseFacetLimit(Integer.parseInt(parameter));
} catch (Exception e){
log.error("Failed to parse base facet limit: " + parameter);
}
}
//Determine resource types selected
parameter = getParameter(request, searchSettings.searchStateParam("RESOURCE_TYPES"));
ArrayList<String> resourceTypes = new ArrayList<String>();
if (parameter == null){
//If resource types aren't specified, load the defaults.
resourceTypes.addAll(searchSettings.defaultResourceTypes);
} else {
String resourceArray[] = parameter.split(",");
for (String resourceType: resourceArray){
if (resourceType != null && resourceType.trim().length() > 0)
resourceTypes.add(resourceType);
}
}
searchState.setResourceTypes(resourceTypes);
//Get search term operator
parameter = getParameter(request, searchSettings.searchStateParam("SEARCH_TERM_OPERATOR"));
if (parameter == null){
//If no operator set, use the default.
searchState.setSearchTermOperator(searchSettings.defaultOperator);
} else {
searchState.setSearchTermOperator(parameter);
}
//Get Start row
int startRow = 0;
try {
startRow = Integer.parseInt(getParameter(request, searchSettings.searchStateParam("START_ROW")));
} catch (Exception e){
}
searchState.setStartRow(startRow);
//Get number of rows per page
int rowsPerPage = 0;
try {
rowsPerPage = Integer.parseInt(getParameter(request, searchSettings.searchStateParam("ROWS_PER_PAGE")));
} catch (Exception e){
//If not specified, then get the appropriate default value based on search content types.
if (!resourceTypes.contains("File") && resourceTypes.contains("Collection")){
rowsPerPage = searchSettings.defaultCollectionsPerPage;
} else {
rowsPerPage = searchSettings.defaultPerPage;
}
}
searchState.setRowsPerPage(rowsPerPage);
//Set sort
parameter = getParameter(request, searchSettings.searchStateParam("SORT_TYPE"));
if (parameter != null) {
String[] sortParts = parameter.split(",");
if (sortParts.length > 0) {
searchState.setSortType(sortParts[0]);
if (sortParts.length == 2)
searchState.setSortNormalOrder(!sortParts[1].equals(searchSettings.sortReverse));
}
}
//facetsToRetrieve
parameter = getParameter(request, searchSettings.searchStateParam("FACET_FIELDS_TO_RETRIEVE"));
ArrayList<String> facetsToRetrieve = new ArrayList<String>();
if (parameter != null){
String facetArray[] = parameter.split(",");
for (String facet: facetArray){
String facetKey = searchSettings.searchFieldKey(facet);
if (facetKey != null && searchSettings.getFacetNames().contains(facetKey))
facetsToRetrieve.add(searchSettings.searchFieldKey(facet));
}
searchState.setFacetsToRetrieve(facetsToRetrieve);
}
parameter = getParameter(request, searchSettings.searchStateParam("ROLLUP"));
if (parameter == null) {
searchState.setRollup(null);
} else {
Boolean rollup = new Boolean(parameter);
searchState.setRollup(rollup);
}
}
/**
* Populates a search state according to parameters expected from an advanced search request.
* @param searchState
* @param request
*/
private void populateSearchStateAdvancedSearch(SearchState searchState, Map<String,String[]> request){
String parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DEFAULT_INDEX.name()));
if (parameter != null && parameter.length() > 0){
searchState.getSearchFields().put(SearchFieldKeys.DEFAULT_INDEX.name(), parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.SUBJECT_INDEX.name()));
if (parameter != null && parameter.length() > 0){
searchState.getSearchFields().put(SearchFieldKeys.SUBJECT_INDEX.name(), parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.CONTRIBUTOR_INDEX.name()));
if (parameter != null && parameter.length() > 0){
searchState.getSearchFields().put(SearchFieldKeys.CONTRIBUTOR_INDEX.name(), parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.TITLE_INDEX.name()));
if (parameter != null && parameter.length() > 0){
searchState.getSearchFields().put(SearchFieldKeys.TITLE_INDEX.name(), parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.ANCESTOR_PATH.name()));
if (parameter != null && parameter.length() > 0){
CutoffFacet hierFacet = new CutoffFacet(SearchFieldKeys.ANCESTOR_PATH.name(), parameter);
searchState.getFacets().put(SearchFieldKeys.ANCESTOR_PATH.name(), hierFacet);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DEPARTMENT.name()));
if (parameter != null && parameter.length() > 0){
CaseInsensitiveFacet facet = new CaseInsensitiveFacet(SearchFieldKeys.DEPARTMENT.name(), parameter);
searchState.getFacets().put(SearchFieldKeys.DEPARTMENT.name(), facet);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.CONTENT_TYPE.name()));
if (parameter != null && parameter.length() > 0){
MultivaluedHierarchicalFacet hierFacet = new MultivaluedHierarchicalFacet(SearchFieldKeys.CONTENT_TYPE.name(), parameter);
searchState.getFacets().put(SearchFieldKeys.CONTENT_TYPE.name(), hierFacet);
}
//Store date added.
SearchState.RangePair dateAdded = new SearchState.RangePair();
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DATE_ADDED.name()) + "Start");
if (parameter != null && parameter.length() > 0){
dateAdded.setLeftHand(parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DATE_ADDED.name()) + "End");
if (parameter != null && parameter.length() > 0){
dateAdded.setRightHand(parameter);
}
if (dateAdded.getLeftHand() != null || dateAdded.getRightHand() != null){
searchState.getRangeFields().put(SearchFieldKeys.DATE_ADDED.name(), dateAdded);
}
//Store date added.
SearchState.RangePair dateCreated = new SearchState.RangePair();
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DATE_CREATED.name()) + "Start");
if (parameter != null && parameter.length() > 0){
dateCreated.setLeftHand(parameter);
}
parameter = getParameter(request, searchSettings.searchFieldParam(SearchFieldKeys.DATE_CREATED.name()) + "End");
if (parameter != null && parameter.length() > 0){
dateCreated.setRightHand(parameter);
}
if (dateCreated.getLeftHand() != null || dateCreated.getRightHand() != null){
searchState.getRangeFields().put(SearchFieldKeys.DATE_CREATED.name(), dateCreated);
}
}
public SearchSettings getSearchSettings() {
return searchSettings;
}
@Autowired
public void setSearchSettings(SearchSettings searchSettings) {
this.searchSettings = searchSettings;
}
public void setFacetFieldFactory(FacetFieldFactory facetFieldFactory) {
this.facetFieldFactory = facetFieldFactory;
}
}