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.cms.taglibs.util;
35
36 import info.magnolia.cms.beans.config.ContentRepository;
37 import info.magnolia.cms.core.Content;
38 import info.magnolia.cms.core.ItemType;
39 import info.magnolia.cms.core.search.Query;
40 import info.magnolia.cms.core.search.QueryResult;
41 import info.magnolia.context.MgnlContext;
42 import org.apache.commons.lang.ArrayUtils;
43 import org.apache.commons.lang.StringUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import javax.jcr.RepositoryException;
48 import javax.servlet.jsp.JspException;
49 import javax.servlet.jsp.PageContext;
50 import javax.servlet.jsp.tagext.TagSupport;
51 import java.text.MessageFormat;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class SimpleSearchTag extends TagSupport {
72
73
74
75
76 private static final long serialVersionUID = 222L;
77
78
79
80
81 private static final String RESERVED_CHARS = "()[]{}<>:/\\@*?=\"'&";
82
83
84
85
86 static final String[] KEYWORDS = new String[]{"and", "or"};
87
88 private static final Logger log = LoggerFactory.getLogger(SimpleSearchTag.class);
89
90 private int startLevel;
91 private String query;
92 private String var;
93 private String repository = ContentRepository.WEBSITE;
94 private String itemType = ItemType.CONTENT.getSystemName();
95 private boolean supportSubstringSearch = false;
96 private boolean useSimpleJcrQuery = true;
97 private String startPath;
98 private int scope = PageContext.PAGE_SCOPE;
99
100
101
102
103
104 public void setQuery(String query) {
105 this.query = query;
106 }
107
108
109
110
111
112 public void setVar(String var) {
113 this.var = var;
114 }
115
116
117
118
119
120 public void setScope(String scope) {
121 if ("request".equalsIgnoreCase(scope)) {
122 this.scope = PageContext.REQUEST_SCOPE;
123 }
124 else if ("session".equalsIgnoreCase(scope)) {
125 this.scope = PageContext.SESSION_SCOPE;
126 }
127 else if ("application".equalsIgnoreCase(scope)) {
128 this.scope = PageContext.APPLICATION_SCOPE;
129 }
130 else {
131
132 this.scope = PageContext.PAGE_SCOPE;
133 }
134 }
135
136
137
138
139
140 public void setStartLevel(int startLevel) {
141 this.startLevel = startLevel;
142 }
143
144 public int doStartTag() throws JspException {
145
146 final String queryString = useSimpleJcrQuery ? generateSimpleQuery(query) : generateComplexXPathQuery();
147
148 if (queryString == null) {
149 if (log.isDebugEnabled()) {
150 log.debug("A valid query could not be built, skipping");
151 }
152 return EVAL_PAGE;
153 }
154
155 if (log.isDebugEnabled()) {
156 log.debug("Executing xpath query " + queryString);
157 }
158
159 Query q;
160 try {
161 q = MgnlContext.getQueryManager(repository).createQuery(queryString, "xpath");
162
163 QueryResult result = q.execute();
164
165 pageContext.setAttribute(var, result.getContent(itemType), scope);
166 }
167 catch (Exception e) {
168 log.error(MessageFormat.format(
169 "{0} caught while parsing query for search term [{1}] - query is [{2}]: {3}",
170 new Object[]{e.getClass().getName(), this.query, queryString, e.getMessage()}), e);
171 }
172
173 return EVAL_PAGE;
174 }
175
176
177
178
179
180
181 protected String generateSimpleQuery(String input) {
182 final String startPathToUse = startPath();
183
184
185 final String escapedQuery = input.replaceAll("'", "\\\\''");
186 final String queryString = startPathToUse + "//*[@jcr:primaryType='mgnl:content']//*[jcr:contains(., '" + escapedQuery + "')]";
187 log.debug("query string: " + queryString);
188 return queryString;
189 }
190
191 protected String startPath() {
192 String cleanStartPath = null;
193
194 if (this.startLevel > 0) {
195 try {
196 Content activePage = MgnlContext.getAggregationState().getMainContent();
197 if (activePage != null) {
198 cleanStartPath = activePage.getAncestor(this.startLevel).getHandle();
199 }
200 } catch (RepositoryException e) {
201 log.error(e.getMessage(), e);
202 }
203 } else {
204 cleanStartPath = this.startPath;
205 }
206 return StringUtils.defaultIfEmpty(StringUtils.strip(cleanStartPath, "/"), "");
207 }
208
209
210
211
212
213 protected String generateComplexXPathQuery() {
214 return generateXPathQuery();
215 }
216
217
218
219
220
221
222
223
224
225 protected String generateXPathQuery() {
226
227 String[] tokens = StringUtils.split(StringUtils.lowerCase(StringUtils.replaceChars(
228 this.query,
229 RESERVED_CHARS,
230 null)));
231
232
233 if (tokens == null) {
234 return null;
235 }
236
237 StringBuffer xpath = new StringBuffer(tokens.length * 20);
238 xpath.append(startPath());
239 xpath.append("//*[@jcr:primaryType=\'mgnl:content\']//*["); //$NON-NLS-1$
240
241 String joinOperator = "and";
242 boolean emptyQuery = true;
243
244 for (int j = 0; j < tokens.length; j++) {
245 String tkn = tokens[j];
246 if (ArrayUtils.contains(KEYWORDS, tkn)) {
247 joinOperator = tkn;
248 }
249 else {
250 if (!emptyQuery) {
251 xpath.append(" ");
252 xpath.append(joinOperator);
253 xpath.append(" ");
254 }
255 xpath.append("jcr:contains(., '");
256 if(supportSubstringSearch){
257 xpath.append("*");
258 xpath.append(tkn);
259 xpath.append("*");
260 }
261 else{
262 xpath.append(tkn);
263 }
264
265 xpath.append("')");
266 emptyQuery = false;
267 }
268
269 }
270
271 xpath.append("]");
272
273
274 if (emptyQuery) {
275 return null;
276 }
277
278 return xpath.toString();
279 }
280
281
282
283
284 public void release() {
285 this.query = null;
286 this.var = null;
287 this.scope = PageContext.PAGE_SCOPE;
288 this.startLevel = 0;
289 this.startPath = null;
290 this.itemType = null;
291 this.repository = null;
292 this.supportSubstringSearch = false;
293 this.useSimpleJcrQuery = true;
294 super.release();
295 }
296
297
298 public String getRepository() {
299 return this.repository;
300 }
301
302
303
304
305
306 public void setRepository(String repository) {
307 this.repository = repository;
308 }
309
310 public boolean isSupportSubstringSearch() {
311 return this.supportSubstringSearch;
312 }
313
314
315
316
317
318
319 public void setSupportSubstringSearch(boolean supportSubstringSearch) {
320 this.supportSubstringSearch = supportSubstringSearch;
321 }
322
323
324
325
326
327
328
329
330 public void setUseSimpleJcrQuery(boolean useSimpleJcrQuery) {
331 this.useSimpleJcrQuery = useSimpleJcrQuery;
332 }
333
334
335
336
337 public String getItemType() {
338 return this.itemType;
339 }
340
341
342
343
344
345 public void setItemType(String itemType) {
346 this.itemType = itemType;
347 }
348
349
350 public String getStartPath() {
351 return this.startPath;
352 }
353
354
355
356
357
358 public void setStartPath(String startPath) {
359 this.startPath = startPath;
360 }
361
362 }