1 16 package org.outerj.daisy.repository.serverimpl.query; 17 18 import org.outerj.daisy.repository.query.*; 19 import org.outerj.daisy.repository.query.SortOrder; 20 import org.outerj.daisy.repository.commonimpl.CommonRepository; 21 import org.outerj.daisy.repository.commonimpl.AuthenticatedUser; 22 import org.outerj.daisy.repository.commonimpl.RepositoryImpl; 23 import org.outerj.daisy.repository.commonimpl.acl.CommonAccessManager; 24 import org.outerj.daisy.repository.*; 25 import org.outerj.daisy.repository.acl.AclResultInfo; 26 import org.outerj.daisy.repository.acl.AclPermission; 27 import org.outerj.daisy.repository.serverimpl.LocalRepositoryManager; 28 import org.outerj.daisy.query.ExtQueryContext; 29 import org.outerj.daisy.query.model.*; 30 import org.outerj.daisy.ftindex.FullTextIndex; 31 import org.outerj.daisy.ftindex.Hits; 32 import org.outerj.daisy.jdbcutil.JdbcHelper; 33 import org.apache.avalon.framework.logger.Logger; 34 import org.apache.xmlbeans.XmlObject; 35 36 import org.outerx.daisy.x10.SearchResultDocument; 37 import org.outerx.daisy.x10.FacetedQueryResultDocument; 38 import org.outerx.daisy.x10.LinkValueType; 39 import org.outerx.daisy.x10.DistinctSearchResultDocument; 40 41 import java.sql.PreparedStatement ; 42 import java.sql.Connection ; 43 import java.sql.ResultSet ; 44 import java.util.*; 45 import java.text.Collator ; 46 import java.text.DateFormat ; 47 import java.text.NumberFormat ; 48 import java.math.BigDecimal ; 49 50 public class LocalQueryManager implements QueryManager { 51 private LocalRepositoryManager.Context context; 52 private AuthenticatedUser systemUser; 53 private AuthenticatedUser user; 54 private Logger logger; 55 private JdbcHelper jdbcHelper; 56 57 public LocalQueryManager(LocalRepositoryManager.Context context, AuthenticatedUser user, AuthenticatedUser systemUser, Logger logger, JdbcHelper jdbcHelper) { 58 this.context = context; 59 this.systemUser = systemUser; 60 this.user = user; 61 this.logger = logger; 62 this.jdbcHelper = jdbcHelper; 63 } 64 65 public SearchResultDocument performQuery(String queryAsString, Locale locale) throws QueryException { 66 return performQuery(queryAsString, null, locale); 67 } 68 69 public VariantKey[] performQueryReturnKeys(String queryAsString, Locale locale) throws RepositoryException { 70 return performQueryReturnKeys(queryAsString, null, locale); 71 } 72 73 public SearchResultDocument performQuery(String query, Locale locale, EvaluationContext evaluationContext) throws QueryException { 74 return performQuery(query, null, null, locale, evaluationContext); 75 } 76 77 public SearchResultDocument performQuery(String query, String extraCond, Locale locale) throws QueryException { 78 return performQuery(query, extraCond, null, locale, new EvaluationContext()); 79 } 80 81 public SearchResultDocument performQuery(String query, String extraCond, Locale locale, EvaluationContext evaluationContext) throws QueryException { 82 return performQuery(query, extraCond, null, locale, evaluationContext); 83 } 84 85 public SearchResultDocument performQuery(String query, String extraCond, Map queryOptions, Locale locale) throws RepositoryException { 86 return performQuery(query, extraCond, queryOptions, locale, new EvaluationContext()); 87 } 88 89 public SearchResultDocument performQuery(String query, String extraCond, Map queryOptions, Locale locale, EvaluationContext evaluationContext) throws QueryException { 90 if (query == null) 91 throw new IllegalArgumentException ("query parameter is null"); 92 evaluationContext.setUserId(user.getId()); 93 Object [] result = performQueryReturnDocuments(query, extraCond, queryOptions, locale, evaluationContext); 94 long beforeBuildResult = System.currentTimeMillis(); 95 SearchResultDocument xml = buildXmlResult(result, 1, -1, locale, evaluationContext); 96 long afterBuildResult = System.currentTimeMillis(); 97 QueryExecutionInfo executionInfo = (QueryExecutionInfo)result[2]; 98 executionInfo.outputGenerationTime = afterBuildResult - beforeBuildResult; 99 xml.getSearchResult().setExecutionInfo(executionInfo.getXml()); 100 return xml; 101 } 102 103 public VariantKey[] performQueryReturnKeys(String queryAsString, String extraCond, Locale locale) throws RepositoryException { 104 return performQueryReturnKeys(queryAsString, extraCond, locale, new EvaluationContext()); 105 } 106 107 public VariantKey[] performQueryReturnKeys(String query, String extraCond, Map queryOptions, Locale locale) throws RepositoryException { 108 return performQueryReturnKeys(query, extraCond, queryOptions, locale, new EvaluationContext()); 109 } 110 111 public VariantKey[] performQueryReturnKeys(String query, Locale locale, EvaluationContext evaluationContext) throws RepositoryException { 112 return performQueryReturnKeys(query, null, locale, evaluationContext); 113 } 114 115 public VariantKey[] performQueryReturnKeys(String queryAsString, String extraCond, Locale locale, EvaluationContext evaluationContext) throws RepositoryException { 116 return performQueryReturnKeys(queryAsString, extraCond, null, locale, evaluationContext); 117 } 118 119 public VariantKey[] performQueryReturnKeys(String queryAsString, String extraCond, Map queryOptions, Locale locale, EvaluationContext evaluationContext) throws RepositoryException { 120 if (queryAsString == null) 121 throw new IllegalArgumentException ("query parameter is null"); 122 123 evaluationContext.setUserId(user.getId()); 124 Object [] result = performQueryReturnDocuments(queryAsString, extraCond, queryOptions, locale, evaluationContext); 125 List documents = (List)result[1]; 126 127 VariantKey[] keys = new VariantKey[documents.size()]; 128 Iterator documentsIt = documents.iterator(); 129 int i = 0; 130 while (documentsIt.hasNext()) { 131 Document document = (Document)documentsIt.next(); 132 keys[i] = new VariantKey(document.getId(), document.getBranchId(), document.getLanguageId()); 133 i++; 134 } 135 136 return keys; 137 } 138 139 private SearchResultDocument buildXmlResult(Object [] queryResult, int chunkOffset, int chunkLength, Locale locale, 140 EvaluationContext evaluationContext) throws QueryException { 141 Query query = (Query)queryResult[0]; 142 List documents = (List)queryResult[1]; 143 144 ValueExpr[] selectExprs = query.getSelectValueExprs(); 146 147 SearchResultDocument searchResultDocument = SearchResultDocument.Factory.newInstance(); 148 SearchResultDocument.SearchResult searchResult = searchResultDocument.addNewSearchResult(); 149 if (query.getStyleHint() != null) 150 searchResult.setStyleHint(query.getStyleHint()); 151 152 SearchResultDocument.SearchResult.Titles titles = searchResult.addNewTitles(); 153 for (int i = 0; i < selectExprs.length; i++) { 154 String title = selectExprs[i].getTitle(locale); 155 SearchResultDocument.SearchResult.Titles.Title titleXml = titles.addNewTitle(); 156 titleXml.setStringValue(title); 157 titleXml.setName(selectExprs[i].getExpression()); 158 } 159 160 int finish = 0; 162 if (documents.size() > 0) { 163 if (chunkOffset < 1) 165 chunkOffset = 1; 166 finish = chunkOffset + chunkLength - 1; 167 if (chunkLength < 1) 168 finish = chunkOffset; 169 if (chunkLength == -1) 170 finish = documents.size(); 171 if (finish > documents.size()) 172 finish = documents.size(); 173 } else { 174 chunkOffset = 0; 175 } 176 chunkLength = 0; 177 178 SearchResultDocument.SearchResult.Rows rows = searchResult.addNewRows(); 179 if (documents.size() > 0) { 180 ValueFormatter valueFormatter = new ValueFormatter(locale); 181 182 for (int i = chunkOffset - 1; i < finish; i++) { 183 chunkLength++; 184 SearchResultDocument.SearchResult.Rows.Row row = rows.addNewRow(); 185 Document document = (Document)documents.get(i); 186 row.setDocumentId(document.getId()); 187 row.setBranchId(document.getBranchId()); 188 row.setLanguageId(document.getLanguageId()); 189 for (int j = 0; j < selectExprs.length; j++) { 190 Version version; 191 try { 192 version = query.getSearchLastVersion() ? document.getLastVersion() : document.getLiveVersion(); 193 } catch (RepositoryException e) { 194 throw new QueryException("Problem retrieving document version.", e); 195 } 196 if (version != null) { 200 Object value = selectExprs[j].getOutputValue(document, version, evaluationContext); 201 QValueType outputValueType = selectExprs[j].getOutputValueType(); 202 if (selectExprs[j].isMultiValue()) { 203 SearchResultDocument.SearchResult.Rows.Row.MultiValue multiValue = row.addNewMultiValue(); 204 if (value != null) { 205 Object [] values = (Object [])value; 206 for (int k = 0; k < values.length; k++) { 207 if (outputValueType == QValueType.LINK) { 208 LinkValueType linkValue = multiValue.addNewLinkValue(); 209 VariantKey variantKey = (VariantKey)values[k]; 210 setLinkValue(linkValue, variantKey, query.getAnnotateLinkFields()); 211 } else { 212 String stringValue = valueFormatter.format(outputValueType, values[k]); 213 multiValue.addValue(stringValue); 214 } 215 } 216 } 217 } else if (outputValueType == QValueType.LINK) { 218 LinkValueType linkValue = row.addNewLinkValue(); 219 if (value != null) { 220 VariantKey variantKey = (VariantKey)value; 221 setLinkValue(linkValue, variantKey, query.getAnnotateLinkFields()); 222 } 223 } else if (outputValueType == QValueType.XML) { 224 row.addNewXmlValue().set((XmlObject)value); 225 } else { 226 String stringValue = valueFormatter.format(outputValueType, value); 227 row.addValue(stringValue); 228 } 229 } 230 } 231 } 232 } 233 234 SearchResultDocument.SearchResult.ResultInfo resultInfo = searchResult.addNewResultInfo(); 235 resultInfo.setSize(documents.size()); 236 resultInfo.setChunkOffset(chunkOffset); 237 resultInfo.setChunkLength(chunkLength); 238 239 return searchResultDocument; 240 } 241 242 private void setLinkValue(LinkValueType linkValue, VariantKey variantKey, boolean annotate) { 243 long documentId = variantKey.getDocumentId(); 244 long branchId = variantKey.getBranchId(); 245 long languageId = variantKey.getLanguageId(); 246 linkValue.setDocumentId(documentId); 247 linkValue.setBranchId(branchId); 248 linkValue.setLanguageId(languageId); 249 String label; 250 if (annotate) { 251 try { 252 Document linkedDoc = context.getCommonRepository().getDocument(documentId, branchId, languageId, false, user); 253 if (linkedDoc.getLiveVersion() != null) 254 label = linkedDoc.getLiveVersion().getDocumentName(); 255 else 256 label = linkedDoc.getName(); 257 } catch (RepositoryException e) { 258 label = "daisy:" + documentId + "@" + branchId + ":" + languageId; 260 } 261 } else { 262 label = "daisy:" + documentId + "@" + branchId + ":" + languageId; 263 } 264 linkValue.setStringValue(label); 265 } 266 267 class ValueFormatter { 268 private final DateFormat dateFormat; 269 private final DateFormat dateTimeFormat; 270 private final NumberFormat decimalFormat; 271 private final Locale locale; 272 273 private ValueFormatter(Locale locale) { 274 this.locale = locale; 275 dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale); 276 dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); 277 decimalFormat = NumberFormat.getNumberInstance(locale); 278 } 279 280 String format(QValueType outputValueType, Object value) { 281 if (value == null) 282 return ""; 283 284 if (outputValueType == QValueType.DATE) 285 return dateFormat.format((Date)value); 286 else if (outputValueType == QValueType.DATETIME) 287 return dateTimeFormat.format((Date)value); 288 else if (outputValueType == QValueType.DECIMAL) 289 return decimalFormat.format(value); 290 else if (outputValueType == QValueType.DOUBLE) 291 return decimalFormat.format(value); 292 else if (outputValueType == QValueType.VERSION_STATE) 293 return getLocalizedString(value.toString(), locale); 294 else if (outputValueType == QValueType.LINK) { 295 VariantKey variantKey = (VariantKey)value; 296 String label; 297 try { 298 Document linkedDoc = context.getCommonRepository().getDocument(variantKey.getDocumentId(), variantKey.getBranchId(), variantKey.getLanguageId(), false, user); 299 if (linkedDoc.getLiveVersion() != null) 300 label = linkedDoc.getLiveVersion().getDocumentName(); 301 else 302 label = linkedDoc.getName(); 303 } catch (RepositoryException e) { 304 label = "daisy:" + variantKey.getDocumentId() + "@" + variantKey.getBranchId() + ":" + variantKey.getLanguageId(); 306 } 307 return label; 308 } 309 else 310 return value.toString(); 311 } 312 } 313 314 private static String getLocalizedString(String name, Locale locale) { 315 ResourceBundle bundle = ResourceBundle.getBundle("org/outerj/daisy/query/model/messages", locale); 316 return bundle.getString(name); 317 } 318 319 322 private Object [] performQueryReturnDocuments(String queryAsString, String extraCond, Map queryOptions, Locale locale, EvaluationContext evaluationContext) throws QueryException { 323 try { 324 QueryExecutionInfo executionInfo = new QueryExecutionInfo(); 325 executionInfo.query = queryAsString; 326 executionInfo.extraCondition = extraCond; 327 executionInfo.locale = locale; 328 329 long beforeParseAndPrepare = System.currentTimeMillis(); 330 Query query = context.getQueryFactory().parseQuery(queryAsString); 331 if (extraCond != null) { 332 PredicateExpr extraPredicateExpr = context.getQueryFactory().parsePredicateExpression(extraCond); 333 query.mergeCondition(extraPredicateExpr); 334 } 335 if (queryOptions != null) { 336 query.setOptions(queryOptions); 337 } 338 CommonRepository repository = context.getCommonRepository(); 339 query.prepare(new ExtQueryContext(new RepositoryImpl(context.getCommonRepository(), user))); 340 long afterParseAndPrepare = System.currentTimeMillis(); 341 executionInfo.parseAndPrepareTime = afterParseAndPrepare - beforeParseAndPrepare; 342 343 Hits fullTextHits = null; 344 if (query.getFullTextQuery() != null) { 345 long beforeFullTextQuery = System.currentTimeMillis(); 346 FullTextIndex fullTextIndex = context.getFullTextIndex(); 347 FullTextQuery ftQuery = query.getFullTextQuery(); 348 fullTextHits = fullTextIndex.search(ftQuery.getQuery(), ftQuery.getBranchId(), ftQuery.getLanguageId(), 349 ftQuery.getSearchName(), ftQuery.getSearchContent(), ftQuery.getSearchFields()); 350 long afterFullTextQuery = System.currentTimeMillis(); 351 executionInfo.fullTextQueryTime = afterFullTextQuery - beforeFullTextQuery; 352 if (logger.isDebugEnabled()) 353 logger.debug("Resultcount from fulltext search: " + fullTextHits.length()); 354 } 355 356 List sqlResultKeys = null; 357 if (query.hasSql()) { 358 long beforeRdbmsQuery = System.currentTimeMillis(); 359 String sql = query.getSql(jdbcHelper); 360 361 if (logger.isDebugEnabled()) 362 logger.debug("Generated SQL: " + sql); 363 364 sqlResultKeys = new ArrayList(); 366 Connection conn = null; 367 PreparedStatement stmt = null; 368 try { 369 conn = context.getDataSource().getConnection(); 370 jdbcHelper.startTransaction(conn); 371 stmt = conn.prepareStatement(sql); 372 query.bindSql(stmt, 1, evaluationContext); 373 ResultSet rs = stmt.executeQuery(); 374 while (rs.next()) 375 sqlResultKeys.add(new VariantKey(rs.getLong(1), rs.getLong(2), rs.getLong(3))); 376 } finally { 377 jdbcHelper.closeStatement(stmt); 378 jdbcHelper.closeConnection(conn); 379 } 380 long afterRdbmsQuery = System.currentTimeMillis(); 381 executionInfo.rdbmsQueryTime = afterRdbmsQuery - beforeRdbmsQuery; 382 if (logger.isDebugEnabled()) 383 logger.debug("Resultcount from SQL database search: " + sqlResultKeys.size()); 384 } 385 386 List mergedResults; 387 if (fullTextHits != null && sqlResultKeys == null) { 388 mergedResults = new ArrayList(fullTextHits.length()); 390 for (int i = 0; i < fullTextHits.length(); i++) 391 mergedResults.add(new VariantKey(fullTextHits.docId(i), fullTextHits.branchId(i), fullTextHits.languageId(i))); 392 } else if (fullTextHits != null && sqlResultKeys != null) { 393 long beforeMergeTime = System.currentTimeMillis(); 395 VariantKey[] sqlKeysArray = (VariantKey[])sqlResultKeys.toArray(new VariantKey[sqlResultKeys.size()]); 396 Arrays.sort(sqlKeysArray); 397 mergedResults = new ArrayList(fullTextHits.length()); 398 int index; 399 for (int i = 0; i < fullTextHits.length(); i++) { 400 VariantKey variantKey = new VariantKey(fullTextHits.docId(i), fullTextHits.branchId(i), fullTextHits.languageId(i)); 401 index = Arrays.binarySearch(sqlKeysArray, variantKey); 402 if (index >= 0) 403 mergedResults.add(variantKey); 404 } 405 long afterMergeTime = System.currentTimeMillis(); 406 executionInfo.mergeTime = afterMergeTime - beforeMergeTime; 407 } else if (sqlResultKeys != null) { 408 mergedResults = sqlResultKeys; 409 } else { 410 throw new RuntimeException ("The impossible has happened: both fulltext and sql search results do not exist."); 412 } 413 414 if (logger.isDebugEnabled()) 415 logger.debug("Resultcount after merge of SQL and Fulltext results: " + mergedResults.size()); 416 417 418 long beforeAclFiltering = System.currentTimeMillis(); 420 CommonAccessManager accessManager = context.getCommonRepository().getAccessManager(); 421 ArrayList documents = new ArrayList(mergedResults.size()); 422 long userId = user.getId(); 423 long[] activeRoleIds = user.getActiveRoleIds(); 424 for (int i = 0; i < mergedResults.size(); i++) { 425 try { 426 VariantKey key = (VariantKey)mergedResults.get(i); 427 Document document = repository.getDocument(key.getDocumentId(), key.getBranchId(), key.getLanguageId(), false, systemUser); 428 AclResultInfo aclInfo = accessManager.getAclInfoOnLive(systemUser, userId, activeRoleIds, document); 429 if (aclInfo.isAllowed(AclPermission.READ)) { 430 documents.add(document); 431 } else if (aclInfo.isAllowed(AclPermission.READ_LIVE)) { 432 if (document.getLiveVersionId() == -1 || document.isRetired()) { 433 } else if (query.getSearchLastVersion()) { 435 if (document.getLiveVersionId() == document.getLastVersionId()) 436 documents.add(document); 437 } else { 438 documents.add(document); 439 } 440 } 441 } catch (DocumentNotFoundException e) { 442 } 444 } 445 long afterAclFiltering = System.currentTimeMillis(); 446 executionInfo.aclFilterTime = afterAclFiltering - beforeAclFiltering; 447 448 if (logger.isDebugEnabled()) 449 logger.debug("Resultcount after applying ACL filtering: " + documents.size()); 450 451 if (query.getOrderByValueExprs() != null) { 453 long beforeSortingTime = System.currentTimeMillis(); 454 ValueExpr[] orderByExprs = query.getOrderByValueExprs(); 455 SortOrder[] sortOrders = query.getOrderBySortOrders(); 456 Collator collator = Collator.getInstance(locale); 457 CompareContext compareContext = new CompareContext(orderByExprs, sortOrders, collator); 458 DocumentComparable[] comparables = new DocumentComparable[documents.size()]; 459 for (int i = 0; i < comparables.length; i++) { 460 comparables[i] = new DocumentComparable(compareContext, (Document)documents.get(i), query.getSearchLastVersion(), evaluationContext); 461 } 462 Arrays.sort(comparables); 463 464 documents = new ArrayList(comparables.length); 465 for (int i = 0; i < comparables.length; i++) { 466 documents.add(i, comparables[i].getDocument()); 467 } 468 long afterSortingTime = System.currentTimeMillis(); 469 executionInfo.sortTime = afterSortingTime - beforeSortingTime; 470 } 471 472 int finish; 474 if (query.getLimit() != -1) 475 finish = documents.size() < query.getLimit() ? documents.size() : query.getLimit(); 476 else 477 finish = documents.size(); 478 479 Object [] result = {query, documents.subList(0, finish), executionInfo}; 480 return result; 481 } catch (Exception e) { 482 throw new QueryException("Error performing query.", e); 483 } 484 } 485 486 public FacetedQueryResultDocument performFacetedQuery(String queryString, FacetConf[] facetConfs, int chunkOffset, int chunkLength, Locale locale) throws RepositoryException { 487 EvaluationContext evaluationContext = new EvaluationContext(); 488 evaluationContext.setUserId(user.getId()); 489 Object [] result = performQueryReturnDocuments(queryString, null, null, locale, evaluationContext); 490 Query query = (Query)result[0]; 491 List documents = (List)result[1]; 492 QueryExecutionInfo executionInfo = (QueryExecutionInfo)result[2]; 493 494 ValueExpr[] selectExprs = query.getSelectValueExprs(); 495 int facetCount = Math.min(facetConfs.length, selectExprs.length); 496 FacetValue[] facetValues = new FacetValue[facetCount]; 497 for (int i = 0; i < facetCount; i++) 498 facetValues[i] = facetConfs[i].isFacet() ? new FacetValue() : null; 499 500 for (int i = 0; i < documents.size(); i++) { 501 Document document = (Document)documents.get(i); 502 for (int j = 0; j < facetCount; j++) { 503 if (facetValues[j] == null) 504 continue; 505 Version version; 506 try { 507 version = query.getSearchLastVersion() ? document.getLastVersion() : document.getLiveVersion(); 508 } catch (RepositoryException e) { 509 throw new QueryException("Problem retrieving document version.", e); 510 } 511 if (version != null) { 512 Object value = selectExprs[j].getOutputValue(document, version, evaluationContext); 513 if (value != null) { 514 if (selectExprs[j].isMultiValue()) { 515 Object [] values = (Object [])value; 516 for (int k = 0; k < values.length; k++) 517 facetValues[j].addValue(values[k]); 518 } else { 519 facetValues[j].addValue(value); 520 } 521 } 522 } 523 } 524 } 525 526 FacetedQueryResultDocument resultDocument = FacetedQueryResultDocument.Factory.newInstance(); 527 FacetedQueryResultDocument.FacetedQueryResult facetedQueryResult = resultDocument.addNewFacetedQueryResult(); 528 FacetedQueryResultDocument.FacetedQueryResult.Facets facetsXml = facetedQueryResult.addNewFacets(); 529 530 for (int i = 0; i < facetValues.length; i++) { 531 if (facetValues[i] != null) 532 facetValues[i].addXml(facetsXml, facetConfs[i], selectExprs[i], locale); 533 } 534 535 SearchResultDocument xmlResult = buildXmlResult(result, chunkOffset, chunkLength, locale, evaluationContext); 536 xmlResult.getSearchResult().setExecutionInfo(executionInfo.getXml()); 537 facetedQueryResult.setSearchResult(xmlResult.getSearchResult()); 538 539 return resultDocument; 540 } 541 542 public DistinctSearchResultDocument performDistinctQuery(String queryString, String extraCond, SortOrder sortOrder, Locale locale) throws RepositoryException { 543 EvaluationContext evaluationContext = new EvaluationContext(); 544 evaluationContext.setUserId(user.getId()); 545 Object [] result = performQueryReturnDocuments(queryString, extraCond, null, locale, evaluationContext); 546 Query query = (Query)result[0]; 547 List documents = (List)result[1]; 548 549 ValueExpr valueExpr = query.getSelectValueExprs()[0]; 550 boolean multiValue = valueExpr.isMultiValue(); 551 HashSet distinctValues = new HashSet(); 552 553 for (int i = 0; i < documents.size(); i++) { 554 Document document = (Document)documents.get(i); 555 Version version; 556 try { 557 version = query.getSearchLastVersion() ? document.getLastVersion() : document.getLiveVersion(); 558 } catch (RepositoryException e) { 559 throw new QueryException("Problem retrieving document version.", e); 560 } 561 if (version != null) { 562 Object value = valueExpr.getOutputValue(document, version, evaluationContext); 563 if (value != null) { 564 if (multiValue) { 565 Object [] values = (Object [])value; 566 for (int k = 0; k < values.length; k++) 567 distinctValues.add(values[k]); 568 } else { 569 distinctValues.add(value); 570 } 571 } 572 } 573 } 574 575 Object [] values = distinctValues.toArray(new Object [distinctValues.size()]); 576 if (sortOrder != SortOrder.NONE) 577 Arrays.sort(values, new ValueComparator(sortOrder == SortOrder.ASCENDING, locale)); 578 579 DistinctSearchResultDocument.DistinctSearchResult.Values.Value[] valuesXml = new DistinctSearchResultDocument.DistinctSearchResult.Values.Value[values.length]; 580 QValueType outputValueType = valueExpr.getOutputValueType(); 581 DistinctResultXmlSetter resultSetter = null; 582 if (outputValueType != QValueType.LINK) 583 resultSetter = getDistinctResultXmlSetter(outputValueType); 584 ValueFormatter valueFormatter = new ValueFormatter(locale); 585 for (int i = 0; i < values.length; i++) { 586 valuesXml[i] = DistinctSearchResultDocument.DistinctSearchResult.Values.Value.Factory.newInstance(); 587 if (outputValueType == QValueType.LINK) { 588 String formattedValue = valueFormatter.format(outputValueType, values[i]); 589 valuesXml[i].setLabel(formattedValue); 590 VariantKey variantKey = (VariantKey)values[i]; 591 DistinctSearchResultDocument.DistinctSearchResult.Values.Value.Link link = valuesXml[i].addNewLink(); 592 link.setDocumentId(variantKey.getDocumentId()); 593 link.setBranchId(variantKey.getBranchId()); 594 link.setLanguageId(variantKey.getLanguageId()); 595 } else { 596 resultSetter.setResult(values[i], valuesXml[i]); 597 } 598 } 599 DistinctSearchResultDocument resultDoc = DistinctSearchResultDocument.Factory.newInstance(); 600 DistinctSearchResultDocument.DistinctSearchResult resultXml = resultDoc.addNewDistinctSearchResult(); 601 DistinctSearchResultDocument.DistinctSearchResult.Values valuesXmlParent = resultXml.addNewValues(); 602 valuesXmlParent.setValueType(outputValueType.toString()); 603 valuesXmlParent.setValueArray(valuesXml); 604 605 return resultDoc; 606 } 607 608 public DistinctSearchResultDocument performDistinctQuery(String queryString, SortOrder sortOrder, Locale locale) throws RepositoryException { 609 return performDistinctQuery(queryString, null, sortOrder, locale); 610 } 611 612 private DistinctResultXmlSetter getDistinctResultXmlSetter(QValueType valueType) { 613 if (valueType == QValueType.STRING) 614 return STRING_DISTINCT_SETTER; 615 else if (valueType == QValueType.LONG) 616 return LONG_DISTINCT_SETTER; 617 else if (valueType == QValueType.DECIMAL) 618 return DECIMAL_DISTINCT_SETTER; 619 else if (valueType == QValueType.DOUBLE) 620 return DOUBLE_DISTINCT_SETTER; 621 else if (valueType == QValueType.BOOLEAN) 622 return BOOLEAN_DISTINCT_SETTER; 623 else if (valueType == QValueType.DATE) 624 return DATE_DISTINCT_SETTER; 625 else if (valueType == QValueType.DATETIME) 626 return DATETIME_DISTINCT_SETTER; 627 else 628 throw new RuntimeException ("Can't handle this type in distinct query result: " + valueType.toString()); 629 } 630 631 private static interface DistinctResultXmlSetter { 632 void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml); 633 } 634 635 private static DistinctResultXmlSetter STRING_DISTINCT_SETTER = new DistinctResultXmlSetter() { 636 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 637 valueXml.setString((String )value); 638 } 639 }; 640 641 private static DistinctResultXmlSetter LONG_DISTINCT_SETTER = new DistinctResultXmlSetter() { 642 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 643 valueXml.setLong(((Long )value).longValue()); 644 } 645 }; 646 647 private static DistinctResultXmlSetter DECIMAL_DISTINCT_SETTER = new DistinctResultXmlSetter() { 648 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 649 valueXml.setDecimal((BigDecimal )value); 650 } 651 }; 652 653 private static DistinctResultXmlSetter DOUBLE_DISTINCT_SETTER = new DistinctResultXmlSetter() { 654 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 655 valueXml.setDouble(((Double )value).doubleValue()); 656 } 657 }; 658 659 private static DistinctResultXmlSetter BOOLEAN_DISTINCT_SETTER = new DistinctResultXmlSetter() { 660 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 661 valueXml.setBoolean(((Boolean )value).booleanValue()); 662 } 663 }; 664 665 private static DistinctResultXmlSetter DATE_DISTINCT_SETTER = new DistinctResultXmlSetter() { 666 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 667 GregorianCalendar calendar = new GregorianCalendar(); 668 calendar.setTime((Date)value); 669 valueXml.setDate(calendar); 670 } 671 }; 672 673 private static DistinctResultXmlSetter DATETIME_DISTINCT_SETTER = new DistinctResultXmlSetter() { 674 public void setResult(Object value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) { 675 GregorianCalendar calendar = new GregorianCalendar(); 676 calendar.setTime((Date)value); 677 valueXml.setDateTime(calendar); 678 } 679 }; 680 681 private static class CompareContext{ 682 public final ValueExpr[] orderKeys; 683 public final SortOrder[] sortOrders; 684 public final Collator collator; 685 686 public CompareContext(ValueExpr[] orderKeys, SortOrder[] sortOrders, Collator collator) { 687 this.orderKeys = orderKeys; 688 this.sortOrders = sortOrders; 689 this.collator = collator; 690 } 691 } 692 693 private static class DocumentComparable implements Comparable { 694 private final CompareContext context; 695 private final Object [] orderValues; 696 private final Document document; 697 private final boolean lastVersion; 698 private final EvaluationContext evaluationContext; 699 700 public DocumentComparable(CompareContext context, Document document, boolean lastVersion, EvaluationContext evaluationContext) { 701 this.context = context; 702 this.document = document; 703 this.orderValues = new Object [context.orderKeys.length]; 704 this.lastVersion = lastVersion; 705 this.evaluationContext = evaluationContext; 706 } 707 708 public Comparable getValue(int pos) { 709 if (orderValues[pos] == null) { 710 Version version; 711 try { 712 version = lastVersion ? document.getLastVersion() : document.getLiveVersion(); 713 } catch (RepositoryException e) { 714 throw new RuntimeException (e); 715 } 716 try { 717 orderValues[pos] = context.orderKeys[pos].getOutputValue(document, version, evaluationContext); 718 } catch (QueryException e) { 719 throw new RuntimeException (e); 720 } 721 if (orderValues[pos] == null) 722 orderValues[pos] = NULL_VALUE; 723 } 724 return (Comparable )orderValues[pos]; 725 } 726 727 public Document getDocument() { 728 return document; 729 } 730 731 public int compareTo(Object o) { 732 DocumentComparable theOtherOne = (DocumentComparable)o; 733 int compareResult; 734 for (int i = 0; i < context.orderKeys.length; i++) { 735 Object myValue = getValue(i); 736 Object otherValue = theOtherOne.getValue(i); 737 738 if (myValue == NULL_VALUE && otherValue == NULL_VALUE) 739 return 0; 740 if (myValue == NULL_VALUE) 741 return 1; 742 if (otherValue == NULL_VALUE) 743 return -1; 744 745 if (myValue instanceof String ) { 746 compareResult = context.collator.compare(myValue, otherValue); 747 } else { 748 compareResult = getValue(i).compareTo(otherValue); 749 } 750 if (compareResult != 0) 751 if (context.sortOrders[i] == SortOrder.DESCENDING) 752 return compareResult * -1; 753 else 754 return compareResult; 755 } 756 return 0; 757 } 758 } 759 760 private static final NullValue NULL_VALUE = new NullValue(); 761 762 private static class NullValue implements Comparable { 763 public int compareTo(Object o) { 764 if (o instanceof NullValue) 765 return 0; 766 else 767 return 1; 768 } 769 } 770 771 static class QueryExecutionInfo { 772 public String query; 773 public String extraCondition; 774 public Locale locale; 775 public long parseAndPrepareTime = -1; 776 public long fullTextQueryTime = -1; 777 public long rdbmsQueryTime = -1; 778 public long mergeTime = -1; 779 public long aclFilterTime = -1; 780 public long sortTime = -1; 781 public long outputGenerationTime = -1; 782 783 public SearchResultDocument.SearchResult.ExecutionInfo getXml() { 784 SearchResultDocument.SearchResult.ExecutionInfo xml = SearchResultDocument.SearchResult.ExecutionInfo.Factory.newInstance(); 785 xml.setQuery(query); 786 if (extraCondition != null) 787 xml.setExtraCondition(extraCondition); 788 xml.setLocale(locale.toString()); 789 if (parseAndPrepareTime != -1) 790 xml.setParseAndPrepareTime(parseAndPrepareTime); 791 if (fullTextQueryTime != -1) 792 xml.setFullTextQueryTime(fullTextQueryTime); 793 if (rdbmsQueryTime != -1) 794 xml.setRdbmsQueryTime(rdbmsQueryTime); 795 if (mergeTime != -1) 796 xml.setMergeTime(mergeTime); 797 if (aclFilterTime != -1) 798 xml.setAclFilterTime(aclFilterTime); 799 if (sortTime != -1) 800 xml.setSortTime(sortTime); 801 if (outputGenerationTime != -1) 802 xml.setOutputGenerationTime(outputGenerationTime); 803 return xml; 804 } 805 } 806 807 private class FacetValue { 808 private Map values = new HashMap(); 809 810 public void addValue(Object value) { 811 Counter counter = (Counter)values.get(value); 812 if (counter != null) { 813 counter.increment(); 814 } else { 815 counter = new Counter(); 816 counter.increment(); 817 values.put(value, counter); 818 } 819 } 820 821 public void addXml(FacetedQueryResultDocument.FacetedQueryResult.Facets facetsXml, FacetConf facetConf, ValueExpr valueExpr, Locale locale) { 822 FacetedQueryResultDocument.FacetedQueryResult.Facets.Facet facetXml = facetsXml.addNewFacet(); 823 Map.Entry[] entries = (Map.Entry[])values.entrySet().toArray(new Map.Entry[values.size()]); 824 Arrays.sort(entries, facetConf.getSortOnValue() ? (Comparator)new MapEntryKeyComparator(facetConf.getSortAscending(), locale) : (Comparator)new MapEntryValueComparator(facetConf.getSortAscending())); 825 826 ValueFormatter valueFormatter = new ValueFormatter(locale); 827 int count = facetConf.getMaxValues() == -1 || facetConf.getMaxValues() > entries.length ? entries.length : facetConf.getMaxValues(); 828 for (int i = 0; i < count; i++) { 829 String userFormat = valueFormatter.format(valueExpr.getOutputValueType(), entries[i].getKey()); 830 String queryFormat = formatQueryValue(valueExpr.getOutputValueType(), entries[i].getKey()); 831 832 FacetedQueryResultDocument.FacetedQueryResult.Facets.Facet.Value valueXml = facetXml.addNewValue(); 833 valueXml.setUserFormat(userFormat); 834 valueXml.setQueryFormat(queryFormat); 835 valueXml.setCount(((Counter)entries[i].getValue()).getValue()); 836 } 837 838 facetXml.setAvailableValues(entries.length); 839 facetXml.setLabel(valueExpr.getTitle(locale)); 840 facetXml.setExpression(valueExpr.getExpression()); 841 facetXml.setMultiValue(valueExpr.isMultiValue()); 842 } 843 } 844 845 846 private static String formatQueryValue(QValueType outputValueType, Object value) { 847 if (outputValueType == QValueType.STRING) { 848 return QueryHelper.formatString((String )value); 849 } else if (outputValueType == QValueType.LONG || outputValueType == QValueType.DOUBLE || outputValueType == QValueType.DECIMAL) { 850 return value.toString(); 851 } else if (outputValueType == QValueType.DATE) { 852 return QueryHelper.formatDate((Date)value); 853 } else if (outputValueType == QValueType.DATETIME) { 854 return QueryHelper.formatDate((Date)value); 855 } else if (outputValueType == QValueType.LINK) { 856 VariantKey key = (VariantKey)value; 857 return "'daisy:" + key.getDocumentId() + '@' + key.getBranchId() + ':' + key.getLanguageId() + '\''; 858 } else { 859 return "'" + value.toString() + "'"; 860 } 861 } 862 863 private static class ValueComparator implements Comparator { 864 protected final boolean ascending; 865 private Collator collator; 866 867 public ValueComparator(boolean ascending, Locale locale) { 868 this.ascending = ascending; 869 this.collator = Collator.getInstance(locale); 870 } 871 872 public int compare(Object value1, Object value2) { 873 int result; 874 if (value1 instanceof String ) { 875 result = collator.compare(value1, value2); 876 } else if (value1 instanceof Comparable ) { 877 result = ((Comparable )value1).compareTo(value2); 878 } else if (value1 instanceof Boolean ) { 879 boolean bool1 = ((Boolean )value1).booleanValue(); 880 boolean bool2 = ((Boolean )value2).booleanValue(); 881 if (bool1 == bool2) 882 result = 0; 883 else if (bool1) 884 result = -1; 885 else 886 result = 1; 887 } else { 888 throw new RuntimeException ("Non-comparable type of object: " + value1.getClass().getName()); 889 } 890 return ascending ? result : result * -1; 891 } 892 } 893 894 private static class MapEntryKeyComparator extends ValueComparator { 895 public MapEntryKeyComparator(boolean ascending, Locale locale) { 896 super(ascending, locale); 897 } 898 899 public int compare(Object o1, Object o2) { 900 Object value1 = ((Map.Entry)o1).getKey(); 901 Object value2 = ((Map.Entry)o2).getKey(); 902 return super.compare(value1, value2); 903 } 904 } 905 906 private static class MapEntryValueComparator implements Comparator { 907 private final boolean ascending; 908 909 public MapEntryValueComparator(boolean ascending) { 910 this.ascending = ascending; 911 } 912 913 public int compare(Object o1, Object o2) { 914 Object value1; 915 Object value2; 916 if (ascending) { 917 value1 = ((Map.Entry)o1).getValue(); 918 value2 = ((Map.Entry)o2).getValue(); 919 } else { 920 value1 = ((Map.Entry)o2).getValue(); 921 value2 = ((Map.Entry)o1).getValue(); 922 } 923 return ((Comparable )value1).compareTo(value2); 924 } 925 } 926 927 private static class Counter implements Comparable { 928 private long count = 0; 929 930 public void increment() { 931 count++; 932 } 933 934 public int compareTo(Object o) { 935 Counter otherCounter = (Counter)o; 936 if (otherCounter.count == count) 937 return 0; 938 else if (count < otherCounter.count) 939 return -1; 940 else 941 return 1; 942 } 943 944 public long getValue() { 945 return count; 946 } 947 } 948 949 public PredicateExpression parsePredicateExpression(String expression) throws QueryException { 950 PredicateExpr predicateExpr = context.getQueryFactory().parsePredicateExpression(expression); 951 predicateExpr.prepare(new ExtQueryContext(new RepositoryImpl(context.getCommonRepository(), user))); 952 return new PredicateExpressionImpl(predicateExpr); 953 } 954 } 955 | Popular Tags |