001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bdelbosc
018 */
019
020package org.nuxeo.elasticsearch.query;
021
022import java.util.Calendar;
023import java.util.Collection;
024import java.util.List;
025
026import org.apache.commons.lang.StringUtils;
027import org.elasticsearch.index.query.QueryBuilder;
028import org.elasticsearch.index.query.QueryBuilders;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.query.sql.NXQL;
031import org.nuxeo.ecm.core.schema.utils.DateParser;
032import org.nuxeo.ecm.platform.query.api.PredicateDefinition;
033import org.nuxeo.ecm.platform.query.api.PredicateFieldDefinition;
034import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition;
035import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder;
036
037/**
038 * Elasticsearch query builder for Native Page provider.
039 */
040public class PageProviderQueryBuilder {
041
042    private PageProviderQueryBuilder() {
043    }
044
045    /**
046     * Create a ES request from a PP pattern
047     */
048    public static QueryBuilder makeQuery(final String pattern, final Object[] params,
049            final boolean quotePatternParameters, final boolean escapePatternParameters, final boolean useNativeQuery) {
050        String query = pattern;
051        if (params != null) {
052            for (Object param : params) {
053                query = query.replaceFirst("\\?", convertParam(param, quotePatternParameters));
054            }
055        }
056        if (useNativeQuery) {
057            return QueryBuilders.queryStringQuery(query);
058        } else {
059            return NxqlQueryConverter.toESQueryBuilder(query);
060        }
061    }
062
063    /**
064     * Create a ES request from a PP whereClause
065     */
066    public static QueryBuilder makeQuery(final DocumentModel model, final WhereClauseDefinition whereClause,
067            final Object[] params, final boolean useNativeQuery) {
068        return makeQuery(model, whereClause, null, params, useNativeQuery);
069    }
070
071    /**
072     * @since 8.4
073     */
074    public static QueryBuilder makeQuery(final DocumentModel model, final WhereClauseDefinition whereClause,
075            final String additionalFixedPart, final Object[] params, final boolean useNativeQuery) {
076        assert (model != null);
077        assert (whereClause != null);
078        NxqlQueryConverter.ExpressionBuilder eb = new NxqlQueryConverter.ExpressionBuilder("AND");
079        String fixedPart = whereClause.getFixedPart();
080        if (!StringUtils.isBlank(additionalFixedPart)) {
081            fixedPart = (!StringUtils.isBlank(fixedPart))
082                    ? NXQLQueryBuilder.appendClause(fixedPart, additionalFixedPart)
083                    : additionalFixedPart;
084        }
085        if (params != null) {
086            for (Object param : params) {
087                fixedPart = fixedPart.replaceFirst("\\?", convertParam(param, true));
088            }
089            if (useNativeQuery) {
090                // Fixed part handled as query_string
091                eb.add(QueryBuilders.queryStringQuery(fixedPart));
092            } else {
093                eb.add(NxqlQueryConverter.toESQueryBuilder(fixedPart));
094            }
095        }
096        // Process predicates
097        for (PredicateDefinition predicate : whereClause.getPredicates()) {
098            PredicateFieldDefinition[] fieldDef = predicate.getValues();
099            Object[] values = new Object[fieldDef.length];
100            for (int fidx = 0; fidx < fieldDef.length; fidx++) {
101                if (fieldDef[fidx].getXpath() != null) {
102                    values[fidx] = model.getPropertyValue(fieldDef[fidx].getXpath());
103                } else {
104                    values[fidx] = model.getProperty(fieldDef[fidx].getSchema(), fieldDef[fidx].getName());
105                }
106            }
107            if (!isNonNullParam(values)) {
108                // skip predicate where all values are null
109                continue;
110            }
111            Object value = values[0];
112            if (values[0] instanceof Collection<?>) {
113                Collection<?> vals = (Collection<?>) values[0];
114                values = vals.toArray(new Object[vals.size()]);
115            } else if (values[0] instanceof Object[]) {
116                values = (Object[]) values[0];
117            }
118            String name = predicate.getParameter();
119            String operator = predicate.getOperator().toUpperCase();
120            if ("FULLTEXT".equals(operator) || "FULLTEXT ALL".equals(operator)) {
121                operator = "=";
122                if (!name.startsWith(NXQL.ECM_FULLTEXT)) {
123                    name = NXQL.ECM_FULLTEXT + "." + name;
124                }
125            }
126            eb.add(NxqlQueryConverter.makeQueryFromSimpleExpression(operator, name, value, values, null, null));
127        }
128        return eb.get();
129    }
130
131    /**
132     * Convert a params for fixed part
133     */
134    protected static String convertParam(final Object param, boolean quote) {
135        String ret;
136        if (param == null) {
137            ret = "";
138        } else if (param instanceof List<?>) {
139            StringBuilder stringBuilder = new StringBuilder("");
140            NXQLQueryBuilder.appendStringList(stringBuilder, (List<?>) param, quote, true);
141            ret = stringBuilder.toString();
142            // quote is already taken in account
143            quote = false;
144        } else if (param instanceof Calendar) {
145            ret = DateParser.formatW3CDateTime(((Calendar) param).getTime());
146        } else {
147            ret = param.toString();
148        }
149        if (quote && param instanceof String) {
150            ret = "\"" + ret + "\"";
151        }
152        return ret;
153    }
154
155    @SuppressWarnings("rawtypes")
156    protected static boolean isNonNullParam(final Object[] val) {
157        if (val == null) {
158            return false;
159        }
160        for (Object v : val) {
161            if (v != null) {
162                if (v instanceof String) {
163                    if (!((String) v).isEmpty()) {
164                        return true;
165                    }
166                } else if (v instanceof String[]) {
167                    if (((String[]) v).length > 0) {
168                        return true;
169                    }
170                } else if (v instanceof Collection) {
171                    if (!((Collection) v).isEmpty()) {
172                        return true;
173                    }
174                } else {
175                    return true;
176                }
177            }
178        }
179        return false;
180    }
181
182}