net.sf.mmm.search.engine.impl.lucene.LuceneSearchEngine.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.mmm.search.engine.impl.lucene.LuceneSearchEngine.java

Source

/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.search.engine.impl.lucene;

import java.io.IOException;

import net.sf.mmm.search.api.SearchEntry;
import net.sf.mmm.search.base.SearchDependencies;
import net.sf.mmm.search.engine.api.SearchHit;
import net.sf.mmm.search.engine.api.SearchQuery;
import net.sf.mmm.search.engine.api.SearchQueryBuilder;
import net.sf.mmm.search.engine.api.SearchResultPage;
import net.sf.mmm.search.engine.base.AbstractSearchEngine;
import net.sf.mmm.search.engine.base.SearchHighlighter;
import net.sf.mmm.search.engine.base.SearchHitImpl;
import net.sf.mmm.search.engine.base.SearchResultPageImpl;
import net.sf.mmm.search.impl.lucene.LuceneSearchEntry;
import net.sf.mmm.util.component.api.PeriodicRefresher;
import net.sf.mmm.util.exception.api.NlsNullPointerException;
import net.sf.mmm.util.exception.api.NlsParseException;
import net.sf.mmm.util.exception.api.ObjectNotFoundException;
import net.sf.mmm.util.io.api.IoMode;
import net.sf.mmm.util.io.api.RuntimeIoException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Formatter;

/**
 * This is the implementation of the {@link net.sf.mmm.search.engine.api.SearchEngine} interface using lucene
 * as underlying search-engine.
 *
 * @author Joerg Hohwiller (hohwille at users.sourceforge.net)
 */
public class LuceneSearchEngine extends AbstractSearchEngine {

    /** The lucene {@link Analyzer}. */
    private final Analyzer analyzer;

    /** @see #getQueryBuilder() */
    private final SearchQueryBuilder queryBuilder;

    /** @see #getHighlightFormatter() */
    private final Formatter highlightFormatter;

    /** @see #getFieldManager() */
    private final LuceneFieldManager fieldManager;

    /** @see #getSearchDependencies() */
    private final SearchDependencies searchDependencies;

    /** The {@link IndexReader} used for the {@link #searcher}. */
    private IndexReader indexReader;

    /** The actual lucene {@link Searcher}. */
    private Searcher searcher;

    /**
     * The constructor.
     *
     * @param indexReader is the {@link IndexReader} to access the index.
     * @param analyzer is the {@link Analyzer} to use.
     * @param queryBuilder is the {@link SearchQueryBuilder} query builder.
     * @param highlightFormatter is the {@link Formatter} for highlighting.
     * @param fieldManager is the {@link LuceneFieldManager}.
     * @param searchDependencies are the {@link SearchDependencies}.
     * @param periodicRefresher is the {@link PeriodicRefresher}.
     */
    public LuceneSearchEngine(IndexReader indexReader, Analyzer analyzer, SearchQueryBuilder queryBuilder,
            Formatter highlightFormatter, LuceneFieldManager fieldManager, SearchDependencies searchDependencies,
            PeriodicRefresher periodicRefresher) {

        super(periodicRefresher);
        NlsNullPointerException.checkNotNull(IndexReader.class, indexReader);
        NlsNullPointerException.checkNotNull(Analyzer.class, analyzer);
        NlsNullPointerException.checkNotNull(SearchQueryBuilder.class, queryBuilder);
        NlsNullPointerException.checkNotNull(Formatter.class, highlightFormatter);
        NlsNullPointerException.checkNotNull(LuceneFieldManager.class, fieldManager);
        NlsNullPointerException.checkNotNull(SearchDependencies.class, searchDependencies);
        // searchEngineRefresher is allowed to be null
        this.indexReader = indexReader;
        this.fieldManager = fieldManager;
        this.searchDependencies = searchDependencies;
        this.searcher = new IndexSearcher(this.indexReader);
        this.analyzer = analyzer;
        this.queryBuilder = queryBuilder;
        this.highlightFormatter = highlightFormatter;
    }

    /**
     * The constructor.
     *
     * @param indexReader is the {@link IndexReader}.
     * @param analyzer is the {@link Analyzer} to use.
     * @param queryBuilder is the {@link SearchQueryBuilder} query builder.
     * @param highlightFormatter is the {@link Formatter} for highlighting.
     * @param fieldManager is the {@link LuceneFieldManager}.
     * @param searchDependencies are the {@link SearchDependencies}.
     */
    public LuceneSearchEngine(IndexReader indexReader, Analyzer analyzer, SearchQueryBuilder queryBuilder,
            Formatter highlightFormatter, LuceneFieldManager fieldManager, SearchDependencies searchDependencies) {

        this(indexReader, analyzer, queryBuilder, highlightFormatter, fieldManager, searchDependencies, null);
    }

    /**
     * This method gets the {@link SearchDependencies}.
     *
     * @return the {@link SearchDependencies}.
     */
    protected SearchDependencies getSearchDependencies() {

        return this.searchDependencies;
    }

