/**
* 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.xmlui.aspect.discovery;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.util.HashUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.excalibur.source.SourceValidity;
import org.apache.log4j.Logger;
import org.dspace.app.util.MetadataExposure;
import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
import org.dspace.app.xmlui.utils.DSpaceValidity;
import org.dspace.app.xmlui.utils.HandleUtil;
import org.dspace.app.xmlui.utils.UIException;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingException;
import org.dspace.app.xmlui.wing.element.*;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.*;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.LogManager;
import org.dspace.discovery.*;
import org.dspace.discovery.configuration.DiscoveryConfiguration;
import org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration;
import org.dspace.discovery.configuration.DiscoverySortConfiguration;
import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration;
import org.dspace.handle.HandleManager;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.*;
import java.util.List;
/**
* This is an abstract search page. It is a collection of search methods that
* are common between different search implementation. An implementer must
* implement at least three methods: addBody(), getQuery(), and generateURL().
* <p/>
* See the SimpleSearch implementation.
*
* @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)
*/
public abstract class AbstractSearch extends AbstractDSpaceTransformer implements CacheableProcessingComponent{
private static final Logger log = Logger.getLogger(AbstractSearch.class);
/**
* Language strings
*/
private static final Message T_head1_community =
message("xmlui.Discovery.AbstractSearch.head1_community");
private static final Message T_head1_collection =
message("xmlui.Discovery.AbstractSearch.head1_collection");
private static final Message T_head1_none =
message("xmlui.Discovery.AbstractSearch.head1_none");
private static final Message T_no_results =
message("xmlui.ArtifactBrowser.AbstractSearch.no_results");
private static final Message T_all_of_dspace =
message("xmlui.ArtifactBrowser.AbstractSearch.all_of_dspace");
private static final Message T_sort_by_relevance =
message("xmlui.Discovery.AbstractSearch.sort_by.relevance");
private static final Message T_sort_by = message("xmlui.Discovery.AbstractSearch.sort_by.head");
private static final Message T_rpp = message("xmlui.Discovery.AbstractSearch.rpp");
private static final Message T_result_head_3 = message("xmlui.Discovery.AbstractSearch.head3");
private static final Message T_result_head_2 = message("xmlui.Discovery.AbstractSearch.head2");
/**
* Cached query results
*/
protected DiscoverResult queryResults;
/**
* Cached query arguments
*/
protected DiscoverQuery queryArgs;
/**
* The options for results per page
*/
private static final int[] RESULTS_PER_PAGE_PROGRESSION = {5, 10, 20, 40, 60, 80, 100};
/**
* Cached validity object
*/
private SourceValidity validity;
/**
* Generate the unique caching key.
* This key must be unique inside the space of this component.
*/
public Serializable getKey() {
try {
String key = "";
// Page Parameter
key += "-" + getParameterPage();
key += "-" + getParameterRpp();
key += "-" + getParameterSortBy();
key += "-" + getParameterOrder();
key += "-" + getParameterEtAl();
// What scope the search is at
DSpaceObject scope = getScope();
if (scope != null)
{
key += "-" + scope.getHandle();
}
// The actual search query.
key += "-" + getQuery();
return HashUtil.hash(key);
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
// Ignore all errors and just don't cache.
return "0";
}
}
/**
* Generate the cache validity object.
* <p/>
* This validity object should never "over cache" because it will
* perform the search, and serialize the results using the
* DSpaceValidity object.
*/
public SourceValidity getValidity() {
if (this.validity == null) {
try {
DSpaceValidity validity = new DSpaceValidity();
DSpaceObject scope = getScope();
validity.add(scope);
performSearch(scope);
List<DSpaceObject> results = this.queryResults.getDspaceObjects();
if (results != null) {
validity.add("total:"+this.queryResults.getTotalSearchResults());
validity.add("start:"+this.queryResults.getStart());
validity.add("size:" + results.size());
for (DSpaceObject dso : results) {
validity.add(dso);
}
}
Map<String, List<DiscoverResult.FacetResult>> facetResults = this.queryResults.getFacetResults();
for(String facetField : facetResults.keySet()){
List<DiscoverResult.FacetResult> facetValues = facetResults.get(facetField);
for (DiscoverResult.FacetResult facetResult : facetValues)
{
validity.add(facetField + facetResult.getAsFilterQuery() + facetResult.getCount());
}
}
this.validity = validity.complete();
} catch (RuntimeException re) {
throw re;
}
catch (Exception e) {
this.validity = null;
}
// add log message that we are viewing the item
// done here, as the serialization may not occur if the cache is valid
logSearch();
}
return this.validity;
}
/**
* Build the resulting search DRI document.
*/
public abstract void addBody(Body body) throws SAXException, WingException,
UIException, SQLException, IOException, AuthorizeException;
/**
* Build the main form that should be the only form that the user interface requires
* This form will be used for all discovery queries, filters, ....
* At the moment however this form is only used to track search result hits
* @param searchDiv the division to add the form to
*/
protected void buildMainForm(Division searchDiv) throws WingException, SQLException {
Request request = ObjectModelHelper.getRequest(objectModel);
DSpaceObject dso = HandleUtil.obtainHandle(objectModel);
//We set our action to context path, since the eventual action will depend on which url we click on
Division mainForm = searchDiv.addInteractiveDivision("main-form", getBasicUrl(), Division.METHOD_POST, "");
String query = getQuery();
//Indicate that the form we are submitting lists search results
mainForm.addHidden("search-result").setValue(Boolean.TRUE.toString());
mainForm.addHidden("query").setValue(query);
mainForm.addHidden("current-scope").setValue(dso == null ? "" : dso.getHandle());
Map<String, String[]> fqs = getParameterFilterQueries();
if (fqs != null)
{
for (String parameter : fqs.keySet())
{
String[] values = fqs.get(parameter);
if(values != null)
{
for (String value : values)
{
mainForm.addHidden(parameter).setValue(value);
}
}
}
}
mainForm.addHidden("rpp").setValue(getParameterRpp());
Hidden sort_by = mainForm.addHidden("sort_by");
if(!StringUtils.isBlank(request.getParameter("sort_by")))
{
sort_by.setValue(request.getParameter("sort_by"));
}else{
sort_by.setValue("score");
}
Hidden order = mainForm.addHidden("order");
if(getParameterOrder() != null)
{
order.setValue(request.getParameter("order"));
}else{
DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(dso);
order.setValue(discoveryConfiguration.getSearchSortConfiguration().getDefaultSortOrder().toString());
}
if(!StringUtils.isBlank(request.getParameter("page")))
{
mainForm.addHidden("page").setValue(request.getParameter("page"));
}
}
protected abstract String getBasicUrl() throws SQLException;
/**
* Attach a division to the given search division named "search-results"
* which contains results for this search query.
*
* @param search The search division to contain the search-results division.
*/
protected void buildSearchResultsDivision(Division search)
throws IOException, SQLException, WingException, SearchServiceException {
try {
if (queryResults == null) {
DSpaceObject scope = getScope();
this.performSearch(scope);
}
}
catch (RuntimeException e) {
log.error(e.getMessage(), e);
queryResults = null;
}
catch (Exception e) {
log.error(e.getMessage(), e);
queryResults = null;
}
Division results = search.addDivision("search-results", "primary");
buildSearchControls(results);
DSpaceObject searchScope = getScope();
int displayedResults;
long totalResults;
float searchTime;
if(queryResults != null && 0 < queryResults.getTotalSearchResults())
{
displayedResults = queryResults.getDspaceObjects().size();
totalResults = queryResults.getTotalSearchResults();
searchTime = ((float) queryResults.getSearchTime() / 1000) % 60;
if (searchScope instanceof Community)
{
Community community = (Community) searchScope;
String communityName = community.getMetadata("name");
results.setHead(T_head1_community.parameterize(displayedResults, totalResults, communityName, searchTime));
} else if (searchScope instanceof Collection){
Collection collection = (Collection) searchScope;
String collectionName = collection.getMetadata("name");
results.setHead(T_head1_collection.parameterize(displayedResults, totalResults, collectionName, searchTime));
} else {
results.setHead(T_head1_none.parameterize(displayedResults, totalResults, searchTime));
}
}
if (queryResults != null && 0 < queryResults.getDspaceObjects().size())
{
// Pagination variables.
int itemsTotal = (int) queryResults.getTotalSearchResults();
int firstItemIndex = (int) this.queryResults.getStart() + 1;
int lastItemIndex = (int) this.queryResults.getStart() + queryResults.getDspaceObjects().size();
//if (itemsTotal < lastItemIndex)
// lastItemIndex = itemsTotal;
int currentPage = this.queryResults.getStart() / this.queryResults.getMaxResults() + 1;
int pagesTotal = (int) ((this.queryResults.getTotalSearchResults() - 1) / this.queryResults.getMaxResults()) + 1;
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("page", "{pageNum}");
String pageURLMask = generateURL(parameters);
pageURLMask = addFilterQueriesToUrl(pageURLMask);
results.setMaskedPagination(itemsTotal, firstItemIndex,
lastItemIndex, currentPage, pagesTotal, pageURLMask);
// Look for any communities or collections in the mix
org.dspace.app.xmlui.wing.element.List dspaceObjectsList = null;
// Put it on the top of level search result list
dspaceObjectsList = results.addList("search-results-repository",
org.dspace.app.xmlui.wing.element.List.TYPE_DSO_LIST, "repository-search-results");
List<DSpaceObject> commCollList = new ArrayList<DSpaceObject>();
List<Item> itemList = new ArrayList<Item>();
for (DSpaceObject resultDso : queryResults.getDspaceObjects())
{
if(resultDso.getType() == Constants.COMMUNITY || resultDso.getType() == Constants.COLLECTION)
{
commCollList.add(resultDso);
}else
if(resultDso.getType() == Constants.ITEM)
{
itemList.add((Item) resultDso);
}
}
if(CollectionUtils.isNotEmpty(commCollList))
{
org.dspace.app.xmlui.wing.element.List commCollWingList = dspaceObjectsList.addList("comm-coll-result-list");
commCollWingList.setHead(T_result_head_2);
for (DSpaceObject dso : commCollList)
{
DiscoverResult.DSpaceObjectHighlightResult highlightedResults = queryResults.getHighlightedResults(dso);
if(dso.getType() == Constants.COMMUNITY)
{
//Render our community !
org.dspace.app.xmlui.wing.element.List communityMetadata = commCollWingList.addList(dso.getHandle() + ":community");
renderCommunity((Community) dso, highlightedResults, communityMetadata);
}else
if(dso.getType() == Constants.COLLECTION)
{
//Render our collection !
org.dspace.app.xmlui.wing.element.List collectionMetadata = commCollWingList.addList(dso.getHandle() + ":collection");
renderCollection((Collection) dso, highlightedResults, collectionMetadata);
}
}
}
if(CollectionUtils.isNotEmpty(itemList))
{
org.dspace.app.xmlui.wing.element.List itemWingList = dspaceObjectsList.addList("item-result-list");
if(CollectionUtils.isNotEmpty(commCollList))
{
itemWingList.setHead(T_result_head_3);
}
for (Item resultDso : itemList)
{
DiscoverResult.DSpaceObjectHighlightResult highlightedResults = queryResults.getHighlightedResults(resultDso);
renderItem(itemWingList, resultDso, highlightedResults);
}
}
} else {
results.addPara(T_no_results);
}
//}// Empty query
}
protected String addFilterQueriesToUrl(String pageURLMask) throws UIException {
Map<String, String[]> filterQueryParams = getParameterFilterQueries();
if(filterQueryParams != null)
{
StringBuilder maskBuilder = new StringBuilder(pageURLMask);
for (String filterQueryParam : filterQueryParams.keySet())
{
String[] filterQueryValues = filterQueryParams.get(filterQueryParam);
if(filterQueryValues != null)
{
for (String filterQueryValue : filterQueryValues)
{
maskBuilder.append("&").append(filterQueryParam).append("=").append(encodeForURL(filterQueryValue));
}
}
}
pageURLMask = maskBuilder.toString();
}
return pageURLMask;
}
/**
* Render the given item, all metadata is added to the given list, which metadata will be rendered where depends on the xsl
* @param dspaceObjectsList a list of DSpace objects
* @param item the DSpace item to be rendered
* @param highlightedResults the highlighted results
* @throws WingException
* @throws SQLException Database failure in services this calls
*/
protected void renderItem(org.dspace.app.xmlui.wing.element.List dspaceObjectsList, Item item, DiscoverResult.DSpaceObjectHighlightResult highlightedResults) throws WingException, SQLException {
org.dspace.app.xmlui.wing.element.List itemList = dspaceObjectsList.addList(item.getHandle() + ":item");
MetadataField[] metadataFields = MetadataField.findAll(context);
for (MetadataField metadataField : metadataFields)
{
//Retrieve the schema for this field
String schema = MetadataSchema.find(context, metadataField.getSchemaID()).getName();
//Check if our field isn't hidden
if (!MetadataExposure.isHidden(context, schema, metadataField.getElement(), metadataField.getQualifier()))
{
//Check if our metadata field is highlighted
StringBuilder metadataKey = new StringBuilder();
metadataKey.append(schema).append(".").append(metadataField.getElement());
if (metadataField.getQualifier() != null)
{
metadataKey.append(".").append(metadataField.getQualifier());
}
StringBuilder itemName = new StringBuilder();
itemName.append(item.getHandle()).append(":").append(metadataKey.toString());
Metadatum[] itemMetadata = item.getMetadata(schema, metadataField.getElement(), metadataField.getQualifier(), Item.ANY);
if(!ArrayUtils.isEmpty(itemMetadata))
{
org.dspace.app.xmlui.wing.element.List metadataFieldList = itemList.addList(itemName.toString());
for (Metadatum metadataValue : itemMetadata)
{
String value = metadataValue.value;
addMetadataField(highlightedResults, metadataKey.toString(), metadataFieldList, value);
}
}
}
}
//Check our highlighted results, we may need to add non-metadata (like our full text)
if(highlightedResults != null)
{
//Also add the full text snippet (if available !)
List<String> fullSnippets = highlightedResults.getHighlightResults("fulltext");
if(CollectionUtils.isNotEmpty(fullSnippets))
{
StringBuilder itemName = new StringBuilder();
itemName.append(item.getHandle()).append(":").append("fulltext");
org.dspace.app.xmlui.wing.element.List fullTextFieldList = itemList.addList(itemName.toString());
for (String snippet : fullSnippets)
{
addMetadataField(fullTextFieldList, snippet);
}
}
}
}
/**
* Render the given collection, all collection metadata is added to the list
* @param collection the collection to be rendered
* @param highlightedResults the highlighted results
* @throws WingException
*/
protected void renderCollection(Collection collection, DiscoverResult.DSpaceObjectHighlightResult highlightedResults, org.dspace.app.xmlui.wing.element.List collectionMetadata) throws WingException {
String description = collection.getMetadata("introductory_text");
String description_abstract = collection.getMetadata("short_description");
String description_table = collection.getMetadata("side_bar_text");
String identifier_uri = "http://hdl.handle.net/" + collection.getHandle();
String provenance = collection.getMetadata("provenance_description");
String rights = collection.getMetadata("copyright_text");
String rights_license = collection.getMetadata("license");
String title = collection.getMetadata("name");
if(StringUtils.isNotBlank(description))
{
addMetadataField(highlightedResults, "dc.description", collectionMetadata.addList(collection.getHandle() + ":dc.description"), description);
}
if(StringUtils.isNotBlank(description_abstract))
{
addMetadataField(highlightedResults, "dc.description.abstract", collectionMetadata.addList(collection.getHandle() + ":dc.description.abstract"), description_abstract);
}
if(StringUtils.isNotBlank(description_table))
{
addMetadataField(highlightedResults, "dc.description.tableofcontents", collectionMetadata.addList(collection.getHandle() + ":dc.description.tableofcontents"), description_table);
}
if(StringUtils.isNotBlank(identifier_uri))
{
addMetadataField(highlightedResults, "dc.identifier.uri", collectionMetadata.addList(collection.getHandle() + ":dc.identifier.uri"), identifier_uri);
}
if(StringUtils.isNotBlank(provenance))
{
addMetadataField(highlightedResults, "dc.provenance", collectionMetadata.addList(collection.getHandle() + ":dc.provenance"), provenance);
}
if(StringUtils.isNotBlank(rights))
{
addMetadataField(highlightedResults, "dc.rights", collectionMetadata.addList(collection.getHandle() + ":dc.rights"), rights);
}
if(StringUtils.isNotBlank(rights_license))
{
addMetadataField(highlightedResults, "dc.rights.license", collectionMetadata.addList(collection.getHandle() + ":dc.rights.license"), rights_license);
}
if(StringUtils.isNotBlank(title))
{
addMetadataField(highlightedResults, "dc.title", collectionMetadata.addList(collection.getHandle() + ":dc.title"), title);
}
}
/**
* Render the given collection, all collection metadata is added to the list
* @param community the community to be rendered
* @param highlightedResults the highlighted results
* @throws WingException
*/
protected void renderCommunity(Community community, DiscoverResult.DSpaceObjectHighlightResult highlightedResults, org.dspace.app.xmlui.wing.element.List communityMetadata) throws WingException {
String description = community.getMetadata("introductory_text");
String description_abstract = community.getMetadata("short_description");
String description_table = community.getMetadata("side_bar_text");
String identifier_uri = "http://hdl.handle.net/" + community.getHandle();
String rights = community.getMetadata("copyright_text");
String title = community.getMetadata("name");
if(StringUtils.isNotBlank(description))
{
addMetadataField(highlightedResults, "dc.description", communityMetadata.addList(community.getHandle() + ":dc.description"), description);
}
if(StringUtils.isNotBlank(description_abstract))
{
addMetadataField(highlightedResults, "dc.description.abstract", communityMetadata.addList(community.getHandle() + ":dc.description.abstract"), description_abstract);
}
if(StringUtils.isNotBlank(description_table))
{
addMetadataField(highlightedResults, "dc.description.tableofcontents", communityMetadata.addList(community.getHandle() + ":dc.description.tableofcontents"), description_table);
}
if(StringUtils.isNotBlank(identifier_uri))
{
addMetadataField(highlightedResults, "dc.identifier.uri", communityMetadata.addList(community.getHandle() + ":dc.identifier.uri"), identifier_uri);
}
if(StringUtils.isNotBlank(rights))
{
addMetadataField(highlightedResults, "dc.rights", communityMetadata.addList(community.getHandle() + ":dc.rights"), rights);
}
if(StringUtils.isNotBlank(title))
{
addMetadataField(highlightedResults, "dc.title", communityMetadata.addList(community.getHandle() + ":dc.title"), title);
}
}
/**
* Add the current value to the wing list
* @param highlightedResults the highlighted results
* @param metadataKey the metadata key {schema}.{element}.{qualifier}
* @param metadataFieldList the wing list we need to add the metadata value to
* @param value the metadata value
* @throws WingException
*/
protected void addMetadataField(DiscoverResult.DSpaceObjectHighlightResult highlightedResults, String metadataKey, org.dspace.app.xmlui.wing.element.List metadataFieldList, String value) throws WingException {
if(value == null){
//In the unlikely event that the value is null, do not attempt to render this
return;
}
if(highlightedResults != null && highlightedResults.getHighlightResults(metadataKey) != null)
{
//Loop over all our highlighted results
for (String highlight : highlightedResults.getHighlightResults(metadataKey))
{
//If our non-highlighted value matches our original one, ensure that the highlighted one is used
DiscoverHitHighlightingField highlightConfig = queryArgs.getHitHighlightingField(metadataKey);
//We might also have it configured for ALL !
if(highlightConfig == null)
{
highlightConfig = queryArgs.getHitHighlightingField("*");
}
switch (highlightConfig.getMaxChars())
{
case DiscoverHitHighlightingField.UNLIMITED_FRAGMENT_LENGTH:
//Exact match required
//\r is not indexed in solr & will cause issues
if(highlight.replaceAll("</?em>", "").equals(value.replace("\r", "")))
{
value = highlight;
}
break;
default:
//Partial match allowed, only render the highlighted part (will also remove \r since this char is not indexed in solr & will cause issues
if(value.replace("\r", "").contains(highlight.replaceAll("</?em>", "")))
{
value = highlight;
}
break;
}
}
}
addMetadataField(metadataFieldList, value);
}
/**
* Add our metadata value, this value will might contain the highlight ("<em></em>") tags, these will be removed & rendered as highlight wing fields.
* @param metadataFieldList the metadata list we need to add the value to
* @param value the metadata value to be rendered
* @throws WingException
*/
protected void addMetadataField(org.dspace.app.xmlui.wing.element.List metadataFieldList, String value) throws WingException {
//We need to put everything in <em> tags in a highlight !
org.dspace.app.xmlui.wing.element.Item metadataItem = metadataFieldList.addItem();
while(value.contains("<em>") && value.contains("</em>"))
{
if(0 < value.indexOf("<em>"))
{
//Add everything before the <em> !
metadataItem.addContent(value.substring(0, value.indexOf("<em>")));
}
metadataItem.addHighlight("highlight").addContent(StringUtils.substringBetween(value, "<em>", "</em>"));
value = StringUtils.substringAfter(value, "</em>");
}
if(0 < value.length())
{
metadataItem.addContent(value);
}
}
/**
* Add options to the search scope field. This field determines in which
* communities or collections to search for the query.
* <p/>
* The scope list will depend upon the current search scope. There are three
* cases:
* <p/>
* No current scope: All top level communities are listed.
* <p/>
* The current scope is a community: All collections contained within the
* community are listed.
* <p/>
* The current scope is a collection: All parent communities are listed.
*
* @param scope The current scope field.
*/
protected void buildScopeList(Select scope) throws SQLException,
WingException {
DSpaceObject scopeDSO = getScope();
if (scopeDSO == null) {
// No scope, display all root level communities
scope.addOption("/", T_all_of_dspace);
scope.setOptionSelected("/");
for (Community community : Community.findAllTop(context)) {
scope.addOption(community.getHandle(), community.getMetadata("name"));
}
} else if (scopeDSO instanceof Community) {
// The scope is a community, display all collections contained
// within
Community community = (Community) scopeDSO;
scope.addOption("/", T_all_of_dspace);
scope.addOption(community.getHandle(), community.getMetadata("name"));
scope.setOptionSelected(community.getHandle());
for (Collection collection : community.getCollections()) {
scope.addOption(collection.getHandle(), collection.getMetadata("name"));
}
} else if (scopeDSO instanceof Collection) {
// The scope is a collection, display all parent collections.
Collection collection = (Collection) scopeDSO;
scope.addOption("/", T_all_of_dspace);
scope.addOption(collection.getHandle(), collection.getMetadata("name"));
scope.setOptionSelected(collection.getHandle());
Community[] communities = collection.getCommunities()[0]
.getAllParents();
for (Community community : communities) {
scope.addOption(community.getHandle(), community.getMetadata("name"));
}
}
}
/**
* Query DSpace for a list of all items / collections / or communities that
* match the given search query.
*
*
* @param scope the dspace object parent
*/
public void performSearch(DSpaceObject scope) throws UIException, SearchServiceException {
if (queryResults != null)
{
return;
}
String query = getQuery();
//DSpaceObject scope = getScope();
int page = getParameterPage();
List<String> filterQueries = new ArrayList<String>();
String[] fqs = getFilterQueries();
if (fqs != null)
{
filterQueries.addAll(Arrays.asList(fqs));
}
this.queryArgs = new DiscoverQuery();
//Add the configured default filter queries
DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(scope);
List<String> defaultFilterQueries = discoveryConfiguration.getDefaultFilterQueries();
queryArgs.addFilterQueries(defaultFilterQueries.toArray(new String[defaultFilterQueries.size()]));
if (filterQueries.size() > 0) {
queryArgs.addFilterQueries(filterQueries.toArray(new String[filterQueries.size()]));
}
queryArgs.setMaxResults(getParameterRpp());
String sortBy = ObjectModelHelper.getRequest(objectModel).getParameter("sort_by");
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());
}
}
}
}
String sortOrder = ObjectModelHelper.getRequest(objectModel).getParameter("order");
if(sortOrder == null && searchSortConfiguration != null){
sortOrder = searchSortConfiguration.getDefaultSortOrder().toString();
}
if (sortOrder == null || sortOrder.equalsIgnoreCase("DESC"))
{
queryArgs.setSortField(sortBy, DiscoverQuery.SORT_ORDER.desc);
}
else
{
queryArgs.setSortField(sortBy, DiscoverQuery.SORT_ORDER.asc);
}
String groupBy = ObjectModelHelper.getRequest(objectModel).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 that 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);
}
queryArgs.setQuery(query != null && !query.trim().equals("") ? query : null);
if (page > 1)
{
queryArgs.setStart((page - 1) * queryArgs.getMaxResults());
}
else
{
queryArgs.setStart(0);
}
if(discoveryConfiguration.getHitHighlightingConfiguration() != null)
{
List<DiscoveryHitHighlightFieldConfiguration> metadataFields = discoveryConfiguration.getHitHighlightingConfiguration().getMetadataFields();
for (DiscoveryHitHighlightFieldConfiguration fieldConfiguration : metadataFields)
{
queryArgs.addHitHighlightingField(new DiscoverHitHighlightingField(fieldConfiguration.getField(), fieldConfiguration.getMaxSize(), fieldConfiguration.getSnippets()));
}
}
queryArgs.setSpellCheck(discoveryConfiguration.isSpellCheckEnabled());
this.queryResults = SearchUtils.getSearchService().search(context, scope, queryArgs);
}
/**
* Returns a list of the filter queries for use in rendering pages, creating page more urls, ....
* @return an array containing the filter queries
*/
protected Map<String, String[]> getParameterFilterQueries()
{
try {
Map<String, String[]> result = new HashMap<String, String[]>();
result.put("fq", ObjectModelHelper.getRequest(objectModel).getParameterValues("fq"));
return result;
}
catch (Exception e) {
return null;
}
}
/**
* Returns all the filter queries for use by solr
* This method returns more expanded filter queries then the getParameterFilterQueries
* @return an array containing the filter queries
*/
protected String[] getFilterQueries() {
try {
return ObjectModelHelper.getRequest(objectModel).getParameterValues("fq");
}
catch (Exception e) {
return null;
}
}
protected String[] getFacetsList() {
try {
return ObjectModelHelper.getRequest(objectModel).getParameterValues("fl");
}
catch (Exception e) {
return null;
}
}
protected int getParameterPage() {
try {
return Integer.parseInt(ObjectModelHelper.getRequest(objectModel).getParameter("page"));
}
catch (Exception e) {
return 1;
}
}
protected int getParameterRpp() {
try {
return Integer.parseInt(ObjectModelHelper.getRequest(objectModel).getParameter("rpp"));
}
catch (Exception e) {
return 10;
}
}
protected String getParameterSortBy() {
String s = ObjectModelHelper.getRequest(objectModel).getParameter("sort_by");
return s != null ? s : null;
}
protected String getParameterGroup() {
String s = ObjectModelHelper.getRequest(objectModel).getParameter("group_by");
return s != null ? s : "none";
}
protected String getParameterOrder() {
return ObjectModelHelper.getRequest(objectModel).getParameter("order");
}
protected String getParameterScope() {
return ObjectModelHelper.getRequest(objectModel).getParameter("scope");
}
protected int getParameterEtAl() {
try {
return Integer.parseInt(ObjectModelHelper.getRequest(objectModel).getParameter("etal"));
}
catch (Exception e) {
return 0;
}
}
/**
* Determine if the scope of the search should fixed or is changeable by the
* user.
* <p/>
* The search scope when performed by url, i.e. they are at the url handle/xxxx/xx/search
* then it is fixed. However at the global level the search is variable.
*
* @return true if the scope is variable, false otherwise.
*/
protected boolean variableScope() throws SQLException {
return (HandleUtil.obtainHandle(objectModel) == null);
}
/**
* Extract the query string. Under most implementations this will be derived
* from the url parameters.
*
* @return The query string.
*/
protected abstract String getQuery() throws UIException;
/**
* Generate a url to the given search implementation with the associated
* parameters included.
*
* @param parameters
* @return The post URL
*/
protected abstract String generateURL(Map<String, String> parameters)
throws UIException;
/**
* Recycle
*/
public void recycle() {
this.queryArgs = null;
this.queryResults = null;
this.validity = null;
super.recycle();
}
protected void buildSearchControls(Division div)
throws WingException, SQLException {
DSpaceObject dso = HandleUtil.obtainHandle(objectModel);
DiscoveryConfiguration discoveryConfiguration = SearchUtils.getDiscoveryConfiguration(dso);
Division searchControlsGear = div.addDivision("masked-page-control").addDivision("search-controls-gear", "controls-gear-wrapper");
/**
* Add sort by options, the gear will be rendered by a combination of javascript & css
*/
String currentSort = getParameterSortBy();
org.dspace.app.xmlui.wing.element.List sortList = searchControlsGear.addList("sort-options", org.dspace.app.xmlui.wing.element.List.TYPE_SIMPLE, "gear-selection");
sortList.addItem("sort-head", "gear-head first").addContent(T_sort_by);
DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration.getSearchSortConfiguration();
org.dspace.app.xmlui.wing.element.List sortOptions = sortList.addList("sort-selections");
boolean selected = ("score".equals(currentSort) || (currentSort == null && searchSortConfiguration.getDefaultSort() == null));
sortOptions.addItem("relevance", "gear-option" + (selected ? " gear-option-selected" : "")).addXref("sort_by=score&order=" + searchSortConfiguration.getDefaultSortOrder(), T_sort_by_relevance);
if(searchSortConfiguration.getSortFields() != null)
{
for (DiscoverySortFieldConfiguration sortFieldConfiguration : searchSortConfiguration.getSortFields())
{
String sortField = SearchUtils.getSearchService().toSortFieldIndex(sortFieldConfiguration.getMetadataField(), sortFieldConfiguration.getType());
boolean selectedAsc = ((sortField.equals(currentSort) && "asc".equals(getParameterOrder())) || (sortFieldConfiguration.equals(searchSortConfiguration.getDefaultSort())) && DiscoverySortConfiguration.SORT_ORDER.asc.equals(searchSortConfiguration.getDefaultSortOrder()));
boolean selectedDesc= ((sortField.equals(currentSort) && "desc".equals(getParameterOrder())) || (sortFieldConfiguration.equals(searchSortConfiguration.getDefaultSort())) && DiscoverySortConfiguration.SORT_ORDER.desc.equals(searchSortConfiguration.getDefaultSortOrder()));
String sortFieldParam = "sort_by=" + sortField + "&order=";
sortOptions.addItem(sortField, "gear-option" + (selectedAsc ? " gear-option-selected" : "")).addXref(sortFieldParam + "asc", message("xmlui.Discovery.AbstractSearch.sort_by." + sortField + "_asc"));
sortOptions.addItem(sortField, "gear-option" + (selectedDesc ? " gear-option-selected" : "")).addXref(sortFieldParam + "desc", message("xmlui.Discovery.AbstractSearch.sort_by." + sortField + "_desc"));
}
}
//Add the rows per page
sortList.addItem("rpp-head", "gear-head").addContent(T_rpp);
org.dspace.app.xmlui.wing.element.List rppOptions = sortList.addList("rpp-selections");
for (int i : RESULTS_PER_PAGE_PROGRESSION)
{
rppOptions.addItem("rpp-" + i, "gear-option" + (i == getParameterRpp() ? " gear-option-selected" : "")).addXref("rpp=" + i, Integer.toString(i));
}
}
/**
* Determine the current scope. This may be derived from the current url
* handle if present or the scope parameter is given. If no scope is
* specified then null is returned.
*
* @return The current scope.
*/
protected DSpaceObject getScope() throws SQLException {
Request request = ObjectModelHelper.getRequest(objectModel);
String scopeString = request.getParameter("scope");
// Are we in a community or collection?
DSpaceObject dso;
if (scopeString == null || "".equals(scopeString))
{
// get the search scope from the url handle
dso = HandleUtil.obtainHandle(objectModel);
}
else
{
// Get the search scope from the location parameter
dso = HandleManager.resolveToObject(context, scopeString);
}
return dso;
}
protected void logSearch() {
int countCommunities = 0;
int countCollections = 0;
int countItems = 0;
/**
* TODO: Maybe we can create a default "type" facet for this
* will give results for Items, Communities and Collection types
* benefits... no iteration over results at all to sum types
* leaves it upto solr...
for (Object type : queryResults.getHitTypes())
{
if (type instanceof Integer)
{
switch (((Integer)type).intValue())
{
case Constants.ITEM: countItems++; break;
case Constants.COLLECTION: countCollections++; break;
case Constants.COMMUNITY: countCommunities++; break;
}
}
}
*/
String logInfo = "";
try {
DSpaceObject dsoScope = getScope();
if (dsoScope instanceof Collection) {
logInfo = "collection_id=" + dsoScope.getID() + ",";
} else if (dsoScope instanceof Community) {
logInfo = "community_id=" + dsoScope.getID() + ",";
}
}
catch (SQLException sqle) {
// Ignore, as we are only trying to get the scope to add detail to the log message
}
log.info(LogManager.getHeader(context, "search", logInfo + "query=\""
+ (queryArgs == null ? "" : queryArgs.getQuery()) + "\",results=(" + countCommunities + ","
+ countCollections + "," + countItems + ")"));
}
}