/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.jackrabbit.core.query.lucene;
import static javax.jcr.PropertyType.DATE;
import static javax.jcr.PropertyType.DECIMAL;
import static javax.jcr.PropertyType.DOUBLE;
import static javax.jcr.PropertyType.LONG;
import static javax.jcr.PropertyType.NAME;
import static javax.jcr.PropertyType.PATH;
import static javax.jcr.PropertyType.STRING;
import static javax.jcr.PropertyType.UNDEFINED;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_LIKE;
import static javax.jcr.query.qom.QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.LOCAL_NAME;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.MVP;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.NAMESPACE_URI;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.PARENT;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.PROPERTIES;
import static org.apache.jackrabbit.core.query.lucene.FieldNames.UUID;
import static org.apache.jackrabbit.core.query.lucene.TransformConstants.TRANSFORM_LOWER_CASE;
import static org.apache.jackrabbit.core.query.lucene.TransformConstants.TRANSFORM_NONE;
import static org.apache.jackrabbit.core.query.lucene.TransformConstants.TRANSFORM_UPPER_CASE;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PRIMARYTYPE;
import static org.apache.lucene.search.BooleanClause.Occur.MUST;
import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Row;
import javax.jcr.query.qom.And;
import javax.jcr.query.qom.ChildNode;
import javax.jcr.query.qom.Comparison;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.DescendantNode;
import javax.jcr.query.qom.DynamicOperand;
import javax.jcr.query.qom.FullTextSearch;
import javax.jcr.query.qom.FullTextSearchScore;
import javax.jcr.query.qom.Length;
import javax.jcr.query.qom.LowerCase;
import javax.jcr.query.qom.NodeLocalName;
import javax.jcr.query.qom.NodeName;
import javax.jcr.query.qom.Not;
import javax.jcr.query.qom.Or;
import javax.jcr.query.qom.PropertyExistence;
import javax.jcr.query.qom.PropertyValue;
import javax.jcr.query.qom.SameNode;
import javax.jcr.query.qom.Selector;
import javax.jcr.query.qom.StaticOperand;
import javax.jcr.query.qom.UpperCase;
import org.apache.jackrabbit.commons.predicate.Predicate;
import org.apache.jackrabbit.commons.predicate.Predicates;
import org.apache.jackrabbit.commons.predicate.RowPredicate;
import org.apache.jackrabbit.commons.query.qom.OperandEvaluator;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.query.lucene.join.SelectorRow;
import org.apache.jackrabbit.core.query.lucene.join.ValueComparator;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.query.qom.FullTextSearchImpl;
import org.apache.jackrabbit.spi.commons.query.qom.PropertyExistenceImpl;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
/**
* Factory that creates Lucene queries from QOM elements.
*/
public class LuceneQueryFactory {
/**
* Session of the user executing this query
*/
protected final SessionImpl session;
/**
* Node type manager
*/
protected final NodeTypeManager ntManager;
/** Lucene search index */
protected final SearchIndex index;
/**
* Namespace mappings to internal prefixes
*/
protected final NamespaceMappings nsMappings;
/**
* NamePathResolver to map namespace mappings to internal prefixes
*/
protected final NamePathResolver npResolver;
/** Operand evaluator */
protected final OperandEvaluator evaluator;
protected final String mixinTypesField;
protected final String primaryTypeField;
private final PerQueryCache cache = new PerQueryCache();
/**
* Creates a new lucene query factory.
*
* @param session the session that executes the query.
* @param index the search index
* @param bindVariables the bind variable values of the query
*/
public LuceneQueryFactory(
SessionImpl session, SearchIndex index,
Map<String, Value> bindVariables) throws RepositoryException {
this.session = session;
this.ntManager = session.getWorkspace().getNodeTypeManager();
this.index = index;
this.nsMappings = index.getNamespaceMappings();
this.npResolver = NamePathResolverImpl.create(nsMappings);
this.evaluator =
new OperandEvaluator(session.getValueFactory(), bindVariables);
this.mixinTypesField = nsMappings.translateName(JCR_MIXINTYPES);
this.primaryTypeField = nsMappings.translateName(JCR_PRIMARYTYPE);
}
/**
* @param columns
* @param selector
* @param constraint
* @param externalSort
* if <code>true</code> it means that the lqf should just let the
* QueryEngine take care of sorting and applying applying offset
* and limit constraints
* @param offsetIn
* used in pagination
* @param limitIn
* used in pagination
* @return a list of rows
* @throws RepositoryException
* @throws IOException
*/
public List<Row> execute(Map<String, PropertyValue> columns,
Selector selector, Constraint constraint, Sort sort,
boolean externalSort, long offsetIn, long limitIn)
throws RepositoryException, IOException {
final IndexReader reader = index.getIndexReader(true);
final int offset = offsetIn < 0 ? 0 : (int) offsetIn;
final int limit = limitIn < 0 ? Integer.MAX_VALUE : (int) limitIn;
QueryHits hits = null;
try {
JackrabbitIndexSearcher searcher = new JackrabbitIndexSearcher(
session, reader, index.getContext().getItemStateManager());
searcher.setSimilarity(index.getSimilarity());
Predicate filter = Predicate.TRUE;
BooleanQuery query = new BooleanQuery();
QueryPair qp = new QueryPair(query);
query.add(create(selector), MUST);
if (constraint != null) {
String name = selector.getSelectorName();
NodeType type =
ntManager.getNodeType(selector.getNodeTypeName());
filter = mapConstraintToQueryAndFilter(qp,
constraint, Collections.singletonMap(name, type),
searcher, reader);
}
List<Row> rows = new ArrayList<Row>();
// TODO depending on the filters, we could push the offset info
// into the searcher
hits = searcher.evaluate(qp.mainQuery, sort, offset + limit);
int currentNode = 0;
int addedNodes = 0;
ScoreNode node = hits.nextScoreNode();
while (node != null) {
Row row = null;
try {
row = new SelectorRow(columns, evaluator,
selector.getSelectorName(),
session.getNodeById(node.getNodeId()),
node.getScore());
} catch (ItemNotFoundException e) {
// skip the node
}
if (row != null && filter.evaluate(row)) {
if (externalSort) {
// return everything and not worry about sort
rows.add(row);
} else {
// apply limit and offset rules locally
if (currentNode >= offset
&& currentNode - offset < limit) {
rows.add(row);
addedNodes++;
}
currentNode++;
// end the loop when going over the limit
if (addedNodes == limit) {
break;
}
}
}
node = hits.nextScoreNode();
}
return rows;
} finally {
if (hits != null) {
hits.close();
}
Util.closeOrRelease(reader);
}
}
/**
* Creates a lucene query for the given QOM selector.
*
* @param selector the selector.
* @return a lucene query for the given selector.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(Selector selector) throws RepositoryException {
List<Term> terms = new ArrayList<Term>();
String name = selector.getNodeTypeName();
NodeTypeIterator allTypes = ntManager.getAllNodeTypes();
while (allTypes.hasNext()) {
NodeType nt = allTypes.nextNodeType();
if (nt.isNodeType(name)) {
terms.add(createNodeTypeTerm(nt));
}
}
if (terms.size() == 1) {
return new JackrabbitTermQuery(terms.get(0));
} else {
BooleanQuery b = new BooleanQuery();
for (Term term : terms) {
b.add(new JackrabbitTermQuery(term), SHOULD);
}
return b;
}
}
protected Term createNodeTypeTerm(NodeType type) throws RepositoryException {
String field;
if (type.isMixin()) {
// search for nodes where jcr:mixinTypes is set to this mixin
field = mixinTypesField;
} else {
// search for nodes where jcr:primaryType is set to this type
field = primaryTypeField;
}
String name = nsMappings.translateName(session.getQName(type.getName()));
return new Term(PROPERTIES, FieldNames.createNamedValue(field, name));
}
/**
* Creates a lucene query for the given QOM full text search.
*
* @param constraint the full text search constraint.
* @return the lucene query for the given constraint.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(FullTextSearchImpl fts) throws RepositoryException {
String fieldname;
if (fts.getPropertyName() == null) {
// fulltext on node
fieldname = FieldNames.FULLTEXT;
} else {
// final path element is a property name
Name propName = fts.getPropertyQName();
StringBuffer tmp = new StringBuffer();
tmp.append(nsMappings.getPrefix(propName.getNamespaceURI()));
tmp.append(":").append(FieldNames.FULLTEXT_PREFIX);
tmp.append(propName.getLocalName());
fieldname = tmp.toString();
}
QueryParser parser = new JackrabbitQueryParser(
fieldname, index.getTextAnalyzer(),
index.getSynonymProvider(), cache);
try {
StaticOperand expr = fts.getFullTextSearchExpression();
return parser.parse(evaluator.getValue(expr).getString());
} catch (ParseException e) {
throw new RepositoryException(e);
}
}
/**
* Creates a lucene query for the given QOM property existence constraint.
*
* @param constraint the QOM constraint.
* @return the lucene query for the given constraint.
* @throws RepositoryException if an error occurs while creating the query.
*/
public Query create(PropertyExistenceImpl prop) throws RepositoryException {
String propName = npResolver.getJCRName(prop.getPropertyQName());
return Util.createMatchAllQuery(
propName, index.getIndexFormatVersion(), cache);
}
protected Predicate mapConstraintToQueryAndFilter(
QueryPair query, Constraint constraint,
Map<String, NodeType> selectorMap,
JackrabbitIndexSearcher searcher, IndexReader reader)
throws RepositoryException, IOException {
Predicate filter = Predicate.TRUE;
if (constraint instanceof And) {
And and = (And) constraint;
filter = mapConstraintToQueryAndFilter(
query, and.getConstraint1(), selectorMap, searcher, reader);
Predicate other = mapConstraintToQueryAndFilter(
query, and.getConstraint2(), selectorMap, searcher, reader);
if (filter == Predicate.TRUE) {
filter = other;
} else if (other != Predicate.TRUE) {
filter = Predicates.and(filter, other);
}
} else if (constraint instanceof Comparison) {
Comparison c = (Comparison) constraint;
Transform transform = new Transform(c.getOperand1());
DynamicOperand left = transform.operand;
final String operator = c.getOperator();
StaticOperand right = c.getOperand2();
if (left instanceof Length
|| left instanceof FullTextSearchScore
|| (((!JCR_OPERATOR_EQUAL_TO.equals(operator) && !JCR_OPERATOR_LIKE
.equals(operator)) || transform.transform != TRANSFORM_NONE) && (left instanceof NodeName || left instanceof NodeLocalName))) {
try {
int type = PropertyType.UNDEFINED;
if (left instanceof Length) {
type = PropertyType.LONG;
} else if (left instanceof FullTextSearchScore) {
type = PropertyType.DOUBLE;
}
final DynamicOperand operand = c.getOperand1();
final Value value = evaluator.getValue(right, type);
filter = new RowPredicate() {
@Override
protected boolean evaluate(Row row)
throws RepositoryException {
return new ValueComparator().evaluate(
operator,
evaluator.getValue(operand, row), value);
}
};
} catch (ValueFormatException e) {
throw new InvalidQueryException(e);
}
} else {
Query cq = getComparisonQuery(
left, transform.transform, operator, right, selectorMap);
query.subQuery.add(cq, MUST);
}
} else if (constraint instanceof DescendantNode) {
final DescendantNode descendantNode = (DescendantNode) constraint;
Query context = getNodeIdQuery(UUID, descendantNode.getAncestorPath());
query.mainQuery = new DescendantSelfAxisQuery(context, query.subQuery, false);
} else {
query.subQuery.add(create(constraint, selectorMap, searcher), MUST);
}
return filter;
}
protected Query create(
Constraint constraint, Map<String, NodeType> selectorMap,
JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
if (constraint instanceof And) {
return getAndQuery((And) constraint, selectorMap, searcher);
} else if (constraint instanceof Or) {
return getOrQuery((Or) constraint, selectorMap, searcher);
} else if (constraint instanceof Not) {
return getNotQuery((Not) constraint, selectorMap, searcher);
} else if (constraint instanceof PropertyExistence) {
return getPropertyExistenceQuery((PropertyExistence) constraint);
} else if (constraint instanceof Comparison) {
Comparison c = (Comparison) constraint;
Transform left = new Transform(c.getOperand1());
return getComparisonQuery(
left.operand, left.transform, c.getOperator(),
c.getOperand2(), selectorMap);
} else if (constraint instanceof FullTextSearch) {
return getFullTextSearchQuery((FullTextSearch) constraint);
} else if (constraint instanceof SameNode) {
SameNode sn = (SameNode) constraint;
return getNodeIdQuery(UUID, sn.getPath());
} else if (constraint instanceof ChildNode) {
ChildNode cn = (ChildNode) constraint;
return getNodeIdQuery(PARENT, cn.getParentPath());
} else if (constraint instanceof DescendantNode) {
DescendantNode dn = (DescendantNode) constraint;
return getDescendantNodeQuery(dn, searcher);
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown constraint type: " + constraint);
}
}
protected Query getDescendantNodeQuery(
DescendantNode dn, JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
int clauses = 0;
try {
LinkedList<String> ids = new LinkedList<String>();
Node ancestor = session.getNode(dn.getAncestorPath());
ids.add(ancestor.getIdentifier());
while (!ids.isEmpty()) {
String id = ids.removeFirst();
Query q = new JackrabbitTermQuery(new Term(FieldNames.PARENT, id));
QueryHits hits = searcher.evaluate(q);
ScoreNode sn = hits.nextScoreNode();
if (sn != null) {
// reset query so it does not overflow because of the max
// clause count condition,
// see JCR-3108
clauses++;
if (clauses == BooleanQuery.getMaxClauseCount()) {
BooleanQuery wrapQ = new BooleanQuery();
wrapQ.add(query, SHOULD);
query = wrapQ;
clauses = 1;
}
query.add(q, SHOULD);
do {
ids.add(sn.getNodeId().toString());
sn = hits.nextScoreNode();
} while (sn != null);
}
}
} catch (PathNotFoundException e) {
query.add(new JackrabbitTermQuery(new Term(
FieldNames.UUID, "invalid-node-id")), // never matches
SHOULD);
}
return query;
}
protected Query getFullTextSearchQuery(FullTextSearch fts)
throws RepositoryException {
String field = FieldNames.FULLTEXT;
String property = fts.getPropertyName();
if (property != null) {
Name name = session.getQName(property);
field = nsMappings.getPrefix(name.getNamespaceURI()) + ":"
+ FieldNames.FULLTEXT_PREFIX + name.getLocalName();
}
StaticOperand expression = fts.getFullTextSearchExpression();
String query = evaluator.getValue(expression).getString();
try {
QueryParser parser = new JackrabbitQueryParser(
field, index.getTextAnalyzer(),
index.getSynonymProvider(), cache);
return parser.parse(query);
} catch (ParseException e) {
throw new RepositoryException(
"Invalid full text search expression: " + query, e);
}
}
protected BooleanQuery getAndQuery(
And and, Map<String, NodeType> selectorMap,
JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
addBooleanConstraint(
query, and.getConstraint1(), MUST, selectorMap, searcher);
addBooleanConstraint(
query, and.getConstraint2(), MUST, selectorMap, searcher);
return query;
}
protected BooleanQuery getOrQuery(
Or or, Map<String, NodeType> selectorMap,
JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
BooleanQuery query = new BooleanQuery();
addBooleanConstraint(
query, or.getConstraint1(), SHOULD, selectorMap, searcher);
addBooleanConstraint(
query, or.getConstraint2(), SHOULD, selectorMap, searcher);
return query;
}
protected void addBooleanConstraint(
BooleanQuery query, Constraint constraint, Occur occur,
Map<String, NodeType> selectorMap, JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
if (occur == MUST && constraint instanceof And) {
And and = (And) constraint;
addBooleanConstraint(
query, and.getConstraint1(), occur, selectorMap, searcher);
addBooleanConstraint(
query, and.getConstraint2(), occur, selectorMap, searcher);
} else if (occur == SHOULD && constraint instanceof Or) {
Or or = (Or) constraint;
addBooleanConstraint(
query, or.getConstraint1(), occur, selectorMap, searcher);
addBooleanConstraint(
query, or.getConstraint2(), occur, selectorMap, searcher);
} else {
query.add(create(constraint, selectorMap, searcher), occur);
}
}
protected NotQuery getNotQuery(
Not not, Map<String, NodeType> selectorMap,
JackrabbitIndexSearcher searcher)
throws RepositoryException, IOException {
return new NotQuery(create(not.getConstraint(), selectorMap, searcher));
}
protected Query getPropertyExistenceQuery(PropertyExistence property)
throws RepositoryException {
String name = npResolver.getJCRName(session.getQName(
property.getPropertyName()));
return Util.createMatchAllQuery(
name, index.getIndexFormatVersion(), cache);
}
protected static class Transform {
private final DynamicOperand operand;
private final int transform;
public Transform(DynamicOperand operand) {
// Check the transformation type
if (operand instanceof UpperCase) {
this.transform = TRANSFORM_UPPER_CASE;
} else if (operand instanceof LowerCase) {
this.transform = TRANSFORM_LOWER_CASE;
} else {
this.transform = TRANSFORM_NONE;
}
// Unwrap any nested transformations
while (true) {
if (operand instanceof UpperCase) {
operand = ((UpperCase) operand).getOperand();
} else if (operand instanceof LowerCase) {
operand = ((LowerCase) operand).getOperand();
} else {
break;
}
}
this.operand = operand;
}
}
protected Query getComparisonQuery(
DynamicOperand left, int transform, String operator,
StaticOperand rigth, Map<String, NodeType> selectorMap)
throws RepositoryException {
if (left instanceof PropertyValue) {
PropertyValue pv = (PropertyValue) left;
String field = npResolver.getJCRName(session.getQName(
pv.getPropertyName()));
int type = PropertyType.UNDEFINED;
NodeType nt = selectorMap.get(pv.getSelectorName());
if (nt != null) {
for (PropertyDefinition pd : nt.getPropertyDefinitions()) {
if (pd.getName().equals(pv.getPropertyName())) {
type = pd.getRequiredType();
}
}
}
return getPropertyValueQuery(
field, operator, evaluator.getValue(rigth), type, transform);
} else if (left instanceof NodeName) {
return getNodeNameQuery(transform, operator, rigth);
} else if (left instanceof NodeLocalName) {
return getNodeLocalNameQuery(transform, operator, rigth);
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown operand type: " + left); // FIXME
}
}
protected Query getNodeNameQuery(
int transform, String operator, StaticOperand right)
throws RepositoryException {
if (transform != TRANSFORM_NONE
|| !JCR_OPERATOR_EQUAL_TO.equals(operator)) {
throw new UnsupportedRepositoryOperationException();
}
Value value = evaluator.getValue(right);
int type = value.getType();
String string = value.getString();
if (type == PropertyType.URI && string.startsWith("./")) {
string = string.substring("./".length());
} else if (type == PropertyType.DOUBLE
|| type == PropertyType.DECIMAL
|| type == PropertyType.LONG
|| type == PropertyType.BOOLEAN
|| type == PropertyType.REFERENCE
|| type == PropertyType.WEAKREFERENCE) {
throw new InvalidQueryException("Invalid name value: " + string);
}
try {
Name name = session.getQName(string);
Term uri = new Term(NAMESPACE_URI, name.getNamespaceURI());
Term local = new Term(LOCAL_NAME, name.getLocalName());
BooleanQuery query = new BooleanQuery();
query.add(new JackrabbitTermQuery(uri), MUST);
query.add(new JackrabbitTermQuery(local), MUST);
return query;
} catch (IllegalNameException e) {
throw new InvalidQueryException("Illegal name: " + string, e);
}
}
protected Query getNodeLocalNameQuery(int transform, String operator,
StaticOperand right) throws RepositoryException {
if (!JCR_OPERATOR_EQUAL_TO.equals(operator) && !JCR_OPERATOR_LIKE.equals(operator)) {
throw new UnsupportedRepositoryOperationException();
}
String name = evaluator.getValue(right).getString();
if (JCR_OPERATOR_LIKE.equals(operator)) {
return new WildcardQuery(LOCAL_NAME, null, name, transform, cache);
}
return new JackrabbitTermQuery(new Term(LOCAL_NAME, name));
}
protected Query getNodeIdQuery(String field, String path)
throws RepositoryException {
String value;
try {
value = session.getNode(path).getIdentifier();
} catch (PathNotFoundException e) {
value = "invalid-node-id"; // can never match a node
}
return new JackrabbitTermQuery(new Term(field, value));
}
protected Query getPropertyValueQuery(
String field, String operator, Value value,
int type, int transform) throws RepositoryException {
String string = getValueString(value, type);
if (JCR_OPERATOR_LIKE.equals(operator)) {
return new WildcardQuery(PROPERTIES, field, string, transform, cache);
}
Term term = getTerm(field, string);
if (JCR_OPERATOR_EQUAL_TO.equals(operator)) {
switch (transform) {
case TRANSFORM_UPPER_CASE:
return new CaseTermQuery.Upper(term);
case TRANSFORM_LOWER_CASE:
return new CaseTermQuery.Lower(term);
default:
return new JackrabbitTermQuery(term);
}
} else if (JCR_OPERATOR_GREATER_THAN.equals(operator)) {
return new RangeQuery(term, getTerm(field, "\uFFFF"), false, transform, cache);
} else if (JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO.equals(operator)) {
return new RangeQuery(term, getTerm(field, "\uFFFF"), true, transform, cache);
} else if (JCR_OPERATOR_LESS_THAN.equals(operator)) {
return new RangeQuery(getTerm(field, ""), term, false, transform, cache);
} else if (JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO.equals(operator)) {
return new RangeQuery(getTerm(field, ""), term, true, transform, cache);
} else if (JCR_OPERATOR_NOT_EQUAL_TO.equals(operator)) {
BooleanQuery query = new BooleanQuery();
query.add(Util.createMatchAllQuery(
field, index.getIndexFormatVersion(), cache), SHOULD);
if (transform == TRANSFORM_UPPER_CASE) {
query.add(new CaseTermQuery.Upper(term), MUST_NOT);
} else if (transform == TRANSFORM_LOWER_CASE) {
query.add(new CaseTermQuery.Lower(term), MUST_NOT);
} else {
query.add(new JackrabbitTermQuery(term), MUST_NOT);
}
// and exclude all nodes where 'field' is multi valued
query.add(new JackrabbitTermQuery(new Term(MVP, field)), MUST_NOT);
return query;
} else {
throw new UnsupportedRepositoryOperationException(); // FIXME
}
}
protected Term getTerm(String field, String value) {
return new Term(PROPERTIES, FieldNames.createNamedValue(field, value));
}
protected String getValueString(Value value, int type)
throws RepositoryException {
switch (value.getType()) {
case DATE:
return DateField.dateToString(value.getDate().getTime());
case DOUBLE:
return DoubleField.doubleToString(value.getDouble());
case LONG:
return LongField.longToString(value.getLong());
case DECIMAL:
return DecimalField.decimalToString(value.getDecimal());
case NAME:
return npResolver.getJCRName(session.getQName(value.getString()));
case PATH:
return npResolver.getJCRPath(session.getQPath(value.getString()));
default:
String string = value.getString();
if (type != UNDEFINED && type != STRING) {
return getValueString(
session.getValueFactory().createValue(string, type),
UNDEFINED);
} else {
return string;
}
}
}
protected static class QueryPair {
Query mainQuery;
BooleanQuery subQuery;
QueryPair(BooleanQuery mainQuery) {
this.mainQuery = mainQuery;
this.subQuery = mainQuery;
}
}
}