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            @Override
056            public BooleanQuery getFullQuery(SearchContext searchContext)
057                    throws SearchException {
058    
059                    try {
060                            BooleanQuery contextQuery = BooleanQueryFactoryUtil.create(
061                                    searchContext);
062    
063                            addContext(contextQuery, searchContext);
064    
065                            BooleanQuery searchQuery = BooleanQueryFactoryUtil.create(
066                                    searchContext);
067    
068                            addSearchKeywords(searchQuery, searchContext);
069    
070                            BooleanQuery fullQuery = BooleanQueryFactoryUtil.create(
071                                    searchContext);
072    
073                            if (contextQuery.hasClauses()) {
074                                    fullQuery.add(contextQuery, BooleanClauseOccur.MUST);
075                            }
076    
077                            if (searchQuery.hasClauses()) {
078                                    fullQuery.add(searchQuery, BooleanClauseOccur.MUST);
079                            }
080    
081                            BooleanClause[] booleanClauses = searchContext.getBooleanClauses();
082    
083                            if (booleanClauses != null) {
084                                    for (BooleanClause booleanClause : booleanClauses) {
085                                            fullQuery.add(
086                                                    booleanClause.getQuery(),
087                                                    booleanClause.getBooleanClauseOccur());
088                                    }
089                            }
090    
091                            fullQuery.setQueryConfig(searchContext.getQueryConfig());
092    
093                            return fullQuery;
094                    }
095                    catch (Exception e) {
096                            throw new SearchException(e);
097                    }
098            }
099    
100            public void setAnalyzer(Analyzer analyzer) {
101                    _analyzer = analyzer;
102            }
103    
104            protected void addContext(
105                            BooleanQuery contextQuery, SearchContext searchContext)
106                    throws Exception {
107    
108                    long[] folderIds = searchContext.getFolderIds();
109    
110                    if ((folderIds == null) || (folderIds.length == 0)) {
111                            return;
112                    }
113    
114                    if (folderIds[0] == DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) {
115                            return;
116                    }
117    
118                    BooleanQuery folderIdsQuery = BooleanQueryFactoryUtil.create(
119                            searchContext);
120    
121                    for (long folderId : folderIds) {
122                            try {
123                                    DLAppServiceUtil.getFolder(folderId);
124                            }
125                            catch (Exception e) {
126                                    continue;
127                            }
128    
129                            folderIdsQuery.addTerm(Field.FOLDER_ID, folderId);
130                    }
131    
132                    contextQuery.add(folderIdsQuery, BooleanClauseOccur.MUST);
133            }
134    
135            protected void addSearchKeywords(
136                            BooleanQuery searchQuery, SearchContext searchContext)
137                    throws Exception {
138    
139                    String keywords = searchContext.getKeywords();
140    
141                    if (Validator.isNull(keywords)) {
142                            return;
143                    }
144    
145                    BooleanQuery titleQuery = BooleanQueryFactoryUtil.create(searchContext);
146    
147                    addTerm(titleQuery, searchContext, Field.TITLE, keywords);
148    
149                    if (titleQuery.hasClauses() && !contains(searchQuery, titleQuery)) {
150                            searchQuery.add(titleQuery, BooleanClauseOccur.SHOULD);
151                    }
152    
153                    BooleanQuery userNameQuery = BooleanQueryFactoryUtil.create(
154                            searchContext);
155    
156                    addTerm(userNameQuery, searchContext, Field.USER_NAME, keywords);
157    
158                    if (userNameQuery.hasClauses() &&
159                            !contains(searchQuery, userNameQuery)) {
160    
161                            searchQuery.add(userNameQuery, BooleanClauseOccur.SHOULD);
162                    }
163    
164                    BooleanQuery contentQuery = BooleanQueryFactoryUtil.create(
165                            searchContext);
166    
167                    addTerm(contentQuery, searchContext, Field.CONTENT, keywords);
168    
169                    if (contentQuery.hasClauses() && !contains(searchQuery, contentQuery)) {
170                            searchQuery.add(contentQuery, BooleanClauseOccur.SHOULD);
171                    }
172            }
173    
174            protected void addTerm(
175                    BooleanQuery booleanQuery, SearchContext searchContext, String field,
176                    String value) {
177    
178                    if (Validator.isNull(value)) {
179                            return;
180                    }
181    
182                    try {
183                            QueryParser queryParser = new QueryParser(
184                                    LuceneHelperUtil.getVersion(), field, _analyzer);
185    
186                            queryParser.setAllowLeadingWildcard(true);
187                            queryParser.setLowercaseExpandedTerms(false);
188    
189                            org.apache.lucene.search.Query query = null;
190    
191                            try {
192                                    query = queryParser.parse(value);
193                            }
194                            catch (Exception e) {
195                                    query = queryParser.parse(KeywordsUtil.escape(value));
196                            }
197    
198                            translateQuery(
199                                    booleanQuery, searchContext, query,
200                                    org.apache.lucene.search.BooleanClause.Occur.SHOULD);
201                    }
202                    catch (Exception e) {
203                            _log.error(e, e);
204                    }
205            }
206    
207            protected boolean contains(Query query1, Query query2) {
208                    if (query1 instanceof BooleanQuery) {
209                            BooleanQuery booleanQuery = (BooleanQuery)query1;
210    
211                            for (com.liferay.portal.kernel.search.BooleanClause booleanClause :
212                                            booleanQuery.clauses()) {
213    
214                                    if (contains(booleanClause.getQuery(), query2)) {
215                                            return true;
216                                    }
217                            }
218    
219                            return false;
220                    }
221                    else if (query2 instanceof BooleanQuery) {
222                            BooleanQuery booleanQuery = (BooleanQuery)query2;
223    
224                            for (com.liferay.portal.kernel.search.BooleanClause booleanClause :
225                                            booleanQuery.clauses()) {
226    
227                                    if (contains(query1, booleanClause.getQuery())) {
228                                            return true;
229                                    }
230                            }
231    
232                            return false;
233                    }
234                    else if ((query1 instanceof TermQuery) &&
235                                     (query2 instanceof TermQuery)) {
236    
237                            TermQuery termQuery1 = (TermQuery)query1;
238    
239                            QueryTerm queryTerm1 = termQuery1.getQueryTerm();
240    
241                            String field1 = queryTerm1.getField();
242                            String value1 = queryTerm1.getValue();
243    
244                            TermQuery termQuery2 = (TermQuery)query2;
245    
246                            QueryTerm queryTerm2 = termQuery2.getQueryTerm();
247    
248                            String field2 = queryTerm2.getField();
249                            String value2 = queryTerm2.getValue();
250    
251                            if (field1.equals(field2) && value1.equals(value2)) {
252                                    return true;
253                            }
254                    }
255                    else if ((query1 instanceof TermRangeQuery) &&
256                                     (query2 instanceof TermRangeQuery)) {
257    
258                            TermRangeQuery termRangeQuery1 = (TermRangeQuery)query1;
259    
260                            boolean includesLower1 = termRangeQuery1.includesLower();
261                            boolean includesUpper1 = termRangeQuery1.includesUpper();
262                            String lowerTerm1 = termRangeQuery1.getLowerTerm();
263                            String upperTerm1 = termRangeQuery1.getUpperTerm();
264    
265                            TermRangeQuery termRangeQuery2 = (TermRangeQuery)query2;
266    
267                            boolean includesLower2 = termRangeQuery2.includesLower();
268                            boolean includesUpper2 = termRangeQuery2.includesUpper();
269                            String lowerTerm2 = termRangeQuery2.getLowerTerm();
270                            String upperTerm2 = termRangeQuery2.getUpperTerm();
271    
272                            if ((includesLower1 == includesLower2) &&
273                                    (includesUpper1 == includesUpper2) &&
274                                    lowerTerm1.equals(lowerTerm2) &&
275                                    upperTerm1.equals(upperTerm2)) {
276    
277                                    return true;
278                            }
279                    }
280                    else if ((query1 instanceof WildcardQuery) &&
281                                     (query2 instanceof WildcardQuery)) {
282    
283                            WildcardQuery wildcardQuery1 = (WildcardQuery)query1;
284    
285                            QueryTerm queryTerm1 = wildcardQuery1.getQueryTerm();
286    
287                            String field1 = queryTerm1.getField();
288                            String value1 = queryTerm1.getValue();
289    
290                            WildcardQuery wildcardQuery2 = (WildcardQuery)query2;
291    
292                            QueryTerm queryTerm2 = wildcardQuery2.getQueryTerm();
293    
294                            String field2 = queryTerm2.getField();
295                            String value2 = queryTerm2.getValue();
296    
297                            if (field1.equals(field2) && value1.equals(value2)) {
298                                    return true;
299                            }
300                    }
301    
302                    return false;
303            }
304    
305            protected org.apache.lucene.search.BooleanClause.Occur
306                    getBooleanClauseOccur(BooleanClauseOccur occur) {
307    
308                    if (occur.equals(BooleanClauseOccur.MUST)) {
309                            return org.apache.lucene.search.BooleanClause.Occur.MUST;
310                    }
311                    else if (occur.equals(BooleanClauseOccur.MUST_NOT)) {
312                            return org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
313                    }
314    
315                    return org.apache.lucene.search.BooleanClause.Occur.SHOULD;
316            }
317    
318            protected BooleanClauseOccur getBooleanClauseOccur(
319                    org.apache.lucene.search.BooleanClause.Occur occur) {
320    
321                    if (occur.equals(org.apache.lucene.search.BooleanClause.Occur.MUST)) {
322                            return BooleanClauseOccur.MUST;
323                    }
324                    else if (occur.equals(
325                                            org.apache.lucene.search.BooleanClause.Occur.MUST_NOT)) {
326    
327                            return BooleanClauseOccur.MUST_NOT;
328                    }
329    
330                    return BooleanClauseOccur.SHOULD;
331            }
332    
333            protected void translateQuery(
334                            BooleanQuery booleanQuery, SearchContext searchContext,
335                            org.apache.lucene.search.Query query,
336                            org.apache.lucene.search.BooleanClause.Occur occur)
337                    throws Exception {
338    
339                    BooleanClauseOccur booleanClauseOccur = getBooleanClauseOccur(occur);
340    
341                    if (query instanceof org.apache.lucene.search.TermQuery) {
342                            Set<Term> terms = new HashSet<Term>();
343    
344                            query.extractTerms(terms);
345    
346                            for (Term term : terms) {
347                                    String termValue = term.text();
348    
349                                    booleanQuery.addTerm(
350                                            term.field(), termValue, false,
351                                            getBooleanClauseOccur(occur));
352                            }
353                    }
354                    else if (query instanceof org.apache.lucene.search.BooleanQuery) {
355                            org.apache.lucene.search.BooleanQuery curBooleanQuery =
356                                    (org.apache.lucene.search.BooleanQuery)query;
357    
358                            BooleanQuery conjunctionQuery = BooleanQueryFactoryUtil.create(
359                                    searchContext);
360                            BooleanQuery disjunctionQuery = BooleanQueryFactoryUtil.create(
361                                    searchContext);
362    
363                            for (org.apache.lucene.search.BooleanClause booleanClause :
364                                            curBooleanQuery.getClauses()) {
365    
366                                    BooleanClauseOccur curBooleanClauseOccur =
367                                            getBooleanClauseOccur(booleanClause.getOccur());
368    
369                                    BooleanQuery subbooleanQuery = null;
370    
371                                    if (curBooleanClauseOccur.equals(BooleanClauseOccur.SHOULD)) {
372                                            subbooleanQuery = disjunctionQuery;
373                                    }
374                                    else {
375                                            subbooleanQuery = conjunctionQuery;
376                                    }
377    
378                                    translateQuery(
379                                            subbooleanQuery, searchContext, booleanClause.getQuery(),
380                                            booleanClause.getOccur());
381    
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    }