/*
* 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.join;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
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.Join;
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.QueryObjectModelFactory;
import javax.jcr.query.qom.SameNode;
import javax.jcr.query.qom.UpperCase;
/**
* Returns a mapped constraint that only refers to the given set of selectors.
* The returned constraint is guaranteed to match an as small as possible
* superset of the node tuples matched by the given original constraints.
*
* @param constraint
* original constraint
* @param selectors
* target selectors
* @return mapped constraint
* @throws RepositoryException
* if the constraint mapping fails
*/
class ConstraintSplitter {
private final QueryObjectModelFactory factory;
private final Set<String> leftSelectors;
private final Set<String> rightSelectors;
private final ConstraintSplitInfo constraintSplitInfo;
public ConstraintSplitter(Constraint constraint,
QueryObjectModelFactory factory, Set<String> leftSelectors,
Set<String> rightSelectors, Join join) throws RepositoryException {
this.factory = factory;
this.leftSelectors = leftSelectors;
this.rightSelectors = rightSelectors;
constraintSplitInfo = new ConstraintSplitInfo(this.factory, join);
if (constraint != null) {
split(constraintSplitInfo, constraint);
}
}
private void split(ConstraintSplitInfo constraintSplitInfo, Constraint constraint) throws RepositoryException {
if (constraint instanceof Not) {
splitNot(constraintSplitInfo, (Not) constraint);
} else if (constraint instanceof And) {
And and = (And) constraint;
split(constraintSplitInfo, and.getConstraint1());
split(constraintSplitInfo, and.getConstraint2());
} else if (constraint instanceof Or) {
if (isReferencingBothSides(getSelectorNames(constraint))) {
Or or = (Or) constraint;
//the problem here is when you split an OR that has both condition sides referencing both join sides.
// it should split into 2 joins
constraintSplitInfo.splitOr();
split(constraintSplitInfo.getLeftInnerConstraints(), or.getConstraint1());
split(constraintSplitInfo.getRightInnerConstraints(),or.getConstraint2());
} else {
splitBySelectors(constraintSplitInfo, constraint, getSelectorNames(constraint));
}
} else {
splitBySelectors(constraintSplitInfo, constraint, getSelectorNames(constraint));
}
}
private boolean isReferencingBothSides(Set<String> selectors) {
return !leftSelectors.containsAll(selectors)
&& !rightSelectors.containsAll(selectors);
}
private void splitNot(ConstraintSplitInfo constraintSplitInfo, Not not) throws RepositoryException {
Constraint constraint = not.getConstraint();
if (constraint instanceof Not) {
split(constraintSplitInfo, ((Not) constraint).getConstraint());
} else if (constraint instanceof And) {
And and = (And) constraint;
split(constraintSplitInfo, factory.or(factory.not(and.getConstraint1()),
factory.not(and.getConstraint2())));
} else if (constraint instanceof Or) {
Or or = (Or) constraint;
split(constraintSplitInfo, factory.and(factory.not(or.getConstraint1()),
factory.not(or.getConstraint2())));
} else {
splitBySelectors(constraintSplitInfo, not, getSelectorNames(constraint));
}
}
private void splitBySelectors(ConstraintSplitInfo constraintSplitInfo, Constraint constraint, Set<String> selectors)
throws UnsupportedRepositoryOperationException {
if (leftSelectors.containsAll(selectors)) {
constraintSplitInfo.addLeftConstraint(constraint);
} else if (rightSelectors.containsAll(selectors)) {
constraintSplitInfo.addRightConstraint(constraint);
} else {
throw new UnsupportedRepositoryOperationException(
"Unable to split a constraint that references"
+ " both sides of a join: " + constraint);
}
}
/**
* Returns the names of the selectors referenced by the given constraint.
*
* @param constraint
* constraint
* @return referenced selector names
* @throws UnsupportedRepositoryOperationException
* if the constraint type is unknown
*/
private Set<String> getSelectorNames(Constraint constraint)
throws UnsupportedRepositoryOperationException {
if (constraint instanceof And) {
And and = (And) constraint;
return getSelectorNames(and.getConstraint1(), and.getConstraint2());
} else if (constraint instanceof Or) {
Or or = (Or) constraint;
return getSelectorNames(or.getConstraint1(), or.getConstraint2());
} else if (constraint instanceof Not) {
Not not = (Not) constraint;
return getSelectorNames(not.getConstraint());
} else if (constraint instanceof PropertyExistence) {
PropertyExistence pe = (PropertyExistence) constraint;
return Collections.singleton(pe.getSelectorName());
} else if (constraint instanceof Comparison) {
Comparison c = (Comparison) constraint;
return Collections.singleton(getSelectorName(c.getOperand1()));
} else if (constraint instanceof SameNode) {
SameNode sn = (SameNode) constraint;
return Collections.singleton(sn.getSelectorName());
} else if (constraint instanceof ChildNode) {
ChildNode cn = (ChildNode) constraint;
return Collections.singleton(cn.getSelectorName());
} else if (constraint instanceof DescendantNode) {
DescendantNode dn = (DescendantNode) constraint;
return Collections.singleton(dn.getSelectorName());
} else if (constraint instanceof FullTextSearch) {
FullTextSearch fts = (FullTextSearch) constraint;
return Collections.singleton(fts.getSelectorName());
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown constraint type: " + constraint);
}
}
/**
* Returns the combined set of selector names referenced by the given two
* constraint.
*
* @param a
* first constraint
* @param b
* second constraint
* @return selector names
* @throws UnsupportedRepositoryOperationException
* if the constraint types are unknown
*/
private Set<String> getSelectorNames(Constraint a, Constraint b)
throws UnsupportedRepositoryOperationException {
Set<String> set = new HashSet<String>();
set.addAll(getSelectorNames(a));
set.addAll(getSelectorNames(b));
return set;
}
/**
* Returns the selector name referenced by the given dynamic operand.
*
* @param operand
* dynamic operand
* @return selector name
* @throws UnsupportedRepositoryOperationException
* if the operand type is unknown
*/
private String getSelectorName(DynamicOperand operand)
throws UnsupportedRepositoryOperationException {
if (operand instanceof FullTextSearchScore) {
FullTextSearchScore ftss = (FullTextSearchScore) operand;
return ftss.getSelectorName();
} else if (operand instanceof Length) {
Length length = (Length) operand;
return getSelectorName(length.getPropertyValue());
} else if (operand instanceof LowerCase) {
LowerCase lower = (LowerCase) operand;
return getSelectorName(lower.getOperand());
} else if (operand instanceof NodeLocalName) {
NodeLocalName local = (NodeLocalName) operand;
return local.getSelectorName();
} else if (operand instanceof NodeName) {
NodeName name = (NodeName) operand;
return name.getSelectorName();
} else if (operand instanceof PropertyValue) {
PropertyValue value = (PropertyValue) operand;
return value.getSelectorName();
} else if (operand instanceof UpperCase) {
UpperCase upper = (UpperCase) operand;
return getSelectorName(upper.getOperand());
} else {
throw new UnsupportedRepositoryOperationException(
"Unknown dynamic operand type: " + operand);
}
}
public ConstraintSplitInfo getConstraintSplitInfo() {
return constraintSplitInfo;
}
}