    /**
     * This method gets the {@link LuceneFieldManager}.
     *
     * @return the {@link LuceneFieldManager}.
     */
    protected LuceneFieldManager getFieldManager() {

        return this.fieldManager;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchQueryBuilder getQueryBuilder() {

        return this.queryBuilder;
    }

    /**
     * @return the formatter
     */
    protected Formatter getHighlightFormatter() {

        return this.highlightFormatter;
    }

    /**
     * This method gets the lucene {@link Query} for the given <code>query</code>.
     *
     * @param query is the {@link SearchQuery} to "convert".
     * @return the lucene {@link Query}.
     */
    protected Query getLuceneQuery(SearchQuery query) {

        try {
            Query luceneQuery = ((AbstractLuceneSearchQuery) query).getLuceneQuery();
            luceneQuery = this.searcher.rewrite(luceneQuery);
            return luceneQuery;
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * This method creates a new instance of the {@link SearchHighlighter} for a {@link SearchResultPage}.
     *
     * @param luceneQuery is the lucene {@link Query} used for highlighting.
     * @return the {@link SearchHighlighter}.
     */
    protected SearchHighlighter createSearchHighlighter(Query luceneQuery) {

        return new LuceneSearchHighlighter(this.analyzer, this.highlightFormatter, luceneQuery);
    }

    /**
     * This method creates an {@link SearchHit} for the given <code>documentId</code> and <code>score</code>.
     *
     * @param documentId is the technical ID of the lucene {@link Document} representing the hit.
     * @param score is the {@link SearchHit#getScore() score} of the hit.
     * @param searchHighlighter is used to create the {@link SearchHit#getHighlightedText() highlighted text}.
     * @return the {@link SearchHit}.
     */
    protected SearchHit createSearchHit(int documentId, double score, SearchHighlighter searchHighlighter) {

        SearchEntry searchEntry = getEntry(documentId);
        return new SearchHitImpl(searchEntry, Integer.toString(documentId), score, searchHighlighter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResultPage search(SearchQuery query, int hitsPerPage) {

        return search(query, hitsPerPage, 0, -1);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchResultPage search(SearchQuery query, int hitsPerPage, int pageIndex, int totalHitCount) {

        try {
            Query luceneQuery = getLuceneQuery(query);
            int start = hitsPerPage * pageIndex;
            int requiredHitCount = hitsPerPage + start;
            // do the actual search...
            TopDocs topDocs = this.searcher.search(luceneQuery, requiredHitCount);
            int pageHitCount = topDocs.scoreDocs.length - start;
            float maxScore = topDocs.getMaxScore();
            if (Float.isNaN(maxScore) || (maxScore <= 0)) {
                maxScore = 1;
            }
            SearchHit[] hits;
            if (pageHitCount > 0) {
                hits = new SearchHit[pageHitCount];
                SearchHighlighter searchHighlighter = createSearchHighlighter(luceneQuery);
                for (int i = 0; i < hits.length; i++) {
                    ScoreDoc scoreDoc = topDocs.scoreDocs[start + i];
                    hits[i] = createSearchHit(scoreDoc.doc, (scoreDoc.score / maxScore), searchHighlighter);
                }
            } else {
                hits = SearchHit.NO_HITS;
            }
            SearchResultPage page = new SearchResultPageImpl(query.toString(), topDocs.totalHits, hitsPerPage,
                    pageIndex, hits);
            return page;
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long count(String field, String value) {

        try {
            Term term = this.fieldManager.createTerm(field, value);
            return this.searcher.docFreq(term);
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getTotalEntryCount() {

        try {
            return this.searcher.maxDoc();
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SearchEntry getEntry(String entryId) {

        try {
            return getEntry(Integer.parseInt(entryId));
        } catch (NumberFormatException e) {
            throw new NlsParseException(e, entryId, Integer.class);
        }
    }

    /**
     * @see #getEntry(String)
     *
     * @param documentId is the technical ID of the lucene {@link Document} representing the hit.
     * @return the document as {@link SearchEntry}.
     */
    public SearchEntry getEntry(int documentId) {

        try {
            Document doc = this.searcher.doc(documentId);
            if (doc == null) {
                throw new ObjectNotFoundException(SearchEntry.class, Integer.toString(documentId));
            }
            return new LuceneSearchEntry(doc, this.fieldManager.getConfigurationHolder().getBean().getFields(),
                    this.searchDependencies);
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized boolean refresh() {

        try {
            boolean updated = this.fieldManager.refresh();
            IndexReader oldReader = this.indexReader;
            this.indexReader = this.indexReader.reopen();
            if (this.indexReader != oldReader) {
                getLogger().debug("search-index has changed and search-engine is refreshed!");
                Searcher oldSearcher = this.searcher;
                this.searcher = new IndexSearcher(this.indexReader);
                try {
                    oldSearcher.close();
                } finally {
                    oldReader.close();
                }
                updated = true;
            }
            this.queryBuilder.refresh();
            return updated;
        } catch (IOException e) {
            throw new RuntimeIoException(e, IoMode.READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {

        super.close();
        try {
            this.searcher.close();
            this.searcher = null;
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

}