/** * $Id: AbstractSearch.java 5144 2010-06-22 12:02:05Z benbosman $ * $URL: http://scm.dspace.org/svn/repo/modules/dspace-discovery/trunk/block/src/main/java/org/dspace/app/xmlui/aspect/discovery/AbstractSearch.java $ * ************************************************************************* * Copyright (c) 2002-2009, DuraSpace. All rights reserved * Licensed under the DuraSpace License. * * A copy of the DuraSpace License has been included in this * distribution and is available at: http://scm.dspace.org/svn/repo/licenses/LICENSE.txt */ package org.dspace.app.xmlui.aspect.discovery; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.util.HashUtil; import org.apache.excalibur.source.SourceValidity; import org.apache.log4j.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.FieldCollapseResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; 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.ConfigurationManager; import org.dspace.core.LogManager; import org.dspace.discovery.*; import org.dspace.handle.HandleManager; import org.dspace.sort.SortException; import org.dspace.sort.SortOption; 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 diffrent search implementation. An implementer must * implement at least three methods: addBody(), getQuery(), and generateURL(). * <p/> * See the two implementors: SimpleSearch and AdvancedSearch. * * @author Scott Phillips * @author Mark Diggory (mdiggory at atmire.com) */ public abstract class AbstractSearch extends AbstractFiltersTransformer { private static final Logger log = Logger.getLogger(AbstractSearch.class); /** * Language strings */ private static final Message T_result_query = message("xmlui.ArtifactBrowser.AbstractSearch.result_query"); private static final Message T_result_empty_query = message("xmlui.ArtifactBrowser.AbstractSearch.result_empty_query"); private static final Message T_head1_community = message("xmlui.ArtifactBrowser.AbstractSearch.head1_community"); private static final Message T_head1_collection = message("xmlui.ArtifactBrowser.AbstractSearch.head1_collection"); private static final Message T_head1_none = message("xmlui.ArtifactBrowser.AbstractSearch.head1_none"); private static final Message T_head2 = message("xmlui.ArtifactBrowser.AbstractSearch.head2"); private static final Message T_head3 = message("xmlui.ArtifactBrowser.AbstractSearch.head3"); 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.ArtifactBrowser.AbstractSearch.sort_by.relevance"); private final static Message T_sort_by = message("xmlui.ArtifactBrowser.AbstractSearch.sort_by"); private final static Message T_order = message("xmlui.ArtifactBrowser.AbstractSearch.order"); private final static Message T_order_asc = message("xmlui.ArtifactBrowser.AbstractSearch.order.asc"); private final static Message T_order_desc = message("xmlui.ArtifactBrowser.AbstractSearch.order.desc"); private final static Message T_rpp = message("xmlui.ArtifactBrowser.AbstractSearch.rpp"); private final static Message T_group_by = message("xmlui.ArtifactBrowser.AbstractSearch.group_by"); private static final Message T_group_by_none = message("xmlui.ArtifactBrowser.AbstractSearch.group_by.none"); private final static Message T_view = message("xmlui.ArtifactBrowser.AbstractSearch.view"); /** * The options for results per page */ private static final int[] RESULTS_PER_PAGE_PROGRESSION = {5, 10, 20, 40, 60, 80, 100}; /** * The types of view */ private static final String[] VIEW_TYPES = {"listing", "grid"}; /** * 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 Request request = ObjectModelHelper.getRequest(objectModel); key += "-" + getParameterPage(); key += "-" + getParameterView(); 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 (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); SolrDocumentList results = this.queryResults.getResults(); if (results != null) { validity.add("size:" + results.size()); for (SolrDocument result : results) { validity.add(result.toString()); } } this.validity = validity.complete(); } catch (Exception e) { // Just ignore all errors and return an invalid cache. } // 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. */ @Override public abstract void addBody(Body body) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException; /** * 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 || queryResults.getResults() == null) { DSpaceObject scope = getScope(); this.performSearch(scope); } } catch (Throwable t) { log.error(t.getMessage(), t); queryResults = null; } if (queryResults != null) { if (getQuery().length() > 0) { search.addPara("result-query", "result-query").addContent(T_result_query.parameterize(getQuery(), queryResults.getResults().getNumFound())); } else { search.addPara("result-query", "result-query").addContent(T_result_empty_query.parameterize(queryResults.getResults().getNumFound())); } } // Adding different types of listing (e.g. list, grid). String view = this.getParameterView(); Division results = search.addDivision("search-results", view); DSpaceObject searchScope = getScope(); if (searchScope instanceof Community) { Community community = (Community) searchScope; String communityName = community.getMetadata("name"); results.setHead(T_head1_community.parameterize(communityName)); } else if (searchScope instanceof Collection) { Collection collection = (Collection) searchScope; String collectionName = collection.getMetadata("name"); results.setHead(T_head1_collection.parameterize(collectionName)); } else { results.setHead(T_head1_none); } if (queryResults != null && queryResults.getResults().getNumFound() > 0) { SolrDocumentList solrResults = queryResults.getResults(); // Pagination variables. int itemsTotal = (int) solrResults.getNumFound(); int firstItemIndex = (int) solrResults.getStart() + 1; int lastItemIndex = (int) solrResults.getStart() + solrResults.size(); //if (itemsTotal < lastItemIndex) // lastItemIndex = itemsTotal; int currentPage = (int) (solrResults.getStart() / this.queryArgs.getRows()) + 1; int pagesTotal = (int) ((solrResults.getNumFound() - 1) / this.queryArgs.getRows()) + 1; Map<String, String> parameters = new HashMap<String, String>(); parameters.put("page", "{pageNum}"); String pageURLMask = generateURL(parameters); //Check for facet queries ? If we have any add them String[] fqs = getParameterFacetQueries(); if(fqs != null){ for (String fq : fqs) { pageURLMask += "&fq=" + fq; } } results.setMaskedPagination(itemsTotal, firstItemIndex, lastItemIndex, currentPage, pagesTotal, pageURLMask); // Look for any communities or collections in the mix // UPDATE: mix everything. It's what is expected from a search. // UPDATE 2: actually not. Comm/coll should appear separated. ReferenceSet referenceSet = null; //* boolean resultsContainsBothContainersAndItems = false; for (SolrDocument doc : solrResults) { DSpaceObject resultDSO = SearchUtils.findDSpaceObject(context, doc); if (resultDSO instanceof Community || resultDSO instanceof Collection) { if (referenceSet == null) { referenceSet = results.addReferenceSet("search-results-repository", ReferenceSet.TYPE_SUMMARY_LIST, null, "repository-search-results"); // Set a heading showing that we will be listing containers that matched: referenceSet.setHead(T_head2); resultsContainsBothContainersAndItems = true; } referenceSet.addReference(resultDSO); } } //*/ // Put in palce top level referenceset referenceSet = results.addReferenceSet("search-results-repository", ReferenceSet.TYPE_SUMMARY_LIST, null, "repository-search-results"); for (SolrDocument doc : solrResults) { DSpaceObject resultDSO = SearchUtils.findDSpaceObject(context, doc); // if (resultDSO instanceof Item) { String group_by = this.getParameterGroup(); // If we are grouping, attempt to acquire the dc.isPartOf parent of the Item and group on it. // Otherwise, Group on the current Item. // TODO: this is a hack to always make sure any subItem is grouped under its parent if (!group_by.equals("none")) { Item parent = getParent((Item) resultDSO); // if parent not null, use parent otherwise use existing item. if (parent != null) { Reference parentRef = referenceSet.addReference(parent); addCollapsedDocuments(parentRef, parent, doc); } else { referenceSet.addReference(resultDSO); } } else { referenceSet.addReference(resultDSO); } // } } // Add hit highlighting information // <dri:referenceSet type="hitHighlighting"> // <dri:reference> // </dri:referenceSet> /* Map<String, Map<String, java.util.List<String>>> hl = queryResults.getHighlighting(); referenceSet = results.addReferenceSet("search-results-repository", ReferenceSet.TYPE_SUMMARY_LIST, null, "repository-hit-highlighting"); Reference ref = referenceSet.addReference(null); */ } else { results.addPara(T_no_results); } //}// Empty query } private void addCollapsedDocuments( Reference parentReference, DSpaceObject parent, SolrDocument doc) throws WingException { ReferenceSet referenceSet = null; // Attach any collapsed groups... FieldCollapseResponse.CollapseGroup grp = getCollapseGroup((String) doc.getFieldValue("handle")); if (grp != null) { for (SolrDocument childDoc : grp.getCollapsedDocuments()) { DSpaceObject child = null; try { child = SearchUtils.findDSpaceObject(context, childDoc); } catch (SQLException e) { log.error(e.getMessage(),e); // TODO:maybe instead of using existing Items, we can use the existing solr record /** * would need to swich reference model to be attribute based rather than element based. * * <referenceSet * rend="repository-search-results" * type="summaryList" * n="search-results-repository" * id="aspect.artifactbrowser.SimpleSearch.referenceSet.search-results-repository"> * <head>Items matching your query</head> * <reference repositoryID="123456789" type="DSpace Item" url="/metadata/handle/123456789/53/mets.xml"/> * <list * rend="repository-search-results" * type="summaryList" * n="search-results-repository" * id="aspect.artifactbrowser.SimpleSearch.referenceSet.search-results-repository"> * <head>Items matching your query</head> * <item * repositoryID="123456789" * type="DSpace Item" * url="/metadata/handle/123456789/53/mets.xml"> * <xref target="/xmlui/handle/123456789/53">DSUG09 Programme</xref> * ... * </item> * * A backward compatable approach would be to loosen the reference and referenceSet classes * to supported nested item list content, * * <referenceSet * rend="repository-search-results" * type="summaryList" * n="search-results-repository" * id="aspect.artifactbrowser.SimpleSearch.referenceSet.search-results-repository"> * <head>Items matching your query</head> * <reference repositoryID="123456789" type="DSpace Community" url="/metadata/handle/123456789/53/mets.xml"> * <xref target="/xmlui/handle/123456789/53">DSUG09 Programme</xref> * ... * </reference> * * Final objective, to use the the Solr search results to present search result contents rather than * pulling DSpace Item/Community or Item into memory just to render the search results. * */ } // TODO: This is a hack to pull out the parent from the collapsed subItems. if (child != null && !child.equals(parent)){ if(referenceSet == null) referenceSet = parentReference.addReferenceSet(ReferenceSet.TYPE_SUMMARY_LIST); referenceSet.addReference(child); } } } } private FieldCollapseResponse.CollapseGroup getCollapseGroup(String field) { FieldCollapseResponse cResp = this.queryResults.getFieldCollapseResponse(); /** make lookup map */ for (FieldCollapseResponse.CollapseGroup grp : cResp.getCollapseGroups()) { String handlePrefix = ConfigurationManager.getProperty("handle.canonical.prefix"); if (handlePrefix == null || handlePrefix.length() == 0) { handlePrefix = "http://hdl.handle.net/"; } String key = grp.getCollapseGroupId().replaceFirst(handlePrefix, ""); if (key.equals(field)) { return grp; } } return null; } private Item getParent(Item item) { String value = getSingleValue(item, "dc.relation.ispartof"); if (value == null) return null; try { String handlePrefix = ConfigurationManager.getProperty("handle.canonical.prefix"); if (handlePrefix == null || handlePrefix.length() == 0) { handlePrefix = "http://hdl.handle.net/"; } DSpaceObject obj = HandleManager.resolveToObject(context, value.replaceFirst(handlePrefix, "")); if (obj != null && obj instanceof Item) return (Item) obj; } catch (Exception e) { } return null; } private String getSingleValue(Item item, String field) { DCValue[] type = item.getMetadata(field); if (type != null && type.length > 0 && type[0] != null) { return type[0].value; } return null; } /** * Add options to the search scope field. This field determines in what * 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. * * @return The associated query results. */ public void performSearch(DSpaceObject scope) throws UIException, SearchServiceException { if (queryResults != null) return; String query = getQuery(); //DSpaceObject scope = getScope(); int page = getParameterPage(); queryArgs = this.prepareDefaultFilters("search"); queryArgs.setRows(getParameterRpp()); String sortBy = ObjectModelHelper.getRequest(objectModel).getParameter("sort_by"); String sortOrder = ObjectModelHelper.getRequest(objectModel).getParameter("order"); //webui.itemlist.sort-option.1 = title:dc.title:title //webui.itemlist.sort-option.2 = dateissued:dc.date.issued:date //webui.itemlist.sort-option.3 = dateaccessioned:dc.date.accessioned:date //webui.itemlist.sort-option.4 = ispartof:dc.relation.ispartof:text if (sortBy != null) { if (sortOrder == null || sortOrder.equals("DESC")) queryArgs.addSortField(sortBy, SolrQuery.ORDER.desc); else queryArgs.addSortField(sortBy, SolrQuery.ORDER.asc); } else { queryArgs.addSortField("score", SolrQuery.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.add("collapse.field", groupBy); queryArgs.add("collapse.threshold", "1"); queryArgs.add("collapse.includeCollapsedDocs.fl", "handle"); queryArgs.add("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.addSortField("dc.type", SolrQuery.ORDER.asc); } queryArgs.setQuery(query != null && !query.trim().equals("") ? query : "*:*"); if (page > 1) queryArgs.setStart((page - 1) * queryArgs.getRows()); else queryArgs.setStart(0); List<String> filterQueries = new ArrayList<String>(); String[] fqs = getParameterFacetQueries(); if (fqs != null) filterQueries.addAll(Arrays.asList(fqs)); queryArgs.add("f.location.facet.mincount", "0"); queryArgs.add("f.location.facet.limit", "-1"); if (scope instanceof Community) { filterQueries.add("{!tag=loc}location:m" + scope.getID()); } else if (scope instanceof Collection) { filterQueries.add("{!tag=loc}location:l" + scope.getID()); } if (filterQueries.size() > 0) { queryArgs.addFilterQuery(filterQueries.toArray(new String[filterQueries.size()])); } // Use mlt /* queryArgs.add("mlt", "true"); // The fields to use for similarity. NOTE: if possible, these should have a stored TermVector queryArgs.add("mlt.fl", "author"); // Minimum Term Frequency - the frequency below which terms will be ignored in the source doc. queryArgs.add("mlt.mintf", "1"); // Minimum Document Frequency - the frequency at which words will be ignored which do not occur in at least this many docs. queryArgs.add("mlt.mindf", "1"); queryArgs.add("mlt.q", ""); */ // mlt.minwl // minimum word length below which words will be ignored. // mlt.maxwl // maximum word length above which words will be ignored. // mlt.maxqt // maximum number of query terms that will be included in any generated query. // mlt.maxntp // maximum number of tokens to parse in each example doc field that is not stored with TermVector support. // mlt.boost // [true/false] set if the query will be boosted by the interesting term relevance. // mlt.qf // Query fields and their boosts using the same format as that used in DisMaxRequestHandler. These fields must also be specified in mlt.fl. //filePost.addParameter("fl", "handle, "search.resourcetype")"); //filePost.addParameter("field", "search.resourcetype"); //Set the default limit to 11 /* ClientUtils.escapeQueryChars(location) //f.category.facet.limit=5 for(Enumeration en = request.getParameterNames(); en.hasMoreElements();) { String key = (String)en.nextElement(); if(key.endsWith(".facet.limit")) { filePost.addParameter(key, request.getParameter(key)); } } */ log.debug("queryArgs: " + queryArgs.toString()); this.queryResults = getSearchService().search(queryArgs); } /** * 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 String[] getParameterFacetQueries() { 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 String getParameterView() { String s = ObjectModelHelper.getRequest(objectModel).getParameter("view"); return s != null ? s : VIEW_TYPES[0]; } 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 : "score"; } protected String getParameterGroup() { String s = ObjectModelHelper.getRequest(objectModel).getParameter("group_by"); return s != null ? s : "none"; } protected String getParameterOrder() { String s = ObjectModelHelper.getRequest(objectModel).getParameter("order"); return s != null ? s : "DESC"; } 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 preformed 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 { if (HandleUtil.obtainHandle(objectModel) == null) return true; else return false; } /** * Extract the query string. Under most implementations this will be derived * from the url parameters. * * @return The query string. */ abstract protected String getQuery() throws UIException; /** * Generate a url to the given search implementation with the associated * parameters included. * * @param parameters * @return The post URL */ abstract protected String generateURL(Map<String, String> parameters) throws UIException; /** * Recycle */ public void recycle() { this.validity = null; super.recycle(); } protected void buildSearchControls(Division div) throws WingException { //Table controlsTable = div.addTable("search-controls", 1, 3); Table controlsTable = div.addTable("search-controls", 1, 4); Row controlsRow = controlsTable.addRow(Row.ROLE_DATA); // Create a control for the type of view to display Cell viewCell = controlsRow.addCell(); try { viewCell.addContent(T_view); Select viewSelect = viewCell.addSelect("view"); for (String v : VIEW_TYPES) { viewSelect.addOption((v.equals(getParameterView())), v, message("xmlui.ArtifactBrowser.AbstractSearch.view." + v)); } } catch (Exception e) { throw new WingException("Unable to get view options", e); } // Create a control for the number of records to display Cell rppCell = controlsRow.addCell(); rppCell.addContent(T_rpp); Select rppSelect = rppCell.addSelect("rpp"); for (int i : RESULTS_PER_PAGE_PROGRESSION) { rppSelect.addOption((i == getParameterRpp()), i, Integer.toString(i)); } /* Cell groupCell = controlsRow.addCell(); try { // Create a drop down of the different sort columns available groupCell.addContent(T_group_by); Select groupSelect = groupCell.addSelect("group_by"); groupSelect.addOption(false, "none", T_group_by_none); String[] groups = {"publication_grp"}; for (String group : groups) { groupSelect.addOption(group.equals(getParameterGroup()), group, message("xmlui.ArtifactBrowser.AbstractSearch.group_by." + group)); } } catch (Exception se) { throw new WingException("Unable to get group options", se); } */ Cell sortCell = controlsRow.addCell(); try { // Create a drop down of the different sort columns available sortCell.addContent(T_sort_by); Select sortSelect = sortCell.addSelect("sort_by"); sortSelect.addOption(false, "score", T_sort_by_relevance); for (SortOption so : SortOption.getSortOptions()) { if (so.isVisible()) { sortSelect.addOption((so.getMetadata().equals(getParameterSortBy())), so.getMetadata(), message("xmlui.ArtifactBrowser.AbstractSearch.sort_by." + so.getName())); } } } catch (SortException se) { throw new WingException("Unable to get sort options", se); } // Create a control to changing ascending / descending order Cell orderCell = controlsRow.addCell(); orderCell.addContent(T_order); Select orderSelect = orderCell.addSelect("order"); orderSelect.addOption(SortOption.ASCENDING.equals(getParameterOrder()), SortOption.ASCENDING, T_order_asc); orderSelect.addOption(SortOption.DESCENDING.equals(getParameterOrder()), SortOption.DESCENDING, T_order_desc); // Create a control for the number of authors per item to display // FIXME This is currently disabled, as the supporting functionality // is not currently present in xmlui //if (isItemBrowse(info)) //{ // controlsForm.addContent(T_etal); // Select etalSelect = controlsForm.addSelect(BrowseParams.ETAL); // // etalSelect.addOption((info.getEtAl() < 0), 0, T_etal_all); // etalSelect.addOption(1 == info.getEtAl(), 1, Integer.toString(1)); // // for (int i = 5; i <= 50; i += 5) // { // etalSelect.addOption(i == info.getEtAl(), i, Integer.toString(i)); // } //} } 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.getQuery() + "\",results=(" + countCommunities + "," + countCollections + "," + countItems + ")")); } }