/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2016 The Ontopia Project * #- * 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 net.ontopia.infoset.fulltext.impl.lucene; import java.io.File; import java.io.IOException; import java.util.WeakHashMap; import net.ontopia.infoset.fulltext.core.FulltextImplementationIF; import net.ontopia.infoset.fulltext.core.SearchResultIF; import net.ontopia.infoset.fulltext.core.SearcherIF; import net.ontopia.infoset.fulltext.topicmaps.DefaultTopicMapDocumentGenerator; import net.ontopia.infoset.fulltext.topicmaps.TopicMapIteratorGenerator; import net.ontopia.topicmaps.core.TopicMapStoreIF; import net.ontopia.topicmaps.entry.AbstractOntopolyURLReference; import net.ontopia.topicmaps.entry.TopicMapReferenceIF; import net.ontopia.topicmaps.impl.basic.InMemoryTopicMapStore; import net.ontopia.topicmaps.impl.utils.AbstractIndexManager; import net.ontopia.topicmaps.impl.utils.FulltextIndexManager; import net.ontopia.utils.FileUtils; import net.ontopia.utils.OntopiaRuntimeException; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.StopAnalyzer; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LuceneFulltextImplementation implements FulltextImplementationIF { private static Logger logger = LoggerFactory.getLogger(LuceneFulltextImplementation.class); private static final Object READER_LOCK = new Object(); private static final Analyzer ANALYZER = new StopAnalyzer(); private final WeakHashMap<TopicMapStoreIF, FulltextIndexManager> managers = new WeakHashMap<>(); private File directoryFile; private Directory directory = null; private IndexReader reader; private TopicMapReferenceIF reference; private String defaultField = "content"; static { } @Override public synchronized void install(TopicMapReferenceIF reference) { this.reference = reference; if (reference instanceof AbstractOntopolyURLReference) { AbstractOntopolyURLReference ref = (AbstractOntopolyURLReference) reference; String indexDirectory = ref.getIndexDirectory(); if ((indexDirectory == null) || (indexDirectory.trim().isEmpty())) { throw new OntopiaRuntimeException("Reference " + ref.getId() + " was marked as fulltext indexable, but 'indexDirectory' configuration is missing"); } directoryFile = new File(indexDirectory + File.separatorChar + ref.getId()); } } @Override public synchronized void storeOpened(TopicMapStoreIF store) { if (store instanceof InMemoryTopicMapStore) { InMemoryTopicMapStore memory = (InMemoryTopicMapStore) store; if (!managers.containsKey(memory)) { managers.put(memory, new FulltextIndexManager(memory)); ((AbstractIndexManager) memory.getTransaction().getIndexManager()) .registerIndex("net.ontopia.infoset.fulltext.core.SearcherIF", new LuceneSearcher()); } } } @Override public synchronized void synchronize(TopicMapStoreIF store) { if (managers.containsKey(store)) { try { try (IndexWriter writer = getWriter()) { managers.get(store).synchronizeIndex(new LuceneIndexer(writer)); } closeReader(); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not synchronize fulltext index for topicmap " + reference.getId() + ": " + ioe.getMessage(), ioe); } } } @Override public synchronized void reindex() { deleteIndex(); try (TopicMapStoreIF store = reference.createStore(true)) { try (IndexWriter writer = getWriter()) { new TopicMapIteratorGenerator(store.getTopicMap(), new LuceneIndexer(writer), DefaultTopicMapDocumentGenerator.INSTANCE) .generate(); } closeReader(); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not fulltext reindex topicmap " + reference.getId() + ": " + ioe.getMessage(), ioe); } } @Override public synchronized void deleteIndex() { close(); if ((directoryFile != null) && (directoryFile.exists())) { try { FileUtils.deleteDirectory(directoryFile, true); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not delete lucene index directory: " + ioe.getMessage(), ioe); } } } @Override public synchronized void close() { managers.clear(); closeReader(); closeDirectory(); } private void openReader() { if (reader == null) { synchronized (READER_LOCK) { try { openDirectory(); reader = DirectoryReader.open(directory); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not open lucene index directory: " + ioe.getMessage(), ioe); } } } } private synchronized IndexWriter getWriter() { openDirectory(); try { return new IndexWriter(directory, new IndexWriterConfig(ANALYZER)); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not open lucene index writer: " + ioe.getMessage(), ioe); } } private synchronized void openDirectory() { if (directory == null) { try { directory = FSDirectory.open(directoryFile.toPath()); } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not open lucene index reader: " + ioe.getMessage(), ioe); } } } private void closeReader() { if (reader != null) { synchronized (READER_LOCK) { try { reader.close(); reader = null; } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not close lucene reader " + ioe.getMessage(), ioe); } } } } private synchronized void closeDirectory() { if (directory != null) { try { directory.close(); directory = null; } catch (IOException ioe) { throw new OntopiaRuntimeException("Could not close lucene directory " + ioe.getMessage(), ioe); } } } private class LuceneSearcher implements SearcherIF { public LuceneSearcher() { } @Override public SearchResultIF search(String query) throws IOException { synchronized (READER_LOCK) { openReader(); IndexSearcher searcher = new IndexSearcher(reader); try { logger.debug("Searching for: '" + query + "'"); Query _query = new QueryParser(defaultField, ANALYZER).parse(query); return new LuceneSearchResult(searcher, searcher.search(_query, Integer.MAX_VALUE)); } catch (org.apache.lucene.queryparser.classic.ParseException e) { logger.error("Error parsing query: '" + e.getMessage() + "'"); throw new IOException(e.getMessage(), e); } } } @Override public void close() throws IOException { closeReader(); } } public String getDefaultField() { return defaultField; } public void setDefaultField(String defaultField) { this.defaultField = defaultField; } // methods for testing public void setDirectoryFile(File directoryFile) { this.directoryFile = directoryFile; } public SearcherIF getSearcher() { return new LuceneSearcher(); } }