001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bdelbosc
016 */
017
018package org.nuxeo.elasticsearch.query;
019
020import java.util.Calendar;
021import java.util.Collection;
022import java.util.List;
023
024import org.elasticsearch.index.query.QueryBuilder;
025import org.elasticsearch.index.query.QueryBuilders;
026import org.nuxeo.ecm.core.api.DocumentModel;
027import org.nuxeo.ecm.core.query.sql.NXQL;
028import org.nuxeo.ecm.core.schema.utils.DateParser;
029import org.nuxeo.ecm.platform.query.api.PredicateDefinition;
030import org.nuxeo.ecm.platform.query.api.PredicateFieldDefinition;
031import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition;
032import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder;
033
034/**
035 * Elasticsearch query builder for Native Page provider.
036 */
037public class PageProviderQueryBuilder {
038
039    private PageProviderQueryBuilder() {
040    }
041
042    /**
043     * Create a ES request from a PP pattern
044     */
045    public static QueryBuilder makeQuery(final String pattern, final Object[] params,
046            final boolean quotePatternParameters, final boolean escapePatternParameters, final boolean useNativeQuery) {
047        String query = pattern;
048        if (params != null) {
049            for (Object param : params) {
050                query = query.replaceFirst("\\?", convertParam(param, quotePatternParameters));
051            }
052        }
053        if (useNativeQuery) {
054            return QueryBuilders.queryStringQuery(query);
055        } else {
056            return NxqlQueryConverter.toESQueryBuilder(query);
057        }
058    }
059
060    /**
061     * Create a ES request from a PP whereClause
062     */
063    public static QueryBuilder makeQuery(final DocumentModel model, final WhereClauseDefinition whereClause,
064            final Object[] params, final boolean useNativeQuery) {
065        assert (model != null);
066        assert (whereClause != null);
067        NxqlQueryConverter.ExpressionBuilder eb = new NxqlQueryConverter.ExpressionBuilder("AND");
068        String fixedPart = whereClause.getFixedPart();
069        if (params != null) {
070            for (Object param : params) {
071                fixedPart = fixedPart.replaceFirst("\\?", convertParam(param, true));
072            }
073            if (useNativeQuery) {
074                // Fixed part handled as query_string
075                eb.add(QueryBuilders.queryStringQuery(fixedPart));
076            } else {
077                eb.add(NxqlQueryConverter.toESQueryBuilder(fixedPart));
078            }
079        }
080        // Process predicates
081        for (PredicateDefinition predicate : whereClause.getPredicates()) {
082            PredicateFieldDefinition[] fieldDef = predicate.getValues();
083            Object[] values = new Object[fieldDef.length];
084            for (int fidx = 0; fidx < fieldDef.length; fidx++) {
085                if (fieldDef[fidx].getXpath() != null) {
086                    values[fidx] = model.getPropertyValue(fieldDef[fidx].getXpath());
087                } else {
088                    values[fidx] = model.getProperty(fieldDef[fidx].getSchema(), fieldDef[fidx].getName());
089                }
090            }
091            if (!isNonNullParam(values)) {
092                // skip predicate where all values are null
093                continue;
094            }
095            Object value = values[0];
096            if (values[0] instanceof Collection<?>) {
097                Collection<?> vals = (Collection<?>) values[0];
098                values = vals.toArray(new Object[vals.size()]);
099            } else if (values[0] instanceof Object[]) {
100                values = (Object[]) values[0];
101            }
102            String name = predicate.getParameter();
103            String operator = predicate.getOperator().toUpperCase();
104            if ("FULLTEXT".equals(operator) || "FULLTEXT ALL".equals(operator)) {
105                operator = "=";
106                if (!name.startsWith(NXQL.ECM_FULLTEXT)) {
107                    name = NXQL.ECM_FULLTEXT + "." + name;
108                }
109            }
110            eb.add(NxqlQueryConverter.makeQueryFromSimpleExpression(operator, name, value, values, null, null));
111        }
112        return eb.get();
113    }
114
115    /**
116     * Convert a params for fixed part
117     */
118    protected static String convertParam(final Object param, boolean quote) {
119        String ret;
120        if (param == null) {
121            ret = "";
122        } else if (param instanceof List<?>) {
123            StringBuilder stringBuilder = new StringBuilder("");
124            NXQLQueryBuilder.appendStringList(stringBuilder, (List<?>) param, quote, true);
125            ret = stringBuilder.toString();
126            // quote is already taken in account
127            quote = false;
128        } else if (param instanceof Calendar) {
129            ret = DateParser.formatW3CDateTime(((Calendar) param).getTime());
130        } else {
131            ret = param.toString();
132        }
133        if (quote && param instanceof String) {
134            ret = "\"" + ret + "\"";
135        }
136        return ret;
137    }
138
139    @SuppressWarnings("rawtypes")
140    protected static boolean isNonNullParam(final Object[] val) {
141        if (val == null) {
142            return false;
143        }
144        for (Object v : val) {
145            if (v != null) {
146                if (v instanceof String) {
147                    if (!((String) v).isEmpty()) {
148                        return true;
149                    }
150                } else if (v instanceof String[]) {
151                    if (((String[]) v).length > 0) {
152                        return true;
153                    }
154                } else if (v instanceof Collection) {
155                    if (!((Collection) v).isEmpty()) {
156                        return true;
157                    }
158                } else {
159                    return true;
160                }
161            }
162        }
163        return false;
164    }
165
166}