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