1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.cocoon.generation;
18
19 import org.apache.avalon.framework.context.Context;
20 import org.apache.avalon.framework.context.ContextException;
21 import org.apache.avalon.framework.context.Contextualizable;
22 import org.apache.avalon.framework.parameters.Parameters;
23 import org.apache.avalon.framework.service.ServiceException;
24
25 import org.apache.cocoon.Constants;
26 import org.apache.cocoon.ProcessingException;
27 import org.apache.cocoon.components.search.LuceneCocoonHelper;
28 import org.apache.cocoon.components.search.LuceneCocoonPager;
29 import org.apache.cocoon.components.search.LuceneCocoonSearcher;
30 import org.apache.cocoon.components.search.LuceneXMLIndexer;
31 import org.apache.cocoon.environment.ObjectModelHelper;
32 import org.apache.cocoon.environment.Request;
33 import org.apache.cocoon.environment.SourceResolver;
34
35 import org.apache.lucene.analysis.Analyzer;
36 import org.apache.lucene.document.Document;
37 import org.apache.lucene.document.Field;
38 import org.apache.lucene.search.Hits;
39 import org.apache.lucene.store.Directory;
40
41 import org.xml.sax.SAXException;
42 import org.xml.sax.helpers.AttributesImpl;
43
44 import java.io.File;
45 import java.io.IOException;
46 import java.util.Enumeration;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Map;
50
51 /**
52 * Generates an XML representation of a search result.
53 *
54 * <p>
55 * This generator generates xml content representening an XML search.
56 * The generated xml content contains the search result,
57 * the search query information, and navigation information about the
58 * search results.
59 * The query is sent to the generator, either via the 'queryString' request parameter
60 * or the 'query' SiteMap parameter. The sitemap overides the request.
61 * </p>
62 *
63 * <p>
64 * Search xml sample generated by this generator:
65 * </p>
66 * <pre><tt>
67 * <?xml version="1.0" encoding="UTF-8"?>
68 *
69 * <search:results date="1008437081064" query-string="cocoon"
70 * start-index="0" page-length="10"
71 * xmlns:search="http://apache.org/cocoon/search/1.0"
72 * xmlns:xlink="http://www.w3.org/1999/xlink">
73 * <search:hits total-count="125" count-of-pages="13">
74 * <search:hit rank="0" score="1.0"
75 * uri="http://localhost:8080/cocoon/documents/hosting.html">
76 * <search:field name="title">Document Title<search:field/>
77 * <search:hit/>
78 * ...
79 * </search:hits>
80 *
81 * <search:navigation total-count="125" count-of-pages="13"
82 * has-next="true" has-previous="false" next-index="10" previous-index="0">
83 * <search:navigation-page start-index="0"/>
84 * <search:navigation-page start-index="10"/>
85 * ...
86 * <search:navigation-page start-index="120"/>
87 * </search:navigation>
88 * </search:results>
89 * </tt></pre>
90 *
91 * @author <a href="mailto:berni_huber@a1.net">Bernhard Huber</a>
92 * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
93 * @author <a href="mailto:jeremy@apache.org">Jeremy Quinn</a>
94 * @author <a href="mailto:conal@nzetc.org">Conal Tuohy</a>
95 * @version CVS $Id: SearchGenerator.java 465914 2006-10-19 22:38:41Z joerg $
96 */
97 public class SearchGenerator extends ServiceableGenerator implements Contextualizable {
98
99 /**
100 * The XML namespace for the output document.
101 */
102 protected final static String NAMESPACE = "http://apache.org/cocoon/search/1.0";
103
104 /**
105 * The XML namespace prefix for the output document.
106 */
107 protected final static String PREFIX = "search";
108
109 /**
110 * The XML namespace for xlink
111 */
112 protected final static String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
113
114 /**
115 * Description of the Field
116 */
117 protected final static String CDATA = "CDATA";
118
119 /**
120 * Name of root element of generated xml content, ie <code>results</code>.
121 */
122 protected final static String RESULTS_ELEMENT = "results";
123
124 /**
125 * Qualified name of root element of generated xml content, ie <code>search:results</code>.
126 */
127 protected final static String Q_RESULTS_ELEMENT = PREFIX + ":" + RESULTS_ELEMENT;
128
129 /**
130 * Attribute <code>date</code> of <code>results</code> element.
131 * It contains the date a long value, indicating when a search
132 * generated this xml content.
133 */
134 protected final static String DATE_ATTRIBUTE = "date";
135
136 /**
137 * Attribute <code>query-string</code> of <code>results</code> element.
138 * Echos the <code>queryString</code> query parameter.
139 */
140 protected final static String QUERY_STRING_ATTRIBUTE = "query-string";
141
142 /**
143 * Attribute <code>start-index</code> of <code>results</code> element.
144 * Echoes the <code>startIndex</code> query parameter.
145 */
146 protected final static String START_INDEX_ATTRIBUTE = "start-index";
147
148 /**
149 * Attribute <code>page-length</code> of <code>results</code> element.
150 * Echoes the <code>pageLenth</code> query parameter.
151 */
152 protected final static String PAGE_LENGTH_ATTRIBUTE = "page-length";
153
154 /**
155 * Attribute <code>name</code> of <code>hit</code> element.
156 */
157 protected final static String NAME_ATTRIBUTE = "name";
158
159 /**
160 * Child element of generated xml content, ie <code>hits</code>.
161 * This element describes all hits.
162 */
163 protected final static String HITS_ELEMENT = "hits";
164
165 /**
166 * QName of child element of generated xml content, ie <code>search:hits</code>.
167 * This element describes all hits.
168 */
169 protected final static String Q_HITS_ELEMENT = PREFIX + ":" + HITS_ELEMENT;
170
171 /**
172 * Attribute <code>total-count</code> of <code>hits</code> element.
173 * The value describes total number of hits found by the search engine.
174 */
175 protected final static String TOTAL_COUNT_ATTRIBUTE = "total-count";
176
177 /**
178 * Attribute <code>count-of-pages</code> of <code>hits</code> element.
179 * The value describes number of pages needed for all hits.
180 */
181 protected final static String COUNT_OF_PAGES_ATTRIBUTE = "count-of-pages";
182
183 /**
184 * Child element of generated xml content, ie <code>hit</code>.
185 * This element describes a single hit.
186 */
187 protected final static String HIT_ELEMENT = "hit";
188
189 /**
190 * QName of child element of generated xml content, ie <code>search:hit</code>.
191 * This element describes a single hit.
192 */
193 protected final static String Q_HIT_ELEMENT = PREFIX + ":" + HIT_ELEMENT;
194
195 /**
196 * Attribute <code>rank</code> of <code>hit</code> element.
197 * The value describes the count index of this hits, ranging between 0, and
198 * total-count minus 1.
199 */
200 protected final static String RANK_ATTRIBUTE = "rank";
201
202 /**
203 * Attribute <code>score</code> of <code>hit</code> element.
204 * The value describes the score of this hits, ranging between 0, and 1.0.
205 */
206 protected final static String SCORE_ATTRIBUTE = "score";
207
208 /**
209 * Attribute <code>uri</code> of <code>hit</code> element.
210 * The value describes the uri of a document matching the search query.
211 */
212 protected final static String URI_ATTRIBUTE = "uri";
213
214 /**
215 * Child element <code>field</code> of the <code>hit</code> element.
216 * This element contains value of the stored field of a hit.
217 */
218 protected final static String FIELD_ELEMENT = "field";
219
220 /**
221 * QName of child element <code>search:field</code> of the <code>hit</code> element.
222 */
223 protected final static String Q_FIELD_ELEMENT = PREFIX + ":" + FIELD_ELEMENT;
224
225 /**
226 * Child element of generated xml content, ie <code>navigation</code>.
227 * This element describes some hints for easier navigation.
228 */
229 protected final static String NAVIGATION_ELEMENT = "navigation";
230
231 /**
232 * QName of child element of generated xml content, ie <code>search:navigation</code>.
233 */
234 protected final static String Q_NAVIGATION_ELEMENT = PREFIX + ":" + NAVIGATION_ELEMENT;
235
236 /**
237 * Child element of generated xml content, ie <code>navigation-page</code>.
238 * This element describes the start-index of page containing hits.
239 */
240 protected final static String NAVIGATION_PAGE_ELEMENT = "navigation-page";
241
242 /**
243 * QName of child element of generated xml content, ie <code>search:navigation-page</code>.
244 * This element describes the start-index of page containing hits.
245 */
246 protected final static String Q_NAVIGATION_PAGE_ELEMENT = PREFIX + ":" + NAVIGATION_PAGE_ELEMENT;
247
248 /**
249 * Attribute <code>has-next</code> of <code>navigation-page</code> element.
250 * The value is true if a next navigation control should be presented.
251 */
252 protected final static String HAS_NEXT_ATTRIBUTE = "has-next";
253
254 /**
255 * Attribute <code>has-next</code> of <code>navigation-page</code> element.
256 * The value is true if a previous navigation control should be presented.
257 */
258 protected final static String HAS_PREVIOUS_ATTRIBUTE = "has-previous";
259
260 /**
261 * Attribute <code>next-index</code> of <code>navigation-page</code> element.
262 * The value describes the start-index of the next-to-be-presented page.
263 */
264 protected final static String NEXT_INDEX_ATTRIBUTE = "next-index";
265
266 /**
267 * Attribute <code>previous-index</code> of <code>navigation-page</code> element.
268 * The value describes the start-index of the previous-to-be-presented page.
269 */
270 protected final static String PREVIOUS_INDEX_ATTRIBUTE = "previous-index";
271
272 /**
273 * Setup parameter name of index directory, ie <code>index</code>.
274 */
275 protected final static String INDEX_PARAM = "index";
276
277 /**
278 * Default value of setup parameter <code>index</code>, ie <code>index</code>.
279 */
280 protected final static String INDEX_PARAM_DEFAULT = "index";
281
282 /**
283 * Setup parameter name of analyzer name, ie <code>analyzer</code>.
284 */
285 protected final static String ANALYZER_PARAM = "analyzer";
286
287 /**
288 * Default value of analyzer parameter <code>analyzer</code>, ie <code>org.apache.lucene.analysis.standard.StandardAnalyzer</code>.
289 */
290 protected final static String ANALYZER_PARAM_DEFAULT = "org.apache.lucene.analysis.standard.StandardAnalyzer";
291
292 /**
293 * Setup the actual query from generator parameter,
294 * ie <code>query</code>.
295 */
296 protected final static String QUERY_PARAM = "query";
297
298 /**
299 * Setup parameter name specifying the name of query-string query parameter,
300 * ie <code>query-string</code>.
301 */
302 protected final static String QUERY_STRING_PARAM = "query-string";
303
304 /**
305 * Default value of setup parameter <code>query-string</code>, ie <code>queryString</code>.
306 */
307 protected final static String QUERY_STRING_PARAM_DEFAULT = "queryString";
308
309 /**
310 * Setup parameter name specifying the name of start-index query parameter,
311 * ie <code>start-index</code>.
312 */
313 protected final static String START_INDEX_PARAM = "start-index";
314
315 /**
316 * Default value of setup parameter <code>start-index</code>, ie <code>startIndex</code>.
317 */
318 protected final static String START_INDEX_PARAM_DEFAULT = "startIndex";
319
320 /**
321 * Setup parameter name specifying the name of start-next-index query parameter,
322 * ie <code>start-next-index</code>.
323 */
324 protected final static String START_INDEX_NEXT_PARAM = "start-next-index";
325
326 /**
327 * Default value of setup parameter <code>start-next-index</code>, ie <code>startNextIndex</code>.
328 */
329 protected final static String START_INDEX_NEXT_PARAM_DEFAULT = "startNextIndex";
330
331 /**
332 * Setup parameter name specifying the name of start-previous-index query parameter,
333 * ie <code>start-previous-index</code>.
334 */
335 protected final static String START_INDEX_PREVIOUS_PARAM = "start-previous-index";
336
337 /**
338 * Default value of setup parameter <code>start-previous-index</code>, ie <code>startPreviousIndex</code>.
339 */
340 protected final static String START_INDEX_PREVIOUS_PARAM_DEFAULT = "startPreviousIndex";
341
342 protected final static int START_INDEX_DEFAULT = 0;
343
344 /**
345 * Setup parameter name specifying the name of page-length query parameter,
346 * ie <code>page-length</code>.
347 */
348 protected final static String PAGE_LENGTH_PARAM = "page-length";
349
350 protected final static String PAGE_LENGTH_PARAM_DEFAULT = "pageLength";
351
352 protected final static int PAGE_LENGTH_DEFAULT = 10;
353
354 /**
355 * Default home directory of index directories.
356 * <p>
357 * Releative index directories specified in the setup of this generator are resolved
358 * relative to this directory.
359 * </p>
360 * <p>
361 * By default this directory is set to the <code>WORKING_DIR</code> of Cocoon.
362 * </p>
363 */
364 private File workDir = null;
365
366 /**
367 * The avalon component to use for searching.
368 */
369 private LuceneCocoonSearcher lcs;
370
371 /**
372 * Analyzer used for searching
373 */
374 private String analyzer = null;
375
376 /**
377 * Absolute filesystem directory of lucene index directory
378 */
379 private File index = null;
380
381 /**
382 * Query-string to search for
383 */
384 private String queryString = "";
385
386 /**
387 * Attributes used when generating xml content.
388 */
389 private final AttributesImpl atts = new AttributesImpl();
390
391 /**
392 * startIndex of query parameter
393 */
394 private Integer startIndex = null;
395
396 /**
397 * pageLength of query parameter
398 */
399 private Integer pageLength = null;
400
401 /**
402 * Contextualize this class.
403 *
404 * <p>
405 * Especially retrieve the work directory.
406 * If the index directory is specified relativly, the working directory is
407 * used as home directory of the index directory.
408 * </p>
409 *
410 * @param context Context to use
411 * @exception ContextException If contextualizing fails.
412 */
413 public void contextualize(Context context) throws ContextException {
414 // retrieve the working directory, assuming that the index may reside there
415 workDir = (File) context.get(Constants.CONTEXT_WORK_DIR);
416 }
417
418 // TODO: parameterize()
419
420 /**
421 * setup all members of this generator.
422 */
423 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
424 throws ProcessingException, SAXException, IOException {
425 super.setup(resolver, objectModel, src, par);
426
427 try {
428 lcs = (LuceneCocoonSearcher) this.manager.lookup(LuceneCocoonSearcher.ROLE);
429 } catch (ServiceException e) {
430 throw new ProcessingException("Unable to lookup " + LuceneCocoonSearcher.ROLE, e);
431 }
432
433 String param_name;
434 Request request = ObjectModelHelper.getRequest(objectModel);
435
436 String index_file_name = par.getParameter(INDEX_PARAM, INDEX_PARAM_DEFAULT);
437 if (request.getParameter(INDEX_PARAM) != null) {
438 index_file_name = request.getParameter(INDEX_PARAM);
439 }
440
441 // now set the index
442 index = new File(index_file_name);
443 if (!index.isAbsolute()) {
444 index = new File(workDir, index.toString());
445 }
446
447 // try to get the analyzer from the sitemap parameter
448 this.analyzer = par.getParameter(ANALYZER_PARAM, ANALYZER_PARAM_DEFAULT);
449 if (getLogger().isDebugEnabled()) {
450 getLogger().debug("Analyzer is set to: " + this.analyzer);
451 }
452
453 // try getting the queryString from the generator sitemap params
454
455 queryString = par.getParameter(QUERY_PARAM, "");
456
457 // try getting the queryString from the request params
458 if (queryString.length() == 0) {
459 param_name = par.getParameter(QUERY_STRING_PARAM, QUERY_STRING_PARAM_DEFAULT);
460 if (request.getParameter(param_name) != null) {
461 queryString = request.getParameter(param_name);
462 }
463 }
464 if (getLogger().isDebugEnabled()) {
465 getLogger().debug("Search index with query: " + queryString);
466 }
467
468 // always try lookup the start index from the request params
469 // get startIndex
470 startIndex = null;
471 param_name = par.getParameter(START_INDEX_NEXT_PARAM, START_INDEX_NEXT_PARAM_DEFAULT);
472 if (request.getParameter(param_name) != null) {
473 startIndex = createInteger(request.getParameter(param_name));
474 }
475
476 if (startIndex == null) {
477 param_name = par.getParameter(START_INDEX_PREVIOUS_PARAM, START_INDEX_PREVIOUS_PARAM_DEFAULT);
478 if (request.getParameter(param_name) != null) {
479 startIndex = createInteger(request.getParameter(param_name));
480 }
481 }
482 if (startIndex == null) {
483 param_name = par.getParameter(START_INDEX_PARAM, START_INDEX_PARAM_DEFAULT);
484 if (request.getParameter(param_name) != null) {
485 startIndex = createInteger(request.getParameter(param_name));
486 }
487 }
488
489 // get pageLength
490 param_name = par.getParameter(PAGE_LENGTH_PARAM, PAGE_LENGTH_PARAM_DEFAULT);
491 if (request.getParameter(param_name) != null) {
492 pageLength = createInteger(request.getParameter(param_name));
493 }
494 }
495
496 /**
497 * Generate xml content describing search results.
498 * Entry point of the ComposerGenerator.
499 * The xml content is generated from the hits object.
500 *
501 * @exception IOException when there is a problem reading the from file system.
502 * @throws SAXException when there is a problem creating the output SAX events.
503 * @throws ProcessingException when there is a problem obtaining the hits
504 */
505 public void generate() throws IOException, SAXException, ProcessingException {
506 // set default parameter value, in case of no values are set yet.
507 if (startIndex == null) {
508 startIndex = new Integer(START_INDEX_DEFAULT);
509 }
510 if (pageLength == null) {
511 pageLength = new Integer(PAGE_LENGTH_DEFAULT);
512 }
513
514 // Start the document and set the namespace.
515 this.contentHandler.startDocument();
516 this.contentHandler.startPrefixMapping(PREFIX, NAMESPACE);
517 this.contentHandler.startPrefixMapping("xlink", XLINK_NAMESPACE);
518
519 generateResults();
520
521 // End the document.
522 this.contentHandler.endPrefixMapping("xlink");
523 this.contentHandler.endPrefixMapping(PREFIX);
524 this.contentHandler.endDocument();
525 }
526
527 /**
528 * Create an Integer.
529 * <p>
530 * Create an Integer from String s, if conversion fails return null.
531 * </p>
532 *
533 * @param s Converting s to an Integer
534 * @return Integer converted value originating from s, or null
535 */
536 private Integer createInteger(String s) {
537 Integer i = null;
538 try {
539 i = new Integer(s);
540 } catch (NumberFormatException nfe) {
541 // ignore it, write only warning
542 if (getLogger().isWarnEnabled()) {
543 getLogger().warn("Cannot convert " + s + " to Integer", nfe);
544 }
545 }
546 return i;
547 }
548
549 /**
550 * Build and generate the search results.
551 * <p>
552 * First build the hits, next generate xml content from the hits,
553 * taking page index, and length into account.
554 * </p>
555 *
556 * @throws SAXException when there is a problem creating the output SAX events.
557 * @throws ProcessingException when there is a problem obtaining the hits
558 */
559 private void generateResults() throws SAXException, ProcessingException, IOException {
560 // Make the hits
561 LuceneCocoonPager pager = buildHits();
562
563 // The current date and time.
564 long time = System.currentTimeMillis();
565
566 atts.clear();
567 atts.addAttribute("", DATE_ATTRIBUTE, DATE_ATTRIBUTE, CDATA, String.valueOf(time));
568 if (queryString != null && queryString.length() > 0)
569 atts.addAttribute("", QUERY_STRING_ATTRIBUTE, QUERY_STRING_ATTRIBUTE, CDATA, String.valueOf(queryString));
570 atts.addAttribute("", START_INDEX_ATTRIBUTE, START_INDEX_ATTRIBUTE, CDATA, String.valueOf(startIndex));
571 atts.addAttribute("", PAGE_LENGTH_ATTRIBUTE, PAGE_LENGTH_ATTRIBUTE, CDATA, String.valueOf(pageLength));
572
573 contentHandler.startElement(NAMESPACE, RESULTS_ELEMENT, Q_RESULTS_ELEMENT, atts);
574
575 // build xml from the hits
576 generateHits(pager);
577 generateNavigation(pager);
578
579 // End root element.
580 contentHandler.endElement(NAMESPACE, RESULTS_ELEMENT, Q_RESULTS_ELEMENT);
581 }
582
583 /**
584 * Generate the xml content of all hits
585 *
586 * @param pager the LuceneContentPager with the search results
587 * @throws SAXException when there is a problem creating the output SAX events.
588 */
589 private void generateHits(LuceneCocoonPager pager) throws SAXException {
590 if (pager != null) {
591 atts.clear();
592 atts.addAttribute("", TOTAL_COUNT_ATTRIBUTE, TOTAL_COUNT_ATTRIBUTE,
593 CDATA, String.valueOf(pager.getCountOfHits()));
594 atts.addAttribute("", COUNT_OF_PAGES_ATTRIBUTE, COUNT_OF_PAGES_ATTRIBUTE,
595 CDATA, String.valueOf(pager.getCountOfPages()));
596 contentHandler.startElement(NAMESPACE, HITS_ELEMENT, Q_HITS_ELEMENT, atts);
597 generateHit(pager);
598 contentHandler.endElement(NAMESPACE, HITS_ELEMENT, Q_HITS_ELEMENT);
599 }
600 }
601
602 /**
603 * Generate the xml content for each hit.
604 *
605 * @param pager the LuceneCocoonPager with the search results.
606 * @throws SAXException when there is a problem creating the output SAX events.
607 */
608 private void generateHit(LuceneCocoonPager pager) throws SAXException {
609 // get the off set to start from
610 int counter = pager.getStartIndex();
611
612 // get an list of hits which should be placed onto a single page
613 List l = (List) pager.next();
614 Iterator i = l.iterator();
615 for (; i.hasNext(); counter++) {
616 LuceneCocoonPager.HitWrapper hw = (LuceneCocoonPager.HitWrapper) i.next();
617 Document doc = hw.getDocument();
618 float score = hw.getScore();
619 String uri = doc.get(LuceneXMLIndexer.URL_FIELD);
620
621 atts.clear();
622 atts.addAttribute("", RANK_ATTRIBUTE, RANK_ATTRIBUTE, CDATA,
623 String.valueOf(counter));
624 atts.addAttribute("", SCORE_ATTRIBUTE, SCORE_ATTRIBUTE, CDATA,
625 String.valueOf(score));
626 atts.addAttribute("", URI_ATTRIBUTE, URI_ATTRIBUTE, CDATA,
627 String.valueOf(uri));
628 contentHandler.startElement(NAMESPACE, HIT_ELEMENT, Q_HIT_ELEMENT, atts);
629 // fix me, add here a summary of this hit
630 for (Enumeration e = doc.fields(); e.hasMoreElements(); ) {
631 Field field = (Field)e.nextElement();
632 if (field.isStored()) {
633 if (LuceneXMLIndexer.URL_FIELD.equals(field.name()))
634 continue;
635 atts.clear();
636 atts.addAttribute("", NAME_ATTRIBUTE, NAME_ATTRIBUTE, CDATA, field.name());
637 contentHandler.startElement(NAMESPACE, FIELD_ELEMENT, Q_FIELD_ELEMENT, atts);
638 String value = field.stringValue();
639 contentHandler.characters(value.toCharArray(), 0, value.length());
640 contentHandler.endElement(NAMESPACE, FIELD_ELEMENT, Q_FIELD_ELEMENT);
641 }
642 }
643
644 contentHandler.endElement(NAMESPACE, HIT_ELEMENT, Q_HIT_ELEMENT);
645 }
646 }
647
648 /**
649 * Generate the navigation element.
650 *
651 * @param pager Description of Parameter
652 * @exception SAXException Description of Exception
653 */
654 private void generateNavigation(LuceneCocoonPager pager) throws SAXException {
655 if (pager != null) {
656 // generate navigation element
657 atts.clear();
658 atts.addAttribute("", TOTAL_COUNT_ATTRIBUTE, TOTAL_COUNT_ATTRIBUTE,
659 CDATA, String.valueOf(pager.getCountOfHits()));
660 atts.addAttribute("", COUNT_OF_PAGES_ATTRIBUTE, COUNT_OF_PAGES_ATTRIBUTE,
661 CDATA, String.valueOf(pager.getCountOfPages()));
662 atts.addAttribute("", HAS_NEXT_ATTRIBUTE, HAS_NEXT_ATTRIBUTE,
663 CDATA, String.valueOf(pager.hasNext()));
664 atts.addAttribute("", HAS_PREVIOUS_ATTRIBUTE, HAS_PREVIOUS_ATTRIBUTE,
665 CDATA, String.valueOf(pager.hasPrevious()));
666 atts.addAttribute("", NEXT_INDEX_ATTRIBUTE, NEXT_INDEX_ATTRIBUTE,
667 CDATA, String.valueOf(pager.nextIndex()));
668 atts.addAttribute("", PREVIOUS_INDEX_ATTRIBUTE, PREVIOUS_INDEX_ATTRIBUTE,
669 CDATA, String.valueOf(pager.previousIndex()));
670 contentHandler.startElement(NAMESPACE, NAVIGATION_ELEMENT, Q_NAVIGATION_ELEMENT, atts);
671 int count_of_pages = pager.getCountOfPages();
672 for (int i = 0, page_start_index = 0;
673 i < count_of_pages;
674 i++, page_start_index += pageLength.intValue()) {
675 atts.clear();
676 atts.addAttribute("", START_INDEX_ATTRIBUTE, START_INDEX_ATTRIBUTE,
677 CDATA, String.valueOf(page_start_index));
678 contentHandler.startElement(NAMESPACE, NAVIGATION_PAGE_ELEMENT, Q_NAVIGATION_PAGE_ELEMENT, atts);
679 contentHandler.endElement(NAMESPACE, NAVIGATION_PAGE_ELEMENT, Q_NAVIGATION_PAGE_ELEMENT);
680 }
681 // navigation is EMPTY element
682 contentHandler.endElement(NAMESPACE, NAVIGATION_ELEMENT, Q_NAVIGATION_ELEMENT);
683 }
684 }
685
686 /**
687 * Build hits from a query input, and setup paging object.
688 *
689 * @throws ProcessingException if an error occurs
690 */
691 private LuceneCocoonPager buildHits() throws ProcessingException, IOException {
692 if (queryString != null && queryString.length() != 0) {
693 Hits hits = null;
694
695 Analyzer analyzer = LuceneCocoonHelper.getAnalyzer(this.analyzer);
696 lcs.setAnalyzer(analyzer);
697 // get the directory where the index resides
698 Directory directory = LuceneCocoonHelper.getDirectory(index, false);
699 lcs.setDirectory(directory);
700 hits = lcs.search(queryString, LuceneXMLIndexer.BODY_FIELD);
701
702 // wrap the hits by an pager help object for accessing only a range of hits
703 LuceneCocoonPager pager = new LuceneCocoonPager(hits);
704
705 int start_index = START_INDEX_DEFAULT;
706 if (this.startIndex != null) {
707 start_index = this.startIndex.intValue();
708 if (start_index <= 0) {
709 start_index = 0;
710 }
711 pager.setStartIndex(start_index);
712 }
713
714 int page_length = PAGE_LENGTH_DEFAULT;
715 if (this.pageLength != null) {
716 page_length = this.pageLength.intValue();
717 if (page_length <= 0) {
718 page_length = hits.length();
719 }
720 pager.setCountOfHitsPerPage(page_length);
721 }
722
723 return pager;
724 }
725
726 return null;
727 }
728
729 /**
730 * Recycle the generator
731 */
732 public void recycle() {
733 super.recycle();
734 if (lcs != null) {
735 this.manager.release(lcs);
736 }
737 this.queryString = null;
738 this.startIndex = null;
739 this.pageLength = null;
740 this.index = null;
741 this.analyzer = null;
742 }
743
744 }