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