Skip to content

Commit c0abdb0

Browse files
authored
Merge pull request #3371 from jasonbrudvikess/CSSTUDIO-3105
Save&Restore search on "Description/Comment" seems to be case sensitive
2 parents 20dd0cc + df81715 commit c0abdb0

File tree

3 files changed

+107
-61
lines changed

3 files changed

+107
-61
lines changed

app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
230230
if (e.getCode() == KeyCode.ENTER) {
231231
LOGGER.log(Level.INFO, "ENTER has been pressed in uniqueIdTextField");
232232
LOGGER.log(Level.INFO, "uniqueIdProperty: " + uniqueIdProperty.getValueSafe());
233-
if (uniqueIdProperty.isEmpty().get()) {
234-
LOGGER.log(Level.INFO, "uniqueIdString: is empty");
235-
} else {
233+
if (!uniqueIdProperty.isEmpty().get()) {
236234
searchResultTableViewController.uniqueIdSearch(uniqueIdProperty.getValueSafe());
237235
}
238236
}
@@ -493,13 +491,13 @@ private void updateParametersAndSearch() {
493491
*/
494492
private String buildQueryString() {
495493
Map<String, String> map = new HashMap<>();
496-
if (nodeNameProperty.get() != null && !nodeNameProperty.get().isEmpty()) {
494+
if (nodeNameProperty.get() != null && !nodeNameProperty.get().trim().isEmpty()) {
497495
map.put(Keys.NAME.getName(), nodeNameProperty.get());
498496
}
499497
if (userNameProperty.get() != null && !userNameProperty.get().isEmpty()) {
500498
map.put(Keys.USER.getName(), userNameProperty.get());
501499
}
502-
if (descProperty.get() != null && !descProperty.get().isEmpty()) {
500+
if (descProperty.get() != null && !descProperty.get().trim().isEmpty()) {
503501
map.put(Keys.DESC.getName(), descProperty.get());
504502
}
505503
if (tagsProperty.get() != null && !tagsProperty.get().isEmpty()) {

app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchResultTableViewController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ public void search(final String query) {
330330
queryString = query;
331331
Map<String, String> searchParams =
332332
SearchQueryUtil.parseHumanReadableQueryString(queryString);
333+
LOGGER.log(Level.INFO, "search() searchParams: = " + searchParams);
333334

334335
searchParams.put(SearchQueryUtil.Keys.FROM.getName(), Integer.toString(pagination.getCurrentPageIndex() * pageSizeProperty.get()));
335336
searchParams.put(SearchQueryUtil.Keys.SIZE.getName(), Integer.toString(pageSizeProperty.get()));
@@ -368,7 +369,6 @@ void uniqueIdSearch(final String uniqueIdString) {
368369
try {
369370
/* Search with the uniqueID */
370371
Node uniqueIdNode = SaveAndRestoreService.getInstance().getNode(uniqueIdString);
371-
LOGGER.log(Level.INFO, "uniqueIDNode: " + uniqueIdNode);
372372

373373
/* Check that there are results, then fill table - should be at most one result */
374374
if (uniqueIdNode != null) {

services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java

Lines changed: 103 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery.Builder;
88
import co.elastic.clients.elasticsearch.core.SearchRequest;
99
import org.phoebus.applications.saveandrestore.model.Tag;
10+
import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil;
1011
import org.springframework.beans.factory.annotation.Value;
1112
import org.springframework.http.HttpStatus;
13+
import org.springframework.ldap.core.support.AbstractContextSource;
1214
import org.springframework.util.MultiValueMap;
1315
import org.springframework.web.server.ResponseStatusException;
1416

@@ -20,6 +22,9 @@
2022
import java.util.Map.Entry;
2123
import java.util.stream.Collectors;
2224

25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
2328
/**
2429
* A utility class for creating a search query for log entries based on time,
2530
* logbooks, tags, properties, description, etc.
@@ -45,6 +50,8 @@ public class SearchUtil {
4550
@Value("${elasticsearch.result.size.search.max:1000}")
4651
private int maxSearchSize;
4752

53+
private static final Logger LOG = LoggerFactory.getLogger(SearchUtil.class);
54+
4855
/**
4956
* @param searchParameters - the various search parameters
5057
* @return A {@link SearchRequest} based on the provided search parameters
@@ -53,6 +60,7 @@ public SearchRequest buildSearchRequest(MultiValueMap<String, String> searchPara
5360
Builder boolQueryBuilder = new Builder();
5461
boolean fuzzySearch = false;
5562
List<String> descriptionTerms = new ArrayList<>();
63+
List<String> descriptionPhraseTerms = new ArrayList<>();
5664
List<String> nodeNameTerms = new ArrayList<>();
5765
List<String> nodeNamePhraseTerms = new ArrayList<>();
5866
List<String> nodeTypeTerms = new ArrayList<>();
@@ -63,6 +71,9 @@ public SearchRequest buildSearchRequest(MultiValueMap<String, String> searchPara
6371
int searchResultSize = defaultSearchSize;
6472
int from = 0;
6573

74+
LOG.info("buildSearchRequest() called");
75+
LOG.info(" searchParameters: " + searchParameters);
76+
6677
for (Entry<String, List<String>> parameter : searchParameters.entrySet()) {
6778
switch (parameter.getKey().strip().toLowerCase()) {
6879
case "uniqueid":
@@ -72,29 +83,41 @@ public SearchRequest buildSearchRequest(MultiValueMap<String, String> searchPara
7283
}
7384
}
7485
break;
86+
7587
// Search for node name. List of names cannot be split on space char as it is allowed in a node name.
7688
case "name":
7789
for (String value : parameter.getValue()) {
78-
for (String pattern : value.split("[|,;]")) {
90+
for (String pattern : getSearchTerms(value)) {
7991
String term = pattern.trim().toLowerCase();
92+
// Quoted strings will be mapped to a phrase query
8093
if(term.startsWith("\"") && term.endsWith("\"")){
8194
nodeNamePhraseTerms.add(term.substring(1, term.length() - 1));
8295
}
8396
else{
84-
nodeNameTerms.add(term);
97+
// add wildcards inorder to search for sub-strings
98+
nodeNameTerms.add("*" + term + "*");
8599
}
86100
}
87101
}
88102
break;
103+
89104
// Search in description/comment
90-
case "description":
91105
case "desc":
106+
case "description":
92107
for (String value : parameter.getValue()) {
93-
for (String pattern : value.split("[|,;]")) {
94-
descriptionTerms.add(pattern.trim());
108+
for (String pattern : getSearchTerms(value)) {
109+
String term = pattern.trim().toLowerCase();
110+
// Quoted strings will be mapped to a phrase query
111+
if (term.startsWith("\"") && term.endsWith("\"")) {
112+
descriptionPhraseTerms.add(term.substring(1, term.length() - 1));
113+
} else {
114+
// add wildcards inorder to search for sub-strings
115+
descriptionTerms.add("*" + term + "*");
116+
}
95117
}
96118
}
97119
break;
120+
98121
// Search for node type.
99122
case "type":
100123
for (String value : parameter.getValue()) {
@@ -212,71 +235,57 @@ public SearchRequest buildSearchRequest(MultiValueMap<String, String> searchPara
212235
}
213236
}
214237

215-
// Add the description query
238+
// Add the description query. Multiple search terms will be AND:ed.
216239
if (!descriptionTerms.isEmpty()) {
217-
DisMaxQuery.Builder descQuery = new DisMaxQuery.Builder();
218-
List<Query> descQueries = new ArrayList<>();
219-
if (fuzzySearch) {
220-
descriptionTerms.forEach(searchTerm -> {
221-
Query fuzzyQuery = FuzzyQuery.of(f -> f.field("node.description").value(searchTerm))._toQuery();
222-
NestedQuery nestedQuery =
223-
NestedQuery.of(n1 -> n1.path("node")
224-
.query(fuzzyQuery));
225-
descQueries.add(nestedQuery._toQuery());
226-
});
227-
} else {
228-
descriptionTerms.forEach(searchTerm -> {
229-
Query wildcardQuery =
230-
WildcardQuery.of(w -> w.field("node.description").value(searchTerm))._toQuery();
231-
NestedQuery nestedQuery =
232-
NestedQuery.of(n1 -> n1.path("node")
233-
.query(wildcardQuery));
234-
descQueries.add(nestedQuery._toQuery());
235-
});
240+
for (String searchTerm : descriptionTerms) {
241+
NestedQuery innerNestedQuery;
242+
if (fuzzySearch) {
243+
FuzzyQuery matchQuery = FuzzyQuery.of(m -> m.field("node.description").value(searchTerm));
244+
innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
245+
} else {
246+
WildcardQuery matchQuery = WildcardQuery.of(m -> m.field("node.description").value(searchTerm));
247+
innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
248+
}
249+
boolQueryBuilder.must(innerNestedQuery._toQuery());
250+
}
251+
}
252+
253+
// Add phrase queries for the description key. Multiple search terms will be AND:ed.
254+
if (!descriptionPhraseTerms.isEmpty()) {
255+
for (String searchTerm : descriptionPhraseTerms) {
256+
MatchPhraseQuery matchQuery = MatchPhraseQuery.of(m -> m.field("node.description").query(searchTerm));
257+
NestedQuery innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
258+
boolQueryBuilder.must(innerNestedQuery._toQuery());
236259
}
237-
descQuery.queries(descQueries);
238-
boolQueryBuilder.must(descQuery.build()._toQuery());
239260
}
240261

241262
// Add uniqueId query
242263
if(!uniqueIdTerms.isEmpty()){
243264
boolQueryBuilder.must(IdsQuery.of(id -> id.values(uniqueIdTerms))._toQuery());
244265
}
245266

246-
// Add the name query
267+
// Add the description query. Multiple search terms will be AND:ed.
247268
if (!nodeNameTerms.isEmpty()) {
248-
DisMaxQuery.Builder nodeNameQuery = new DisMaxQuery.Builder();
249-
List<Query> nodeNameQueries = new ArrayList<>();
250-
if (fuzzySearch) {
251-
nodeNameTerms.forEach(searchTerm -> {
252-
NestedQuery innerNestedQuery;
269+
for (String searchTerm : nodeNameTerms) {
270+
NestedQuery innerNestedQuery;
271+
if (fuzzySearch) {
253272
FuzzyQuery matchQuery = FuzzyQuery.of(m -> m.field("node.name").value(searchTerm));
254-
innerNestedQuery = NestedQuery.of(n1 -> n1.path("node").query(matchQuery._toQuery()));
255-
nodeNameQueries.add(innerNestedQuery._toQuery());
256-
});
257-
} else {
258-
nodeNameTerms.forEach(searchTerm -> {
259-
NestedQuery innerNestedQuery;
273+
innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
274+
} else {
260275
WildcardQuery matchQuery = WildcardQuery.of(m -> m.field("node.name").value(searchTerm));
261-
innerNestedQuery = NestedQuery.of(n1 -> n1.path("node").query(matchQuery._toQuery()));
262-
nodeNameQueries.add(innerNestedQuery._toQuery());
263-
});
276+
innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
277+
}
278+
boolQueryBuilder.must(innerNestedQuery._toQuery());
264279
}
265-
nodeNameQuery.queries(nodeNameQueries);
266-
boolQueryBuilder.must(nodeNameQuery.build()._toQuery());
267280
}
268281

269-
if(!nodeNamePhraseTerms.isEmpty()){
270-
DisMaxQuery.Builder nodeNamePhraseQueryBuilder = new DisMaxQuery.Builder();
271-
List<NestedQuery> nestedQueries = new ArrayList<>();
272-
nodeNamePhraseTerms.forEach(phraseSearchTerm -> {
273-
NestedQuery innerNestedQuery;
274-
MatchPhraseQuery matchPhraseQuery = MatchPhraseQuery.of(m -> m.field("node.name").query(phraseSearchTerm));
275-
innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchPhraseQuery._toQuery()));
276-
nestedQueries.add(innerNestedQuery);
277-
});
278-
nodeNamePhraseQueryBuilder.queries(nestedQueries.stream().map(QueryVariant::_toQuery).collect(Collectors.toList()));
279-
boolQueryBuilder.must(nodeNamePhraseQueryBuilder.build()._toQuery());
282+
// Add phrase queries for the nodeName key. Multiple search terms will be AND:ed.
283+
if (!nodeNamePhraseTerms.isEmpty()) {
284+
for (String searchTerm : nodeNamePhraseTerms) {
285+
MatchPhraseQuery matchQuery = MatchPhraseQuery.of(m -> m.field("node.name").query(searchTerm));
286+
NestedQuery innerNestedQuery = NestedQuery.of(n -> n.path("node").query(matchQuery._toQuery()));
287+
boolQueryBuilder.must(innerNestedQuery._toQuery());
288+
}
280289
}
281290

282291
// Add node type query. Fuzzy search not needed as node types are well-defined and limited in number.
@@ -337,4 +346,43 @@ public SearchRequest buildSearchRequestForPvs(List<String> pvNames) {
337346
.size(Math.min(searchResultSize, maxSearchSize))
338347
.from(0));
339348
}
349+
350+
/**
351+
* Parses a search query terms string into a string array. In particular,
352+
* quoted search terms must be maintained even if they contain the
353+
* separator chars used to tokenize the terms.
354+
*
355+
* @param searchQueryTerms String as specified by client
356+
* @return A {@link List} of search terms, some of which may be
357+
* quoted. Is void of any zero-length strings.
358+
*/
359+
public List<String> getSearchTerms(String searchQueryTerms) {
360+
// Count double quote chars. Odd number of quote chars
361+
// is not supported -> throw exception
362+
long quoteCount = searchQueryTerms.chars().filter(c -> c == '\"').count();
363+
if (quoteCount == 0) {
364+
return Arrays.stream(searchQueryTerms.split("[\\|,;\\s+]")).filter(t -> t.length() > 0).collect(Collectors.toList());
365+
}
366+
if (quoteCount % 2 == 1) {
367+
throw new IllegalArgumentException("Unbalanced quotes in search query");
368+
}
369+
// If we come this far then at least one quoted term is
370+
// contained in user input
371+
List<String> terms = new ArrayList<>();
372+
int nextStartIndex = searchQueryTerms.indexOf('\"');
373+
while (nextStartIndex >= 0) {
374+
int endIndex = searchQueryTerms.indexOf('\"', nextStartIndex + 1);
375+
String quotedTerm = searchQueryTerms.substring(nextStartIndex, endIndex + 1);
376+
terms.add(quotedTerm);
377+
// Remove the quoted term from user input
378+
searchQueryTerms = searchQueryTerms.replace(quotedTerm, "");
379+
// Check next occurrence
380+
nextStartIndex = searchQueryTerms.indexOf('\"');
381+
}
382+
// Add remaining terms...
383+
List<String> remaining = Arrays.asList(searchQueryTerms.split("[\\|,;\\s+]"));
384+
//...but remove empty strings, which are "leftovers" when quoted terms are removed
385+
terms.addAll(remaining.stream().filter(t -> t.length() > 0).collect(Collectors.toList()));
386+
return terms;
387+
}
340388
}

0 commit comments

Comments
 (0)