package play.modules.search.store;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.FSDirectory;
import play.Logger;
import play.Play;
import play.classloading.ApplicationClasses.ApplicationClass;
import play.db.jpa.JPA;
import play.db.jpa.JPABase;
import play.exceptions.UnexpectedException;
import play.libs.Files;
import play.modules.search.Indexed;
import play.modules.search.Search;
public class FilesystemStore implements Store {
protected Map<String, IndexWriter> indexWriters = new HashMap<String, IndexWriter>();
protected Map<String, IndexSearcher> indexSearchers = new HashMap<String, IndexSearcher>();
public static String DATA_PATH;
public static boolean sync = true;
public void unIndex(Object object) {
try {
if (!(object instanceof JPABase))
return;
if (object.getClass().getAnnotation(Indexed.class) == null)
return;
JPABase jpaBase = (JPABase ) object;
String index = object.getClass().getName();
getIndexWriter(index).deleteDocuments(new Term("_docID", ConvertionUtils.getIdValueFor(jpaBase) + ""));
if (sync) {
getIndexWriter(index).commit();
dirtyReader(index);
}
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public void index(Object object, String index) {
try {
if (!(object instanceof JPABase)) {
Logger.warn("Unable to index " + object + ", unsupported class type. Only play.db.jpa.JPABase classes are supported.");
return;
}
JPABase jpaABase = (JPABase ) object;
Document document = ConvertionUtils.toDocument(object);
if (document == null)
return;
getIndexWriter(index).deleteDocuments(new Term("_docID", ConvertionUtils.getIdValueFor(jpaABase) + ""));
getIndexWriter(index).addDocument(document);
if (sync) {
getIndexWriter(index).commit();
dirtyReader(index);
} else {
if (getIndexWriter(index).ramSizeInBytes() > 1024 * 1024 * 48) {
getIndexWriter(index).commit();
dirtyReader(index);
}
}
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public IndexSearcher getIndexSearcher(String name) {
try {
if (!indexSearchers.containsKey(name)) {
synchronized (this) {
File root = new File(DATA_PATH, name);
if (!root.exists())
getIndexWriter(name);
IndexSearcher reader = new IndexSearcher(FSDirectory.open(root));
indexSearchers.put(name, reader);
}
}
return indexSearchers.get(name);
} catch (Exception e) {
throw new UnexpectedException("Cannot open index", e);
}
}
/**
* Used to synchronize reads after writes
*
* @param name of the reader to be reopened
*/
public void dirtyReader(String name) {
synchronized (this) {
try {
if (indexSearchers.containsKey(name)) {
IndexReader rd = indexSearchers.get(name).getIndexReader();
indexSearchers.get(name).close();
indexSearchers.remove(name);
}
} catch (Exception e) {
throw new UnexpectedException("Can't reopen reader", e);
}
}
}
private IndexWriter getIndexWriter(String name) {
try {
if (!indexWriters.containsKey(name)) {
synchronized (this) {
File root = new File(DATA_PATH, name);
if (!root.exists())
root.mkdirs();
if (new File(root, "write.lock").exists())
new File(root, "write.lock").delete();
IndexWriter writer = new IndexWriter(FSDirectory.open(root), Search.getAnalyser(), MaxFieldLength.UNLIMITED);
indexWriters.put(name, writer);
}
}
return indexWriters.get(name);
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public void rebuildAllIndexes() throws Exception {
stop();
File fl = new File(DATA_PATH);
Files.deleteDirectory(fl);
fl.mkdirs();
List<ApplicationClass> classes = Play.classes.getAnnotatedClasses(Indexed.class);
for (ApplicationClass applicationClass : classes) {
List<JPABase> objects = JPA.em().createQuery(
"select e from " + applicationClass.javaClass.getCanonicalName() + " as e")
.getResultList();
for (JPABase jpaBase : objects) {
index(jpaBase, applicationClass.javaClass.getName());
}
}
Logger.info("Rebuild index finished");
}
public List<ManagedIndex> listIndexes() {
List<ManagedIndex> indexes = new ArrayList<ManagedIndex>();
List<ApplicationClass> classes = Play.classes.getAnnotatedClasses(Indexed.class);
for (ApplicationClass applicationClass : classes) {
ManagedIndex index = new ManagedIndex();
index.name = applicationClass.javaClass.getName();
index.optimized = getIndexSearcher(index.name).getIndexReader().isOptimized();
index.documentCount = getIndexSearcher(index.name).getIndexReader().numDocs();
index.jpaCount = (Long ) JPA.em().createQuery("select count (*) from " + applicationClass.javaClass.getCanonicalName()+ ")").getSingleResult();
indexes.add(index);
}
return indexes;
}
public void start() {
if (Play.configuration.containsKey("play.search.path"))
DATA_PATH = Play.configuration.getProperty("play.search.path");
else
DATA_PATH = Play.applicationPath.getAbsolutePath() + "/data/search/";
Logger.trace("Search module repository is in " + DATA_PATH);
sync = Boolean.parseBoolean(Play.configuration.getProperty("play.search.synch", "true"));
Logger.trace("Write operations sync: " + sync);
}
public void stop() throws Exception {
for (IndexWriter writer : indexWriters.values()) {
writer.close();
}
for (IndexSearcher searcher : indexSearchers.values()) {
searcher.close();
}
indexWriters.clear();
indexSearchers.clear();
}
public void optimize(String name) {
try {
getIndexWriter(name).optimize(true);
getIndexWriter(name).commit();
dirtyReader(name);
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public void rebuild(String name) {
String id = UUID.randomUUID().toString();
File oldFolder = new File(DATA_PATH, name);
File newFolder = new File(DATA_PATH, name + id);
Class cl = Play.classes.getApplicationClass(name).javaClass;
List<JPABase> objects = JPA.em().createQuery("select e from " + cl.getCanonicalName() + " as e").getResultList();
String index = cl.getName() + id;
IndexWriter indexWriter = getIndexWriter(index);
try {
for (JPABase jpaBase : objects) {
Document document = ConvertionUtils.toDocument(jpaBase);
if (document == null)
return;
indexWriter.addDocument(document);
}
getIndexWriter(index).commit();
dirtyReader(index);
getIndexSearcher(name).close();
indexSearchers.remove(name);
getIndexWriter(name).close();
indexWriters.remove(name);
Files.deleteDirectory(oldFolder);
newFolder.renameTo(oldFolder);
} catch (IOException e) {
throw new UnexpectedException(e);
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public void reopen(String name) {
dirtyReader(name);
}
public void delete(String name) {
synchronized (this) {
try {
if (indexSearchers.containsKey(name)) {
IndexReader rd = indexSearchers.get(name).getIndexReader();
indexSearchers.get(name).close();
indexSearchers.remove(name);
}
if (indexWriters.containsKey(name)) {
indexWriters.get(name).close();
indexWriters.remove(name);
}
File target = new File(DATA_PATH, name);
if (target.exists() && target.isDirectory())
Files.deleteDirectory(target);
} catch (Exception e) {
throw new UnexpectedException("Can't reopen reader", e);
}
}
}
public void deleteAll() {
File root = new File(DATA_PATH);
if (root.exists() && root.isDirectory()) {
File[] indexes = root.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
for (File file : indexes) {
delete(file.getName());
}
}
}
public boolean hasIndex(String name) {
return new File(DATA_PATH, name).exists();
}
}