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) : additionalFixedPart;
083        }
084        if (params != null) {
085            for (Object param : params) {
086                fixedPart = fixedPart.replaceFirst("\\?", convertParam(param, true));
087            }
088            if (useNativeQuery) {
089                // Fixed part handled as query_string
090                eb.add(QueryBuilders.queryStringQuery(fixedPart));
091            } else {
092                eb.add(NxqlQueryConverter.toESQueryBuilder(fixedPart));
093            }
094        }
095        // Process predicates
096        for (PredicateDefinition predicate : whereClause.getPredicates()) {
097            PredicateFieldDefinition[] fieldDef = predicate.getValues();
098            Object[] values = new Object[fieldDef.length];
099            for (int fidx = 0; fidx < fieldDef.length; fidx++) {
100                if (fieldDef[fidx].getXpath() != null) {
101                    values[fidx] = model.getPropertyValue(fieldDef[fidx].getXpath());
102                } else {
103                    values[fidx] = model.getProperty(fieldDef[fidx].getSchema(), fieldDef[fidx].getName());
104                }
105            }
106            if (!isNonNullParam(values)) {
107                // skip predicate where all values are null
108                continue;
109            }
110            Object value = values[0];
111            if (values[0] instanceof Collection<?>) {
112                Collection<?> vals = (Collection<?>) values[0];
113                values = vals.toArray(new Object[vals.size()]);
114            } else if (values[0] instanceof Object[]) {
115                values = (Object[]) values[0];
116            }
117            String name = predicate.getParameter();
118            String operator = predicate.getOperator().toUpperCase();
119            if ("FULLTEXT".equals(operator) || "FULLTEXT ALL".equals(operator)) {
120                operator = "=";
121                if (!name.startsWith(NXQL.ECM_FULLTEXT)) {
122                    name = NXQL.ECM_FULLTEXT + "." + name;
123                }
124            }
125            eb.add(NxqlQueryConverter.makeQueryFromSimpleExpression(operator, name, value, values, null, null));
126        }
127        return eb.get();
128    }
129
130    /**
131     * Convert a params for fixed part
132     */
133    protected static String convertParam(final Object param, boolean quote) {
134        String ret;
135        if (param == null) {
136            ret = "";
137        } else if (param instanceof List<?>) {
138            StringBuilder stringBuilder = new StringBuilder("");
139            NXQLQueryBuilder.appendStringList(stringBuilder, (List<?>) param, quote, true);
140            ret = stringBuilder.toString();
141            // quote is already taken in account
142            quote = false;
143        } else if (param instanceof Calendar) {
144            ret = DateParser.formatW3CDateTime(((Calendar) param).getTime());
145        } else {
146            ret = param.toString();
147        }
148        if (quote && param instanceof String) {
149            ret = "\"" + ret + "\"";
150        }
151        return ret;
152    }
153
154    @SuppressWarnings("rawtypes")
155    protected static boolean isNonNullParam(final Object[] val) {
156        if (val == null) {
157            return false;
158        }
159        for (Object v : val) {
160            if (v != null) {
161                if (v instanceof String) {
162                    if (!((String) v).isEmpty()) {
163                        return true;
164                    }
165                } else if (v instanceof String[]) {
166                    if (((String[]) v).length > 0) {
167                        return true;
168                    }
169                } else if (v instanceof Collection) {
170                    if (!((Collection) v).isEmpty()) {
171                        return true;
172                    }
173                } else {
174                    return true;
175                }
176            }
177        }
178        return false;
179    }
180
181}