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