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