-
Notifications
You must be signed in to change notification settings - Fork 7
/
SearchableEntity.java
114 lines (102 loc) · 4.21 KB
/
SearchableEntity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.biz.elastic;
import sirius.biz.protocol.NoJournal;
import sirius.db.es.ElasticEntity;
import sirius.db.es.annotations.Analyzed;
import sirius.db.es.annotations.ESOption;
import sirius.db.es.annotations.IndexMode;
import sirius.db.mixing.Mapping;
import sirius.db.mixing.annotations.BeforeSave;
import sirius.db.mixing.annotations.NullAllowed;
import sirius.db.mixing.types.NestedList;
import sirius.db.mixing.types.StringList;
import sirius.db.mixing.types.StringListMap;
import sirius.db.mixing.types.StringMap;
import sirius.db.text.BasicIndexTokenizer;
import sirius.db.text.Tokenizer;
import sirius.kernel.commons.Strings;
/**
* Maintains a <tt>searchField</tt> in which all fields annotated with {@link SearchContent} are indexed.
* <p>
* Uses a {@link BasicIndexTokenizer} to tokenize the searchable content.
*/
public abstract class SearchableEntity extends ElasticEntity {
/**
* Contains manually maintained content to be added to the search field.
*/
public static final Mapping SEARCHABLE_CONTENT = Mapping.named("searchableContent");
@NullAllowed
@SearchContent
@NoJournal
@IndexMode(indexed = ESOption.FALSE, docValues = ESOption.FALSE)
private String searchableContent;
/**
* Contains the actual search data. This field should be used in search queries but must not
* be modified manually.
*/
public static final Mapping SEARCH_FIELD = Mapping.named("searchField");
@Analyzed(analyzer = Analyzed.ANALYZER_WHITESPACE, indexOptions = Analyzed.IndexOption.DOCS)
@IndexMode(indexed = ESOption.TRUE, normsEnabled = ESOption.TRUE, stored = ESOption.FALSE)
@NullAllowed
@NoJournal
private String searchField;
@BeforeSave
protected void updateSearchField() {
if (!isNew() && !isAnyMappingChanged()) {
return;
}
BasicIndexTokenizer tokenizer = new BasicIndexTokenizer();
StringBuilder contentBuilder = new StringBuilder();
this.getDescriptor()
.getProperties()
.stream()
.filter(p -> p.getAnnotation(SearchContent.class).isPresent())
.map(p -> p.getValue(this))
.filter(Strings::isFilled)
.forEach(obj -> addContent(tokenizer, contentBuilder, obj));
searchField = contentBuilder.toString();
}
/**
* Transforms a property value into searchable text.
*
* @param contentBuilder the output to write to
* @param propertyValue the value to transform
*/
protected void addContent(Tokenizer tokenizer, StringBuilder contentBuilder, Object propertyValue) {
if (propertyValue instanceof StringList list) {
list.forEach(value -> addContentAsTokens(tokenizer, contentBuilder, value));
} else if (propertyValue instanceof StringMap map) {
map.forEach(entry -> {
addContentAsTokens(tokenizer, contentBuilder, entry.getKey());
addContentAsTokens(tokenizer, contentBuilder, entry.getValue());
});
} else if (propertyValue instanceof StringListMap map) {
map.forEach(entry -> {
addContentAsTokens(tokenizer, contentBuilder, entry.getKey());
entry.getValue().forEach(value -> addContentAsTokens(tokenizer, contentBuilder, value));
});
} else if (propertyValue instanceof NestedList) {
throw new UnsupportedOperationException("Nested objects are not yet supported by SearchableEntity");
} else if (propertyValue != null) {
addContentAsTokens(tokenizer, contentBuilder, propertyValue.toString());
}
}
private void addContentAsTokens(Tokenizer tokenizer, StringBuilder contentBuilder, String value) {
tokenizer.acceptPlain(value, token -> contentBuilder.append(" ").append(token));
}
public String getSearchableContent() {
return searchableContent;
}
public void setSearchableContent(String searchableContent) {
this.searchableContent = searchableContent;
}
public String getSearchField() {
return searchField;
}
}