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.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                                    CMISContainsValueExpression containsValueExpression =
342                                            new CMISContainsValueExpression(term);
343    
344                                    cmisConjunction.add(containsValueExpression);
345                            }
346    
347                            cmisJunction.add(cmisConjunction);
348                    }
349                    else if (query instanceof TermRangeQuery) {
350                            return;
351                    }
352            }
353    
354            protected void traversePropertiesQuery(
355                            CMISJunction cmisJunction, Query query, QueryConfig queryConfig)
356                    throws SearchException {
357    
358                    if (query instanceof BooleanQuery) {
359                            BooleanQuery booleanQuery = (BooleanQuery)query;
360    
361                            List<BooleanClause> booleanClauses = booleanQuery.clauses();
362    
363                            CMISConjunction anyCMISConjunction = new CMISConjunction();
364                            CMISDisjunction cmisDisjunction = new CMISDisjunction();
365                            CMISConjunction notCMISConjunction = new CMISConjunction();
366    
367                            for (BooleanClause booleanClause : booleanClauses) {
368                                    CMISJunction currentCMISJunction = cmisDisjunction;
369    
370                                    BooleanClauseOccur booleanClauseOccur =
371                                            booleanClause.getBooleanClauseOccur();
372    
373                                    if (booleanClauseOccur.equals(BooleanClauseOccur.MUST)) {
374                                            currentCMISJunction = anyCMISConjunction;
375                                    }
376                                    else if (booleanClauseOccur.equals(
377                                                            BooleanClauseOccur.MUST_NOT)) {
378    
379                                            currentCMISJunction = notCMISConjunction;
380                                    }
381    
382                                    Query booleanClauseQuery = booleanClause.getQuery();
383    
384                                    traversePropertiesQuery(
385                                            currentCMISJunction, booleanClauseQuery, queryConfig);
386                            }
387    
388                            if (!anyCMISConjunction.isEmpty()) {
389                                    cmisJunction.add(anyCMISConjunction);
390                            }
391    
392                            if (!cmisDisjunction.isEmpty()) {
393                                    cmisJunction.add(cmisDisjunction);
394                            }
395    
396                            if (!notCMISConjunction.isEmpty()) {
397                                    cmisJunction.add(new CMISNotExpression(notCMISConjunction));
398                            }
399                    }
400                    else if (query instanceof TermQuery) {
401                            TermQuery termQuery = (TermQuery)query;
402    
403                            QueryTerm queryTerm = termQuery.getQueryTerm();
404    
405                            if (!isSupportedField(queryTerm.getField())) {
406                                    return;
407                            }
408    
409                            CMISCriterion cmisCriterion = buildFieldExpression(
410                                    queryTerm.getField(), queryTerm.getValue(),
411                                    CMISSimpleExpressionOperator.EQ, queryConfig);
412    
413                            if (cmisCriterion != null) {
414                                    cmisJunction.add(cmisCriterion);
415                            }
416                    }
417                    else if (query instanceof TermRangeQuery) {
418                            TermRangeQuery termRangeQuery = (TermRangeQuery)query;
419    
420                            if (!isSupportedField(termRangeQuery.getField())) {
421                                    return;
422                            }
423    
424                            String fieldName = termRangeQuery.getField();
425    
426                            String cmisField = getCmisField(fieldName);
427                            String cmisLowerTerm = CMISParameterValueUtil.formatParameterValue(
428                                    fieldName, termRangeQuery.getLowerTerm(), false, queryConfig);
429                            String cmisUpperTerm = CMISParameterValueUtil.formatParameterValue(
430                                    fieldName, termRangeQuery.getUpperTerm(), false, queryConfig);
431    
432                            CMISCriterion cmisCriterion = new CMISBetweenExpression(
433                                    cmisField, cmisLowerTerm, cmisUpperTerm,
434                                    termRangeQuery.includesLower(), termRangeQuery.includesUpper());
435    
436                            cmisJunction.add(cmisCriterion);
437                    }
438                    else if (query instanceof WildcardQuery) {
439                            WildcardQuery wildcardQuery = (WildcardQuery)query;
440    
441                            QueryTerm queryTerm = wildcardQuery.getQueryTerm();
442    
443                            if (!isSupportedField(queryTerm.getField())) {
444                                    return;
445                            }
446    
447                            CMISCriterion cmisCriterion = buildFieldExpression(
448                                    queryTerm.getField(), queryTerm.getValue(),
449                                    CMISSimpleExpressionOperator.LIKE, queryConfig);
450    
451                            if (cmisCriterion != null) {
452                                    cmisJunction.add(cmisCriterion);
453                            }
454                    }
455            }
456    
457            private boolean _isContentFieldQueryTerm(QueryTerm queryTerm) {
458                    String fieldName = queryTerm.getField();
459    
460                    return fieldName.equals(Field.CONTENT);
461            }
462    
463            private static final String _STAR_PATTERN = Pattern.quote(StringPool.STAR);
464    
465            private static Log _log = LogFactoryUtil.getLog(
466                    BaseCmisSearchQueryBuilder.class);
467    
468            private static Map<String, String> _cmisFields;
469            private static Set<String> _supportedFields;
470    
471            static {
472                    _cmisFields = new HashMap<String, String>();
473    
474                    _cmisFields.put(Field.CREATE_DATE, "cmis:creationDate");
475                    _cmisFields.put(Field.MODIFIED_DATE, "cmis:lastModificationDate");
476                    _cmisFields.put(Field.NAME, "cmis:name");
477                    _cmisFields.put(Field.TITLE, "cmis:name");
478                    _cmisFields.put(Field.USER_ID, "cmis:createdBy");
479                    _cmisFields.put(Field.USER_NAME, "cmis:createdBy");
480    
481                    _supportedFields = new HashSet<String>();
482    
483                    _supportedFields.add(Field.CREATE_DATE);
484                    _supportedFields.add(Field.FOLDER_ID);
485                    _supportedFields.add(Field.MODIFIED_DATE);
486                    _supportedFields.add(Field.NAME);
487                    _supportedFields.add(Field.TITLE);
488                    _supportedFields.add(Field.USER_ID);
489                    _supportedFields.add(Field.USER_NAME);
490            }
491    
492    }