KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > search > impl > lucene > LuceneSearcherImpl


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.search.impl.lucene;
18
19 import java.io.IOException JavaDoc;
20 import java.io.Serializable JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.ListIterator JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Set JavaDoc;
28
29 import org.alfresco.repo.search.CannedQueryDef;
30 import org.alfresco.repo.search.EmptyResultSet;
31 import org.alfresco.repo.search.Indexer;
32 import org.alfresco.repo.search.QueryRegisterComponent;
33 import org.alfresco.repo.search.SearcherException;
34 import org.alfresco.repo.search.impl.NodeSearcher;
35 import org.alfresco.service.cmr.dictionary.DictionaryService;
36 import org.alfresco.service.cmr.repository.InvalidNodeRefException;
37 import org.alfresco.service.cmr.repository.NodeRef;
38 import org.alfresco.service.cmr.repository.NodeService;
39 import org.alfresco.service.cmr.repository.Path;
40 import org.alfresco.service.cmr.repository.StoreRef;
41 import org.alfresco.service.cmr.repository.XPathException;
42 import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
43 import org.alfresco.service.cmr.search.QueryParameter;
44 import org.alfresco.service.cmr.search.QueryParameterDefinition;
45 import org.alfresco.service.cmr.search.ResultSet;
46 import org.alfresco.service.cmr.search.SearchParameters;
47 import org.alfresco.service.cmr.search.SearchService;
48 import org.alfresco.service.namespace.NamespacePrefixResolver;
49 import org.alfresco.service.namespace.QName;
50 import org.alfresco.util.ISO9075;
51 import org.alfresco.util.SearchLanguageConversion;
52 import org.apache.lucene.search.Hits;
53 import org.apache.lucene.search.Query;
54 import org.apache.lucene.search.Searcher;
55 import org.apache.lucene.search.Sort;
56 import org.apache.lucene.search.SortField;
57 import org.saxpath.SAXPathException;
58
59 import com.werken.saxpath.XPathReader;
60
61 /**
62  * The Lucene implementation of Searcher At the moment we support only lucene
63  * based queries.
64  *
65  * TODO: Support for other query languages
66  *
67  * @author andyh
68  *
69  */

