001/* 002 * (C) Copyright 2014-2018 Nuxeo (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.lang3.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}