001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.model.filter; 021 022 023import java.util.HashMap; 024import java.util.Map; 025 026import org.apache.directory.api.i18n.I18n; 027import org.apache.directory.api.ldap.model.entry.BinaryValue; 028import org.apache.directory.api.ldap.model.entry.StringValue; 029import org.apache.directory.api.ldap.model.entry.Value; 030import org.apache.directory.api.util.Strings; 031 032 033/** 034 * Abstract implementation of a expression node. 035 * 036 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 037 */ 038public abstract class AbstractExprNode implements ExprNode 039{ 040 /** The map of annotations */ 041 protected Map<String, Object> annotations; 042 043 /** The node type */ 044 protected final AssertionType assertionType; 045 046 /** A flag set to true if the Node is Schema aware */ 047 protected boolean isSchemaAware; 048 049 050 /** 051 * Creates a node by setting abstract node type. 052 * 053 * @param assertionType The node's type 054 */ 055 protected AbstractExprNode( AssertionType assertionType ) 056 { 057 this.assertionType = assertionType; 058 } 059 060 061 /** 062 * @see ExprNode#getAssertionType() 063 * 064 * @return the node's type 065 */ 066 @Override 067 public AssertionType getAssertionType() 068 { 069 return assertionType; 070 } 071 072 073 /** 074 * @see Object#equals(Object) 075 *@return <code>true</code> if both objects are equal 076 */ 077 @Override 078 public boolean equals( Object o ) 079 { 080 // Shortcut for equals object 081 if ( this == o ) 082 { 083 return true; 084 } 085 086 if ( !( o instanceof AbstractExprNode ) ) 087 { 088 return false; 089 } 090 091 AbstractExprNode that = ( AbstractExprNode ) o; 092 093 // Check the node type 094 if ( this.assertionType != that.assertionType ) 095 { 096 return false; 097 } 098 099 if ( annotations == null ) 100 { 101 return that.annotations == null; 102 } 103 else if ( that.annotations == null ) 104 { 105 return false; 106 } 107 108 // Check all the annotation 109 for ( Map.Entry<String, Object> entry : annotations.entrySet() ) 110 { 111 String key = entry.getKey(); 112 113 if ( !that.annotations.containsKey( key ) ) 114 { 115 return false; 116 } 117 118 Object thisAnnotation = entry.getValue(); 119 Object thatAnnotation = that.annotations.get( key ); 120 121 if ( thisAnnotation == null ) 122 { 123 if ( thatAnnotation != null ) 124 { 125 return false; 126 } 127 } 128 else 129 { 130 if ( !thisAnnotation.equals( thatAnnotation ) ) 131 { 132 return false; 133 } 134 } 135 } 136 137 return true; 138 } 139 140 141 /** 142 * Handles the escaping of special characters in LDAP search filter assertion values using the 143 * <valueencoding> rule as described in 144 * <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. Needed so that 145 * {@link ExprNode#printRefinementToBuffer(StringBuilder)} results in a valid filter string that can be parsed 146 * again (as a way of cloning filters). 147 * 148 * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter. 149 * @return Escaped version of <code>value</code> 150 */ 151 protected static Value<?> escapeFilterValue( Value<?> value ) 152 { 153 if ( value.isNull() ) 154 { 155 return value; 156 } 157 158 StringBuilder sb; 159 String val; 160 161 if ( !value.isHumanReadable() ) 162 { 163 sb = new StringBuilder( ( ( BinaryValue ) value ).getReference().length * 3 ); 164 165 for ( byte b : ( ( BinaryValue ) value ).getReference() ) 166 { 167 if ( ( b < 0x7F ) && ( b >= 0 ) ) 168 { 169 switch ( b ) 170 { 171 case '*': 172 sb.append( "\\2A" ); 173 break; 174 175 case '(': 176 sb.append( "\\28" ); 177 break; 178 179 case ')': 180 sb.append( "\\29" ); 181 break; 182 183 case '\\': 184 sb.append( "\\5C" ); 185 break; 186 187 case '\0': 188 sb.append( "\\00" ); 189 break; 190 191 default: 192 sb.append( ( char ) b ); 193 } 194 } 195 else 196 { 197 sb.append( '\\' ); 198 String digit = Integer.toHexString( b & 0x00FF ); 199 200 if ( digit.length() == 1 ) 201 { 202 sb.append( '0' ); 203 } 204 205 sb.append( Strings.upperCase( digit ) ); 206 } 207 } 208 209 return new StringValue( sb.toString() ); 210 } 211 212 val = ( ( StringValue ) value ).getString(); 213 String encodedVal = FilterEncoder.encodeFilterValue( val ); 214 if ( val.equals( encodedVal ) ) 215 { 216 return value; 217 } 218 else 219 { 220 return new StringValue( encodedVal ); 221 } 222 } 223 224 225 /** 226 * @see Object#hashCode() 227 * @return the instance's hash code 228 */ 229 @Override 230 public int hashCode() 231 { 232 int h = 37; 233 234 if ( annotations != null ) 235 { 236 for ( Map.Entry<String, Object> entry : annotations.entrySet() ) 237 { 238 String key = entry.getKey(); 239 Object value = entry.getValue(); 240 241 h = h * 17 + key.hashCode(); 242 h = h * 17 + ( value == null ? 0 : value.hashCode() ); 243 } 244 } 245 246 return h; 247 } 248 249 250 /** 251 * @see ExprNode#get(java.lang.Object) 252 * 253 * @return the annotation value. 254 */ 255 @Override 256 public Object get( Object key ) 257 { 258 if ( null == annotations ) 259 { 260 return null; 261 } 262 263 return annotations.get( key ); 264 } 265 266 267 /** 268 * @see ExprNode#set(String, java.lang.Object) 269 */ 270 @Override 271 public void set( String key, Object value ) 272 { 273 if ( null == annotations ) 274 { 275 annotations = new HashMap<>( 2 ); 276 } 277 278 annotations.put( key, value ); 279 } 280 281 282 /** 283 * Gets the annotations as a Map. 284 * 285 * @return the annotation map. 286 */ 287 protected Map<String, Object> getAnnotations() 288 { 289 return annotations; 290 } 291 292 293 /** 294 * Tells if this Node is Schema aware. 295 * 296 * @return true if the Node is SchemaAware 297 */ 298 @Override 299 public boolean isSchemaAware() 300 { 301 return isSchemaAware; 302 } 303 304 305 /** 306 * Default implementation for this method : just throw an exception. 307 * 308 * @param buf the buffer to append to. 309 * @return The buffer in which the refinement has been appended 310 * @throws UnsupportedOperationException if this node isn't a part of a refinement. 311 */ 312 @Override 313 public StringBuilder printRefinementToBuffer( StringBuilder buf ) 314 { 315 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04144 ) ); 316 } 317 318 319 /** 320 * Clone the object 321 */ 322 @Override 323 public ExprNode clone() 324 { 325 try 326 { 327 ExprNode clone = ( ExprNode ) super.clone(); 328 329 if ( annotations != null ) 330 { 331 for ( Map.Entry<String, Object> entry : annotations.entrySet() ) 332 { 333 // Note : the value aren't cloned ! 334 ( ( AbstractExprNode ) clone ).annotations.put( entry.getKey(), entry.getValue() ); 335 } 336 } 337 338 return clone; 339 } 340 catch ( CloneNotSupportedException cnse ) 341 { 342 return null; 343 } 344 } 345 346 347 /** 348 * @see Object#toString() 349 */ 350 @Override 351 public String toString() 352 { 353 if ( ( null != annotations ) && annotations.containsKey( "count" ) ) 354 { 355 Long count = ( Long ) annotations.get( "count" ); 356 357 if ( count == Long.MAX_VALUE ) 358 { 359 return ":[\u221E]"; 360 } 361 362 return ":[" + count + "]"; 363 } 364 else 365 { 366 return ""; 367 } 368 } 369}