/* ================================================================== * SearchFilter.java - Aug 8, 2010 8:15:59 PM * * Copyright 2007-2010 SolarNetwork.net Dev Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== * $Revision$ * ================================================================== */ package net.solarnetwork.support; import java.util.Arrays; import java.util.Collections; import java.util.Map; /** * Generic search filter supporting LDAP-style search queries. * * <p> * This filter supports a group of key-value pairs joined by a common logical * operator. The key-value pairs are provided by a {@code Map}. * </p> * * @author matt * @version 1.0 */ public class SearchFilter { /** * A filter logic qualifier for multiple filters. */ public enum LogicOperator { /** * Combine filters with a logical AND (the default operator). */ AND, /** Combine filters with a logical OR. */ OR, /** Combine filters with a logical NOT. */ NOT; @Override public String toString() { switch (this) { case AND: return "&"; case OR: return "|"; case NOT: return "!"; default: throw new AssertionError(this); } } } /** * A comparison operator for a single filter. */ public enum CompareOperator { /** Match exactly this attribute value. */ EQUAL, /** Match anything but exactly this attribute value. */ NOT_EQUAL, /** Match attribute values less than this attribute value. */ LESS_THAN, /** Match attribute values less than or equal to this attribute value. */ LESS_THAN_EQUAL, /** Match attribute values greater than this attribute value. */ GREATER_THAN, /** * Match attribute values greater than or equal to this attribute value. */ GREATER_THAN_EQUAL, /** Match a substring (this attribute value) within attribute values. */ SUBSTRING, /** * Match a substring (this attribute value) at the start of an attribute * value. */ SUBSTRING_AT_START, /** Match if the attribute name is present, regardless of its value. */ PRESENT, /** Approximately match the attribute value to this attribute value. */ APPROX, /** For array comparison, an overlap operator. */ OVERLAP; @Override public String toString() { switch (this) { case EQUAL: return "="; case NOT_EQUAL: return "<>"; case LESS_THAN: return "<"; case LESS_THAN_EQUAL: return "<="; case GREATER_THAN: return ">"; case GREATER_THAN_EQUAL: return ">="; case SUBSTRING: return "**"; case SUBSTRING_AT_START: return "*"; case PRESENT: return "?"; case APPROX: return "~"; case OVERLAP: return "&&"; default: throw new AssertionError(this); } } } private final Map<String, ?> filter; private final CompareOperator compareOp; private final LogicOperator logicOp; /** * Construct with a filter. Uses {@link CompareOperator#EQUAL} and * {@link LogicOperator#AND}. */ public SearchFilter(Map<String, ?> filter) { this(filter, CompareOperator.EQUAL, LogicOperator.AND); } /** * Construct with values. * * @param compareOp * the comparison operator * @param logicOp * the logical operator */ public SearchFilter(Map<String, ?> filter, CompareOperator compareOp, LogicOperator logicOp) { super(); this.filter = filter; this.compareOp = compareOp; this.logicOp = logicOp; } /** * Construct with a single key-value pair. * * @param key * the key * @param value * the value * @param compareOp * the comparison operator */ public SearchFilter(String key, Object value, CompareOperator compareOp) { this(Collections.singletonMap(key, value), compareOp, LogicOperator.AND); } /** * Construct with a filter and logic operator and * {@link CompareOperator#EQUAL} comparison operator. * * @param filter * the filter * @param logicOp * the logic operator */ public SearchFilter(Map<String, ?> filter, LogicOperator logicOp) { this(filter, CompareOperator.EQUAL, logicOp); } /** * Appends this filter as a LDAP query string to a StringBuilder. If any * value in the {@code filter} is itself a {@code SearchFilter}, the * associated key is ignored and the {@code SearchFilter} is itself appended * to the buffer. If the {@code filter} has only one key-value pair, the * {@code logicOp} is ignored and {@bold not} appended to the buffer. * If the {@code filter} has more than one key-value pair and the * {@code logicOp} is {@link LogicOperator#NOT}, the filter will * automatically be written as {@code NOT(AND((x)(y)))}. * * @param buf * the buffer to append to */ public void appendLDAPSearchFilter(StringBuilder buf) { if ( filter == null ) { return; } if ( filter == null || filter.size() < 1 ) { return; } if ( filter.size() > 1 || logicOp == LogicOperator.NOT ) { buf.append('(').append(logicOp.toString()); if ( filter.size() > 1 && logicOp == LogicOperator.NOT ) { // automatically becomes NOT(AND(x)) buf.append("(&"); } } for ( Map.Entry<String, ?> me : filter.entrySet() ) { String attributeName = me.getKey(); Object value = me.getValue(); if ( value instanceof SearchFilter ) { ((SearchFilter) value).appendLDAPSearchFilter(buf); continue; } buf.append('('); buf.append(attributeName); switch (compareOp) { case GREATER_THAN: buf.append(">"); break; case GREATER_THAN_EQUAL: buf.append(">="); break; case LESS_THAN: buf.append("<"); break; case LESS_THAN_EQUAL: buf.append("<="); break; case PRESENT: buf.append("=*"); break; case APPROX: buf.append("~="); break; default: buf.append("="); break; } if ( compareOp == CompareOperator.SUBSTRING ) { if ( value == null ) { buf.append("*"); } else { buf.append("*"); buf.append(value); buf.append("*"); } } else if ( compareOp == CompareOperator.SUBSTRING_AT_START ) { if ( value != null ) { buf.append(value); } buf.append("*"); } else if ( compareOp != CompareOperator.PRESENT ) { if ( value == null ) { buf.append("*"); } else if ( value.getClass().isArray() ) { buf.append(Arrays.toString((Object[]) value)); } else { buf.append(value); } } buf.append(')'); } if ( filter.size() > 1 || logicOp == LogicOperator.NOT ) { buf.append(')'); if ( filter.size() > 1 && logicOp == LogicOperator.NOT ) { buf.append(')'); } } } /** * Return an LDAP search filter string. * * @return String * @see #appendLDAPSearchFilter(StringBuilder) */ public String asLDAPSearchFilterString() { StringBuilder buf = new StringBuilder(); appendLDAPSearchFilter(buf); return buf.toString(); } /** * Return an LDAP search filter string. This simply calls * {@link #asLDAPSearchFilterString()}. * * @return String */ @Override public String toString() { return asLDAPSearchFilterString(); } public CompareOperator getCompareOperator() { return compareOp; } public LogicOperator getLogicOperator() { return logicOp; } }