package pl.net.bluesoft.rnd.processtool.plugins.osgi;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.aperteworkflow.search.ProcessInstanceSearchAttribute;
import org.aperteworkflow.search.ProcessInstanceSearchData;
import org.aperteworkflow.search.SearchProvider;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* User: POlszewski
* Date: 2012-11-27
* Time: 16:39
*/
class LuceneSearchService implements SearchProvider {
private static final String AWF__ID = "__AWF__ID";
private static final String AWF__TYPE = "__AWF__TYPE";
private static final String AWF__ROLE = "__AWF__ROLE";
private static final String AWF_RUNNING = "__AWF__running";
private static final String AWF__ASSIGNEE = "__AWF__assignee";
private static final String AWF__QUEUE = "__AWF__queue";
private static final String PROCESS_INSTANCE = "PROCESS_INSTANCE";
private static final int SEARCH_LIMIT = 1000;
private String luceneDir;
private Directory index;
private IndexSearcher indexSearcher;
private IndexReader indexReader;
private Logger LOGGER;
public LuceneSearchService(Logger logger) {
this.LOGGER = logger;
}
public void initialize() {
try {
File path = new File(luceneDir);
if (!path.exists()) {
LOGGER.severe("Default lucene index directory: " + luceneDir + " not found, attempting to create...");
if (!path.mkdir()) {
LOGGER.severe("Failed to create Default lucene index directory: " + luceneDir);
} else {
LOGGER.severe("Created Default lucene index directory: " + luceneDir);
}
}
try { if (indexSearcher != null) {
indexSearcher.close();
} } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); }
try { if (indexReader != null) {
indexReader.close();
} } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); }
try { if (index != null) {
index.close();
} } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); }
index = FSDirectory.open(path);
indexReader = IndexReader.open(index);
indexSearcher = new IndexSearcher(indexReader);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
public void updateIndex(ProcessInstanceSearchData processInstanceSearchData) {
Document doc = new Document();
doc.add(new Field(AWF__ID,
String.valueOf(processInstanceSearchData.getProcessInstanceId()),
Field.Store.YES,Field.Index.NOT_ANALYZED));
doc.add(new Field(AWF__TYPE, PROCESS_INSTANCE, Field.Store.YES, Field.Index.NOT_ANALYZED));
for (ProcessInstanceSearchAttribute attr : processInstanceSearchData.getSearchAttributes()) {
if (attr.getValue() != null && !attr.getValue().trim().isEmpty()) {
Field field = new Field(attr.getName(),
attr.isKeyword() ? attr.getValue().toLowerCase() : attr.getValue(),
Field.Store.YES,
attr.isKeyword() ? Field.Index.NOT_ANALYZED : Field.Index.ANALYZED);
doc.add(field);
}
}
updateIndex(doc);
}
@Override
public List<Long> searchProcesses(String query, Integer offset,
Integer limit, boolean onlyRunning, String[] userRoles,
String assignee, String... queues) {
List<Document> results;
List<Query> addQueries = new ArrayList<Query>();
if (offset == null) {
offset = null; // ROFL!!!
}
if (limit == null) {
limit = SEARCH_LIMIT;
}
if (assignee != null) {
addQueries.add(new TermQuery(new Term(AWF__ASSIGNEE, assignee)));
}
if (queues != null)
for (String queue : queues) {
addQueries.add(new TermQuery(new Term(AWF__QUEUE, queue)));
}
if (onlyRunning) {
addQueries.add(new TermQuery(new Term(AWF_RUNNING, String
.valueOf(true))));
}
if (userRoles != null) {
BooleanQuery bq = new BooleanQuery();
bq.add(new TermQuery(new Term(AWF__ROLE, "__AWF__ROLE_ALL"
.toLowerCase())), BooleanClause.Occur.SHOULD);
for (String roleName : userRoles) {
bq.add(new TermQuery(new Term(AWF__ROLE, roleName.replace(' ',
'_').toLowerCase())), BooleanClause.Occur.SHOULD);
}
addQueries.add(bq);
}
results = search(query, 0, SEARCH_LIMIT,
addQueries.toArray(new Query[addQueries.size()]));
// always check 1000 first results - larger limit means no sense and
// Lucene provides the results
// with no sort guarantees
List<Long> res = new ArrayList<Long>(results.size());
for (Document doc : results) {
Fieldable fieldable = doc.getFieldable(AWF__ID);
if (fieldable != null) {
String s = fieldable.stringValue();
if (s != null) {
res.add(Long.parseLong(s));
}
}
}
Collections.sort(res);
Collections.reverse(res);
return res.subList(offset, Math.min(offset + limit, res.size()));
}
public List<Document> search(String query, int offset, int limit, Query... addQueries) {
try {
LOGGER.fine("Parsing lucene search query: " + query);
QueryParser qp = new QueryParser(Version.LUCENE_35, "all", new StandardAnalyzer(Version.LUCENE_35));
Query q = qp.parse(query);
BooleanQuery bq = new BooleanQuery();
bq.add(new TermQuery(new Term(AWF__TYPE, PROCESS_INSTANCE)), BooleanClause.Occur.MUST);
for (Query qq : addQueries) {
bq.add(qq, BooleanClause.Occur.MUST);
}
bq.add(q, BooleanClause.Occur.MUST);
LOGGER.fine("Searching lucene index with query: " + bq.toString());
TopDocs search = indexSearcher.search(bq, offset + limit);
List<Document> results = new ArrayList<Document>(limit);
LOGGER.fine("Total result count for query: " + bq.toString() + " is " + search.totalHits);
for (int i = offset; i < offset+limit && i < search.totalHits; i++) {
ScoreDoc scoreDoc = search.scoreDocs[i];
results.add(indexSearcher.doc(scoreDoc.doc));
}
return results;
} catch (Exception e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new RuntimeException(e);
}
}
public synchronized void updateIndex(Document... docs) {
try {
//how awesome to force programmer to hardcode library version with no reasonable default
IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_35, new StandardAnalyzer(Version.LUCENE_35));
IndexWriter indexWriter = new IndexWriter(index, cfg);
for (Document doc : docs) {
LOGGER.fine("Updating index for document: " + doc.getFieldable(AWF__ID));
indexWriter.deleteDocuments(new Term(AWF__ID, doc.getFieldable(AWF__ID).stringValue()));
StringBuilder all = new StringBuilder();
for (Fieldable f : doc.getFields()) {
all.append(f.stringValue());
all.append(' ');
}
LOGGER.fine("Updated field all for "+ doc.getFieldable(AWF__ID) + " with value: " + all);
doc.add(new Field("all", all.toString(), Field.Store.NO, Field.Index.ANALYZED));
}
indexWriter.addDocuments(Arrays.asList(docs));
LOGGER.fine("reindexing Lucene...");
indexWriter.commit();
indexWriter.close();
LOGGER.fine("reindexing Lucene... DONE!");
try { if (indexSearcher != null) {
indexSearcher.close();
} } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); }
try { if (indexReader != null) {
indexReader.close();
} } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); }
indexReader = IndexReader.open(index);
indexSearcher = new IndexSearcher(indexReader);
LOGGER.fine("reopened Lucene index handles");
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new RuntimeException(e);
}
}
public void setLuceneDir(String luceneDir) {
this.luceneDir = luceneDir;
}
}