70 public class LuceneSearcherImpl extends LuceneBase implements LuceneSearcher
71 {
72     /**
73      * Default field name
74      */

75     private static final String JavaDoc DEFAULT_FIELD = "TEXT";
76
77     private NamespacePrefixResolver namespacePrefixResolver;
78
79     private NodeService nodeService;
80
81     private DictionaryService dictionaryService;
82
83     private QueryRegisterComponent queryRegister;
84
85     private LuceneIndexer indexer;
86
87     /*
88      * Searcher implementation
89      */

90
91     /**
92      * Get an initialised searcher for the store and transaction Normally we do
93      * not search against a a store and delta. Currently only gets the searcher
94      * against the main index.
95      *
96      * @param storeRef
97      * @param deltaId
98      * @return
99      */

100     public static LuceneSearcherImpl getSearcher(StoreRef storeRef, LuceneIndexer indexer, LuceneConfig config)
101     {
102         LuceneSearcherImpl searcher = new LuceneSearcherImpl();
103         searcher.setLuceneConfig(config);
104         try
105         {
106             searcher.initialise(storeRef, indexer == null ? null : indexer.getDeltaId(), false, false);
107             searcher.indexer = indexer;
108         }
109         catch (LuceneIndexException e)
110         {
111             throw new SearcherException(e);
112         }
113         return searcher;
114     }
115
116     /**
117      * Get an intialised searcher for the store. No transactional ammendsmends
118      * are searched.
119      *
120      *
121      * @param storeRef
122      * @return
123      */

124     public static LuceneSearcherImpl getSearcher(StoreRef storeRef, LuceneConfig config)
125     {
126         return getSearcher(storeRef, null, config);
127     }
128
129     public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver)
130     {
131         this.namespacePrefixResolver = namespacePrefixResolver;
132     }
133
134     public boolean indexExists()
135     {
136         return mainIndexExists();
137     }
138
139     public void setNodeService(NodeService nodeService)
140     {
141         this.nodeService = nodeService;
142     }
143
144     public void setDictionaryService(DictionaryService dictionaryService)
145     {
146         this.dictionaryService = dictionaryService;
147     }
148
149     public void setQueryRegister(QueryRegisterComponent queryRegister)
150     {
151         this.queryRegister = queryRegister;
152     }
153
154     public ResultSet query(StoreRef store, String JavaDoc language, String JavaDoc queryString, Path[] queryOptions,
155             QueryParameterDefinition[] queryParameterDefinitions) throws SearcherException
156     {
157         SearchParameters sp = new SearchParameters();
158         sp.addStore(store);
159         sp.setLanguage(language);
160         sp.setQuery(queryString);
161         if (queryOptions != null)
162         {
163             for (Path path : queryOptions)
164             {
165                 sp.addAttrbutePath(path);
166             }
167         }
168         if (queryParameterDefinitions != null)
169         {
170             for (QueryParameterDefinition qpd : queryParameterDefinitions)
171             {
172                 sp.addQueryParameterDefinition(qpd);
173             }
174         }
175         sp.excludeDataInTheCurrentTransaction(true);
176
177         return query(sp);
178     }
179
180     public ResultSet query(SearchParameters searchParameters)
181     {
182         if (searchParameters.getStores().size() != 1)
183         {
184             throw new IllegalStateException JavaDoc("Only one store can be searched at present");
185         }
186
187         String JavaDoc parameterisedQueryString;
188         if (searchParameters.getQueryParameterDefinitions().size() > 0)
189         {
190             Map JavaDoc<QName, QueryParameterDefinition> map = new HashMap JavaDoc<QName, QueryParameterDefinition>();
191
192             for (QueryParameterDefinition qpd : searchParameters.getQueryParameterDefinitions())
193             {
194                 map.put(qpd.getQName(), qpd);
195             }
196
197             parameterisedQueryString = parameterise(searchParameters.getQuery(), map, null, namespacePrefixResolver);
198         }
199         else
200         {
201             parameterisedQueryString = searchParameters.getQuery();
202         }
203
204         if (searchParameters.getLanguage().equalsIgnoreCase(SearchService.LANGUAGE_LUCENE))
205         {
206             try
207             {
208
209                 int defaultOperator;
210                 if (searchParameters.getDefaultOperator() == SearchParameters.AND)
211                 {
212                     defaultOperator = LuceneQueryParser.DEFAULT_OPERATOR_AND;
213                 }
214                 else
215                 {
216                     defaultOperator = LuceneQueryParser.DEFAULT_OPERATOR_OR;
217                 }
218
219                 Query query = LuceneQueryParser.parse(parameterisedQueryString, DEFAULT_FIELD, new LuceneAnalyser(
220                         dictionaryService), namespacePrefixResolver, dictionaryService, defaultOperator);
221                 Searcher searcher = getSearcher(indexer);
222                 if (searcher == null)
223                 {
224                     // no index return an empty result set
225
return new EmptyResultSet();
226                 }
227
228                 Hits hits;
229
230                 if (searchParameters.getSortDefinitions().size() > 0)
231                 {
232                     int index = 0;
233                     SortField[] fields = new SortField[searchParameters.getSortDefinitions().size()];
234                     for (SearchParameters.SortDefinition sd : searchParameters.getSortDefinitions())
235                     {
236                         switch (sd.getSortType())
237                         {
238                         case FIELD:
239                             fields[index++] = new SortField(sd.getField(), !sd.isAscending());
240                             break;
241                         case DOCUMENT:
242                             fields[index++] = new SortField(null, SortField.DOC, !sd.isAscending());
243                             break;
244                         case SCORE:
245                             fields[index++] = new SortField(null, SortField.SCORE, !sd.isAscending());
246                             break;
247                         }
248
249                     }
250                     hits = searcher.search(query, new Sort(fields));
251                 }
252                 else
253                 {
254                     hits = searcher.search(query);
255                 }
256
257                 return new LuceneResultSet(hits, searcher, nodeService, searchParameters.getAttributePaths().toArray(
258                         new Path[0]), searchParameters);
259
260             }
261             catch (ParseException e)
262             {
263                 throw new SearcherException("Failed to parse query: " + parameterisedQueryString, e);
264             }
265             catch (IOException JavaDoc e)
266             {
267                 throw new SearcherException("IO exception during search", e);
268             }
269         }
270         else if (searchParameters.getLanguage().equalsIgnoreCase(SearchService.LANGUAGE_XPATH))
271         {
272             try
273             {
274                 XPathReader reader = new XPathReader();
275                 LuceneXPathHandler handler = new LuceneXPathHandler();
276                 handler.setNamespacePrefixResolver(namespacePrefixResolver);
277                 handler.setDictionaryService(dictionaryService);
278                 // TODO: Handler should have the query parameters to use in
279
// building its lucene query
280
// At the moment xpath style parameters in the PATH
281
// expression are not supported.
282
reader.setXPathHandler(handler);
283                 reader.parse(parameterisedQueryString);
284                 Query query = handler.getQuery();
285                 Searcher searcher = getSearcher(null);
286                 if (searcher == null)
287                 {
288                     // no index return an empty result set
289
return new EmptyResultSet();
290                 }
291                 Hits hits = searcher.search(query);
292                 return new LuceneResultSet(hits, searcher, nodeService, searchParameters.getAttributePaths().toArray(
293                         new Path[0]), searchParameters);
294             }
295             catch (SAXPathException e)
296             {
297                 throw new SearcherException("Failed to parse query: " + searchParameters.getQuery(), e);
298             }
299             catch (IOException JavaDoc e)
300             {
301                 throw new SearcherException("IO exception during search", e);
302             }
303         }
304         else
305         {
306             throw new SearcherException("Unknown query language: " + searchParameters.getLanguage());
307         }
308     }
309
310     public ResultSet query(StoreRef store, String JavaDoc language, String JavaDoc query)
311     {
312         return query(store, language, query, null, null);
313     }
314
315     public ResultSet query(StoreRef store, String JavaDoc language, String JavaDoc query,
316             QueryParameterDefinition[] queryParameterDefintions)
317     {
318         return query(store, language, query, null, queryParameterDefintions);
319     }
320
321     public ResultSet query(StoreRef store, String JavaDoc language, String JavaDoc query, Path[] attributePaths)
322     {
323         return query(store, language, query, attributePaths, null);
324     }
325
326     public ResultSet query(StoreRef store, QName queryId, QueryParameter[] queryParameters)
327     {
328         CannedQueryDef definition = queryRegister.getQueryDefinition(queryId);
329
330         // Do parameter replacement
331
// As lucene phrases are tokensied it is correct to just do straight
332
// string replacement.
333
// The string will be formatted by the tokeniser.
334
//
335
// For non phrase queries this is incorrect but string replacement is
336
// probably the best we can do.
337
// As numbers and text are indexed specially, direct term queries only
338
// make sense against textual data
339

340         checkParameters(definition, queryParameters);
341
342         String JavaDoc queryString = parameterise(definition.getQuery(), definition.getQueryParameterMap(), queryParameters,
343                 definition.getNamespacePrefixResolver());
344
345         return query(store, definition.getLanguage(), queryString, null, null);
346     }
347
348     /**
349      * The definitions must provide a default value, or of not there must be a
350      * parameter to provide the value
351      *
352      * @param definition
353      * @param queryParameters
354      * @throws QueryParameterisationException
355      */

