/** * 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.search; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.sort.SortOption; import org.apache.commons.lang.StringUtils; /** * Contains the arguments for a query. Fill it out and pass to the query engine */ public class QueryArgs { // the query string private String query; // start and count defines a search 'cursor' or page // query will return 'count' hits beginning at offset 'start' private int start = 0; // default values private int pageSize = 10; private SortOption sortOption = null; private String sortOrder = SortOption.DESCENDING; /** number of metadata elements to display before truncating using "et al" */ private int etAl = ConfigurationManager.getIntProperty("webui.itemlist.author-limit"); /** * @return the number of metadata fields at which to truncate with "et al" */ public int getEtAl() { return etAl; } /** * set the number of metadata fields at which to truncate with "et al" * * @param etAl */ public void setEtAl(int etAl) { this.etAl = etAl; } /** * set the query string * * @param newQuery */ public void setQuery(String newQuery) { query = newQuery; } /** * retrieve the query string * * @return the current query string */ public String getQuery() { return query; } /** * set the offset of the desired search results, beginning with 0 ; used to * page results (the default value is 0) * * @param newStart * index of first desired result */ public void setStart(int newStart) { start = newStart; } /** * read the search's starting offset * * @return current index of first desired result */ public int getStart() { return start; } /** * set the count of hits to return; used to implement paged searching see * the initializer for the default value * * @param newSize * number of hits per page */ public void setPageSize(int newSize) { pageSize = newSize; } /** * get the count of hits to return * * @return number of results per page */ public int getPageSize() { return pageSize; } public SortOption getSortOption() { return sortOption; } public void setSortOption(SortOption sortOption) { this.sortOption = sortOption; } public String getSortOrder() { return sortOrder; } public void setSortOrder(String sortOrder) { this.sortOrder = sortOrder; } /** * Builds an advanced-query description string. * * The string is built using the passed in values * query{1,2,3}, field{1,2,3} and conjunction{1,2} taken from * the parameter request. * * @param request the request object to take the values from * * @return the query description string built */ public String buildQuery(HttpServletRequest request) { String newquery = "("; String numFieldStr = request.getParameter("num_search_field"); // for backward compatibility if (numFieldStr == null) { numFieldStr = "3"; } int numField = Integer.parseInt(numFieldStr); List<String> query = new ArrayList<String>(); List<String> field = new ArrayList<String>(); List<String> conjunction = new ArrayList<String>(); for (int i = 1; i <= numField; i++) { String tmp_query = request.getParameter("query"+i); String tmp_field = request.getParameter("field"+i); // TODO: Ensure a valid field from config // Disarm fields with regexp control characters if (tmp_field != null) { tmp_field = tmp_field.replace('/', ' '); tmp_field = tmp_field.replace('<', ' '); tmp_field = tmp_field.replace('\\', ' '); tmp_field = tmp_field.replace(':', ' '); } if (tmp_query != null && !tmp_query.equals("")) { query.add(tmp_query.trim()); if (tmp_field == null) { field.add("ANY"); } else { field.add(tmp_field.trim()); } if (i != numField) { conjunction.add(request.getParameter("conjunction"+i) != null? request.getParameter("conjunction"+i):"AND"); } } } Iterator<String> iquery = query.iterator(); Iterator<String> ifield = field.iterator(); Iterator<String> iconj = conjunction.iterator(); String conj_curr = ""; while (iquery.hasNext()) { newquery = newquery + conj_curr; String query_curr = iquery.next(); String field_curr = ifield.next(); newquery = newquery + buildQueryPart(query_curr,field_curr); if (iconj.hasNext()) { conj_curr = " " + iconj.next() + " "; } } newquery = newquery + ")"; return (newquery); } /** * Builds a query-part using the field and value passed in * with ' --> " (single to double quote) translation. * * @param myquery the value the query will look for * @param myfield the field myquery will be looked for in * * @return the query created */ private String buildQueryPart(String myquery, String myfield) { StringBuilder newQuery = new StringBuilder(); newQuery.append("("); boolean newTerm = true; boolean inPhrase = false; char phraseChar = '\"'; StringTokenizer qtok = new StringTokenizer(myquery, " \t\n\r\f\"\'", true); while (qtok.hasMoreTokens()) { String token = qtok.nextToken(); if (StringUtils.isWhitespace(token)) { if (!inPhrase) { newTerm = true; } newQuery.append(token); } else { // Matched the end of the phrase if (inPhrase && token.charAt(0) == phraseChar) { newQuery.append("\""); inPhrase = false; } else { // If we aren't dealing with a new term, and have a single quote // don't touch it. (for example, the apostrophe in it's). if (!newTerm && token.charAt(0) == '\'') { newQuery.append(token); } else { // Treat - my"phrased query" - as - my "phrased query" if (!newTerm && token.charAt(0) == '\"') { newQuery.append(" "); newTerm = true; } // This is a new term in the query (ie. preceeded by nothing or whitespace) // so apply a field restriction if specified if (newTerm && !myfield.equals("ANY")) { newQuery.append(myfield).append(":"); } // Open a new phrase, and closing at the corresponding character // ie. 'my phrase' or "my phrase" if (token.charAt(0) == '\"' || token.charAt(0) == '\'') { newQuery.append("\""); inPhrase = true; newTerm = false; phraseChar = token.charAt(0); } else { newQuery.append(token); newTerm = false; } } } } } newQuery.append(")"); return newQuery.toString(); } /** * Constructs a HashMap with the keys field{1,2,3}, query{1,2,3} and * conjunction{1,2} taking the values from the passed-in argument * defaulting to "". * * @param request the request-describing object to take the values from * * @return the created HashMap */ public Map<String, String> buildQueryMap(HttpServletRequest request) { Map<String, String> queryMap = new HashMap<String, String>(); String numFieldStr = request.getParameter("num_search_field"); // for backward compatibility if (numFieldStr == null) { numFieldStr = "3"; } int numField = Integer.parseInt(numFieldStr); for (int i = 1; i < numField; i++) { String queryStr = "query" + i; String fieldStr = "field" + i; String conjunctionStr = "conjunction" + i; queryMap.put(queryStr, StringUtils.defaultString(request.getParameter(queryStr), "")); queryMap.put(fieldStr, StringUtils.defaultString(request.getParameter(fieldStr), "ANY")); queryMap.put(conjunctionStr, StringUtils.defaultString(request.getParameter(conjunctionStr), "AND")); } String queryStr = "query" + numField; String fieldStr = "field" + numField; queryMap.put(queryStr, StringUtils.defaultString(request.getParameter(queryStr), "")); queryMap.put(fieldStr, StringUtils.defaultString(request.getParameter(fieldStr), "ANY")); return (queryMap); } /** * Builds an HTTP query string for some parameters with the value * taken from the request context passed in. * * The returned string includes key/value pairs in the HTTP query string * format (key1=value1&key2=value2...) for the keys query{1,2,3}, * field{1,2,3} and conjunction{1,2} with values taken from request * and defaulting to "". * <P> * Note, that the values are url-encoded using the UTF-8 encoding scheme * as the corresponding W3C recommendation states. * <P> * Also note that neither leading ? (question mark) * nor leading & (ampersand mark) is included. * Take this into account when appending to a real URL. * * @param request the request object to take the values from * * @return the query string that can be used without further * transformationin URLs * */ public String buildHTTPQuery(HttpServletRequest request) throws UnsupportedEncodingException { StringBuilder queryString = new StringBuilder(); Map<String, String> queryMap = buildQueryMap(request); for (Map.Entry<String, String> query : queryMap.entrySet()) { queryString.append("&") .append(query.getKey()) .append("=") .append(URLEncoder.encode(query.getValue(), Constants.DEFAULT_ENCODING)); } if (request.getParameter("num_search_field") != null) { queryString.append("&num_search_field=").append(request.getParameter("num_search_field")); } // return the result with the leading "&" removed return queryString.substring(1); } }