/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.model;
import org.hibernate.Criteria;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.LikeExpression;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* FilterBuilder
*
* Contains the logic to apply filter Criterion to a base criteria.
*/
public abstract class FilterBuilder {
private static Logger log = LoggerFactory.getLogger(FilterBuilder.class);
public static final String WILDCARD_REGEX = "((?:[^*?\\\\]*(?:\\\\.?)*)*)([*?]|\\z)";
public static final Pattern WILDCARD_PATTERN = Pattern.compile(WILDCARD_REGEX);
private Map<String, List<String>> attributeFilters;
private List<String> idFilters;
protected List<Criterion> otherCriteria;
public FilterBuilder() {
this.attributeFilters = new HashMap<String, List<String>>();
this.idFilters = new LinkedList<String>();
this.otherCriteria = new LinkedList<Criterion>();
}
public FilterBuilder addIdFilter(String id) {
idFilters.add(id);
return this;
}
public FilterBuilder addIdFilters(Collection<String> ids) {
if (ids != null) {
idFilters.addAll(ids);
}
return this;
}
public Collection<String> getIdFilters() {
return Collections.unmodifiableList(this.idFilters);
}
public void addAttributeFilter(String attrName) {
if (!attributeFilters.containsKey(attrName)) {
attributeFilters.put(attrName, new LinkedList<String>());
}
}
public void addAttributeFilter(String attrName, String attrValue) {
if (!attributeFilters.containsKey(attrName)) {
attributeFilters.put(attrName, new LinkedList<String>());
}
attributeFilters.get(attrName).add(attrValue);
}
public Map<String, List<String>> getAttributeFilters() {
return Collections.unmodifiableMap(this.attributeFilters);
}
public List<String> getAttributeFilters(String attrName) {
return this.attributeFilters.get(attrName);
}
/**
* Adds the constructed filters to the given criteria object.
*
* @param parentCriteria
* The criteria to which filters should be added
*
* @deprecated
* Applying filtering to criteria through this class has been deprecated, as it requires too
* much pre-configuration to the criteria to be general enough for generic use. As such, it
* is often better, faster and overall more efficient to hand-craft the criteria filtering
* than it is to use this class, which adds its filters via subqueries off the main query.
*/
@Deprecated
public void applyTo(Criteria parentCriteria) {
if (!attributeFilters.isEmpty() || !idFilters.isEmpty() || !otherCriteria.isEmpty()) {
parentCriteria.add(getCriteria());
}
}
public void applyTo(DetachedCriteria parentCriteria) {
if (!attributeFilters.isEmpty() || !idFilters.isEmpty() || !otherCriteria.isEmpty()) {
parentCriteria.add(getCriteria());
}
}
public Criterion getCriteria() {
Conjunction all = Restrictions.conjunction();
if (!attributeFilters.isEmpty()) {
all.add(buildAttributeCriteria());
}
if (!idFilters.isEmpty()) {
all.add(buildIdFilters());
}
if (!otherCriteria.isEmpty()) {
for (Criterion c : otherCriteria) {
all.add(c);
}
}
return all;
}
private Criterion buildIdFilters() {
return Restrictions.in("id", idFilters);
}
private Criterion buildAttributeCriteria() {
Conjunction all = Restrictions.conjunction();
for (Entry<String, List<String>> entry : attributeFilters.entrySet()) {
all.add(buildCriteriaForKey(entry.getKey(), entry.getValue()));
}
// Currently all attributes of different names are ANDed.
return all;
}
protected abstract Criterion buildCriteriaForKey(String key, List<String> values);
/**
* FilterLikeExpression to easily build like clauses, escaping all sql wildcards
* from input while allowing us to use a custom wildcard
*/
@SuppressWarnings("serial")
public static class FilterLikeExpression extends LikeExpression {
public FilterLikeExpression(String propertyName, String value, boolean ignoreCase) {
super(propertyName, escape(value), '!', ignoreCase);
}
private static String escape(String raw) {
// If our escape char is already here, escape it
log.debug("Searching for entries like: ", raw);
String dbEscaped = raw.replace("!", "!!")
// Escape anything that would be a database wildcard
.replace("_", "!_").replace("%", "!%");
log.debug("DB characters excaped: ", dbEscaped);
// Possibly could merge this with FilterBuilder.FilterLikeExpression:
Matcher matcher = WILDCARD_PATTERN.matcher(dbEscaped);
StringBuffer searchBuf = new StringBuffer();
while (matcher.find()) {
searchBuf.append(matcher.group(1));
if (matcher.group(2).equals("*")) {
searchBuf.append("%");
}
else if (matcher.group(2).equals("?")) {
searchBuf.append("_");
}
}
// If regex didn't match anything it must be a plain search string:
String searchString = (searchBuf.length() > 0 ? searchBuf.toString() : dbEscaped);
log.debug("Final database search string: {} -> {}", raw, searchString);
return searchString;
}
}
}