io.github.msurdi.redeye.core.lucene.AbstractIndex.java Source code

Java tutorial

Introduction

Here is the source code for io.github.msurdi.redeye.core.lucene.AbstractIndex.java

Source

/*******************************************************************************
 * Copyright 2013 Matias Surdi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 ******************************************************************************/
package io.github.msurdi.redeye.core.lucene;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import io.github.msurdi.redeye.api.Indexable;
import io.github.msurdi.redeye.core.Index;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.queryparser.simple.SimpleQueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * This class encapsulates the Lucene API calls. All indexing is done using {@link io.github.msurdi.redeye.core.lucene.RedeyeAnalyzer}.
 *
 * @param <T> the type of objects stored in the lucene. {@code <T>} must implement {@link Indexable}
 */
public abstract class AbstractIndex<T extends Indexable> implements Index<T> {
    final static String DEFAULT_FIELD = Indexable.PRIVATE_PREFIX + "default";
    private final static Version LUCENE_VERSION = Version.LUCENE_47;
    private final static String MATCH_ALL = "*";
    private final Directory index;
    private final Analyzer analyzer = new RedeyeAnalyzer(LUCENE_VERSION);
    private final IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, analyzer);
    private IndexWriter writer;
    private SearcherManager searcherManager;
    private boolean opened = false;

    protected AbstractIndex(Directory index) {
        this.index = index;
    }

    /**
     * This method must convert a Lucene {@link Document} object into a {@code <T>} object.
     *
     * @param document the lucene {@link Document}
     * @return The {@code <T>} object constructed from the lucene {@link org.apache.lucene.document.Document}
     */
    protected abstract T buildEntity(Document document);

    /**
     * This method must convert an instance of {@code <T>} into a Lucene {@code Document}
     *
     * @param id        the id of this entry in the lucene
     * @param indexable the {@code <T>} object implementing {@link io.github.msurdi.redeye.api.Indexable}
     * @return a lucene {@link org.apache.lucene.document.Document}
     */
    protected abstract Document buildDocument(String id, T indexable);

    /**
     * This method provides a very simplistic view of the lucene status.
     *
     * @return true if the lucene exists, false otherwise.
     * @throws IOException
     */
    @Override
    public boolean isOk() throws IOException {
        return DirectoryReader.indexExists(index);
    }

    /**
     * This method opens the lucene and ensures it is ready for use.
     *
     * @throws IOException
     */
    @Override
    public void open() throws IOException {
        ensureClosed();
        writer = new IndexWriter(index, config);
        writer.commit();
        searcherManager = new SearcherManager(writer, true, null);
        this.opened = true;
    }

    /**
     * This method closes the lucene guaranteeing its consistency.
     *
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        ensureOpened();
        searcherManager.close();
        writer.close();
        index.close();
        this.opened = false;
    }

    private void ensureOpened() {
        if (!opened) {
            throw new IllegalStateException("The lucene is not ready");
        }
    }

    private void ensureClosed() {
        if (opened) {
            throw new IllegalStateException("The lucene is already closed");
        }
    }

    /**
     * Add or replace a document by id
     *
     * @param id        the id used as the key in the lucene
     * @param indexable the {@link io.github.msurdi.redeye.api.Indexable} instance to store
     * @return the entity as an exact representation of how it will be returned when retrieved from the lucene
     * @throws IOException
     */
    @Override
    public T put(String id, T indexable) throws IOException {
        ensureOpened();
        Document doc = buildDocument(id, indexable);

        // Add or replace in the lucene
        Term idTerm = new Term(Indexable.ID_FIELD, id);
        writer.updateDocument(idTerm, doc);
        return buildEntity(doc);
    }

    /**
     * Retrieve a list of documents matching given query. The query must be a valid lucene query or '*'
     * for matching all documents. If the query is not valid, a best effort search is done.
     *
     * @param q a query string
     * @return a list of the {@link io.github.msurdi.redeye.api.Indexable} documents matching.
     * @throws IOException
     */
    @Override
    public List<T> query(String q) throws IOException {
        ensureOpened();

        ArrayList<T> results = Lists.newArrayList();
        Query query;
        try {
            if (MATCH_ALL.equals(q)) {
                query = new MatchAllDocsQuery();
            } else {
                query = new QueryParser(LUCENE_VERSION, DEFAULT_FIELD, analyzer).parse(q);
            }
        } catch (ParseException e) {
            query = new SimpleQueryParser(analyzer, DEFAULT_FIELD).parse(q);
        }

        IndexSearcher searcher = null;

        try {
            searcherManager.maybeRefresh();
            searcher = searcherManager.acquire();
            TopDocs docs = searcher.search(query, Math.max(1, searcher.getIndexReader().maxDoc()));
            for (ScoreDoc scoreDoc : docs.scoreDocs) {
                Document document = searcher.doc(scoreDoc.doc);
                results.add(buildEntity(document));
            }
        } finally {
            searcherManager.release(searcher);
        }

        return results;
    }

    /**
     * Get a document from the lucene by its id
     *
     * @param id the id of the document to retrieve
     * @return An {@link com.google.common.base.Optional} from the document instance.
     * @throws IOException
     */
    @Override
    public Optional<T> get(String id) throws IOException {
        ensureOpened();
        final Query query = new TermQuery(new Term(Indexable.ID_FIELD, id));
        IndexSearcher searcher = null;
        try {
            searcherManager.maybeRefresh();
            searcher = searcherManager.acquire();
            TopDocs docs = searcher.search(query, 1);
            if (docs.totalHits < 1) {
                return Optional.absent();
            } else {
                return Optional.of(buildEntity(searcher.doc(docs.scoreDocs[0].doc)));
            }
        } finally {
            searcherManager.release(searcher);
        }
    }

    /**
     * Delete a document from the lucene by its id
     *
     * @param id the id of the document to remove
     * @throws IOException
     */
    @Override
    public void delete(String id) throws IOException {
        ensureOpened();
        final Query query = new TermQuery(new Term(Indexable.ID_FIELD, id));
        writer.deleteDocuments(query);
    }

    /**
     * Get the number of documents in the lucene
     *
     * @return the number of documents in the lucene
     * @throws IOException
     */
    @Override
    public long getCount() throws IOException {
        ensureOpened();
        IndexSearcher searcher = null;
        try {
            searcherManager.maybeRefresh();
            searcher = searcherManager.acquire();
            return searcher.getIndexReader().maxDoc();
        } finally {
            searcherManager.release(searcher);
        }
    }

}