001    /**
002     * Copyright (c) 2000-present 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.kernel.repository.cmis.search;
016    
017    import com.liferay.portal.kernel.exception.SystemException;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
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.Field;
024    import com.liferay.portal.kernel.search.Query;
025    import com.liferay.portal.kernel.search.QueryConfig;
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.Sort;
030    import com.liferay.portal.kernel.search.TermQuery;
031    import com.liferay.portal.kernel.search.TermRangeQuery;
032    import com.liferay.portal.kernel.search.WildcardQuery;
033    import com.liferay.portal.kernel.util.ArrayUtil;
034    import com.liferay.portal.kernel.util.GetterUtil;
035    import com.liferay.portal.kernel.util.StringBundler;
036    import com.liferay.portal.kernel.util.StringPool;
037    import com.liferay.portal.kernel.util.Validator;
038    import com.liferay.portal.model.RepositoryEntry;
039    import com.liferay.portal.model.User;
040    import com.liferay.portal.service.RepositoryEntryLocalServiceUtil;
041    import com.liferay.portal.service.UserLocalServiceUtil;
042    
043    import java.util.HashMap;
044    import java.util.HashSet;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    import java.util.regex.Pattern;
049    
050    /**
051     * @author Mika Koivisto
052     */
053    public class BaseCmisSearchQueryBuilder implements CMISSearchQueryBuilder {
054    
055            @Override
056            public String buildQuery(SearchContext searchContext, Query query)
057                    throws SearchException {
058    
059                    StringBundler sb = new StringBundler();
060    
061                    sb.append("SELECT cmis:objectId");
062    
063                    QueryConfig queryConfig = searchContext.getQueryConfig();
064    
065                    if (queryConfig.isScoreEnabled()) {
066                            sb.append(", SCORE() AS HITS");
067                    }
068    
069                    sb.append(" FROM cmis:document");
070    
071                    CMISDisjunction cmisDisjunction = new CMISDisjunction();
072    
073                    if (_log.isDebugEnabled()) {
074                            _log.debug(
075                                    "Repository query support " +
076                                            queryConfig.getAttribute("capabilityQuery"));
077                    }
078    
079                    if (!isSupportsOnlyFullText(queryConfig)) {
080                            traversePropertiesQuery(cmisDisjunction, query, queryConfig);
081                    }
082    
083                    if (isSupportsFullText(queryConfig)) {
084                            CMISContainsExpression cmisContainsExpression =
085                                    new CMISContainsExpression();
086    
087                            traverseContentQuery(cmisContainsExpression, query, queryConfig);
088    
089                            if (!cmisContainsExpression.isEmpty()) {
090                                    cmisDisjunction.add(cmisContainsExpression);
091                            }
092                    }
093    
094                    if (!cmisDisjunction.isEmpty()) {
095                            sb.append(" WHERE ");
096                            sb.append(cmisDisjunction.toQueryFragment());
097                    }
098    
099                    Sort[] sorts = searchContext.getSorts();
100    
101                    if (queryConfig.isScoreEnabled() || ArrayUtil.isNotEmpty(sorts)) {
102                            sb.append(" ORDER BY ");
103                    }
104    
105                    if (ArrayUtil.isNotEmpty(sorts)) {
106                            int i = 0;
107    
108                            for (Sort sort : sorts) {
109                                    String fieldName = sort.getFieldName();
110    
111                                    if (!isSupportedField(fieldName)) {
112                                            continue;
113                                    }
114    
115                                    if (i > 0) {
116                                            sb.append(", ");
117                                    }
118    
119                                    sb.append(getCmisField(fieldName));
120    
121                                    if (sort.isReverse()) {
122                                            sb.append(" DESC");
123                                    }
124                                    else {
125                                            sb.append(" ASC");
126                                    }
127    
128                                    i++;
129                            }
130                    }
131                    else if (queryConfig.isScoreEnabled()) {
132                            sb.append("HITS DESC");
133                    }
134    
135                    if (_log.isDebugEnabled()) {
136                            _log.debug("CMIS query " + sb);
137                    }
138    
139                    return sb.toString();
140            }
141    
142            protected CMISCriterion buildFieldExpression(
143                            String field, String value,
144                            CMISSimpleExpressionOperator cmisSimpleExpressionOperator,
145                            QueryConfig queryConfig)
146                    throws SearchException {
147    
148                    CMISCriterion cmisCriterion = null;
149    
150                    boolean wildcard =
151                            CMISSimpleExpressionOperator.LIKE == cmisSimpleExpressionOperator;
152    
153                    if (field.equals(Field.FOLDER_ID)) {
154                            long folderId = GetterUtil.getLong(value);
155    
156                            try {
157                                    RepositoryEntry repositoryEntry =
158                                            RepositoryEntryLocalServiceUtil.fetchRepositoryEntry(
159                                                    folderId);
160    
161                                    if (repositoryEntry != null) {
162                                            String objectId = repositoryEntry.getMappedId();
163    
164                                            objectId = CMISParameterValueUtil.formatParameterValue(
165                                                    field, objectId, wildcard, queryConfig);
166    
167                                            if (queryConfig.isSearchSubfolders()) {
168                                                    cmisCriterion = new CMISInTreeExpression(objectId);
169                                            }
170                                            else {
171                                                    cmisCriterion = new CMISInFolderExpression(objectId);
172                                            }
173                                    }
174                            }
175                            catch (SystemException se) {
176                                    throw new SearchException(
177                                            "Unable to determine folder {folderId=" + folderId + "}",
178                                            se);
179                            }
180                    }
181                    else if (field.equals(Field.USER_ID)) {
182                            try {
183                                    long userId = GetterUtil.getLong(value);
184    
185                                    User user = UserLocalServiceUtil.getUserById(userId);
186    
187                                    String screenName = CMISParameterValueUtil.formatParameterValue(
188                                            field, user.getScreenName(), wildcard, queryConfig);
189    
190                                    cmisCriterion = new CMISSimpleExpression(
191                                            getCmisField(field), screenName,
192                                            cmisSimpleExpressionOperator);
193                            }
194                            catch (Exception e) {
195                                    if (e instanceof SearchException) {
196                                            throw (SearchException)e;
197                                    }
198    
199                                    throw new SearchException(
200                                            "Unable to determine user {" + field + "=" + value + "}",
201                                            e);
202                            }
203                    }
204                    else {
205                            value = CMISParameterValueUtil.formatParameterValue(
206                                    field, value, wildcard, queryConfig);
207    
208                            cmisCriterion = new CMISSimpleExpression(
209                                    getCmisField(field), value, cmisSimpleExpressionOperator);
210                    }
211    
212                    return cmisCriterion;
213            }
214    
215            protected String getCmisField(String field) {
216                    return _cmisFields.get(field);
217            }
218    
219            protected boolean isSupportedField(String field) {
220                    return _supportedFields.contains(field);
221            }
222    
223            protected boolean isSupportsFullText(QueryConfig queryConfig) {
224                    String capabilityQuery = (String)queryConfig.getAttribute(
225                            "capabilityQuery");
226    
227                    if (Validator.isNull(capabilityQuery)) {
228                            return false;
229                    }
230    
231                    if (capabilityQuery.equals("bothcombined") ||
232                            capabilityQuery.equals("fulltextonly")) {
233    
234                            return true;
235                    }
236    
237                    return false;
238            }
239    
240            protected boolean isSupportsOnlyFullText(QueryConfig queryConfig) {
241                    String capabilityQuery = (String)queryConfig.getAttribute(
242                            "capabilityQuery");
243    
244                    if (Validator.isNull(capabilityQuery)) {
245                            return false;
246                    }
247    
248                    if (capabilityQuery.equals("fulltextonly")) {
249                            return true;
250                    }
251    
252                    return false;
253            }
254    
255            protected void traverseContentQuery(
256                            CMISJunction cmisJunction, Query query, QueryConfig queryConfig)
257                    throws SearchException {
258    
259                    if (query instanceof BooleanQuery) {
260                            BooleanQuery booleanQuery = (BooleanQuery)query;
261    
262                            List<BooleanClause> booleanClauses = booleanQuery.clauses();
263    
264                            CMISFullTextConjunction anyCMISConjunction =
265                                    new CMISFullTextConjunction();
266                            CMISDisjunction cmisDisjunction = new CMISDisjunction();
267                            CMISFullTextConjunction notCMISConjunction =
268                                    new CMISFullTextConjunction();
269    
270                            for (BooleanClause booleanClause : booleanClauses) {
271                                    CMISJunction currentCMISJunction = cmisDisjunction;
272    
273                                    BooleanClauseOccur booleanClauseOccur =
274                                            booleanClause.getBooleanClauseOccur();
275    
276                                    if (booleanClauseOccur.equals(BooleanClauseOccur.MUST)) {
277                                            currentCMISJunction = anyCMISConjunction;
278                                    }
279                                    else if (booleanClauseOccur.equals(
280                                                            BooleanClauseOccur.MUST_NOT)) {
281    
282                                            currentCMISJunction = notCMISConjunction;
283                                    }
284    
285                                    Query booleanClauseQuery = booleanClause.getQuery();
286    
287                                    traverseContentQuery(
288                                            currentCMISJunction, booleanClauseQuery, queryConfig);
289                            }
290    
291                            if (!anyCMISConjunction.isEmpty()) {
292                                    cmisJunction.add(anyCMISConjunction);
293                            }
294    
295                            if (!cmisDisjunction.isEmpty()) {
296                                    cmisJunction.add(cmisDisjunction);
297                            }
298    
299                            if (!notCMISConjunction.isEmpty()) {
300                                    CMISContainsNotExpression cmisContainsNotExpression =
301                                            new CMISContainsNotExpression(notCMISConjunction);
302    
303                                    cmisJunction.add(cmisContainsNotExpression);
304                            }
305                    }
306                    else if (query instanceof TermQuery) {
307                            TermQuery termQuery = (TermQuery)query;
308    
309                            QueryTerm queryTerm = termQuery.getQueryTerm();
310    
311                            if (!_isContentFieldQueryTerm(queryTerm)) {
312                                    return;
313                            }
314    
315                            String field = queryTerm.getField();
316                            String value = queryTerm.getValue();
317    
318                            value = CMISParameterValueUtil.formatParameterValue(
319                                    field, value, false, queryConfig);
320    
321                            CMISContainsValueExpression cmisContainsValueExpression =
322                                    new CMISContainsValueExpression(value);
323    
324                            cmisJunction.add(cmisContainsValueExpression);
325                    }
326                    else if (query instanceof WildcardQuery) {
327                            WildcardQuery wildcardQuery = (WildcardQuery)query;
328    
329                            QueryTerm queryTerm = wildcardQuery.getQueryTerm();
330    
331                            if (!_isContentFieldQueryTerm(queryTerm)) {
332                                    return;
333                            }
334    
335                            String value = queryTerm.getValue();
336                            String[] terms = value.split(_STAR_PATTERN);
337    
338                            CMISConjunction cmisConjunction = new CMISConjunction();
339    
340                            for (String term : terms) {
341                                    if (Validator.isNotNull(term)) {
342                                            CMISContainsValueExpression containsValueExpression =
343                                                    new CMISContainsValueExpression(term);
344    
345                                            cmisConjunction.add(containsValueExpression);
346                                    }
347                            }
348    
349                            cmisJunction.add(cmisConjunction);
350                    }
351                    else if (query instanceof TermRangeQuery) {
352                            return;
353                    }
354            }
355    
356            protected void traversePropertiesQuery(
357                            CMISJunction cmisJunction, Query query, QueryConfig queryConfig)
358                    throws SearchException {
359    
360                    if (query instanceof BooleanQuery) {
361                            BooleanQuery booleanQuery = (BooleanQuery)query;
362    
363                            List<BooleanClause> booleanClauses = booleanQuery.clauses();
364    
365                            CMISConjunction anyCMISConjunction = new CMISConjunction();
366                            CMISDisjunction cmisDisjunction = new CMISDisjunction();
367                            CMISConjunction notCMISConjunction = new CMISConjunction();
368    
369                            for (BooleanClause booleanClause : booleanClauses) {
370                                    CMISJunction currentCMISJunction = cmisDisjunction;
371    
372                                    BooleanClauseOccur booleanClauseOccur =
373                                            booleanClause.getBooleanClauseOccur();
374    
375                                    if (booleanClauseOccur.equals(BooleanClauseOccur.MUST)) {
376                                            currentCMISJunction = anyCMISConjunction;
377                                    }
378                                    else if (booleanClauseOccur.equals(
379                                                            BooleanClauseOccur.MUST_NOT)) {
380    
381                                            currentCMISJunction = notCMISConjunction;
382                                    }
383    
384                                    Query booleanClauseQuery = booleanClause.getQuery();
385    
386                                    traversePropertiesQuery(
387                                            currentCMISJunction, booleanClauseQuery, queryConfig);
388                            }
389    
390                            if (!anyCMISConjunction.isEmpty()) {
391                                    cmisJunction.add(anyCMISConjunction);
392                            }
393    
394                            if (!cmisDisjunction.isEmpty()) {
395                                    cmisJunction.add(cmisDisjunction);
396                            }
397    
398                            if (!notCMISConjunction.isEmpty()) {
399                                    cmisJunction.add(new CMISNotExpression(notCMISConjunction));
400                            }
401                    }
402                    else if (query instanceof TermQuery) {
403                            TermQuery termQuery = (TermQuery)query;
404    
405                            QueryTerm queryTerm = termQuery.getQueryTerm();
406    
407                            if (!isSupportedField(queryTerm.getField())) {
408                                    return;
409                            }
410    
411                            CMISCriterion cmisCriterion = buildFieldExpression(
412                                    queryTerm.getField(), queryTerm.getValue(),
413                                    CMISSimpleExpressionOperator.EQ, queryConfig);
414    
415                            if (cmisCriterion != null) {
416                                    cmisJunction.add(cmisCriterion);
417                            }
418                    }
419                    else if (query instanceof TermRangeQuery) {
420                            TermRangeQuery termRangeQuery = (TermRangeQuery)query;
421    
422                            if (!isSupportedField(termRangeQuery.getField())) {
423                                    return;
424                            }
425    
426                            String fieldName = termRangeQuery.getField();
427    
428                            String cmisField = getCmisField(fieldName);
429                            String cmisLowerTerm = CMISParameterValueUtil.formatParameterValue(
430                                    fieldName, termRangeQuery.getLowerTerm(), false, queryConfig);
431                            String cmisUpperTerm = CMISParameterValueUtil.formatParameterValue(
432                                    fieldName, termRangeQuery.getUpperTerm(), false, queryConfig);
433    
434                            CMISCriterion cmisCriterion = new CMISBetweenExpression(
435                                    cmisField, cmisLowerTerm, cmisUpperTerm,
436                                    termRangeQuery.includesLower(), termRangeQuery.includesUpper());
437    
438                            cmisJunction.add(cmisCriterion);
439                    }
440                    else if (query instanceof WildcardQuery) {
441                            WildcardQuery wildcardQuery = (WildcardQuery)query;
442    
443                            QueryTerm queryTerm = wildcardQuery.getQueryTerm();
444    
445                            if (!isSupportedField(queryTerm.getField())) {
446                                    return;
447                            }
448    
449                            CMISCriterion cmisCriterion = buildFieldExpression(
450                                    queryTerm.getField(), queryTerm.getValue(),
451                                    CMISSimpleExpressionOperator.LIKE, queryConfig);
452    
453                            if (cmisCriterion != null) {
454                                    cmisJunction.add(cmisCriterion);
455                            }
456                    }
457            }
458    
459            private boolean _isContentFieldQueryTerm(QueryTerm queryTerm) {
460                    String fieldName = queryTerm.getField();
461    
462                    return fieldName.equals(Field.CONTENT);
463            }
464    
465            private static final String _STAR_PATTERN = Pattern.quote(StringPool.STAR);
466    
467            private static Log _log = LogFactoryUtil.getLog(
468                    BaseCmisSearchQueryBuilder.class);
469    
470            private static Map<String, String> _cmisFields;
471            private static Set<String> _supportedFields;
472    
473            static {
474                    _cmisFields = new HashMap<String, String>();
475    
476                    _cmisFields.put(Field.CREATE_DATE, "cmis:creationDate");
477                    _cmisFields.put(Field.MODIFIED_DATE, "cmis:lastModificationDate");
478                    _cmisFields.put(Field.NAME, "cmis:name");
479                    _cmisFields.put(Field.TITLE, "cmis:name");
480                    _cmisFields.put(Field.USER_ID, "cmis:createdBy");
481                    _cmisFields.put(Field.USER_NAME, "cmis:createdBy");
482    
483                    _supportedFields = new HashSet<String>();
484    
485                    _supportedFields.add(Field.CREATE_DATE);
486                    _supportedFields.add(Field.FOLDER_ID);
487                    _supportedFields.add(Field.MODIFIED_DATE);
488                    _supportedFields.add(Field.NAME);
489                    _supportedFields.add(Field.TITLE);
490                    _supportedFields.add(Field.USER_ID);
491                    _supportedFields.add(Field.USER_NAME);
492            }
493    
494    }