package org.jboss.seam.wiki.core.search.metamodel;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.jboss.seam.log.Log;
import org.jboss.seam.wiki.core.search.PropertySearch;
import org.jboss.seam.wiki.core.search.annotations.SearchableType;
import java.io.Serializable;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Pattern;
/**
* Meta-information about a logical searchable property.
* <p>
* Generalized building of Lucene queries, called by subclasses.
* <p>
* TODO: Implement NUMRANGE query building
*
* @author Christian Bauer
*/
public abstract class SearchableProperty implements Serializable, Comparable {
public static final String TERM_INCLUDE = "include";
public static final String TERM_EXCLUDE = "exclude";
public static final String TERM_MATCHEXACTPHRASE = "matchExactPhrase";
public static final String TERM_NUMOFDAYS = "numOfDays";
protected Log log = org.jboss.seam.log.Logging.getLog(getClass());
private String description;
private SearchableType type;
public SearchableProperty() {}
public SearchableProperty(String description, SearchableType type) {
this.description = description;
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public SearchableType getType() {
return type;
}
public void setType(SearchableType type) {
this.type = type;
}
public int compareTo(Object o) {
return getDescription().compareTo( ((SearchableProperty)o).getDescription() );
}
public abstract Query getQuery(PropertySearch search);
protected Query buildIncludeQuery(String fieldName, PropertySearch search) {
Query query = null;
if (getType().equals(SearchableType.PHRASE)) {
String includeString = (String)search.getTerms().get(TERM_INCLUDE);
Boolean matchExactPhrase = (Boolean)search.getTerms().get(TERM_MATCHEXACTPHRASE);
if (includeString != null && includeString.length() >0) {
if(matchExactPhrase != null && matchExactPhrase) {
log.debug("building include phrase query for field: " + fieldName);
query = buildPhraseQuery(fieldName, includeString);
} else {
log.debug("building include term query for field: " + fieldName);
query = buildTermQuery(fieldName, includeString);
}
}
} else if (getType().equals(SearchableType.PASTDATE)) {
String numOfDays = (String)search.getTerms().get(TERM_NUMOFDAYS);
if (numOfDays != null && numOfDays.length() >0) {
log.debug("building past date query for field: " + fieldName);
DateFormat df = new SimpleDateFormat("yyyyMMdd");
Calendar today = new GregorianCalendar();
Calendar startDate = new GregorianCalendar();
startDate.add(Calendar.DAY_OF_YEAR, -Integer.valueOf(numOfDays));
log.debug("date range query start: " + df.format(startDate.getTime()) );
log.debug("date range query end: " + df.format(today.getTime()) );
query = buildRangeQuery(fieldName, df.format(startDate.getTime()), df.format(today.getTime()));
}
} else if (getType().equals(SearchableType.STRING)) {
String includeString = (String)search.getTerms().get(TERM_INCLUDE);
if (includeString != null && includeString.length() >0) {
log.debug("building include term query for field: " + fieldName);
query = buildTermQuery(fieldName, includeString);
}
}
return query;
}
protected Query buildExcludeQuery(String fieldName, PropertySearch search) {
Query query = null;
if (getType().equals(SearchableType.PHRASE)) {
log.debug("building exclude phrase query for field: " + fieldName);
String includeString = (String)search.getTerms().get(TERM_INCLUDE);
String excludeString = (String)search.getTerms().get(TERM_EXCLUDE);
Boolean matchExactPhrase = (Boolean)search.getTerms().get(TERM_MATCHEXACTPHRASE);
if (includeString != null && includeString.length() >0 && excludeString != null && excludeString.length() > 0) {
if(matchExactPhrase != null && matchExactPhrase) {
log.debug("building exclude phrase query for field: " + fieldName);
query = buildPhraseQuery(fieldName, includeString);
} else {
log.debug("building exclude term query for field: " + fieldName);
query = buildTermQuery(fieldName, excludeString);
}
}
}
return query;
}
private Query buildPhraseQuery(String fieldName, String terms) {
try {
PhraseQuery query = new PhraseQuery();
query.setSlop(0);
TokenStream includeStream =
new StandardAnalyzer().tokenStream(null, new StringReader(escape(terms).toLowerCase()));
while (true) {
Token t = includeStream.next();
if (t == null) break;
query.add( new Term(fieldName, t.termText()) );
}
return query.getTerms().length > 0 ? query : null;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private Query buildTermQuery(String fieldName, String terms) {
String[] termsArray = escape(terms).toLowerCase().split("\\s"); // Just split user input by whitespace
if (termsArray.length > 1) {
BooleanQuery query = new BooleanQuery();
for (String s: termsArray) {
TermQuery termQuery = new TermQuery(new Term(fieldName, s) );
query.add(termQuery, BooleanClause.Occur.SHOULD);
}
return query.getClauses().length > 0 ? query : null;
} else {
return termsArray.length != 0 ? new TermQuery(new Term(fieldName, termsArray[0])) : null;
}
}
private Query buildRangeQuery(String fieldName, String begin, String end) {
return new ConstantScoreRangeQuery(fieldName, begin, end, true, true); // Inclusive of begin and end
}
private String escape(String userInput) {
userInput = userInput.replaceAll(Pattern.quote("("), "\\(");
userInput = userInput.replaceAll(Pattern.quote(")"), "\\)");
return userInput;
}
}