net.sourceforge.atunes.kernel.modules.search.SearchHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.atunes.kernel.modules.search.SearchHandler.java

Source

/*
 * aTunes 1.14.0
 * Copyright (C) 2006-2009 Alex Aranda, Sylvain Gaudard, Thomas Beckers and contributors
 *
 * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors
 *
 * http://www.atunes.org
 * http://sourceforge.net/projects/atunes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package net.sourceforge.atunes.kernel.modules.search;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.swing.SwingWorker;

import net.sourceforge.atunes.kernel.ControllerProxy;
import net.sourceforge.atunes.kernel.Handler;
import net.sourceforge.atunes.kernel.modules.state.ApplicationState;
import net.sourceforge.atunes.misc.log.LogCategories;
import net.sourceforge.atunes.model.AudioObject;
import net.sourceforge.atunes.utils.StringUtils;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
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.Searcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.LockObtainFailedException;

/**
 * The Class SearchHandler.
 */
public final class SearchHandler extends Handler {

    /** Default lucene field. */
    public static final String DEFAULT_INDEX = "any";

    /** Dummy lucene field to retrieve all elements. */
    private static final String INDEX_FIELD_DUMMY = "dummy";

    /** Singleton instance. */
    private static SearchHandler instance = new SearchHandler();

    /**
     * Logical operators used to create complex rules.
     */
    public enum LogicalOperator {

        /** The AND. */
        AND,

        /** The OR. */
        OR,

        /** The NOT. */
        NOT
    }

    /** List of searchable objects available. */
    private List<SearchableObject> searchableObjects;

    /** List of operators for simple rules. */
    private List<String> searchOperators;

    /**
     * List which indicates if an indexing process is currently running for a
     * given searchable object.
     */
    volatile Map<SearchableObject, Boolean> currentIndexingWorks;

    /** Map with locks for each index. */
    Map<SearchableObject, ReadWriteLock> indexLocks;

    /**
     * Constructor.
     */
    private SearchHandler() {
    }

    @Override
    public void applicationFinish() {
    }

    @Override
    public void applicationStateChanged(ApplicationState newState) {
    }

    @Override
    public void applicationStarted() {
    }

    @Override
    protected void initHandler() {
        currentIndexingWorks = new HashMap<SearchableObject, Boolean>();
        searchableObjects = new ArrayList<SearchableObject>();
        indexLocks = new HashMap<SearchableObject, ReadWriteLock>();

        searchOperators = new ArrayList<String>();
        searchOperators.add(":");
    }

    /**
     * Method to register a searchable object. All searchable objects must call
     * this method to initialize searches
     * 
     * @param so
     *            the so
     */
    public void registerSearchableObject(SearchableObject so) {
        currentIndexingWorks.put(so, Boolean.FALSE);
        indexLocks.put(so, new ReentrantReadWriteLock(true));
        searchableObjects.add(so);
    }

    /**
     * Method to unregister a searchable object. After this method is called,
     * searchable object is not searchable, and must be registered again
     * 
     * @param so
     */
    public void unregisterSearchableObject(SearchableObject so) {
        currentIndexingWorks.remove(so);
        indexLocks.remove(so);
        searchableObjects.remove(so);
    }

    /**
     * Returns SearchHandler unique instance.
     * 
     * @return the instance
     */
    public static SearchHandler getInstance() {
        return instance;
    }

    /**
     * Starts a search by updating indexes and showing search dialog.
     */
    public void startSearch() {
        // Updates indexes
        for (SearchableObject searchableObject : searchableObjects) {
            updateSearchIndex(searchableObject);
        }

        // Set list of searchable objects
        ControllerProxy.getInstance().getCustomSearchController().setListOfSearchableObjects(searchableObjects);

        // Set list of operators
        ControllerProxy.getInstance().getCustomSearchController().setListOfOperators(searchOperators);

        // Show dialog to start search
        ControllerProxy.getInstance().getCustomSearchController().showSearchDialog();
    }

