package songbook.song;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.SortField.Type;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.util.BytesRef;
import songbook.server.Server;
import songbook.server.Templates;
import java.io.IOException;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* Index songs
*
* Created by laurent on 08/05/2014.
*/
public class IndexDatabase {
private final Logger logger = Logger.getLogger("Songbook");
private final SongDatabase songDb;
private final IndexWriter indexWriter;
private StandardAnalyzer analyzer;
private Directory index;
public IndexDatabase(Path indexFolder, SongDatabase songDb) throws IOException {
this.songDb = songDb;
analyzer = new StandardAnalyzer();
index = new NIOFSDirectory(indexFolder);
indexWriter = new IndexWriter(index, new IndexWriterConfig(analyzer));
if (!DirectoryReader.indexExists(index)) {
analyzeSongs();
}
}
public void addOrUpdateDocument(Document document) throws IOException {
indexWriter.updateDocument(new Term("id", document.get("id")), document);
indexWriter.commit();
}
/** Returns the title of a song*/
public String getTitle(String id) throws IOException {
DirectoryReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
ScoreDoc[] scoreDocs = searcher.search(new TermQuery(new Term("id", id)), 1).scoreDocs;
String title = null;
if (scoreDocs.length > 0) {
title = reader.document(scoreDocs[0].doc).get("title");
}
reader.close();
return title;
}
public void removeDocument(String id) throws IOException {
indexWriter.deleteDocuments(new Term("id", id));
indexWriter.commit();
}
public void analyzeSongs() throws IOException {
// clears index
indexWriter.deleteAll();
indexWriter.commit();
songDb.listSongIds().forEach(
(id) -> {
String contents = songDb.getSongContents(id);
if (contents != null) {
Document document = SongUtils.indexSong(contents);
document.add(new StringField("id", id, Field.Store.YES));
try {
indexWriter.addDocument(document);
indexWriter.commit();
} catch (IOException e) {
logger.log(Level.WARNING, "Can't index song '" + id + "'", e);
}
}
}
);
indexWriter.commit();
}
public void listArtists(Appendable out, String mimeType) throws IOException, ParseException {
DirectoryReader reader = DirectoryReader.open(index);
Fields fields = MultiFields.getFields(reader);
Terms artists = fields.terms("artist");
TermsEnum termsEnum = artists.iterator();
BytesRef term;
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.startItems(out);
}
while ((term = termsEnum.next()) != null) {
String artist = term.utf8ToString();
switch (mimeType) {
case Server.MIME_TEXT_HTML:
Templates.artistItem(out, artist, termsEnum.docFreq());
break;
case Server.MIME_TEXT_PLAIN:
default:
out.append(artist).append(": ").append(Integer.toString(termsEnum.docFreq()));
break;
}
}
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.endItems(out);
}
}
public void search(String querystr, Appendable out, String mimeType) throws ParseException, IOException {
int hitsPerPage = 500;
IndexReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
ScoreDoc[] hits;
if (querystr == null || querystr.isEmpty()) {
Query query = new MatchAllDocsQuery();
TopFieldDocs topFieldDocs = searcher.search(query, hitsPerPage, new Sort(new SortField("title", Type.STRING)));
hits = topFieldDocs.scoreDocs;
} else {
// the "song" arg specifies the default field to use
// when no field is explicitly specified in the query.
Query query = new QueryParser("song", analyzer).parse(querystr);
TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage);
searcher.search(query, collector);
hits = collector.topDocs().scoreDocs;
}
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.startItems(out);
}
for (ScoreDoc hit : hits) {
int docId = hit.doc;
Document doc = searcher.doc(docId);
switch (mimeType) {
case Server.MIME_TEXT_HTML:
String artists = Stream.of(doc.getValues("artist")).collect(Collectors.joining(", "));
Templates.songItem(out, doc.get("id"), doc.get("title"), artists);
break;
case Server.MIME_TEXT_PLAIN:
default:
out.append(doc.get("id")).append("\n");
break;
}
}
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.endItems(out);
}
// reader can only be closed when there
// is no need to access the documents any more.
reader.close();
}
public void songsByArtist(String artist, Appendable out, String mimeType) throws ParseException, IOException {
int hitsPerPage = 500;
IndexReader reader = DirectoryReader.open(index);
IndexSearcher searcher = new IndexSearcher(reader);
ScoreDoc[] hits;
TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage);
Query tq = new TermQuery(new Term("artist", artist));
searcher.search(tq, collector);
hits = collector.topDocs().scoreDocs;
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.startItems(out);
}
for (ScoreDoc hit : hits) {
int docId = hit.doc;
Document doc = searcher.doc(docId);
switch (mimeType) {
case Server.MIME_TEXT_HTML:
String artists = Stream.of(doc.getValues("artist")).collect(Collectors.joining(", "));
Templates.songItem(out, doc.get("id"), doc.get("title"), artists);
break;
case Server.MIME_TEXT_PLAIN:
default:
out.append(doc.get("id")).append("\n");
break;
}
}
if (Server.MIME_TEXT_HTML.equals(mimeType)) {
Templates.endItems(out);
}
// reader can only be closed when there
// is no need to access the documents any more.
reader.close();
}
}