org.xerela.provider.configstore.ConfigSearch.java Source code

Java tutorial

Introduction

Here is the source code for org.xerela.provider.configstore.ConfigSearch.java

Source

/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 */
package org.xerela.provider.configstore;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermPositionVector;
import org.apache.lucene.index.TermVectorOffsetInfo;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.xerela.provider.devices.ZDeviceCore;

/**
 * ConfigSearch
 */
public class ConfigSearch implements IConfigSearch {
    private static final Logger LOGGER = Logger.getLogger(ConfigSearch.class);

    private static final String LUCENE_CONFIGURATION = "lucene/configurations"; //$NON-NLS-1$
    private static final String FIELD_UNIQUE_KEY = "uniqueKey"; //$NON-NLS-1$
    private static final String FIELD_DEVICE_ID = "deviceId"; //$NON-NLS-1$
    private static final String FIELD_CONFIGURATION = "text"; //$NON-NLS-1$
    private static final String FIELD_IPADDRESS = "address"; //$NON-NLS-1$
    private static final String FIELD_NETWORK = "network"; //$NON-NLS-1$
    private static final String FIELD_REPOSITORY_PATH = "name"; //$NON-NLS-1$
    private static final String FIELD_TIMESTAMP = "timestamp"; //$NON-NLS-1$
    private static final String FIELD_MIME_TYPE = "mimetype"; //$NON-NLS-1$
    private static final int OPTIMIZATION_FREQ = 500;
    private static int MAX_SEARCH_RESULTS;

    private Lock optimizeLock;
    private AtomicInteger writeCounter;
    private AtomicBoolean indexDirty;
    private IndexWriter writer;
    private AtomicReference<IndexReader> readerReference;
    private AtomicReference<IndexSearcher> searcherReference;
    private File indexFile;

    // CHECKSTYLE:OFF
    static {
        MAX_SEARCH_RESULTS = Integer.getInteger("org.xerela.lucene.maxresults", 500); //$NON-NLS-1$
    }
    // CHECKSTYLE:ON

    /**
     * Default constructor.
     */
    public ConfigSearch() {
        optimizeLock = new ReentrantLock();
        writeCounter = new AtomicInteger();
        indexDirty = new AtomicBoolean(true);
        readerReference = new AtomicReference<IndexReader>();
        searcherReference = new AtomicReference<IndexSearcher>();

        indexFile = new File(LUCENE_CONFIGURATION);
        checkIndexIntegrity();
    }

    // ----------------------------------------------------------------------
    //                    IConfigSearch (Remote) Implementation
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public List<ConfigSearchResult> searchConfig(String expression) {
        ArrayList<ConfigSearchResult> list = new ArrayList<ConfigSearchResult>();
        if (expression == null || expression.trim().length() == 0) {
            return list;
        }

        try {
            if (indexDirty.getAndSet(false)) {
                writer.flush();

                Directory directory = FSDirectory.getDirectory(indexFile);
                readerReference.set(IndexReader.open(directory));
                searcherReference.set(new IndexSearcher(readerReference.get()));
            }

            QueryParser parser = new QueryParser(FIELD_CONFIGURATION, new ZLuceneAnalyzer());
            Query query = parser.parse(expression);

            TopDocs topDocs = searcherReference.get().search(query, null, MAX_SEARCH_RESULTS);
            for (int i = 0; i < topDocs.totalHits; i++) {
                ScoreDoc scoreDoc = topDocs.scoreDocs[i];

                Document document = readerReference.get().document(scoreDoc.doc);
                ConfigSearchResult result = new ConfigSearchResult();
                try {
                    result.setLastChanged(DateTools.stringToDate(document.getField(FIELD_TIMESTAMP).stringValue()));
                } catch (java.text.ParseException e) {
                    LOGGER.warn(Messages.ConfigSearch_errorParsingLastChangedDate, e);
                }
                result.setPath(document.getField(FIELD_REPOSITORY_PATH).stringValue());
                result.setIpAddress(document.getField(FIELD_IPADDRESS).stringValue());
                result.setManagedNetwork(document.getField(FIELD_NETWORK).stringValue());
                result.setMimeType(document.getField(FIELD_MIME_TYPE).stringValue());

                TermPositionVector termVector = (TermPositionVector) readerReference.get()
                        .getTermFreqVector(scoreDoc.doc, FIELD_CONFIGURATION);
                if (termVector != null) {
                    List<ConfigSearchTerm> resultTerms = result.getTerms();

                    Set<Term> termSet = new HashSet<Term>();
                    query.extractTerms(termSet);

                    for (Term term : termSet) {
                        int ndx = termVector.indexOf(term.text());
                        if (ndx >= 0) {
                            TermVectorOffsetInfo[] offsets = termVector.getOffsets(ndx);
                            for (TermVectorOffsetInfo offset : offsets) {
                                ConfigSearchTerm searchTerm = new ConfigSearchTerm();
                                searchTerm.setTerm(term.text());
                                searchTerm.setStartOffset(offset.getStartOffset());
                                searchTerm.setEndOffset(offset.getEndOffset());
                                resultTerms.add(searchTerm);
                            }
                        }
                    }
                }

                list.add(result);
            }
        } catch (CorruptIndexException e) {
            LOGGER.error(Messages.ConfigSearch_luceneCorrupt, e);
        } catch (IOException e) {
            LOGGER.error(Messages.ConfigSearch_errorAccessingLucene, e);
        } catch (ParseException e) {
            throw new RuntimeException(Messages.ConfigSearch_errorParsingSearchExpression, e);
        }

        return list;
    }

