@@ -29,6 +29,7 @@ import play.api.libs.json._
2929import _root_ .util .SearchUtils
3030import org .apache .commons .lang .StringUtils
3131import org .elasticsearch .action .admin .indices .exists .indices .IndicesExistsRequest
32+ import org .elasticsearch .search .sort .SortOrder
3233
3334
3435/**
@@ -130,7 +131,8 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
130131 * "field_leaf_key": name of immediate field only, e.g. 'lines'
131132 */
132133 val queryObj = prepareElasticJsonQuery(query, grouping, permitted, user)
133- accumulatePageResult(queryObj, user, from.getOrElse(0 ), size.getOrElse(maxResults))
134+ // TODO: Support sorting in GUI search
135+ accumulatePageResult(queryObj, user, from.getOrElse(0 ), size.getOrElse(maxResults), None , None )
134136 }
135137
136138 /**
@@ -152,8 +154,8 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
152154 */
153155 def search (query : String , resource_type : Option [String ], datasetid : Option [String ], collectionid : Option [String ],
154156 spaceid : Option [String ], folderid : Option [String ], field : Option [String ], tag : Option [String ],
155- from : Option [Int ], size : Option [Int ], permitted : List [ UUID ], user : Option [User ],
156- index : String = nameOfIndex): ElasticsearchResult = {
157+ from : Option [Int ], size : Option [Int ], sort : Option [ String ], order : Option [String ], permitted : List [ UUID ],
158+ user : Option [ User ], index : String = nameOfIndex): ElasticsearchResult = {
157159
158160 // Convert any parameters from API into the query syntax equivalent so we can parse it all together later
159161 var expanded_query = query
@@ -166,16 +168,16 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
166168 folderid.foreach(fid => expanded_query += s " in: $fid" )
167169
168170 val queryObj = prepareElasticJsonQuery(expanded_query.stripPrefix(" " ), permitted, user)
169- accumulatePageResult(queryObj, user, from.getOrElse(0 ), size.getOrElse(maxResults))
171+ accumulatePageResult(queryObj, user, from.getOrElse(0 ), size.getOrElse(maxResults), sort, order )
170172 }
171173
172174 /** Perform search, check permissions, and keep searching again if page isn't filled with permitted resources */
173175 def accumulatePageResult (queryObj : XContentBuilder , user : Option [User ], from : Int , size : Int ,
174- index : String = nameOfIndex): ElasticsearchResult = {
176+ sort : Option [ String ], order : Option [ String ], index : String = nameOfIndex): ElasticsearchResult = {
175177 var total_results = ListBuffer .empty[ResourceRef ]
176178
177179 // Fetch initial page & filter by permissions
178- val (results, total_size) = _search(queryObj, index, Some (from), Some (size))
180+ val (results, total_size) = _search(queryObj, index, Some (from), Some (size), sort, order )
179181 Logger .debug(s " Found ${results.length} results with ${total_size} total " )
180182 val filtered = checkResultPermissions(results, user)
181183 Logger .debug(s " Permission to see ${filtered.length} results " )
@@ -187,7 +189,7 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
187189 var exhausted = false
188190 while (total_results.length < size && ! exhausted) {
189191 Logger .debug(s " Only have ${total_results.length} total results; searching for ${size* 2 } more from ${new_from}" )
190- val (results, total_size) = _search(queryObj, index, Some (new_from), Some (size* 2 ))
192+ val (results, total_size) = _search(queryObj, index, Some (new_from), Some (size* 2 ), sort, order )
191193 Logger .debug(s " Found ${results.length} results with ${total_size} total " )
192194 if (results.length == 0 || new_from+ results.length == total_size) exhausted = true // No more results to find
193195 val filtered = checkResultPermissions(results, user)
@@ -251,17 +253,39 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
251253
252254 /** * Execute query and return list of results and total result count as tuple */
253255 def _search (queryObj : XContentBuilder , index : String = nameOfIndex,
254- from : Option [Int ] = Some (0 ), size : Option [Int ] = Some (maxResults)): (List [ResourceRef ], Long ) = {
256+ from : Option [Int ] = Some (0 ), size : Option [Int ] = Some (maxResults),
257+ sort : Option [String ], order : Option [String ]): (List [ResourceRef ], Long ) = {
255258 connect()
256259 val response = client match {
257260 case Some (x) => {
258- Logger .info(" Searching Elasticsearch: " + queryObj.string())
261+ Logger .debug(" Searching Elasticsearch: " + queryObj.string())
262+
263+ // Exclude _sort fields in response object
264+ var sortFilter = jsonBuilder().startObject().startArray(" exclude" ).value(" *._sort" ).endArray().endObject()
265+
259266 var responsePrep = x.prepareSearch(index)
260267 .setSearchType(SearchType .DFS_QUERY_THEN_FETCH )
268+ .setSource(sortFilter)
261269 .setQuery(queryObj)
262270
263271 responsePrep = responsePrep.setFrom(from.getOrElse(0 ))
264272 responsePrep = responsePrep.setSize(size.getOrElse(maxResults))
273+ // Default to ascending if no order provided but a field is
274+ val searchOrder = order match {
275+ case Some (" asc" ) => SortOrder .ASC
276+ case Some (" desc" ) => SortOrder .DESC
277+ case Some (" DESC" ) => SortOrder .DESC
278+ case _ => SortOrder .ASC
279+ }
280+ // Default to created field if order is provided but no field is
281+ sort match {
282+ // case Some("name") => responsePrep = responsePrep.addSort("name._sort", searchOrder) TODO: Not yet supported
283+ case Some (x) => responsePrep = responsePrep.addSort(x, searchOrder)
284+ case None => order match {
285+ case Some (o) => responsePrep = responsePrep.addSort(" created" , searchOrder)
286+ case None => {}
287+ }
288+ }
265289
266290 val response = responsePrep.setExplain(true ).execute().actionGet()
267291 Logger .debug(" Search hits: " + response.getHits().getTotalHits())
@@ -291,8 +315,7 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
291315 .field(" type" , " custom" )
292316 .field(" tokenizer" , " uax_url_email" )
293317 .endObject()
294- .endObject()
295- .endObject()
318+ .endObject().endObject()
296319 .startObject(" index" )
297320 .startObject(" mapping" )
298321 .field(" ignore_malformed" , true )
@@ -697,10 +720,14 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
697720 * as strings for datatypes besides Objects. In the future, this could
698721 * be removed, but only once the Search API better supports those data types (e.g. Date).
699722 */
723+
724+ // TODO: With Elastic 6.8+ we can use "normalizer": "case_insensitive" for _sort fields
725+
700726 """ {"clowder_object": {
701727 |"numeric_detection": true,
702728 |"properties": {
703- |"name": {"type": "string"},
729+ |"name": {"type": "string", "fields": {
730+ | "_sort": {"type":"string", "index": "not_analyzed"}}},
704731 |"description": {"type": "string"},
705732 |"resource_type": {"type": "string", "include_in_all": false},
706733 |"child_of": {"type": "string", "include_in_all": false},
@@ -925,7 +952,7 @@ class ElasticsearchPlugin(application: Application) extends Plugin {
925952 }
926953 }
927954
928- // If a term is specified that isn't in this list, it's assumed to be a metadata field
955+ // If a term is specified that isn't in this list, it's assumed to be a metadata field (for sorting and filtering)
929956 val official_terms = List (" name" , " creator" , " created" , " email" , " resource_type" , " in" , " contains" , " tag" , " exists" , " missing" )
930957
931958 // Create list of (key, operator, value) for passing to builder
0 commit comments