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.elasticsearch.index.query.QueryBuilder;
027import org.elasticsearch.index.query.QueryBuilders;
028import org.nuxeo.ecm.core.api.DocumentModel;
029import org.nuxeo.ecm.core.query.sql.NXQL;
030import org.nuxeo.ecm.core.schema.utils.DateParser;
031import org.nuxeo.ecm.platform.query.api.PredicateDefinition;
032import org.nuxeo.ecm.platform.query.api.PredicateFieldDefinition;
033import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition;
034import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder;
035
036/**
037 * Elasticsearch query builder for Native Page provider.
038 */
039public class PageProviderQueryBuilder {
040
041    private PageProviderQueryBuilder() {
042    }
043
044    /**
045     * Create a ES request from a PP pattern
046     */
047    public static QueryBuilder makeQuery(final String pattern, final Object[] params,
048            final boolean quotePatternParameters, final boolean escapePatternParameters, final boolean useNativeQuery) {
049        String query = pattern;
050        if (params != null) {
051            for (Object param : params) {
052                query = query.replaceFirst("\\?", convertParam(param, quotePatternParameters));
053            }
054        }
055        if (useNativeQuery) {
056            return QueryBuilders.queryStringQuery(query);
057        } else {
058            return NxqlQueryConverter.toESQueryBuilder(query);
059        }
060    }
061
062    /**
063     * Create a ES request from a PP whereClause
064     */
065    public static QueryBuilder makeQuery(final DocumentModel model, final WhereClauseDefinition whereClause,
066            final Object[] params, final boolean useNativeQuery) {
067        assert (model != null);
068        assert (whereClause != null);
069        NxqlQueryConverter.ExpressionBuilder eb = new NxqlQueryConverter.ExpressionBuilder("AND");
070        String fixedPart = whereClause.getFixedPart();
071        if (params != null) {
072            for (Object param : params) {
073                fixedPart = fixedPart.replaceFirst("\\?", convertParam(param, true));
074            }
075            if (useNativeQuery) {
076                // Fixed part handled as query_string
077                eb.add(QueryBuilders.queryStringQuery(fixedPart));
078            } else {
079                eb.add(NxqlQueryConverter.toESQueryBuilder(fixedPart));
080            }
081        }
082        // Process predicates
083        for (PredicateDefinition predicate : whereClause.getPredicates()) {
084            PredicateFieldDefinition[] fieldDef = predicate.getValues();
085            Object[] values = new Object[fieldDef.length];
086            for (int fidx = 0; fidx < fieldDef.length; fidx++) {
087                if (fieldDef[fidx].getXpath() != null) {
088                    values[fidx] = model.getPropertyValue(fieldDef[fidx].getXpath());
089                } else {
090                    values[fidx] = model.getProperty(fieldDef[fidx].getSchema(), fieldDef[fidx].getName());
091                }
092            }
093            if (!isNonNullParam(values)) {
094                // skip predicate where all values are null
095                continue;
096            }
097            Object value = values[0];
098            if (values[0] instanceof Collection<?>) {
099                Collection<?> vals = (Collection<?>) values[0];
100                values = vals.toArray(new Object[vals.size()]);
101            } else if (values[0] instanceof Object[]) {
102                values = (Object[]) values[0];
103            }
104            String name = predicate.getParameter();
105            String operator = predicate.getOperator().toUpperCase();
106            if ("FULLTEXT".equals(operator) || "FULLTEXT ALL".equals(operator)) {
107                operator = "=";
108                if (!name.startsWith(NXQL.ECM_FULLTEXT)) {
109                    name = NXQL.ECM_FULLTEXT + "." + name;
110                }
111            }
112            eb.add(NxqlQueryConverter.makeQueryFromSimpleExpression(operator, name, value, values, null, null));
113        }
114        return eb.get();
115    }
116
117    /**
118     * Convert a params for fixed part
119     */
120    protected static String convertParam(final Object param, boolean quote) {
121        String ret;
122        if (param == null) {
123            ret = "";
124        } else if (param instanceof List<?>) {
125            StringBuilder stringBuilder = new StringBuilder("");
126            NXQLQueryBuilder.appendStringList(stringBuilder, (List<?>) param, quote, true);
127            ret = stringBuilder.toString();
128            // quote is already taken in account
129            quote = false;
130        } else if (param instanceof Calendar) {
131            ret = DateParser.formatW3CDateTime(((Calendar) param).getTime());
132        } else {
133            ret = param.toString();
134        }
135        if (quote && param instanceof String) {
136            ret = "\"" + ret + "\"";
137        }
138        return ret;
139    }
140
141    @SuppressWarnings("rawtypes")
142    protected static boolean isNonNullParam(final Object[] val) {
143        if (val == null) {
144            return false;
145        }
146        for (Object v : val) {
147            if (v != null) {
148                if (v instanceof String) {
149                    if (!((String) v).isEmpty()) {
150                        return true;
151                    }
152                } else if (v instanceof String[]) {
153                    if (((String[]) v).length > 0) {
154                        return true;
155                    }
156                } else if (v instanceof Collection) {
157                    if (!((Collection) v).isEmpty()) {
158                        return true;
159                    }
160                } else {
161                    return true;
162                }
163            }
164        }
165        return false;
166    }
167
168}