/*
* Copyright 2008 The Topaz Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* Contributions:
*/
package org.mulgara.resolver.lucene;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import org.apache.log4j.Logger;
import org.jrdf.graph.URIReference;
import org.mulgara.query.Constraint;
import org.mulgara.query.ConstraintElement;
import org.mulgara.query.Value;
import org.mulgara.query.Variable;
import org.mulgara.resolver.spi.QueryEvaluationContext;
import org.mulgara.resolver.spi.SymbolicTransformationException;
import static org.mulgara.util.ObjectUtil.eq;
/**
* A constraint representing a lucene query and a score.
*
* @created 2008-09-28
* @author Ronald Tschalär
* @licence Apache License v2.0
*/
public class LuceneConstraint implements Constraint {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(LuceneConstraint.class);
private static final long serialVersionUID = 1L;
private ConstraintElement model;
private ConstraintElement subject;
private ConstraintElement predicate;
private ConstraintElement object;
private Variable binder;
private Variable score;
/**
* Empty constructor. Used internally during constraint rewriting.
*/
private LuceneConstraint() {
}
/**
* Create a new lucene constraint wrapping a non-lucene constraint.
*
* @param constraint the underlying constraint to wrap
*/
LuceneConstraint(Constraint constraint) {
this();
subject = constraint.getElement(0);
predicate = constraint.getElement(1);
object = constraint.getElement(2);
model = constraint.getModel();
}
/**
* Create a new lucene constraint.
*
* @param constraint the first raw constraint
* @param searchPred our predicate indicating a lucene search
* @param scorePred our predicate indicating the variable to hold the score
* @throws SymbolicTransformationException
*/
LuceneConstraint(Constraint constraint, URIReference searchPred, URIReference scorePred)
throws SymbolicTransformationException {
// extract model
model = constraint.getModel();
ConstraintElement s = constraint.getElement(0);
ConstraintElement p = constraint.getElement(1);
ConstraintElement o = constraint.getElement(2);
// extract predicate, object, score
if (p.equals(searchPred)) {
if (s.equals(o)) {
throw new SymbolicTransformationException("subject and object of '" + searchPred +
"' may not be the same: " + s);
}
subject = s;
assignBinder(o);
} else if (p.equals(scorePred)) {
if (!(o instanceof Variable)) {
throw new SymbolicTransformationException("Lucene query score must be a variable: " + o);
}
score = (Variable)o;
assignBinder(s);
} else {
subject = s;
predicate = p;
object = o;
}
}
private final void assignBinder(ConstraintElement b) throws SymbolicTransformationException {
if (!(b instanceof Variable)) {
throw new SymbolicTransformationException("Lucene query binder must be a variable: " + b);
}
binder = (Variable)b;
}
/**
* Merge the given constraint into this lucene constraint.
*
* @param constraint another raw constraint
*/
void conjoinWith(LuceneConstraint constraint) throws SymbolicTransformationException {
model = getNoDup(constraint.model, model, "Can't combine lucene constraints against different models", "model");
if (binder != null && constraint.binder != null) {
subject = getNoDup(constraint.subject, subject, "Can't combine lucene constraints with different subjects", "subj");
assignBinder(getNoDup(constraint.binder, binder, "Mismatched binder variable", "var"));
} else if (binder != null) {
assignBinder(getNoDup(constraint.subject, binder, "Mismatched binder variable", "var"));
} else if (constraint.binder != null) {
assignBinder(getNoDup(constraint.binder, subject, "Mismatched binder variable", "var"));
subject = constraint.subject;
} else {
subject = getNoDup(constraint.subject, subject, "Can't combine lucene constraints with different subjects", "subj");
}
predicate = getNoDup(constraint.predicate, predicate, "Only one predicate supported per search", "pred");
object = getNoDup(constraint.object, object, "Only one object supported per search", "obj");
score = getNoDup(constraint.score, score, "Only one score supported per search", "score");
}
private static <T extends ConstraintElement> T getNoDup(T elem, T existing, String msg, String elemType)
throws SymbolicTransformationException {
if (existing == null) {
return elem;
} else if (elem == null) {
return existing;
} else if (existing.equals(elem)) {
return existing;
} else {
throw new SymbolicTransformationException(msg + ": " + elemType + "1=" + existing + ", " +
elemType + "2=" + elem);
}
}
/**
* Do some basic validations on the finished constraint.
*
* @throws SymbolicTransformationException if validation fails
*/
void validate() throws SymbolicTransformationException {
if (predicate == null)
throw new SymbolicTransformationException("Missing predicate for lucene constraint: " +
"subject=" + subject + ", binder=" + binder);
if (subject == null && score != null)
throw new SymbolicTransformationException("Missing <mulgara:search> for lucene constraint: " +
"binder=" + binder + ", predicate=" + predicate +
", query=" + object + ", score=" + score);
}
public ConstraintElement getModel() {
return model;
}
public ConstraintElement getElement(int index) {
throw new UnsupportedOperationException("Cannot index LuceneConstraint");
}
public boolean isRepeating() {
return false;
}
public Set<Variable> getVariables() {
Set<Variable> vars = new HashSet<Variable>();
if (subject instanceof Variable)
vars.add((Variable)subject);
if (predicate instanceof Variable)
vars.add((Variable)predicate);
if (score != null)
vars.add(score);
return vars;
}
/** the subject of the search triple */
ConstraintElement getSubject() {
return subject;
}
/** the predicate of the search triple */
ConstraintElement getPredicate() {
return predicate;
}
/** the object of the search triple */
ConstraintElement getObject() {
return object;
}
/** the variable that binds the raw constraints */
Variable getBindingVar() {
return binder;
}
/** the variable that holds the score, or null */
Variable getScoreVar() {
return score;
}
static LuceneConstraint localize(QueryEvaluationContext context, LuceneConstraint constraint)
throws Exception {
LuceneConstraint localized = new LuceneConstraint();
localized.subject = (constraint.subject != null) ? context.localize(constraint.subject) : null;
localized.predicate = context.localize(constraint.predicate);
localized.object = context.localize(constraint.object);
localized.model = context.localize(constraint.model);
localized.binder = constraint.binder;
localized.score = constraint.score;
return localized;
}
static LuceneConstraint bind(Map<Variable,Value> bindings, LuceneConstraint constraint)
throws Exception {
LuceneConstraint bound = new LuceneConstraint();
Value val = bindings.get(constraint.subject);
bound.subject = (val != null) ? val : constraint.subject;
bound.predicate = constraint.predicate;
bound.object = constraint.object;
bound.model = constraint.model;
bound.binder = constraint.binder;
bound.score = constraint.score;
return bound;
}
public String toString() {
return "LC{subj=" + subject + ", pred=" + predicate + ", obj=" + object + ", score=" + score +
", binder=" + binder + "}";
}
public boolean equals(Object o) {
if (!(o instanceof LuceneConstraint)) return false;
LuceneConstraint l = (LuceneConstraint)o;
return eq(model, l.model) && eq(subject, l.subject) && eq(predicate, l.predicate) &&
eq(object, l.object) && eq(binder, l.binder) && eq(score, l.score);
}
/**
* Not a binary operation, so not associative
* @return <code>false</code>
*/
public boolean isAssociative() {
return false;
}
}