001/* 002 * (C) Copyright 2010 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 * Anahide Tchertchian 018 */ 019package org.nuxeo.ecm.platform.query.nxql; 020 021import java.text.DateFormat; 022import java.text.SimpleDateFormat; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Date; 026import java.util.GregorianCalendar; 027import java.util.List; 028import java.util.Map; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.NuxeoException; 037import org.nuxeo.ecm.core.api.PropertyException; 038import org.nuxeo.ecm.core.api.SortInfo; 039import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 040import org.nuxeo.ecm.core.query.sql.NXQL; 041import org.nuxeo.ecm.core.query.sql.model.Literal; 042import org.nuxeo.ecm.core.schema.SchemaManager; 043import org.nuxeo.ecm.core.schema.types.Field; 044import org.nuxeo.ecm.core.schema.types.Schema; 045import org.nuxeo.ecm.core.schema.types.SimpleTypeImpl; 046import org.nuxeo.ecm.core.schema.types.Type; 047import org.nuxeo.ecm.core.schema.types.primitives.StringType; 048import org.nuxeo.ecm.core.search.api.client.querymodel.Escaper; 049import org.nuxeo.ecm.platform.query.api.PageProviderService; 050import org.nuxeo.ecm.platform.query.api.PredicateDefinition; 051import org.nuxeo.ecm.platform.query.api.PredicateFieldDefinition; 052import org.nuxeo.ecm.platform.query.api.WhereClauseDefinition; 053import org.nuxeo.ecm.platform.query.core.FieldDescriptor; 054import org.nuxeo.runtime.api.Framework; 055import org.nuxeo.runtime.services.config.ConfigurationService; 056 057/** 058 * Helper to generate NXQL queries from XMap descriptors 059 * 060 * @since 5.4 061 * @author Anahide Tchertchian 062 */ 063public class NXQLQueryBuilder { 064 065 private static final Log log = LogFactory.getLog(NXQLQueryBuilder.class); 066 067 // @since 5.9.2 068 public static final String DEFAULT_SELECT_STATEMENT = "SELECT * FROM Document"; 069 070 // @since 5.9 071 public static final String SORTED_COLUMN = "SORTED_COLUMN"; 072 073 public static final String REGEXP_NAMED_PARAMETER = "[^a-zA-Z]:\\s*" + "([a-zA-Z0-9:]*)"; 074 075 public static final String REGEXP_EXCLUDE_QUOTE = "'[^']*'"; 076 077 public static final String REGEXP_EXCLUDE_DOUBLE_QUOTE = "\"[^\"]*\""; 078 079 private NXQLQueryBuilder() { 080 } 081 082 public static String getSortClause(SortInfo... sortInfos) { 083 StringBuilder queryBuilder = new StringBuilder(); 084 if (sortInfos != null) { 085 int index = 0; 086 for (SortInfo sortInfo : sortInfos) { 087 String sortColumn = sortInfo.getSortColumn(); 088 boolean sortAscending = sortInfo.getSortAscending(); 089 if (index == 0) { 090 queryBuilder.append("ORDER BY ").append(sortColumn).append(' ').append(sortAscending ? "" : "DESC"); 091 } else { 092 queryBuilder.append(", ").append(sortColumn).append(' ').append(sortAscending ? "" : "DESC"); 093 } 094 index++; 095 } 096 } 097 return queryBuilder.toString(); 098 } 099 100 public static String getQuery(DocumentModel model, WhereClauseDefinition whereClause, Object[] params, 101 SortInfo... sortInfos) { 102 return getQuery(model, whereClause, null, params, sortInfos); 103 } 104 105 /** 106 * @since 8.4 107 */ 108 public static String getQuery(DocumentModel model, WhereClauseDefinition whereClause, String quickFiltersClause, 109 Object[] params, SortInfo... sortInfos) { 110 StringBuilder queryBuilder = new StringBuilder(); 111 String selectStatement = whereClause.getSelectStatement(); 112 if (StringUtils.isBlank(selectStatement)) { 113 selectStatement = DEFAULT_SELECT_STATEMENT; 114 } 115 queryBuilder.append(selectStatement); 116 if (whereClause != null) { 117 queryBuilder.append(getQueryElement(model, whereClause, quickFiltersClause, params)); 118 } 119 String sortClause = getSortClause(sortInfos); 120 if (sortClause != null && sortClause.length() > 0) { 121 queryBuilder.append(" "); 122 queryBuilder.append(sortClause); 123 } 124 return queryBuilder.toString().trim(); 125 } 126 127 public static String getQueryElement(DocumentModel model, WhereClauseDefinition whereClause, Object[] params) { 128 return getQueryElement(model, whereClause, null, params); 129 } 130 131 /** 132 * @since 8.4 133 */ 134 public static String getQueryElement(DocumentModel model, WhereClauseDefinition whereClause, 135 String quickFiltersClause, Object[] params) { 136 List<String> elements = new ArrayList<String>(); 137 PredicateDefinition[] predicates = whereClause.getPredicates(); 138 if (predicates != null) { 139 Escaper escaper = null; 140 Class<? extends Escaper> escaperClass = whereClause.getEscaperClass(); 141 if (escaperClass != null) { 142 try { 143 escaper = escaperClass.newInstance(); 144 } catch (ReflectiveOperationException e) { 145 throw new NuxeoException(e); 146 } 147 } 148 for (PredicateDefinition predicate : predicates) { 149 String predicateString = getQueryElement(model, predicate, escaper); 150 if (predicateString == null) { 151 continue; 152 } 153 154 predicateString = predicateString.trim(); 155 if (!predicateString.equals("")) { 156 elements.add(predicateString); 157 } 158 } 159 } 160 // add fixed part if applicable 161 String fixedPart = whereClause.getFixedPart(); 162 if (!StringUtils.isBlank(fixedPart)) { 163 if (StringUtils.isNotBlank(quickFiltersClause)) { 164 fixedPart = appendClause(fixedPart, quickFiltersClause); 165 } 166 if (elements.isEmpty()) { 167 elements.add(getQuery(fixedPart, params, whereClause.getQuoteFixedPartParameters(), 168 whereClause.getEscapeFixedPartParameters(), model)); 169 } else { 170 elements.add('(' + getQuery(fixedPart, params, whereClause.getQuoteFixedPartParameters(), 171 whereClause.getEscapeFixedPartParameters(), model) + ')'); 172 } 173 } else if (StringUtils.isNotBlank(quickFiltersClause)) { 174 fixedPart = quickFiltersClause; 175 } 176 177 if (elements.isEmpty()) { 178 return ""; 179 } 180 181 // XXX: for now only a one level implement conjunctive WHERE clause 182 String clauseValues = StringUtils.join(elements, " AND ").trim(); 183 184 // GR: WHERE (x = 1) is invalid NXQL 185 while (elements.size() == 1 && clauseValues.startsWith("(") && clauseValues.endsWith(")")) { 186 clauseValues = clauseValues.substring(1, clauseValues.length() - 1).trim(); 187 } 188 if (clauseValues.length() == 0) { 189 return ""; 190 } 191 return " WHERE " + clauseValues; 192 } 193 194 public static String getQuery(String pattern, Object[] params, boolean quoteParameters, boolean escape, 195 DocumentModel searchDocumentModel, SortInfo... sortInfos) { 196 String sortedColumn; 197 if (sortInfos == null || sortInfos.length == 0) { 198 // If there is no ORDER BY use the id 199 sortedColumn = NXQL.ECM_UUID; 200 } else { 201 sortedColumn = sortInfos[0].getSortColumn(); 202 } 203 if (pattern != null && pattern.contains(SORTED_COLUMN)) { 204 pattern = pattern.replace(SORTED_COLUMN, sortedColumn); 205 } 206 StringBuilder queryBuilder; 207 208 // handle named parameters replacements 209 if (searchDocumentModel != null) { 210 // Find all query named parameters as ":parameter" not between 211 // quotes and add them to matches 212 String query = pattern.replaceAll(REGEXP_EXCLUDE_DOUBLE_QUOTE, StringUtils.EMPTY); 213 query = query.replaceAll(REGEXP_EXCLUDE_QUOTE, StringUtils.EMPTY); 214 Pattern p1 = Pattern.compile(REGEXP_NAMED_PARAMETER); 215 Matcher m1 = p1.matcher(query); 216 List<String> matches = new ArrayList<String>(); 217 while (m1.find()) { 218 matches.add(m1.group().substring(m1.group().indexOf(":") + 1)); 219 } 220 for (String key : matches) { 221 Object parameter = getRawValue(searchDocumentModel, new FieldDescriptor(key)); 222 if (parameter == null) { 223 continue; 224 } 225 key = ":" + key; 226 if (parameter instanceof String[]) { 227 replaceStringList(pattern, Arrays.asList((String[]) parameter), quoteParameters, escape, key); 228 } else if (parameter instanceof List) { 229 replaceStringList(pattern, (List<?>) parameter, quoteParameters, escape, key); 230 } else if (parameter instanceof Boolean) { 231 pattern = buildPattern(pattern, key, ((Boolean) parameter) ? "1" : "0"); 232 } else if (parameter instanceof Number) { 233 pattern = buildPattern(pattern, key, parameter.toString()); 234 } else if (parameter instanceof Literal) { 235 if (quoteParameters) { 236 pattern = buildPattern(pattern, key, "'" + parameter.toString() + "'"); 237 } else { 238 pattern = buildPattern(pattern, key, ((Literal) parameter).asString()); 239 } 240 } else { 241 if (quoteParameters) { 242 pattern = buildPattern(pattern, key, "'" + parameter + "'"); 243 } else { 244 pattern = buildPattern(pattern, key, parameter != null ? parameter.toString() : null); 245 } 246 } 247 } 248 } 249 250 if (params == null) { 251 queryBuilder = new StringBuilder(pattern + ' '); 252 } else { 253 // handle "standard" parameters replacements (referenced by ? characters) 254 // XXX: the + " " is a workaround for the buggy implementation 255 // of the split function in case the pattern ends with '?' 256 String[] queryStrList = (pattern + ' ').split("\\?"); 257 queryBuilder = new StringBuilder(queryStrList[0]); 258 for (int i = 0; i < params.length; i++) { 259 if (params[i] instanceof String[]) { 260 appendStringList(queryBuilder, Arrays.asList((String[]) params[i]), quoteParameters, escape); 261 } else if (params[i] instanceof List) { 262 appendStringList(queryBuilder, (List<?>) params[i], quoteParameters, escape); 263 } else if (params[i] instanceof Boolean) { 264 boolean b = ((Boolean) params[i]).booleanValue(); 265 queryBuilder.append(b ? 1 : 0); 266 } else if (params[i] instanceof Number) { 267 queryBuilder.append(params[i]); 268 } else if (params[i] instanceof Literal) { 269 if (quoteParameters) { 270 queryBuilder.append(params[i].toString()); 271 } else { 272 queryBuilder.append(((Literal) params[i]).asString()); 273 } 274 } else { 275 if (params[i] == null) { 276 if (quoteParameters) { 277 queryBuilder.append("''"); 278 } 279 } else { 280 String queryParam = params[i].toString(); 281 queryBuilder.append(prepareStringLiteral(queryParam, quoteParameters, escape)); 282 } 283 } 284 queryBuilder.append(queryStrList[i + 1]); 285 } 286 } 287 queryBuilder.append(getSortClause(sortInfos)); 288 return queryBuilder.toString().trim(); 289 } 290 291 public static void appendStringList(StringBuilder queryBuilder, List<?> listParam, boolean quoteParameters, 292 boolean escape) { 293 // avoid appending parentheses if the query builder ends with one 294 boolean addParentheses = !queryBuilder.toString().endsWith("("); 295 if (addParentheses) { 296 queryBuilder.append('('); 297 } 298 List<String> result = new ArrayList<String>(listParam.size()); 299 for (Object param : listParam) { 300 result.add(prepareStringLiteral(param.toString(), quoteParameters, escape)); 301 } 302 queryBuilder.append(StringUtils.join(result, ", ")); 303 if (addParentheses) { 304 queryBuilder.append(')'); 305 } 306 } 307 308 public static String replaceStringList(String pattern, List<?> listParams, boolean quoteParameters, boolean escape, 309 String key) { 310 List<String> result = new ArrayList<String>(listParams.size()); 311 for (Object param : listParams) { 312 result.add(prepareStringLiteral(param.toString(), quoteParameters, escape)); 313 } 314 315 return buildPattern(pattern, key, '(' + StringUtils.join(result, ", " + "") + ')'); 316 } 317 318 /** 319 * Return the string literal in a form ready to embed in an NXQL statement. 320 */ 321 public static String prepareStringLiteral(String s, boolean quoteParameter, boolean escape) { 322 if (escape) { 323 if (quoteParameter) { 324 return NXQL.escapeString(s); 325 } else { 326 return NXQL.escapeStringInner(s); 327 } 328 } else { 329 if (quoteParameter) { 330 return "'" + s + "'"; 331 } else { 332 return s; 333 } 334 } 335 } 336 337 public static String getQueryElement(DocumentModel model, PredicateDefinition predicateDescriptor, Escaper escaper) { 338 String type = predicateDescriptor.getType(); 339 if (PredicateDefinition.ATOMIC_PREDICATE.equals(type)) { 340 return atomicQueryElement(model, predicateDescriptor, escaper); 341 } 342 if (PredicateDefinition.SUB_CLAUSE_PREDICATE.equals(type)) { 343 return subClauseQueryElement(model, predicateDescriptor); 344 } 345 throw new NuxeoException("Unknown predicate type: " + type); 346 } 347 348 protected static String subClauseQueryElement(DocumentModel model, PredicateDefinition predicateDescriptor) { 349 PredicateFieldDefinition[] values = predicateDescriptor.getValues(); 350 if (values == null || values.length != 1) { 351 throw new NuxeoException("subClause predicate needs exactly one field"); 352 } 353 PredicateFieldDefinition fieldDescriptor = values[0]; 354 if (!getFieldType(model, fieldDescriptor).equals("string")) { 355 if (fieldDescriptor.getXpath() != null) { 356 throw new NuxeoException(String.format("type of field %s is not string", fieldDescriptor.getXpath())); 357 } else { 358 throw new NuxeoException(String.format("type of field %s.%s is not string", 359 fieldDescriptor.getSchema(), fieldDescriptor.getName())); 360 } 361 } 362 Object subclauseValue = getRawValue(model, fieldDescriptor); 363 if (subclauseValue == null) { 364 return ""; 365 } 366 367 return "(" + subclauseValue + ")"; 368 } 369 370 protected static String atomicQueryElement(DocumentModel model, PredicateDefinition predicateDescriptor, 371 Escaper escaper) { 372 String operator = null; 373 String operatorField = predicateDescriptor.getOperatorField(); 374 String operatorSchema = predicateDescriptor.getOperatorSchema(); 375 PredicateFieldDefinition[] values = predicateDescriptor.getValues(); 376 if (operatorField != null && operatorSchema != null) { 377 PredicateFieldDefinition operatorFieldDescriptor = new FieldDescriptor(operatorSchema, operatorField); 378 operator = getPlainStringValue(model, operatorFieldDescriptor); 379 if (operator != null) { 380 operator = operator.toUpperCase(); 381 } 382 } 383 if (StringUtils.isBlank(operator)) { 384 operator = predicateDescriptor.getOperator(); 385 } 386 String hint = predicateDescriptor.getHint(); 387 String parameter = getParameterWithHint(operator, predicateDescriptor.getParameter(), hint); 388 389 if (operator.equals("=") || operator.equals("!=") || operator.equals("<") || operator.equals(">") 390 || operator.equals("<=") || operator.equals(">=") || operator.equals("<>") || operator.equals("LIKE") 391 || operator.equals("ILIKE")) { 392 // Unary predicate 393 String value = getStringValue(model, values[0]); 394 if (value == null) { 395 // value not provided: ignore predicate 396 return ""; 397 } 398 if (escaper != null && (operator.equals("LIKE") || operator.equals("ILIKE"))) { 399 value = escaper.escape(value); 400 } 401 return serializeUnary(parameter, operator, value); 402 403 } else if (operator.equals("BETWEEN")) { 404 String min = getStringValue(model, values[0]); 405 String max = getStringValue(model, values[1]); 406 407 if (min != null && max != null) { 408 StringBuilder builder = new StringBuilder(); 409 builder.append(parameter); 410 builder.append(' '); 411 builder.append(operator); 412 builder.append(' '); 413 builder.append(min); 414 builder.append(" AND "); 415 builder.append(max); 416 return builder.toString(); 417 } else if (max != null) { 418 return serializeUnary(parameter, "<=", max); 419 } else if (min != null) { 420 return serializeUnary(parameter, ">=", min); 421 } else { 422 // both min and max are not provided, ignore predicate 423 return ""; 424 } 425 } else if (operator.equals("IN")) { 426 List<String> options = getListValue(model, values[0]); 427 if (options == null || options.isEmpty()) { 428 return ""; 429 } else if (options.size() == 1) { 430 return serializeUnary(parameter, "=", options.get(0)); 431 } else { 432 StringBuilder builder = new StringBuilder(); 433 builder.append(parameter); 434 builder.append(" IN ("); 435 for (int i = 0; i < options.size(); i++) { 436 if (i != 0) { 437 builder.append(", "); 438 } 439 builder.append(options.get(i)); 440 } 441 builder.append(')'); 442 return builder.toString(); 443 } 444 } else if (operator.equals("STARTSWITH")) { 445 String fieldType = getFieldType(model, values[0]); 446 if (fieldType.equals("string")) { 447 String value = getStringValue(model, values[0]); 448 if (value == null) { 449 return ""; 450 } else { 451 return serializeUnary(parameter, operator, value); 452 } 453 } else { 454 List<String> options = getListValue(model, values[0]); 455 if (options == null || options.isEmpty()) { 456 return ""; 457 } else if (options.size() == 1) { 458 return serializeUnary(parameter, operator, options.get(0)); 459 } else { 460 StringBuilder builder = new StringBuilder(); 461 builder.append('('); 462 for (int i = 0; i < options.size() - 1; i++) { 463 builder.append(serializeUnary(parameter, operator, options.get(i))); 464 builder.append(" OR "); 465 } 466 builder.append(serializeUnary(parameter, operator, options.get(options.size() - 1))); 467 builder.append(')'); 468 return builder.toString(); 469 } 470 } 471 } else if (operator.equals("EMPTY") || operator.equals("ISEMPTY")) { 472 return parameter + " = ''"; 473 } else if (operator.equals("FULLTEXT ALL") // BBB 474 || operator.equals("FULLTEXT")) { 475 String value = getPlainStringValue(model, values[0]); 476 if (value == null) { 477 // value not provided: ignore predicate 478 return ""; 479 } 480 if (escaper != null) { 481 value = escaper.escape(value); 482 } 483 return parameter + ' ' + serializeFullText(value); 484 } else if (operator.equals("IS NULL")) { 485 Boolean value = getBooleanValue(model, values[0]); 486 if (value == null) { 487 // value not provided: ignore predicate 488 return ""; 489 } else if (Boolean.TRUE.equals(value)) { 490 return parameter + " IS NULL"; 491 } else { 492 return parameter + " IS NOT NULL"; 493 } 494 } else { 495 throw new NuxeoException("Unsupported operator: " + operator); 496 } 497 } 498 499 protected static String getParameterWithHint(String operator, String parameter, String hint) { 500 String ret = parameter; 501 // add ecm:fulltext. prefix if needed 502 if ((operator.equals("FULLTEXT ALL") || operator.equals("FULLTEXT")) 503 && !parameter.startsWith(NXQL.ECM_FULLTEXT)) { 504 ret = NXQL.ECM_FULLTEXT + '.' + parameter; 505 } 506 // add the hint 507 if (hint != null && !hint.isEmpty()) { 508 ret = String.format("/*+%s */ %s", hint.trim(), ret); 509 } 510 return ret; 511 } 512 513 /** 514 * Prepares a statement for a fulltext field by converting FULLTEXT virtual operators to a syntax that the search 515 * syntax accepts. 516 * 517 * @param value 518 * @return the serialized statement 519 */ 520 521 public static final String DEFAULT_SPECIAL_CHARACTERS_REGEXP = "!#$%&'()+,./\\\\:-@{|}`^~"; 522 523 public static final String IGNORED_CHARS_KEY = "org.nuxeo.query.builder.ignored.chars"; 524 525 /** 526 * Remove any special character that could be mis-interpreted as a low level full-text query operator. This method 527 * should be used by user facing callers of CoreQuery*PageProviders that use a fixed part or a pattern query. Fields 528 * in where clause already dealt with. 529 * 530 * @since 5.6 531 * @return sanitized text value 532 */ 533 public static String sanitizeFulltextInput(String value) { 534 // Ideally the low level full-text language 535 // parser should be robust to any user input however this is much more 536 // complicated to implement correctly than the following simple user 537 // input filtering scheme. 538 ConfigurationService cs = Framework.getService(ConfigurationService.class); 539 String ignoredChars = cs.getProperty(IGNORED_CHARS_KEY, DEFAULT_SPECIAL_CHARACTERS_REGEXP); 540 String res = ""; 541 value = value.replaceAll("[" + ignoredChars + "]", " "); 542 value = value.trim(); 543 String[] tokens = value.split("[\\s]+"); 544 for (int i = 0; i < tokens.length; i++) { 545 if ("-".equals(tokens[i]) || "*".equals(tokens[i]) || "*-".equals(tokens[i]) || "-*".equals(tokens[i])) { 546 continue; 547 } 548 if (res.length() > 0) { 549 res += " "; 550 } 551 if (tokens[i].startsWith("-") || tokens[i].endsWith("*")) { 552 res += tokens[i]; 553 } else { 554 res += tokens[i].replace("-", " ").replace("*", " "); 555 } 556 } 557 return res.trim(); 558 } 559 560 public static String serializeFullText(String value) { 561 value = sanitizeFulltextInput(value); 562 return "= " + NXQL.escapeString(value); 563 } 564 565 protected static String serializeUnary(String parameter, String operator, String rvalue) { 566 StringBuilder builder = new StringBuilder(); 567 builder.append(parameter); 568 builder.append(' '); 569 builder.append(operator); 570 builder.append(' '); 571 builder.append(rvalue); 572 return builder.toString(); 573 } 574 575 public static String getPlainStringValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 576 Object rawValue = getRawValue(model, fieldDescriptor); 577 if (rawValue == null) { 578 return null; 579 } 580 String value = (String) rawValue; 581 if (value.equals("")) { 582 return null; 583 } 584 return value; 585 } 586 587 public static Integer getIntValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 588 Object rawValue = getRawValue(model, fieldDescriptor); 589 if (rawValue == null || "".equals(rawValue)) { 590 return null; 591 } else if (rawValue instanceof Integer) { 592 return (Integer) rawValue; 593 } else if (rawValue instanceof String) { 594 return Integer.valueOf((String) rawValue); 595 } else { 596 return Integer.valueOf(rawValue.toString()); 597 } 598 } 599 600 public static String getFieldType(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 601 String xpath = fieldDescriptor.getXpath(); 602 String schema = fieldDescriptor.getSchema(); 603 String name = fieldDescriptor.getName(); 604 try { 605 SchemaManager typeManager = Framework.getService(SchemaManager.class); 606 Field field = null; 607 if (xpath != null) { 608 if (model != null) { 609 field = model.getProperty(xpath).getField(); 610 } 611 } else { 612 if (schema != null) { 613 Schema schemaObj = typeManager.getSchema(schema); 614 if (schemaObj == null) { 615 throw new NuxeoException("failed to obtain schema: " + schema); 616 } 617 field = schemaObj.getField(name); 618 } else { 619 // assume named parameter use case: hard-code on String in this case 620 return StringType.ID; 621 } 622 } 623 if (field == null) { 624 throw new NuxeoException("failed to obtain field: " + schema + ":" + name); 625 } 626 Type type = field.getType(); 627 if (type instanceof SimpleTypeImpl) { 628 // type with constraint 629 type = type.getSuperType(); 630 } 631 return type.getName(); 632 } catch (PropertyException e) { 633 e.addInfo("failed to get field type for " + (xpath != null ? xpath : (schema + ":" + name))); 634 throw e; 635 } 636 } 637 638 @SuppressWarnings("unchecked") 639 public static Object getRawValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 640 String xpath = fieldDescriptor.getXpath(); 641 String schema = fieldDescriptor.getSchema(); 642 String name = fieldDescriptor.getName(); 643 try { 644 if (xpath != null) { 645 return model.getPropertyValue(xpath); 646 } else if (schema == null) { 647 return model.getPropertyValue(name); 648 } else { 649 return model.getProperty(schema, name); 650 } 651 } catch (PropertyNotFoundException e) { 652 // fall back on named parameters if any 653 Map<String, Object> params = (Map<String, Object>) model.getContextData( 654 PageProviderService.NAMED_PARAMETERS); 655 if (params != null) { 656 if (xpath != null) { 657 return params.get(xpath); 658 } else { 659 return params.get(name); 660 } 661 } 662 } catch (PropertyException e) { 663 return null; 664 } 665 return null; 666 } 667 668 public static String getStringValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 669 Object rawValue = getRawValue(model, fieldDescriptor); 670 if (rawValue == null) { 671 return null; 672 } 673 String value; 674 if (rawValue instanceof GregorianCalendar) { 675 GregorianCalendar gc = (GregorianCalendar) rawValue; 676 value = "TIMESTAMP '" + getDateFormat().format(gc.getTime()) + "'"; 677 } else if (rawValue instanceof Date) { 678 Date date = (Date) rawValue; 679 value = "TIMESTAMP '" + getDateFormat().format(date) + "'"; 680 } else if (rawValue instanceof Integer || rawValue instanceof Long || rawValue instanceof Double) { 681 value = rawValue.toString(); // no quotes 682 } else if (rawValue instanceof Boolean) { 683 value = ((Boolean) rawValue).booleanValue() ? "1" : "0"; 684 } else { 685 value = rawValue.toString().trim(); 686 if (value.equals("")) { 687 return null; 688 } 689 String fieldType = getFieldType(model, fieldDescriptor); 690 if ("long".equals(fieldType) || "integer".equals(fieldType) || "double".equals(fieldType)) { 691 return value; 692 } else { 693 return NXQL.escapeString(value); 694 } 695 } 696 return value; 697 } 698 699 protected static DateFormat getDateFormat() { 700 // not thread-safe so don't use a static instance 701 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 702 } 703 704 @SuppressWarnings("unchecked") 705 public static List<String> getListValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 706 Object rawValue = getRawValue(model, fieldDescriptor); 707 if (rawValue == null) { 708 return null; 709 } 710 List<String> values = new ArrayList<String>(); 711 if (rawValue instanceof ArrayList) { 712 rawValue = ((ArrayList<Object>) rawValue).toArray(); 713 } 714 for (Object element : (Object[]) rawValue) { 715 if (element != null) { 716 if (element instanceof Number) { 717 values.add(element.toString()); 718 } else { 719 String value = element.toString().trim(); 720 if (!value.equals("")) { 721 values.add(NXQL.escapeString(value)); 722 } 723 } 724 } 725 } 726 return values; 727 } 728 729 public static Boolean getBooleanValue(DocumentModel model, PredicateFieldDefinition fieldDescriptor) { 730 Object rawValue = getRawValue(model, fieldDescriptor); 731 if (rawValue == null) { 732 return null; 733 } else { 734 return (Boolean) rawValue; 735 } 736 } 737 738 /** 739 * @since 8.4 740 */ 741 public static String appendClause(String query, String clause) { 742 return query + " AND " + clause; 743 } 744 745 /** 746 * @since 8.4 747 */ 748 public static String buildPattern(String pattern, String key, String replacement) { 749 int index = pattern.indexOf(key); 750 while (index >= 0) { 751 // All keys not prefixed by a letter or a digit has to be replaced, because 752 // It could be part of a schema name 753 if (!Character.isLetterOrDigit(pattern.charAt(index - 1)) && (index + key.length() == pattern.length() 754 || !Character.isLetterOrDigit(pattern.charAt(index + key.length())))) { 755 pattern = pattern.substring(0, index) + pattern.substring(index).replaceFirst(key, replacement); 756 } 757 index = pattern.indexOf(key, index + 1); 758 } 759 return pattern; 760 } 761 762}