package er.neo4jadaptor.query.lucene;
import java.util.Collection;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.regex.RegexQuery;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOQualifier;
import er.neo4jadaptor.ersatz.lucene.LuceneErsatz;
import er.neo4jadaptor.ersatz.lucene.LuceneTranslator;
import er.neo4jadaptor.query.QueryConverter;
import er.neo4jadaptor.query.expression.sentence.operators.ComparisonOperator;
import er.neo4jadaptor.storage.lucene.LuceneStore;
/**
* Builds Lucene {@link Query} equal to, or more liberal than the given {@link EOQualifier}. Search results
* found using returned qualifier might therefore need additional filtering.
*
* @author Jedrzej Sobanski
*
*/
public class LuceneQueryConverter extends QueryConverter<Query> {
@SuppressWarnings("unused")
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LuceneQueryConverter.class);
// public static LuceneQueryConverter instance = new LuceneQueryConverter();
private boolean isQueryFullyCovered = true;
public LuceneQueryConverter() {
}
@Override
protected Query negate(Query query) {
BooleanQuery boolQuery = new BooleanQuery();
// When using MUST_NOT clause it can not appear on its own, it must be accompanied by another query in BooleanQuery
// For this reason this artificial match-all query is added
// @see Boolean.Clause.Occur.MUST_NOT
boolQuery.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
boolQuery.add(query, BooleanClause.Occur.MUST_NOT);
return boolQuery;
}
private Query joinWithOccur(Collection<Query> queries, Occur occur) {
if (BooleanQuery.getMaxClauseCount() < queries.size()) {
BooleanQuery.setMaxClauseCount(queries.size());
}
BooleanQuery boolQuery = new BooleanQuery();
for (Query q : queries) {
boolQuery.add(q, occur);
}
return boolQuery;
}
@Override
protected Query joinWithAndOperator(Collection<Query> queries) {
return joinWithOccur(queries, Occur.MUST);
}
@Override
protected Query joinWithOrOperator(Collection<Query> queries) {
return joinWithOccur(queries, Occur.SHOULD);
}
@Override
protected Query matchAll() {
return new MatchAllDocsQuery();
}
public static Query matchAllOfEntity(EOEntity entity) {
Term term = new Term(LuceneStore.TYPE_PROPERTY_NAME, entity.name());
return new TermQuery(term);
}
@Override
protected Query convertKeyValueQualifier(EOEntity entity, EOKeyValueQualifier qual) {
String key = qual.key();
if (entity.attributeNamed(key) == null) {
isQueryFullyCovered = false;
return matchAllOfEntity(entity);
} else {
return super.convertKeyValueQualifier(entity, qual);
}
}
private Query rangeQuery(String key, EOAttribute att, ComparisonOperator operator, Object value) {
String min = null;
String max = null;
boolean minInclusive = false;
boolean maxInclusive = false;
switch (operator) {
case LESS_THAN:
case LESS_OR_EQUAL:
min = null;
max = LuceneTranslator.instance.fromNeutralValue(value, att);
break;
case GREATER_THAN:
case GREATER_OR_EQUAL:
min = LuceneTranslator.instance.fromNeutralValue(value, att);
max = null;
break;
}
if (ComparisonOperator.LESS_OR_EQUAL.equals(operator)) {
maxInclusive = true;
}
if (ComparisonOperator.GREATER_OR_EQUAL.equals(operator)) {
minInclusive = true;
}
return new TermRangeQuery(key, min, max, minInclusive, maxInclusive);
}
@Override
protected Query comparison(EOEntity entity, String key, ComparisonOperator operator, Object value) {
EOAttribute att = entity.attributeNamed(key);
String luceneValue = LuceneTranslator.instance.fromNeutralValue(value, att);
switch (operator) {
case EQUAL:
return new TermQuery(new Term(key, luceneValue));
case LESS_THAN:
case LESS_OR_EQUAL:
case GREATER_THAN:
case GREATER_OR_EQUAL:
return rangeQuery(key, att, operator, value);
case LIKE:
return new WildcardQuery(new Term(key, luceneValue));
case ILIKE:
String lowercaseKey = LuceneErsatz.lowercasePropertyName(key);
if (value == null) {
return new WildcardQuery(new Term(lowercaseKey, luceneValue));
} else {
return new WildcardQuery(new Term(lowercaseKey, luceneValue.toLowerCase()));
}
case MATCHES:
return new RegexQuery(new Term(key, luceneValue));
}
throw new UnsupportedOperationException("Operator " + operator + " not covered in switch-case");
}
@Override
public Query fullQuery(EOEntity entity, EOQualifier qualifier) {
Query q = convert(entity, qualifier);
BooleanQuery boolQuery = new BooleanQuery();
boolQuery.add(q, Occur.MUST);
boolQuery.add(matchAllOfEntity(entity), Occur.MUST);
return boolQuery;
}
public boolean isQueryFullyCovered() {
return isQueryFullyCovered;
}
}