001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.repository.search;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.repository.search.RepositorySearchQueryBuilder;
020    import com.liferay.portal.kernel.search.BooleanClause;
021    import com.liferay.portal.kernel.search.BooleanClauseOccur;
022    import com.liferay.portal.kernel.search.BooleanQuery;
023    import com.liferay.portal.kernel.search.BooleanQueryFactoryUtil;
024    import com.liferay.portal.kernel.search.Field;
025    import com.liferay.portal.kernel.search.Query;
026    import com.liferay.portal.kernel.search.QueryTerm;
027    import com.liferay.portal.kernel.search.SearchContext;
028    import com.liferay.portal.kernel.search.SearchException;
029    import com.liferay.portal.kernel.search.TermQuery;
030    import com.liferay.portal.kernel.search.TermRangeQuery;
031    import com.liferay.portal.kernel.search.WildcardQuery;
032    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
033    import com.liferay.portal.kernel.util.ArrayUtil;
034    import com.liferay.portal.kernel.util.StringBundler;
035    import com.liferay.portal.kernel.util.StringPool;
036    import com.liferay.portal.kernel.util.Validator;
037    import com.liferay.portal.search.lucene.LuceneHelperUtil;
038    import com.liferay.portlet.documentlibrary.model.DLFolderConstants;
039    import com.liferay.portlet.documentlibrary.service.DLAppServiceUtil;
040    import com.liferay.util.lucene.KeywordsUtil;
041    
042    import java.util.HashSet;
043    import java.util.Set;
044    
045    import org.apache.lucene.analysis.Analyzer;
046    import org.apache.lucene.index.Term;
047    import org.apache.lucene.queryParser.QueryParser;
048    
049    /**
050     * @author Mika Koivisto
051     */
052    @DoPrivileged
053    public class RepositorySearchQueryBuilderImpl
054            implements RepositorySearchQueryBuilder {
055    
056            @Override
057            public BooleanQuery getFullQuery(SearchContext searchContext)
058                    throws SearchException {
059    
060                    try {
061                            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create(
062                                    searchContext);
063    
064                            addContext(contextQuery, searchContext);
065    
066                            BooleanQuery searchQuery = BooleanQueryFactoryUtil.create(
067                                    searchContext);
068    
069                            addSearchKeywords(searchQuery, searchContext);
070    
071                            BooleanQuery fullQuery = BooleanQueryFactoryUtil.create(
072                                    searchContext);
073    
074                            if (contextQuery.hasClauses()) {
075                                    fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
076                            }
077    
078                            if (searchQuery.hasClauses()) {
079                                    fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
080                            }
081    
082                            BooleanClause[] booleanClauses = searchContext.getBooleanClauses();
083    
084                            if (booleanClauses != null) {
085                                    for (BooleanClause booleanClause : booleanClauses) {
086                                            fullQuery.add(
087                                                    booleanClause.getQuery(),
088                                                    booleanClause.getBooleanClauseOccur());
089                                    }
090                            }
091    
092                            fullQuery.setQueryConfig(searchContext.getQueryConfig());
093    
094                            return fullQuery;
095                    }
096                    catch (Exception e) {
097                            throw new SearchException(e);
098                    }
099            }
100    
101            public void setAnalyzer(Analyzer analyzer) {
102                    _analyzer = analyzer;
103            }
104    
105            protected void addContext(
106                            BooleanQuery contextQuery, SearchContext searchContext)
107                    throws Exception {
108    
109                    long[] folderIds = searchContext.getFolderIds();
110    
111                    if (ArrayUtil.isEmpty(folderIds)) {
112                            return;
113                    }
114    
115                    if (folderIds[0] == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
116                            return;
117                    }
118    
119                    BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create(
120                            searchContext);
121    
122                    for (long folderId : folderIds) {
123                            try {
124                                    DLAppServiceUtil.getFolder(folderId);
125                            }
126                            catch (Exception e) {
127                                    continue;
128                            }
129    
130                            folderIdsQuery.addTerm(Field.FOLDER_ID, folderId);
131                    }
132    
133                    contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
134            }
135    
136            protected void addSearchKeywords(
137                            BooleanQuery searchQuery, SearchContext searchContext)
138                    throws Exception {
139    
140                    String keywords = searchContext.getKeywords();
141    
142                    if (Validator.isNull(keywords)) {
143                            return;
144                    }
145    
146                    BooleanQuery titleQuery = BooleanQueryFactoryUtil.create(searchContext);
147    
148                    addTerm(titleQuery, searchContext, Field.TITLE, keywords);
149    
150                    if (titleQuery.hasClauses() && !contains(searchQuery, titleQuery)) {
151                            searchQuery.add(titleQuery, BooleanClauseOccur.SHOULD);
152                    }
153    
154                    BooleanQuery userNameQuery = BooleanQueryFactoryUtil.create(
155                            searchContext);
156    
157                    addTerm(userNameQuery, searchContext, Field.USER_NAME, keywords);
158    
159                    if (userNameQuery.hasClauses() &&
160                            !contains(searchQuery, userNameQuery)) {
161    
162                            searchQuery.add(userNameQuery, BooleanClauseOccur.SHOULD);
163                    }
164    
165                    BooleanQuery contentQuery = BooleanQueryFactoryUtil.create(
166                            searchContext);
167    
168                    addTerm(contentQuery, searchContext, Field.CONTENT, keywords);
169    
170                    if (contentQuery.hasClauses() && !contains(searchQuery, contentQuery)) {
171                            searchQuery.add(contentQuery, BooleanClauseOccur.SHOULD);
172                    }
173            }
174    
175            protected void addTerm(
176                    BooleanQuery booleanQuery, SearchContext searchContext, String field,
177                    String value) {
178    
179                    if (Validator.isNull(value)) {
180                            return;
181                    }
182    
183                    try {
184                            QueryParser queryParser = new QueryParser(
185                                    LuceneHelperUtil.getVersion(), field, _analyzer);
186    
187                            queryParser.setAllowLeadingWildcard(true);
188                            queryParser.setLowercaseExpandedTerms(false);
189    
190                            org.apache.lucene.search.Query query = null;
191    
192                            try {
193                                    query = queryParser.parse(value);
194                            }
195                            catch (Exception e) {
196                                    query = queryParser.parse(KeywordsUtil.escape(value));
197                            }
198    
199                            translateQuery(
200                                    booleanQuery, searchContext, query,
201                                    org.apache.lucene.search.BooleanClause.Occur.SHOULD);
202                    }
203                    catch (Exception e) {
204                            _log.error(e, e);
205                    }
206            }
207    
208            protected boolean contains(Query query1, Query query2) {
209                    if (query1 instanceof BooleanQuery) {
210                            BooleanQuery booleanQuery = (BooleanQuery)query1;
211    
212                            for (com.liferay.portal.kernel.search.BooleanClause booleanClause :
213                                            booleanQuery.clauses()) {
214    
215                                    if (contains(booleanClause.getQuery(), query2)) {
216                                            return true;
217                                    }
218                            }
219    
220                            return false;
221                    }
222                    else if (query2 instanceof BooleanQuery) {
223                            BooleanQuery booleanQuery = (BooleanQuery)query2;
224    
225                            for (com.liferay.portal.kernel.search.BooleanClause booleanClause :
226                                            booleanQuery.clauses()) {
227    
228                                    if (contains(query1, booleanClause.getQuery())) {
229                                            return true;
230                                    }
231                            }
232    
233                            return false;
234                    }
235                    else if ((query1 instanceof TermQuery) &&
236                                     (query2 instanceof TermQuery)) {
237    
238                            TermQuery termQuery1 = (TermQuery)query1;
239    
240                            QueryTerm queryTerm1 = termQuery1.getQueryTerm();
241    
242                            String field1 = queryTerm1.getField();
243                            String value1 = queryTerm1.getValue();
244    
245                            TermQuery termQuery2 = (TermQuery)query2;
246    
247                            QueryTerm queryTerm2 = termQuery2.getQueryTerm();
248    
249                            String field2 = queryTerm2.getField();
250                            String value2 = queryTerm2.getValue();
251    
252                            if (field1.equals(field2) && value1.equals(value2)) {
253                                    return true;
254                            }
255                    }
256                    else if ((query1 instanceof TermRangeQuery) &&
257                                     (query2 instanceof TermRangeQuery)) {
258    
259                            TermRangeQuery termRangeQuery1 = (TermRangeQuery)query1;
260    
261                            boolean includesLower1 = termRangeQuery1.includesLower();
262                            boolean includesUpper1 = termRangeQuery1.includesUpper();
263                            String lowerTerm1 = termRangeQuery1.getLowerTerm();
264                            String upperTerm1 = termRangeQuery1.getUpperTerm();
265    
266                            TermRangeQuery termRangeQuery2 = (TermRangeQuery)query2;
267    
268                            boolean includesLower2 = termRangeQuery2.includesLower();
269                            boolean includesUpper2 = termRangeQuery2.includesUpper();
270                            String lowerTerm2 = termRangeQuery2.getLowerTerm();
271                            String upperTerm2 = termRangeQuery2.getUpperTerm();
272    
273                            if ((includesLower1 == includesLower2) &&
274                                    (includesUpper1 == includesUpper2) &&
275                                    lowerTerm1.equals(lowerTerm2) &&
276                                    upperTerm1.equals(upperTerm2)) {
277    
278                                    return true;
279                            }
280                    }
281                    else if ((query1 instanceof WildcardQuery) &&
282                                     (query2 instanceof WildcardQuery)) {
283    
284                            WildcardQuery wildcardQuery1 = (WildcardQuery)query1;
285    
286                            QueryTerm queryTerm1 = wildcardQuery1.getQueryTerm();
287    
288                            String field1 = queryTerm1.getField();
289                            String value1 = queryTerm1.getValue();
290    
291                            WildcardQuery wildcardQuery2 = (WildcardQuery)query2;
292    
293                            QueryTerm queryTerm2 = wildcardQuery2.getQueryTerm();
294    
295                            String field2 = queryTerm2.getField();
296                            String value2 = queryTerm2.getValue();
297    
298                            if (field1.equals(field2) && value1.equals(value2)) {
299                                    return true;
300                            }
301                    }
302    
303                    return false;
304            }
305    
306            protected org.apache.lucene.search.BooleanClause.Occur
307                    getBooleanClauseOccur(BooleanClauseOccur occur) {
308    
309                    if (occur.equals(BooleanClauseOccur.MUST)) {
310                            return org.apache.lucene.search.BooleanClause.Occur.MUST;
311                    }
312                    else if (occur.equals(BooleanClauseOccur.MUST_NOT)) {
313                            return org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
314                    }
315    
316                    return org.apache.lucene.search.BooleanClause.Occur.SHOULD;
317            }
318    
319            protected BooleanClauseOccur getBooleanClauseOccur(
320                    org.apache.lucene.search.BooleanClause.Occur occur) {
321    
322                    if (occur.equals(org.apache.lucene.search.BooleanClause.Occur.MUST)) {
323                            return BooleanClauseOccur.MUST;
324                    }
325                    else if (occur.equals(
326                                            org.apache.lucene.search.BooleanClause.Occur.MUST_NOT)) {
327    
328                            return BooleanClauseOccur.MUST_NOT;
329                    }
330    
331                    return BooleanClauseOccur.SHOULD;
332            }
333    
334            protected void translateQuery(
335                            BooleanQuery booleanQuery, SearchContext searchContext,
336                            org.apache.lucene.search.Query query,
337                            org.apache.lucene.search.BooleanClause.Occur occur)
338                    throws Exception {
339    
340                    BooleanClauseOccur booleanClauseOccur = getBooleanClauseOccur(occur);
341    
342                    if (query instanceof org.apache.lucene.search.TermQuery) {
343                            Set<Term> terms = new HashSet<Term>();
344    
345                            query.extractTerms(terms);
346    
347                            for (Term term : terms) {
348                                    String termValue = term.text();
349    
350                                    booleanQuery.addTerm(
351                                            term.field(), termValue, false,
352                                            getBooleanClauseOccur(occur));
353                            }
354                    }
355                    else if (query instanceof org.apache.lucene.search.BooleanQuery) {
356                            org.apache.lucene.search.BooleanQuery curBooleanQuery =
357                                    (org.apache.lucene.search.BooleanQuery)query;
358    
359                            BooleanQuery conjunctionQuery = BooleanQueryFactoryUtil.create(
360                                    searchContext);
361                            BooleanQuery disjunctionQuery = BooleanQueryFactoryUtil.create(
362                                    searchContext);
363    
364                            for (org.apache.lucene.search.BooleanClause booleanClause :
365                                            curBooleanQuery.getClauses()) {
366    
367                                    BooleanClauseOccur curBooleanClauseOccur =
368                                            getBooleanClauseOccur(booleanClause.getOccur());
369    
370                                    BooleanQuery subbooleanQuery = null;
371    
372                                    if (curBooleanClauseOccur.equals(BooleanClauseOccur.SHOULD)) {
373                                            subbooleanQuery = disjunctionQuery;
374                                    }
375                                    else {
376                                            subbooleanQuery = conjunctionQuery;
377                                    }
378    
379                                    translateQuery(
380                                            subbooleanQuery, searchContext, booleanClause.getQuery(),
381                                            booleanClause.getOccur());
382                            }
383    
384                            if (conjunctionQuery.hasClauses()) {
385                                    booleanQuery.add(conjunctionQuery, BooleanClauseOccur.MUST);
386                            }
387    
388                            if (disjunctionQuery.hasClauses()) {
389                                    booleanQuery.add(disjunctionQuery, BooleanClauseOccur.SHOULD);
390                            }
391                    }
392                    else if (query instanceof org.apache.lucene.search.FuzzyQuery) {
393                            org.apache.lucene.search.FuzzyQuery fuzzyQuery =
394                                    (org.apache.lucene.search.FuzzyQuery)query;
395    
396                            Term term = fuzzyQuery.getTerm();
397    
398                            String termValue = term.text().concat(StringPool.STAR);
399    
400                            booleanQuery.addTerm(
401                                    term.field(), termValue, true, booleanClauseOccur);
402                    }
403                    else if (query instanceof org.apache.lucene.search.PhraseQuery) {
404                            org.apache.lucene.search.PhraseQuery phraseQuery =
405                                    (org.apache.lucene.search.PhraseQuery)query;
406    
407                            Term[] terms = phraseQuery.getTerms();
408    
409                            StringBundler sb = new StringBundler(terms.length * 2);
410    
411                            for (Term term : terms) {
412                                    sb.append(term.text());
413                                    sb.append(StringPool.SPACE);
414                            }
415    
416                            booleanQuery.addTerm(
417                                    terms[0].field(), sb.toString().trim(), false,
418                                    booleanClauseOccur);
419                    }
420                    else if (query instanceof org.apache.lucene.search.PrefixQuery) {
421                            org.apache.lucene.search.PrefixQuery prefixQuery =
422                                    (org.apache.lucene.search.PrefixQuery)query;
423    
424                            Term prefixTerm = prefixQuery.getPrefix();
425    
426                            String termValue = prefixTerm.text().concat(StringPool.STAR);
427    
428                            booleanQuery.addTerm(
429                                    prefixTerm.field(), termValue, true, booleanClauseOccur);
430                    }
431                    else if (query instanceof org.apache.lucene.search.TermRangeQuery) {
432                            org.apache.lucene.search.TermRangeQuery termRangeQuery =
433                                    (org.apache.lucene.search.TermRangeQuery)query;
434    
435                            booleanQuery.addRangeTerm(
436                                    termRangeQuery.getField(), termRangeQuery.getLowerTerm(),
437                                    termRangeQuery.getUpperTerm());
438                    }
439                    else if (query instanceof org.apache.lucene.search.WildcardQuery) {
440                            org.apache.lucene.search.WildcardQuery wildcardQuery =
441                                    (org.apache.lucene.search.WildcardQuery)query;
442    
443                            Term wildcardTerm = wildcardQuery.getTerm();
444    
445                            booleanQuery.addTerm(
446                                    wildcardTerm.field(), wildcardTerm.text(), true,
447                                    booleanClauseOccur);
448                    }
449                    else {
450                            if (_log.isWarnEnabled()) {
451                                    _log.warn(
452                                            "Ignoring unknown query type " + query.getClass() +
453                                                    " with query " + query);
454                            }
455                    }
456            }
457    
458            private static Log _log = LogFactoryUtil.getLog(
459                    RepositorySearchQueryBuilderImpl.class);
460    
461            private Analyzer _analyzer;
462    
463    }