/**
* 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.app.webui.discovery;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.app.webui.util.UIUtil;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.discovery.DiscoverFacetField;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverQuery.SORT_ORDER;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.SearchUtils;
import org.dspace.discovery.configuration.DiscoveryConfiguration;
import org.dspace.discovery.configuration.DiscoveryConfigurationParameters;
import org.dspace.discovery.configuration.DiscoverySearchFilterFacet;
import org.dspace.discovery.configuration.DiscoverySortConfiguration;
import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration;
import org.dspace.handle.HandleManager;
public class DiscoverUtility
{
/** log4j category */
private static Logger log = Logger.getLogger(DiscoverUtility.class);
public static final int TYPE_FACETS = 1;
public static final int TYPE_TAGCLOUD = 2;
/**
* Get the scope of the search using the parameter found in the request.
*
* @param context
* @param request
* @throws IllegalStateException
* @throws SQLException
*/
public static DSpaceObject getSearchScope(Context context,
HttpServletRequest request) throws IllegalStateException,
SQLException
{
// Get the location parameter, if any
String location = request.getParameter("location");
if (location == null)
{
if (UIUtil.getCollectionLocation(request) != null)
{
return UIUtil.getCollectionLocation(request);
}
if (UIUtil.getCommunityLocation(request) != null)
{
return UIUtil.getCommunityLocation(request);
}
return null;
}
DSpaceObject scope = HandleManager.resolveToObject(context, location);
return scope;
}
/**
* Build a DiscoverQuery object using the parameter in the request
*
* @param request
* @return the query.
* @throws SearchServiceException
*/
public static DiscoverQuery getDiscoverQuery(Context context,
HttpServletRequest request, DSpaceObject scope, boolean enableFacet)
{
DiscoverQuery queryArgs = new DiscoverQuery();
DiscoveryConfiguration discoveryConfiguration = SearchUtils
.getDiscoveryConfiguration(scope);
List<String> userFilters = setupBasicQuery(context,
discoveryConfiguration, request, queryArgs);
setPagination(request, queryArgs, discoveryConfiguration);
if (enableFacet
&& !"submit_export_metadata".equals(UIUtil.getSubmitButton(
request, "submit")))
{
setFacet(context, request, scope, queryArgs,
discoveryConfiguration, userFilters, discoveryConfiguration
.getSidebarFacets(), TYPE_FACETS);
}
return queryArgs;
}
/**
* Build a DiscoverQuery object using the tag cloud parameter in the request
*
* @param request
* @return the query.
* @throws SearchServiceException
*/
public static DiscoverQuery getTagCloudDiscoverQuery(Context context,
HttpServletRequest request, DSpaceObject scope, boolean enableFacet)
{
DiscoverQuery queryArgs = new DiscoverQuery();
DiscoveryConfiguration discoveryConfiguration = SearchUtils
.getDiscoveryConfiguration(scope);
List<String> userFilters = setupBasicQuery(context,
discoveryConfiguration, request, queryArgs);
setPagination(request, queryArgs, discoveryConfiguration);
if (enableFacet
&& !"submit_export_metadata".equals(UIUtil.getSubmitButton(
request, "submit")))
{
setFacet(context, request, scope, queryArgs,
discoveryConfiguration, userFilters, discoveryConfiguration
.getTagCloudFacetConfiguration().getTagCloudFacets(), TYPE_TAGCLOUD);
}
return queryArgs;
}
/**
* Build the DiscoverQuery object for an autocomplete search using
* parameters in the request
*
* @param context
* @param request
* @param scope
* @return the query.
*/
public static DiscoverQuery getDiscoverAutocomplete(Context context,
HttpServletRequest request, DSpaceObject scope)
{
DiscoverQuery queryArgs = new DiscoverQuery();
DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration();
setupBasicQuery(context, discoveryConfiguration, request, queryArgs);
String autoIndex = request.getParameter("auto_idx");
String autoQuery = request.getParameter("auto_query");
String sort = request.getParameter("auto_sort");
String autoType = request.getParameter("auto_type");
if ("contains".equals(autoType) || "notcontains".equals(autoType))
{
autoType = DiscoveryConfigurationParameters.TYPE_STANDARD;
}
else if ("authority".equals(autoType) || "notauthority".equals(autoType))
{
autoType = DiscoveryConfigurationParameters.TYPE_AUTHORITY;
}
else
{
autoType = DiscoveryConfigurationParameters.TYPE_AC;
}
DiscoveryConfigurationParameters.SORT sortBy = DiscoveryConfigurationParameters.SORT.VALUE;
if (StringUtils.isNotBlank(sort))
{
if ("count".equalsIgnoreCase(sort))
{
sortBy = DiscoveryConfigurationParameters.SORT.COUNT;
}
else
{
sortBy = DiscoveryConfigurationParameters.SORT.VALUE;
}
}
// no user choices... default for autocomplete should be alphabetic
// sorting in all cases except empty query where count is preferable
else if ("".equals(autoQuery))
{
sortBy = DiscoveryConfigurationParameters.SORT.COUNT;
}
if (autoIndex == null)
{
autoIndex = "all";
}
if (autoQuery == null)
{
autoQuery = "";
}
int limit = UIUtil.getIntParameter(request, "autocomplete.limit");
if (limit == -1)
{
limit = 10;
}
DiscoverFacetField autocompleteField = new DiscoverFacetField(autoIndex,
autoType,
limit, sortBy, autoQuery.toLowerCase());
queryArgs.addFacetField(autocompleteField);
queryArgs.setMaxResults(0);
queryArgs.setFacetMinCount(1);
return queryArgs;
}
/**
* Setup the basic query arguments: the main query and all the filters
* (default + user). Return the list of user filter
*
* @param context
* @param request
* @param queryArgs
* the query object to populate
* @return the list of user filer (as filter query)
*/
private static List<String> setupBasicQuery(Context context,
DiscoveryConfiguration discoveryConfiguration,
HttpServletRequest request, DiscoverQuery queryArgs)
{
// Get the query
String query = request.getParameter("query");
if (StringUtils.isNotBlank(query))
{
queryArgs.setQuery(query);
}
List<String> defaultFilterQueries = discoveryConfiguration
.getDefaultFilterQueries();
if (defaultFilterQueries != null)
{
for (String f : defaultFilterQueries)
{
queryArgs.addFilterQueries(f);
}
}
List<String[]> filters = getFilters(request);
List<String> userFilters = new ArrayList<String>();
for (String[] f : filters)
{
try
{
String newFilterQuery = SearchUtils.getSearchService()
.toFilterQuery(context, f[0], f[1], f[2])
.getFilterQuery();
if (newFilterQuery != null)
{
queryArgs.addFilterQueries(newFilterQuery);
userFilters.add(newFilterQuery);
}
}
catch (SQLException e)
{
log.error(LogManager.getHeader(context,
"Error in discovery while setting up user facet query",
"filter_field: " + f[0] + ",filter_type:"
+ f[1] + ",filer_value:"
+ f[2]), e);
}
}
return userFilters;
}
private static void setPagination(HttpServletRequest request,
DiscoverQuery queryArgs,
DiscoveryConfiguration discoveryConfiguration)
{
int start = UIUtil.getIntParameter(request, "start");
// can't start earlier than 0 in the results!
if (start < 0)
{
start = 0;
}
String sortBy = request.getParameter("sort_by");
String sortOrder = request.getParameter("order");
DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration
.getSearchSortConfiguration();
if (sortBy == null)
{
// Attempt to find the default one, if none found we use SCORE
sortBy = "score";
if (searchSortConfiguration != null)
{
for (DiscoverySortFieldConfiguration sortFieldConfiguration : searchSortConfiguration
.getSortFields())
{
if (sortFieldConfiguration.equals(searchSortConfiguration
.getDefaultSort()))
{
sortBy = SearchUtils
.getSearchService()
.toSortFieldIndex(
sortFieldConfiguration
.getMetadataField(),
sortFieldConfiguration.getType());
}
}
}
}
if (sortOrder == null && searchSortConfiguration != null)
{
sortOrder = searchSortConfiguration.getDefaultSortOrder()
.toString();
}
if (sortBy != null)
{
if ("asc".equalsIgnoreCase(sortOrder))
{
queryArgs.setSortField(sortBy, SORT_ORDER.asc);
}
else
{
queryArgs.setSortField(sortBy, SORT_ORDER.desc);
}
}
int rpp = UIUtil.getIntParameter(request, "rpp");
// Override the page setting if exporting metadata
if ("submit_export_metadata".equals(UIUtil.getSubmitButton(request,
"submit")))
{
queryArgs.setStart(0);
queryArgs.setMaxResults(Integer.MAX_VALUE);
// search only for items other objects are not exported
queryArgs.addFilterQueries("search.resourcetype:2");
}
else
{
// String groupBy = request.getParameter("group_by");
//
// // Enable groupBy collapsing if designated
// if (groupBy != null && !groupBy.equalsIgnoreCase("none")) {
// /** Construct a Collapse Field Query */
// queryArgs.addProperty("collapse.field", groupBy);
// queryArgs.addProperty("collapse.threshold", "1");
// queryArgs.addProperty("collapse.includeCollapsedDocs.fl",
// "handle");
// queryArgs.addProperty("collapse.facet", "before");
//
// //queryArgs.a type:Article^2
//
// // TODO: This is a hack to get Publications (Articles) to always
// be at the top of Groups.
// // TODO: I think the can be more transparently done in the solr
// solrconfig.xml with DISMAX and boosting
// /** sort in groups to get publications to top */
// queryArgs.setSortField("dc.type", DiscoverQuery.SORT_ORDER.asc);
//
// }
if (rpp > 0)
{
queryArgs.setMaxResults(rpp);
}
else
{
queryArgs.setMaxResults(discoveryConfiguration.getDefaultRpp());
}
queryArgs.setStart(start);
}
}
private static void setFacet(Context context, HttpServletRequest request,
DSpaceObject scope, DiscoverQuery queryArgs,
DiscoveryConfiguration discoveryConfiguration,
List<String> userFilters, List<DiscoverySearchFilterFacet> facets, int type)
{
log.info("facets for scope, " + scope + ": "
+ (facets != null ? facets.size() : null));
if (facets != null)
{
queryArgs.setFacetMinCount(1);
}
/** enable faceting of search results */
if (facets != null)
{
queryArgs.setFacetMinCount(1);
for (DiscoverySearchFilterFacet facet : facets)
{
if (facet.getType().equals(
DiscoveryConfigurationParameters.TYPE_DATE))
{
String dateFacet = facet.getIndexFieldName() + ".year";
List<String> filterQueriesList = queryArgs
.getFilterQueries();
String[] filterQueries = new String[0];
if (filterQueriesList != null)
{
filterQueries = new String[filterQueries.length];
filterQueries = filterQueriesList
.toArray(filterQueries);
}
try
{
// Get a range query so we can create facet
// queries
// ranging from out first to our last date
// Attempt to determine our oldest & newest year
// by
// checking for previously selected filters
int oldestYear = -1;
int newestYear = -1;
for (String filterQuery : filterQueries)
{
if (filterQuery.startsWith(dateFacet + ":"))
{
// Check for a range
Pattern pattern = Pattern
.compile("\\[(.*? TO .*?)\\]");
Matcher matcher = pattern.matcher(filterQuery);
boolean hasPattern = matcher.find();
if (hasPattern)
{
filterQuery = matcher.group(0);
// We have a range
// Resolve our range to a first &
// endyear
int tempOldYear = Integer
.parseInt(filterQuery.split(" TO ")[0]
.replace("[", "").trim());
int tempNewYear = Integer
.parseInt(filterQuery.split(" TO ")[1]
.replace("]", "").trim());
// Check if we have a further filter
// (or
// a first one found)
if (tempNewYear < newestYear
|| oldestYear < tempOldYear
|| newestYear == -1)
{
oldestYear = tempOldYear;
newestYear = tempNewYear;
}
}
else
{
if (filterQuery.indexOf(" OR ") != -1)
{
// Should always be the case
filterQuery = filterQuery.split(" OR ")[0];
}
// We should have a single date
oldestYear = Integer.parseInt(filterQuery
.split(":")[1].trim());
newestYear = oldestYear;
// No need to look further
break;
}
}
}
// Check if we have found a range, if not then
// retrieve our first & last year by using solr
if (oldestYear == -1 && newestYear == -1)
{
DiscoverQuery yearRangeQuery = new DiscoverQuery();
yearRangeQuery.setFacetMinCount(1);
yearRangeQuery.setMaxResults(1);
// Set our query to anything that has this
// value
yearRangeQuery.addFieldPresentQueries(dateFacet);
// Set sorting so our last value will appear
// on
// top
yearRangeQuery.setSortField(dateFacet + "_sort",
DiscoverQuery.SORT_ORDER.asc);
yearRangeQuery.addFilterQueries(filterQueries);
yearRangeQuery.addSearchField(dateFacet);
DiscoverResult lastYearResult = SearchUtils
.getSearchService().search(context, scope,
yearRangeQuery);
if (0 < lastYearResult.getDspaceObjects().size())
{
java.util.List<DiscoverResult.SearchDocument> searchDocuments = lastYearResult
.getSearchDocument(lastYearResult
.getDspaceObjects().get(0));
if (0 < searchDocuments.size()
&& 0 < searchDocuments
.get(0)
.getSearchFieldValues(dateFacet)
.size())
{
oldestYear = Integer
.parseInt(searchDocuments
.get(0)
.getSearchFieldValues(
dateFacet).get(0));
}
}
// Now get the first year
yearRangeQuery.setSortField(dateFacet + "_sort",
DiscoverQuery.SORT_ORDER.desc);
DiscoverResult firstYearResult = SearchUtils
.getSearchService().search(context, scope,
yearRangeQuery);
if (0 < firstYearResult.getDspaceObjects().size())
{
java.util.List<DiscoverResult.SearchDocument> searchDocuments = firstYearResult
.getSearchDocument(firstYearResult
.getDspaceObjects().get(0));
if (0 < searchDocuments.size()
&& 0 < searchDocuments
.get(0)
.getSearchFieldValues(dateFacet)
.size())
{
newestYear = Integer
.parseInt(searchDocuments
.get(0)
.getSearchFieldValues(
dateFacet).get(0));
}
}
// No values found!
if (newestYear == -1 || oldestYear == -1)
{
continue;
}
}
int gap = 1;
// Attempt to retrieve our gap by the algorithm
// below
int yearDifference = newestYear - oldestYear;
if (yearDifference != 0)
{
while (10 < ((double) yearDifference / gap))
{
gap *= 10;
}
}
// We need to determine our top year so we can
// start
// our count from a clean year
// Example: 2001 and a gap from 10 we need the
// following result: 2010 - 2000 ; 2000 - 1990
// hence
// the top year
int topYear = (int) (Math.ceil((float) (newestYear)
/ gap) * gap);
if (gap == 1)
{
// We need a list of our years
// We have a date range add faceting for our
// field
// The faceting will automatically be
// limited to
// the 10 years in our span due to our
// filterquery
queryArgs.addFacetField(new DiscoverFacetField(
facet.getIndexFieldName(), facet.getType(),
10, facet.getSortOrder()));
}
else
{
java.util.List<String> facetQueries = new ArrayList<String>();
// Create facet queries but limit then to 11
// (11
// == when we need to show a show more url)
for (int year = topYear; year > oldestYear
&& (facetQueries.size() < 11); year -= gap)
{
// Add a filter to remove the last year
// only
// if we aren't the last year
int bottomYear = year - gap;
// Make sure we don't go below our last
// year
// found
if (bottomYear < oldestYear)
{
bottomYear = oldestYear;
}
// Also make sure we don't go above our
// newest year
int currentTop = year;
if ((year == topYear))
{
currentTop = newestYear;
}
else
{
// We need to do -1 on this one to
// get a
// better result
currentTop--;
}
facetQueries.add(dateFacet + ":[" + bottomYear
+ " TO " + currentTop + "]");
}
for (String facetQuery : facetQueries)
{
queryArgs.addFacetQuery(facetQuery);
}
}
}
catch (Exception e)
{
log.error(
LogManager
.getHeader(
context,
"Error in discovery while setting up date facet range",
"date facet: " + dateFacet), e);
}
}
else
{
int facetLimit = type==TYPE_FACETS?facet.getFacetLimit():-1;
int facetPage = UIUtil.getIntParameter(request,
facet.getIndexFieldName() + "_page");
if (facetPage < 0)
{
facetPage = 0;
}
// at most all the user filters belong to this facet
int alreadySelected = userFilters.size();
// Add one to our facet limit to make sure that if
// we
// have more then the shown facets that we show our
// show
// more url
// add the already selected facet so to have a full
// top list
// if possible
int limit = 0;
if (type==TYPE_FACETS){
limit = facetLimit + 1 + alreadySelected;
}
else
limit = facetLimit;
queryArgs.addFacetField(new DiscoverFacetField(facet
.getIndexFieldName(),
DiscoveryConfigurationParameters.TYPE_TEXT,
limit, facet
.getSortOrder(), facetPage * facetLimit));
}
}
}
}
public static List<String[]> getFilters(HttpServletRequest request)
{
String submit = UIUtil.getSubmitButton(request, "submit");
int ignore = -1;
if (submit.startsWith("submit_filter_remove_"))
{
ignore = Integer.parseInt(submit.substring("submit_filter_remove_".length()));
}
List<String[]> appliedFilters = new ArrayList<String[]>();
List<String> filterValue = new ArrayList<String>();
List<String> filterOp = new ArrayList<String>();
List<String> filterField = new ArrayList<String>();
for (int idx = 1; ; idx++)
{
String op = request.getParameter("filter_type_"+idx);
if (StringUtils.isBlank(op))
{
break;
}
else if (idx != ignore)
{
filterOp.add(op);
filterField.add(request.getParameter("filter_field_"+idx));
filterValue.add(request.getParameter("filter_value_"+idx));
}
}
String op = request.getParameter("filtertype");
if (StringUtils.isNotBlank(op))
{
filterOp.add(op);
filterField.add(request.getParameter("filtername"));
filterValue.add(request.getParameter("filterquery"));
}
for (int idx = 0; idx < filterOp.size(); idx++)
{
appliedFilters.add(new String[] { filterField.get(idx),
filterOp.get(idx), filterValue.get(idx) });
}
return appliedFilters;
}
// /**
// * Build the query from the advanced search form
// *
// * @param request
// * @return
// */
// public static String buildQuery(HttpServletRequest request)
// {
// int num_field = UIUtil.getIntParameter(request, "num_search_field");
// if (num_field <= 0)
// {
// num_field = 3;
// }
// StringBuffer query = new StringBuffer();
// buildQueryPart(query, request.getParameter("field"),
// request.getParameter("query"), null);
// for (int i = 1; i < num_field; i++)
// {
// buildQueryPart(query, request.getParameter("field" + i),
// request.getParameter("query" + i),
// request.getParameter("conjuction" + i));
// }
// return query.toString();
// }
//
// private static void buildQueryPart(StringBuffer currQuery, String field,
// String queryPart, String conjuction)
// {
// if (StringUtils.isBlank(queryPart))
// {
// return;
// }
// else
// {
// StringBuffer tmp = new StringBuffer(queryPart);
// if (StringUtils.isNotBlank(field))
// {
// tmp.insert(0, field + ":(").append(")");
// }
//
// if (StringUtils.isNotBlank(conjuction) && currQuery.length() > 0)
// {
// currQuery.append(conjuction);
// }
// currQuery.append(tmp);
// }
// }
}