KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > repository > serverimpl > query > LocalQueryManager


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

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 JavaDoc;
42 import java.sql.Connection JavaDoc;
43 import java.sql.ResultSet JavaDoc;
44 import java.util.*;
45 import java.text.Collator JavaDoc;
46 import java.text.DateFormat JavaDoc;
47 import java.text.NumberFormat JavaDoc;
48 import java.math.BigDecimal JavaDoc;
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 JavaDoc queryAsString, Locale locale) throws QueryException {
66         return performQuery(queryAsString, null, locale);
67     }
68
69     public VariantKey[] performQueryReturnKeys(String JavaDoc queryAsString, Locale locale) throws RepositoryException {
70         return performQueryReturnKeys(queryAsString, null, locale);
71     }
72
73     public SearchResultDocument performQuery(String JavaDoc query, Locale locale, EvaluationContext evaluationContext) throws QueryException {
74         return performQuery(query, null, null, locale, evaluationContext);
75     }
76
77     public SearchResultDocument performQuery(String JavaDoc query, String JavaDoc extraCond, Locale locale) throws QueryException {
78         return performQuery(query, extraCond, null, locale, new EvaluationContext());
79     }
80
81     public SearchResultDocument performQuery(String JavaDoc query, String JavaDoc extraCond, Locale locale, EvaluationContext evaluationContext) throws QueryException {
82         return performQuery(query, extraCond, null, locale, evaluationContext);
83     }
84
85     public SearchResultDocument performQuery(String JavaDoc query, String JavaDoc extraCond, Map queryOptions, Locale locale) throws RepositoryException {
86         return performQuery(query, extraCond, queryOptions, locale, new EvaluationContext());
87     }
88
89     public SearchResultDocument performQuery(String JavaDoc query, String JavaDoc extraCond, Map queryOptions, Locale locale, EvaluationContext evaluationContext) throws QueryException {
90         if (query == null)
91             throw new IllegalArgumentException JavaDoc("query parameter is null");
92         evaluationContext.setUserId(user.getId());
93         Object JavaDoc[] 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 JavaDoc queryAsString, String JavaDoc extraCond, Locale locale) throws RepositoryException {
104         return performQueryReturnKeys(queryAsString, extraCond, locale, new EvaluationContext());
105     }
106
107     public VariantKey[] performQueryReturnKeys(String JavaDoc query, String JavaDoc extraCond, Map queryOptions, Locale locale) throws RepositoryException {
108         return performQueryReturnKeys(query, extraCond, queryOptions, locale, new EvaluationContext());
109     }
110
111     public VariantKey[] performQueryReturnKeys(String JavaDoc query, Locale locale, EvaluationContext evaluationContext) throws RepositoryException {
112         return performQueryReturnKeys(query, null, locale, evaluationContext);
113     }
114
115     public VariantKey[] performQueryReturnKeys(String JavaDoc queryAsString, String JavaDoc extraCond, Locale locale, EvaluationContext evaluationContext) throws RepositoryException {
116         return performQueryReturnKeys(queryAsString, extraCond, null, locale, evaluationContext);
117     }
118
119     public VariantKey[] performQueryReturnKeys(String JavaDoc queryAsString, String JavaDoc extraCond, Map queryOptions, Locale locale, EvaluationContext evaluationContext) throws RepositoryException {
120         if (queryAsString == null)
121             throw new IllegalArgumentException JavaDoc("query parameter is null");
122
123         evaluationContext.setUserId(user.getId());
124         Object JavaDoc[] 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 JavaDoc[] 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         // generate result
145
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 JavaDoc 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         // calculate output window
161
int finish = 0;
162         if (documents.size() > 0) {
163             // calculate chunk
164
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                     // Note: version can only be null in border cases, e.g. when the document lost
197
// its live version in between the querying on the database and the retrieval
198
// of the version object
199
if (version != null) {
200                         Object JavaDoc 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 JavaDoc[] values = (Object JavaDoc[])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 JavaDoc 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 JavaDoc 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 JavaDoc 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                 // ignore exception
259
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 JavaDoc dateFormat;
269         private final DateFormat JavaDoc dateTimeFormat;
270         private final NumberFormat JavaDoc 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 JavaDoc format(QValueType outputValueType, Object JavaDoc 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 JavaDoc 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                     // ignore exception
305
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 JavaDoc getLocalizedString(String JavaDoc name, Locale locale) {
315         ResourceBundle bundle = ResourceBundle.getBundle("org/outerj/daisy/query/model/messages", locale);
316         return bundle.getString(name);
317     }
318
319     /**
320      * @return An array containing 3 elements: the Query object, the List of Document objects (= result of the query), and an QueryExecutionInfo object.
321      */

322     private Object JavaDoc[] performQueryReturnDocuments(String JavaDoc queryAsString, String JavaDoc 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 JavaDoc sql = query.getSql(jdbcHelper);
360
361                 if (logger.isDebugEnabled())
362                     logger.debug("Generated SQL: " + sql);
363
364                 // perform the query on the database
365
sqlResultKeys = new ArrayList();
366                 Connection JavaDoc conn = null;
367                 PreparedStatement JavaDoc 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 JavaDoc 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                 // only full text results, copy hit results over in mergedResults list
389
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                 // merge the results
394
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                 // This situation can never occur
411
throw new RuntimeException JavaDoc("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             // retrieve documents and filter acording to ACL
419
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                             // don't include document in query results
434
} 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                     // the document has been deleted since we got the search results, skip it
443
}
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             // perform sorting
452
if (query.getOrderByValueExprs() != null) {
453                 long beforeSortingTime = System.currentTimeMillis();
454                 ValueExpr[] orderByExprs = query.getOrderByValueExprs();
455                 SortOrder[] sortOrders = query.getOrderBySortOrders();
456                 Collator JavaDoc 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             // take limit clause into account
473
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 JavaDoc[] result = {query, documents.subList(0, finish), executionInfo};
480             return result;
481         } catch (Exception JavaDoc e) {
482             throw new QueryException("Error performing query.", e);
483         }
484     }
485
486     public FacetedQueryResultDocument performFacetedQuery(String JavaDoc queryString, FacetConf[] facetConfs, int chunkOffset, int chunkLength, Locale locale) throws RepositoryException {
487         EvaluationContext evaluationContext = new EvaluationContext();
488         evaluationContext.setUserId(user.getId());
489         Object JavaDoc[] 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 JavaDoc value = selectExprs[j].getOutputValue(document, version, evaluationContext);
513                     if (value != null) {
514                         if (selectExprs[j].isMultiValue()) {
515                             Object JavaDoc[] values = (Object JavaDoc[])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 JavaDoc queryString, String JavaDoc extraCond, SortOrder sortOrder, Locale locale) throws RepositoryException {
543         EvaluationContext evaluationContext = new EvaluationContext();
544         evaluationContext.setUserId(user.getId());
545         Object JavaDoc[] 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 JavaDoc value = valueExpr.getOutputValue(document, version, evaluationContext);
563                 if (value != null) {
564                     if (multiValue) {
565                         Object JavaDoc[] values = (Object JavaDoc[])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 JavaDoc[] values = distinctValues.toArray(new Object JavaDoc[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 JavaDoc 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 JavaDoc 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 JavaDoc("Can't handle this type in distinct query result: " + valueType.toString());
629     }
630
631     private static interface DistinctResultXmlSetter {
632         void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml);
633     }
634
635     private static DistinctResultXmlSetter STRING_DISTINCT_SETTER = new DistinctResultXmlSetter() {
636         public void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) {
637             valueXml.setString((String JavaDoc)value);
638         }
639     };
640
641     private static DistinctResultXmlSetter LONG_DISTINCT_SETTER = new DistinctResultXmlSetter() {
642         public void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) {
643             valueXml.setLong(((Long JavaDoc)value).longValue());
644         }
645     };
646
647     private static DistinctResultXmlSetter DECIMAL_DISTINCT_SETTER = new DistinctResultXmlSetter() {
648         public void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) {
649             valueXml.setDecimal((BigDecimal JavaDoc)value);
650         }
651     };
652
653     private static DistinctResultXmlSetter DOUBLE_DISTINCT_SETTER = new DistinctResultXmlSetter() {
654         public void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) {
655             valueXml.setDouble(((Double JavaDoc)value).doubleValue());
656         }
657     };
658
659     private static DistinctResultXmlSetter BOOLEAN_DISTINCT_SETTER = new DistinctResultXmlSetter() {
660         public void setResult(Object JavaDoc value, DistinctSearchResultDocument.DistinctSearchResult.Values.Value valueXml) {
661             valueXml.setBoolean(((Boolean JavaDoc)value).booleanValue());
662         }
663     };
664
665     private static DistinctResultXmlSetter DATE_DISTINCT_SETTER = new DistinctResultXmlSetter() {
666         public void setResult(Object JavaDoc 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 JavaDoc 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 JavaDoc collator;
685
686         public CompareContext(ValueExpr[] orderKeys, SortOrder[] sortOrders, Collator JavaDoc collator) {
687             this.orderKeys = orderKeys;
688             this.sortOrders = sortOrders;
689             this.collator = collator;
690         }
691     }
692
693     private static class DocumentComparable implements Comparable JavaDoc {
694         private final CompareContext context;
695         private final Object JavaDoc[] 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 JavaDoc[context.orderKeys.length];
704             this.lastVersion = lastVersion;
705             this.evaluationContext = evaluationContext;
706         }
707
708         public Comparable JavaDoc 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 JavaDoc(e);
715                 }
716                 try {
717                     orderValues[pos] = context.orderKeys[pos].getOutputValue(document, version, evaluationContext);
718                 } catch (QueryException e) {
719                     throw new RuntimeException JavaDoc(e);
720                 }
721                 if (orderValues[pos] == null)
722                     orderValues[pos] = NULL_VALUE;
723             }
724             return (Comparable JavaDoc)orderValues[pos];
725         }
726
727         public Document getDocument() {
728             return document;
729         }
730
731         public int compareTo(Object JavaDoc o) {
732             DocumentComparable theOtherOne = (DocumentComparable)o;
733             int compareResult;
734             for (int i = 0; i < context.orderKeys.length; i++) {
735                 Object JavaDoc myValue = getValue(i);
736                 Object JavaDoc 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 JavaDoc) {
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 JavaDoc {
763         public int compareTo(Object JavaDoc o) {
764             if (o instanceof NullValue)
765                 return 0;
766             else
767                 return 1;
768         }
769     }
770
771     static class QueryExecutionInfo {
772         public String JavaDoc query;
773         public String JavaDoc 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 JavaDoc 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 JavaDoc userFormat = valueFormatter.format(valueExpr.getOutputValueType(), entries[i].getKey());
830                 String JavaDoc 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 JavaDoc formatQueryValue(QValueType outputValueType, Object JavaDoc value) {
847         if (outputValueType == QValueType.STRING) {
848             return QueryHelper.formatString((String JavaDoc)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 JavaDoc 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 JavaDoc value1, Object JavaDoc value2) {
873             int result;
874             if (value1 instanceof String JavaDoc) {
875                 result = collator.compare(value1, value2);
876             } else if (value1 instanceof Comparable JavaDoc) {
877                 result = ((Comparable JavaDoc)value1).compareTo(value2);
878             } else if (value1 instanceof Boolean JavaDoc) {
879                 boolean bool1 = ((Boolean JavaDoc)value1).booleanValue();
880                 boolean bool2 = ((Boolean JavaDoc)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 JavaDoc("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 JavaDoc o1, Object JavaDoc o2) {
900             Object JavaDoc value1 = ((Map.Entry)o1).getKey();
901             Object JavaDoc 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 JavaDoc o1, Object JavaDoc o2) {
914             Object JavaDoc value1;
915             Object JavaDoc 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 JavaDoc)value1).compareTo(value2);
924         }
925     }
926
927     private static class Counter implements Comparable JavaDoc {
928         private long count = 0;
929
930         public void increment() {
931             count++;
932         }
933
934         public int compareTo(Object JavaDoc 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 JavaDoc 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