356     private void checkParameters(CannedQueryDef definition, QueryParameter[] queryParameters)
357             throws QueryParameterisationException
358     {
359         List JavaDoc<QName> missing = new ArrayList JavaDoc<QName>();
360
361         Set JavaDoc<QName> parameterQNameSet = new HashSet JavaDoc<QName>();
362         if (queryParameters != null)
363         {
364             for (QueryParameter parameter : queryParameters)
365             {
366                 parameterQNameSet.add(parameter.getQName());
367             }
368         }
369
370         for (QueryParameterDefinition parameterDefinition : definition.getQueryParameterDefs())
371         {
372             if (!parameterDefinition.hasDefaultValue())
373             {
374                 if (!parameterQNameSet.contains(parameterDefinition.getQName()))
375                 {
376                     missing.add(parameterDefinition.getQName());
377                 }
378             }
379         }
380
381         if (missing.size() > 0)
382         {
383             StringBuilder JavaDoc buffer = new StringBuilder JavaDoc(128);
384             buffer.append("The query is missing values for the following parameters: ");
385             for (QName qName : missing)
386             {
387                 buffer.append(qName);
388                 buffer.append(", ");
389             }
390             buffer.delete(buffer.length() - 1, buffer.length() - 1);
391             buffer.delete(buffer.length() - 1, buffer.length() - 1);
392             throw new QueryParameterisationException(buffer.toString());
393         }
394     }
395
396     /*
397      * Parameterise the query string - not sure if it is required to escape
398      * lucence spacials chars The parameters could be used to build the query -
399      * the contents of parameters should alread have been escaped if required.
400      * ... mush better to provide the parameters and work out what to do TODO:
401      * conditional query escapement - may be we should have a parameter type
402      * that is not escaped
403      */

