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 package info.magnolia.ui.workbench.search;
35
36 import info.magnolia.jcr.util.NodeTypes;
37 import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinition;
38 import info.magnolia.ui.workbench.container.OrderBy;
39 import info.magnolia.ui.workbench.list.FlatJcrContainer;
40
41 import java.util.ArrayList;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
46
47 import javax.jcr.nodetype.NodeType;
48
49 import org.apache.commons.lang3.StringUtils;
50 import org.apache.jackrabbit.util.Text;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54
55
56
57
58
59 public class SearchJcrContainer extends FlatJcrContainer {
60
61 private static final Logger log = LoggerFactory.getLogger(SearchJcrContainer.class);
62
63 protected static final String WHERE_TEMPLATE_FOR_SEARCH = "lower(localname()) LIKE '%1$s%%' or " + SELECTOR_NAME + ".['%2$s'] IS NOT NULL %3$s";
64
65 protected static final String CONTAINS_TEMPLATE_FOR_SEARCH = "contains(" + SELECTOR_NAME + ".*, '%1$s')";
66
67 protected static final String JCR_SCORE_FUNCTION = "score(" + SELECTOR_NAME + ")";
68
69 private String fullTextExpression;
70
71 private String whereCauseNodeTypes;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 private static final Pattern simpleTermsRegexPattern = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
87
88 public SearchJcrContainer(JcrContentConnectorDefinition definition) {
89 super(definition);
90 whereCauseNodeTypes = super.getQueryWhereClauseNodeTypes();
91
92 for (NodeType nt : getSearchableNodeTypes()) {
93
94 if (NodeTypes.Folder.NAME.equals(nt.getName())) {
95 whereCauseNodeTypes += " or [jcr:primaryType] = '" + NodeTypes.Folder.NAME + "'";
96 break;
97 }
98 }
99 }
100
101
102
103
104 @Override
105 protected String getQueryWhereClause() {
106 final String clauseWorkspacePath = getQueryWhereClauseWorkspacePath();
107 final String whereClauseSearch = getQueryWhereClauseSearch();
108
109 String whereClause = "(" + getQueryWhereClauseNodeTypes() + ")";
110
111 if (!"".equals(whereClauseSearch)) {
112 whereClause += " and (" + whereClauseSearch + ") ";
113 }
114
115 if (!"".equals(clauseWorkspacePath)) {
116 if (!"".equals(whereClause)) {
117 whereClause = clauseWorkspacePath + " and " + whereClause;
118 } else {
119 whereClause += clauseWorkspacePath;
120 }
121 }
122
123 if (!"".equals(whereClause)) {
124 whereClause = " where (" + whereClause + ")";
125 }
126
127 log.debug("JCR query WHERE clause is {}", whereClause);
128 return whereClause;
129 }
130
131 @Override
132 protected String getQueryWhereClauseNodeTypes() {
133 return whereCauseNodeTypes;
134 }
135
136
137
138
139
140
141
142 protected String getQueryWhereClauseSearch() {
143 if (StringUtils.isBlank(getFullTextExpression())) {
144 return "";
145 }
146 final String unescapedFullTextExpression = getFullTextExpression();
147 final String escapedSearch = Text.escapeIllegalJcrChars(unescapedFullTextExpression);
148 final String escapedSearchLowercase = Text.escapeIllegalJcrChars(unescapedFullTextExpression.toLowerCase());
149
150 final String stmt = String.format(WHERE_TEMPLATE_FOR_SEARCH, escapedSearchLowercase, escapedSearch, String.format("or " + CONTAINS_TEMPLATE_FOR_SEARCH, escapeFullTextExpression(unescapedFullTextExpression)));
151
152 log.debug("Search where-clause is {}", stmt);
153 return stmt;
154 }
155
156 public void setFullTextExpression(String fullTextExpression) {
157 this.fullTextExpression = fullTextExpression;
158 }
159
160 public String getFullTextExpression() {
161 return fullTextExpression;
162 }
163
164 @Override
165 protected String getJcrNameOrderByFunction() {
166 return JCR_SCORE_FUNCTION;
167 }
168
169 @Override
170
171
172
173 protected OrderBy getDefaultOrderBy(String property) {
174 return new OrderBy(property, false);
175 }
176
177
178
179
180 private String escapeFullTextExpression(final String fulltextExpression) {
181
182 List<String> matchList = findSimpleTerms(fulltextExpression);
183
184 final List<String> simpleTerms = new ArrayList<String>();
185 for (String token : matchList) {
186 if ("or".equals(token)) {
187 simpleTerms.add("OR");
188 } else {
189 simpleTerms.add(escapeIllegalFullTextSearchChars(token));
190 }
191 }
192
193 if ("\"".equals(fullTextExpression)) {
194 simpleTerms.add("\\\"");
195 }
196 String returnValue = StringUtils.join(simpleTerms, " ");
197
198 return returnValue.replaceAll("'", "''").trim();
199 }
200
201
202
203
204
205 private List<String> findSimpleTerms(final String unescapedFullTextExpression) {
206 List<String> matchList = new LinkedList<String>();
207 Matcher regexMatcher = simpleTermsRegexPattern.matcher(unescapedFullTextExpression);
208 while (regexMatcher.find()) {
209 matchList.add(regexMatcher.group());
210 }
211 return matchList;
212 }
213
214
215
216
217
218
219
220
221 final String escapeIllegalFullTextSearchChars(final String simpleTerm) {
222 StringBuilder sb = new StringBuilder(simpleTerm.length());
223
224 for (int i = 0; i < simpleTerm.length(); i++) {
225 char ch = simpleTerm.charAt(i);
226 if (("\\+-".contains(String.valueOf(ch)) && simpleTerm.length() == 1)
227 || ("()[]{}".contains(String.valueOf(ch)))
228 || ("\"".contains(String.valueOf(ch)) && (i != 0 && i != simpleTerm.length() - 1))) {
229 sb.append('\\');
230 }
231 sb.append(ch);
232 }
233 return sb.toString();
234 }
235
236 }