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