404     private String JavaDoc parameterise(String JavaDoc unparameterised, Map JavaDoc<QName, QueryParameterDefinition> map,
405             QueryParameter[] queryParameters, NamespacePrefixResolver nspr) throws QueryParameterisationException
406     {
407
408         Map JavaDoc<QName, List JavaDoc<Serializable JavaDoc>> valueMap = new HashMap JavaDoc<QName, List JavaDoc<Serializable JavaDoc>>();
409
410         if (queryParameters != null)
411         {
412             for (QueryParameter parameter : queryParameters)
413             {
414                 List JavaDoc<Serializable JavaDoc> list = valueMap.get(parameter.getQName());
415                 if (list == null)
416                 {
417                     list = new ArrayList JavaDoc<Serializable JavaDoc>();
418                     valueMap.put(parameter.getQName(), list);
419                 }
420                 list.add(parameter.getValue());
421             }
422         }
423
424         Map JavaDoc<QName, ListIterator JavaDoc<Serializable JavaDoc>> iteratorMap = new HashMap JavaDoc<QName, ListIterator JavaDoc<Serializable JavaDoc>>();
425
426         List JavaDoc<QName> missing = new ArrayList JavaDoc<QName>(1);
427         StringBuilder JavaDoc buffer = new StringBuilder JavaDoc(unparameterised);
428         int index = 0;
429         while ((index = buffer.indexOf("${", index)) != -1)
430         {
431             int endIndex = buffer.indexOf("}", index);
432             String JavaDoc qNameString = buffer.substring(index + 2, endIndex);
433             QName key = QName.createQName(qNameString, nspr);
434             QueryParameterDefinition parameterDefinition = map.get(key);
435             if (parameterDefinition == null)
436             {
437                 missing.add(key);
438                 buffer.replace(index, endIndex + 1, "");
439             }
440             else
441             {
442                 ListIterator JavaDoc<Serializable JavaDoc> it = iteratorMap.get(key);
443                 if ((it == null) || (!it.hasNext()))
444                 {
445                     List JavaDoc<Serializable JavaDoc> list = valueMap.get(key);
446                     if ((list != null) && (list.size() > 0))
447                     {
448                         it = list.listIterator();
449                     }
450                     if (it != null)
451                     {
452                         iteratorMap.put(key, it);
453                     }
454                 }
455                 String JavaDoc value;
456                 if (it == null)
457                 {
458                     value = parameterDefinition.getDefault();
459                 }
460                 else
461                 {
462                     value = DefaultTypeConverter.INSTANCE.convert(String JavaDoc.class, it.next());
463                 }
464                 buffer.replace(index, endIndex + 1, value);
465             }
466         }
467         if (missing.size() > 0)
468         {
469             StringBuilder JavaDoc error = new StringBuilder JavaDoc();
470             error.append("The query uses the following parameters which are not defined: ");
471             for (QName qName : missing)
472             {
473                 error.append(qName);
474                 error.append(", ");
475             }
476             error.delete(error.length() - 1, error.length() - 1);
477             error.delete(error.length() - 1, error.length() - 1);
478             throw new QueryParameterisationException(error.toString());
479         }
480         return buffer.toString();
481     }
482
483     /**
484      * @see org.alfresco.repo.search.impl.NodeSearcher
485      */