    /**
     * Evaluates a search query and returns a result list of audio objects and
     * scores (0 <= score <= 1).
     * 
     * @param queryStr
     *            The search query
     * @param searchableObject
     *            the searchable object
     * 
     * @return The search result
     * 
     * @throws SearchIndexNotAvailableException
     *             If no search index was found
     * @throws SearchQuerySyntaxException
     *             If the search query has invalid syntax
     */
    public List<SearchResult> search(SearchableObject searchableObject, String queryStr)
            throws SearchIndexNotAvailableException, SearchQuerySyntaxException {
        ReadWriteLock searchIndexLock = indexLocks.get(searchableObject);
        try {
            searchIndexLock.readLock().lock();

            String queryString = applyQueryTransformations(queryStr);

            Query query = new QueryParser(DEFAULT_INDEX, new SimpleAnalyzer()).parse(queryString);
            Searcher searcher = new IndexSearcher(searchableObject.getPathToIndex());
            TopDocs topDocs = searcher.search(query, 1000);
            List<RawSearchResult> rawSearchResults = new ArrayList<RawSearchResult>();
            for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
                rawSearchResults.add(new RawSearchResult(searcher.doc(scoreDoc.doc), scoreDoc.score));
            }
            List<SearchResult> result = searchableObject.getSearchResult(rawSearchResults);
            getLogger().debug(LogCategories.REPOSITORY,
                    "Query: " + queryString + " (" + result.size() + " search results)");
            return result;
        } catch (IOException e) {
            throw new SearchIndexNotAvailableException();
        } catch (ParseException e) {
            throw new SearchQuerySyntaxException();
        } finally {
            searchIndexLock.readLock().unlock();
        }
    }

    /**
     * Applies a set of transformations to match Lucene syntax.
     * 
     * @param query
     *            the query
     * 
     * @return Query transformed in Lucene language
     */
    private String applyQueryTransformations(String query) {
        String tempQuery;

        // Replace "NOT xx" by Lucene syntax: "dummy:dummy NOT xx"
        tempQuery = query.replaceAll(LogicalOperator.NOT.toString(),
                StringUtils.getString(INDEX_FIELD_DUMMY, ":", INDEX_FIELD_DUMMY, " NOT"));

        // Find numeric values and replace to Lucene numeric ranges:
        // year: 2000 ---> year: [2000 TO 2000]
        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(tempQuery, " ");
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (token.matches("\"[0-9]+\"")) {
                sb.append(StringUtils.getString("[", token, " TO ", token, "]"));
            } else {
                sb.append(token);
            }
            sb.append(" ");
        }

        return sb.toString();
    }

    /**
     * Generic method to update any searchable object.
     * 
     * @param searchableObject
     *            the searchable object
     */

    private void updateSearchIndex(final SearchableObject searchableObject) {
        SwingWorker<Void, Void> refreshSearchIndex = new SwingWorker<Void, Void>() {
            private IndexWriter indexWriter;

            @Override
            protected Void doInBackground() {
                ReadWriteLock searchIndexLock = indexLocks.get(searchableObject);
                try {
                    searchIndexLock.writeLock().lock();
                    initSearchIndex();
                    updateSearchIndex(searchableObject.getElementsToIndex());
                    finishSearchIndex();
                    return null;
                } finally {
                    searchIndexLock.writeLock().unlock();
                    currentIndexingWorks.put(searchableObject, Boolean.FALSE);
                }
            }

            @Override
            protected void done() {
                // Nothing to do
            }

            private void initSearchIndex() {
                getLogger().info(LogCategories.HANDLER, "Updating index for " + searchableObject.getClass());
                try {
                    FileUtils.deleteDirectory(new File(searchableObject.getPathToIndex()));
                    indexWriter = new IndexWriter(searchableObject.getPathToIndex(), new SimpleAnalyzer(),
                            IndexWriter.MaxFieldLength.UNLIMITED);
                } catch (CorruptIndexException e) {
                    getLogger().error(LogCategories.HANDLER, e);
                } catch (LockObtainFailedException e) {
                    getLogger().error(LogCategories.HANDLER, e);
                } catch (IOException e) {
                    getLogger().error(LogCategories.HANDLER, e);
                }
            }

            private void updateSearchIndex(List<AudioObject> audioObjects) {
                getLogger().info(LogCategories.HANDLER, "update search index");
                if (indexWriter != null) {
                    for (AudioObject audioObject : audioObjects) {
                        Document d = searchableObject.getDocumentForElement(audioObject);
                        // Add dummy field
                        d.add(new Field(INDEX_FIELD_DUMMY, INDEX_FIELD_DUMMY, Field.Store.YES,
                                Field.Index.NOT_ANALYZED_NO_NORMS));

                        try {
                            indexWriter.addDocument(d);
                        } catch (CorruptIndexException e) {
                            getLogger().error(LogCategories.HANDLER, e);
                        } catch (IOException e) {
                            getLogger().error(LogCategories.HANDLER, e);
                        }
                    }
                }
            }

            private void finishSearchIndex() {
                getLogger().info(LogCategories.HANDLER,
                        StringUtils.getString("Update index for ", searchableObject.getClass(), " finished"));
                if (indexWriter != null) {
                    try {
                        indexWriter.optimize();
                        indexWriter.close();

                        indexWriter = null;
                    } catch (CorruptIndexException e) {
                        getLogger().error(LogCategories.HANDLER, e);
                    } catch (IOException e) {
                        getLogger().error(LogCategories.HANDLER, e);
                    }
                }
            }

        };
        if (currentIndexingWorks.get(searchableObject) == null || !currentIndexingWorks.get(searchableObject)) {
            currentIndexingWorks.put(searchableObject, Boolean.TRUE);
            refreshSearchIndex.execute();
        }

    }

}