    // ----------------------------------------------------------------------
    //                         Local Server Implementation
    // ----------------------------------------------------------------------

    /**
     * Update (re-index) a configuration file in the index.
     *
     * @param device the device whose configuration is to be updated in the index
     * @param config the ConfigHolder object holding revision information
     */
    public void updateIndex(ZDeviceCore device, ConfigHolder config) {
        deleteFromIndex(device, config);
        addToIndex(device, config);
    }

    /**
     * Delete a configuration file from the index.
     *
     * @param device the device whose configuration is to be deleted from
     *    the index
     * @param config the name in the repository of the configuration, this is are repository-
     *    relative path
     */
    public void deleteFromIndex(ZDeviceCore device, ConfigHolder config) {
        Term term = new Term(FIELD_UNIQUE_KEY, device.getDeviceId() + config.getFullName());
        try {
            maybeOptimize();

            writer.deleteDocuments(term);
            indexDirty.set(true);
        } catch (CorruptIndexException e) {
            LOGGER.error(Messages.ConfigSearch_luceneCorrupt, e);
        } catch (IOException e) {
            LOGGER.error(Messages.ConfigSearch_errorAccessingLucene, e);
        }
    }

    /**
     * Delete all the documents associated with the specified device.
     *
     * @param device the device whose documents to delete.
     */
    public void deleteFromIndex(ZDeviceCore device) {
        try {
            maybeOptimize();

            Term term = new Term(FIELD_DEVICE_ID, String.valueOf(device.getDeviceId()));
            writer.deleteDocuments(term);
            indexDirty.set(true);
        } catch (CorruptIndexException e) {
            LOGGER.error(Messages.ConfigSearch_luceneCorrupt, e);
        } catch (IOException e) {
            LOGGER.error(Messages.ConfigSearch_errorAccessingLucene, e);
        }
    }

    /**
     * Add a new configuration file to the index.
     *
     * @param device the device whose configuration is to be indexed
     * @param config the ConfigHolder containing change information
     */
    public void addToIndex(ZDeviceCore device, ConfigHolder config) {
        try {
            maybeOptimize();

            Reader reader = new FileReader(config.getConfigFile());

            Document document = new Document();
            document.add(new Field(FIELD_UNIQUE_KEY, device.getDeviceId() + config.getFullName(), Field.Store.YES,
                    Field.Index.UN_TOKENIZED));
            document.add(new Field(FIELD_DEVICE_ID, String.valueOf(device.getDeviceId()), Field.Store.YES,
                    Field.Index.NO_NORMS));
            document.add(new Field(FIELD_IPADDRESS, String.valueOf(device.getIpAddress()), Field.Store.YES,
                    Field.Index.UN_TOKENIZED));
            document.add(new Field(FIELD_NETWORK, String.valueOf(device.getManagedNetwork()), Field.Store.YES,
                    Field.Index.UN_TOKENIZED));
            document.add(new Field(FIELD_TIMESTAMP,
                    DateTools.dateToString(config.getTimestamp(), DateTools.Resolution.SECOND), Field.Store.YES,
                    Field.Index.NO));
            document.add(new Field(FIELD_REPOSITORY_PATH, config.getFullName(), Field.Store.YES,
                    Field.Index.UN_TOKENIZED));
            document.add(new Field(FIELD_MIME_TYPE, config.getMediaType(), Field.Store.YES, Field.Index.NO));
            document.add(new Field(FIELD_CONFIGURATION, reader, Field.TermVector.WITH_POSITIONS_OFFSETS));

            writer.addDocument(document);

            indexDirty.set(true);
        } catch (IOException io) {
            LOGGER.error(Messages.bind(Messages.ConfigSearch_unableToIndexConfig, device.getIpAddress()));
        }
    }

    // ----------------------------------------------------------------------
    //                      P R I V A T E   M E T H O D S
    // ----------------------------------------------------------------------

    private void checkIndexIntegrity() {
        try {
            boolean shouldCreate = !indexFile.exists();

            if (!shouldCreate) {
                // Clear any prior write locks
                File writeLock = new File(indexFile, IndexWriter.WRITE_LOCK_NAME);
                if (writeLock.exists()) {
                    writeLock.delete();
                }
            }

            Directory directory = FSDirectory.getDirectory(indexFile);
            writer = new IndexWriter(directory, new ZLuceneAnalyzer(), shouldCreate);

            indexDirty.set(true);
        } catch (CorruptIndexException e) {
            LOGGER.error(Messages.ConfigSearch_luceneCorrupt, e);
            throw new RuntimeException(e);
        } catch (LockObtainFailedException e) {
            LOGGER.error(Messages.ConfigSearch_luceneLockFailure, e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            LOGGER.error(Messages.ConfigSearch_errorAccessingLucene, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Depending on the write counter, optimize the index.
     */
    private void maybeOptimize() {
        optimizeLock.lock();
        try {
            if (writeCounter.incrementAndGet() % OPTIMIZATION_FREQ == 0) {
                writer.optimize();
            }
        } catch (CorruptIndexException e) {
            LOGGER.error(Messages.ConfigSearch_luceneCorrupt, e);
        } catch (IOException e) {
            LOGGER.error(Messages.ConfigSearch_errorAccessingLucene, e);
        } finally {
            optimizeLock.unlock();
        }
    }
}