486     public List JavaDoc<NodeRef> selectNodes(NodeRef contextNodeRef, String JavaDoc xpath, QueryParameterDefinition[] parameters,
487             NamespacePrefixResolver namespacePrefixResolver, boolean followAllParentLinks, String JavaDoc language)
488             throws InvalidNodeRefException, XPathException
489     {
490         NodeSearcher nodeSearcher = new NodeSearcher(nodeService, dictionaryService, this);
491         return nodeSearcher.selectNodes(contextNodeRef, xpath, parameters, namespacePrefixResolver,
492                 followAllParentLinks, language);
493     }
494
495     /**
496      * @see org.alfresco.repo.search.impl.NodeSearcher
497      */

498     public List JavaDoc<Serializable JavaDoc> selectProperties(NodeRef contextNodeRef, String JavaDoc xpath,
499             QueryParameterDefinition[] parameters, NamespacePrefixResolver namespacePrefixResolver,
500             boolean followAllParentLinks, String JavaDoc language) throws InvalidNodeRefException, XPathException
501     {
502         NodeSearcher nodeSearcher = new NodeSearcher(nodeService, dictionaryService, this);
503         return nodeSearcher.selectProperties(contextNodeRef, xpath, parameters, namespacePrefixResolver,
504                 followAllParentLinks, language);
505     }
506
507     /**
508      * @return Returns true if the pattern is present, otherwise false.
509      */

510     public boolean contains(NodeRef nodeRef, QName propertyQName, String JavaDoc googleLikePattern)
511     {
512         return contains(nodeRef, propertyQName, googleLikePattern, SearchParameters.Operator.OR);
513     }
514
515     /**
516      * @return Returns true if the pattern is present, otherwise false.
517      */

518     public boolean contains(NodeRef nodeRef, QName propertyQName, String JavaDoc googleLikePattern,
519             SearchParameters.Operator defaultOperator)
520     {
521         ResultSet resultSet = null;
522         try
523         {
524             // build Lucene search string specific to the node
525
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
526             sb.append("+ID:\"").append(nodeRef.toString()).append("\" +(TEXT:(")
527                     .append(googleLikePattern.toLowerCase()).append(") ");
528             if (propertyQName != null)
529             {
530                 sb.append(" OR @").append(
531                         LuceneQueryParser.escape(QName.createQName(propertyQName.getNamespaceURI(),
532                                 ISO9075.encode(propertyQName.getLocalName())).toString()));
533                 sb.append(":(").append(googleLikePattern.toLowerCase()).append(")");
534             }
535             else
536             {
537                 for (QName key : nodeService.getProperties(nodeRef).keySet())
538                 {
539                     sb.append(" OR @").append(
540                             LuceneQueryParser.escape(QName.createQName(key.getNamespaceURI(),
541                                     ISO9075.encode(key.getLocalName())).toString()));
542                     sb.append(":(").append(googleLikePattern.toLowerCase()).append(")");
543                 }
544             }
545             sb.append(")");
546
547             SearchParameters sp = new SearchParameters();
548             sp.setLanguage(SearchService.LANGUAGE_LUCENE);
549             sp.setQuery(sb.toString());
550             sp.setDefaultOperator(defaultOperator);
551             sp.addStore(nodeRef.getStoreRef());
552
553             resultSet = this.query(sp);
554             boolean answer = resultSet.length() > 0;
555             return answer;
556         }
557         finally
558         {
559             if (resultSet != null)
560             {
561                 resultSet.close();
562             }
563         }
564     }
565
566     /**
567      * @return Returns true if the pattern is present, otherwise false.
568      *
569      * @see #setIndexer(Indexer)
570      * @see #setSearcher(SearchService)
571      */

572     public boolean like(NodeRef nodeRef, QName propertyQName, String JavaDoc sqlLikePattern, boolean includeFTS)
573     {
574         if (propertyQName == null)
575         {
576             throw new IllegalArgumentException JavaDoc("Property QName is mandatory for the like expression");
577         }
578
579         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(sqlLikePattern.length() * 3);
580
581         if (includeFTS)
582         {
583             // convert the SQL-like pattern into a Lucene-compatible string
584
String JavaDoc pattern = SearchLanguageConversion.convertXPathLikeToLucene(sqlLikePattern.toLowerCase());
585
586             // build Lucene search string specific to the node
587
sb = new StringBuilder JavaDoc();
588             sb.append("+ID:\"").append(nodeRef.toString()).append("\" +(");
589             // FTS or attribute matches
590
if (includeFTS)
591             {
592                 sb.append("TEXT:(").append(pattern).append(") ");
593             }
594             if (propertyQName != null)
595             {
596                 sb.append(" @").append(
597                         LuceneQueryParser.escape(QName.createQName(propertyQName.getNamespaceURI(),
598                                 ISO9075.encode(propertyQName.getLocalName())).toString())).append(":(").append(pattern)
599                         .append(")");
600             }
601             sb.append(")");
602
603             ResultSet resultSet = null;
604             try
605             {
606                 resultSet = this.query(nodeRef.getStoreRef(), "lucene", sb.toString());
607                 boolean answer = resultSet.length() > 0;
608                 return answer;
609             }
610             finally
611             {
612                 if (resultSet != null)
613                 {
614                     resultSet.close();
615                 }
616             }
617         }
618         else
619         {
620             // convert the SQL-like pattern into a Lucene-compatible string
621
String JavaDoc pattern = SearchLanguageConversion.convertXPathLikeToRegex(sqlLikePattern.toLowerCase());
622
623             Serializable JavaDoc property = nodeService.getProperty(nodeRef, propertyQName);
624             if (property == null)
625             {
626                 return false;
627             }
628             else
629             {
630                 String JavaDoc propertyString = DefaultTypeConverter.INSTANCE.convert(String JavaDoc.class, nodeService.getProperty(
631                         nodeRef, propertyQName));
632                 return propertyString.toLowerCase().matches(pattern);
633             }
634         }
635     }
636
637     public List JavaDoc<NodeRef> selectNodes(NodeRef contextNodeRef, String JavaDoc xpath, QueryParameterDefinition[] parameters,
638             NamespacePrefixResolver namespacePrefixResolver, boolean followAllParentLinks)
639             throws InvalidNodeRefException, XPathException
640     {
641         return selectNodes(contextNodeRef, xpath, parameters, namespacePrefixResolver, followAllParentLinks,
642                 SearchService.LANGUAGE_XPATH);
643     }
644
645     public List JavaDoc<Serializable JavaDoc> selectProperties(NodeRef contextNodeRef, String JavaDoc xpath,
646             QueryParameterDefinition[] parameters, NamespacePrefixResolver namespacePrefixResolver,
647             boolean followAllParentLinks) throws InvalidNodeRefException, XPathException
648     {
649         return selectProperties(contextNodeRef, xpath, parameters, namespacePrefixResolver, followAllParentLinks,
650                 SearchService.LANGUAGE_XPATH);
651     }
652 }
653
Popular Tags