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 */ 020 package org.apache.directory.shared.ldap.filter; 021 022 023 import java.util.HashMap; 024 import java.util.Map; 025 026 import org.apache.directory.shared.i18n.I18n; 027 import org.apache.directory.shared.ldap.entry.BinaryValue; 028 import org.apache.directory.shared.ldap.entry.StringValue; 029 import org.apache.directory.shared.ldap.entry.Value; 030 031 032 /** 033 * Abstract implementation of a expression node. 034 * 035 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 036 * @version $Rev: 928945 $ 037 */ 038 public 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 047 /** 048 * Creates a node by setting abstract node type. 049 * 050 * @param assertionType The node's type 051 */ 052 protected AbstractExprNode( AssertionType assertionType ) 053 { 054 this.assertionType = assertionType; 055 } 056 057 058 /** 059 * @see ExprNode#getAssertionType() 060 * 061 * @return the node's type 062 */ 063 public AssertionType getAssertionType() 064 { 065 return assertionType; 066 } 067 068 069 /** 070 * Tests to see if this node is a leaf or branch node. 071 * 072 * @return true if the node is a leaf,false otherwise 073 */ 074 public abstract boolean isLeaf(); 075 076 077 /** 078 * @see Object#equals(Object) 079 *@return <code>true</code> if both objects are equal 080 */ 081 public boolean equals( Object o ) 082 { 083 // Shortcut for equals object 084 if ( this == o ) 085 { 086 return true; 087 } 088 089 if ( !( o instanceof AbstractExprNode ) ) 090 { 091 return false; 092 } 093 094 AbstractExprNode that = (AbstractExprNode)o; 095 096 // Check the node type 097 if ( this.assertionType != that.assertionType ) 098 { 099 return false; 100 } 101 102 if ( annotations == null ) 103 { 104 return that.annotations == null; 105 } 106 else if ( that.annotations == null ) 107 { 108 return false; 109 } 110 111 // Check all the annotation 112 for ( String key:annotations.keySet() ) 113 { 114 if ( !that.annotations.containsKey( key ) ) 115 { 116 return false; 117 } 118 119 Object thisAnnotation = annotations.get( key ); 120 Object thatAnnotation = that.annotations.get( key ); 121 122 if ( thisAnnotation == null ) 123 { 124 if ( thatAnnotation != null ) 125 { 126 return false; 127 } 128 } 129 else 130 { 131 if ( !thisAnnotation.equals( thatAnnotation ) ) 132 { 133 return false; 134 } 135 } 136 } 137 138 return true; 139 } 140 141 142 /** 143 * Handles the escaping of special characters in LDAP search filter assertion values using the 144 * <valueencoding> rule as described in 145 * <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. Needed so that 146 * {@link ExprNode#printToBuffer(StringBuffer)} results in a valid filter string that can be parsed 147 * again (as a way of cloning filters). 148 * 149 * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter. 150 * @return Escaped version of <code>value</code> 151 */ 152 protected static Value<?> escapeFilterValue( Value<?> value ) 153 { 154 StringBuilder sb = null; 155 String val; 156 157 if ( value.isBinary() ) 158 { 159 sb = new StringBuilder( ((BinaryValue)value).getReference().length * 3 ); 160 161 for ( byte b:((BinaryValue)value).getReference() ) 162 { 163 if ( ( b < 0x7F ) && ( b >= 0 ) ) 164 { 165 switch ( b ) 166 { 167 case '*' : 168 sb.append( "\\2A" ); 169 break; 170 171 case '(' : 172 sb.append( "\\28" ); 173 break; 174 175 case ')' : 176 sb.append( "\\29" ); 177 break; 178 179 case '\\' : 180 sb.append( "\\5C" ); 181 break; 182 183 case '\0' : 184 sb.append( "\\00" ); 185 break; 186 187 default : 188 sb.append( (char)b ); 189 } 190 } 191 else 192 { 193 sb.append( '\\' ); 194 String digit = Integer.toHexString( ((byte)b) & 0x00FF ); 195 196 if ( digit.length() == 1 ) 197 { 198 sb.append( '0' ); 199 } 200 201 sb.append( digit.toUpperCase() ); 202 } 203 } 204 205 return new StringValue( sb.toString() ); 206 } 207 208 val = ( ( StringValue ) value ).getString(); 209 210 for ( int i = 0; i < val.length(); i++ ) 211 { 212 char ch = val.charAt( i ); 213 String replace = null; 214 215 switch ( ch ) 216 { 217 case '*': 218 replace = "\\2A"; 219 break; 220 221 case '(': 222 replace = "\\28"; 223 break; 224 225 case ')': 226 replace = "\\29"; 227 break; 228 229 case '\\': 230 replace = "\\5C"; 231 break; 232 233 case '\0': 234 replace = "\\00"; 235 break; 236 } 237 238 if ( replace != null ) 239 { 240 if ( sb == null ) 241 { 242 sb = new StringBuilder( val.length() * 2 ); 243 sb.append( val.substring( 0, i ) ); 244 } 245 sb.append( replace ); 246 } 247 else if ( sb != null ) 248 { 249 sb.append( ch ); 250 } 251 } 252 253 return ( sb == null ? value : new StringValue( sb.toString() ) ); 254 } 255 256 257 /** 258 * @see Object#hashCode() 259 * @return the instance's hash code 260 */ 261 public int hashCode() 262 { 263 int h = 37; 264 265 if ( annotations != null ) 266 { 267 for ( String key:annotations.keySet() ) 268 { 269 Object value = annotations.get( key ); 270 271 h = h*17 + key.hashCode(); 272 h = h*17 + ( value == null ? 0 : value.hashCode() ); 273 } 274 } 275 276 return h; 277 } 278 279 280 /** 281 * @see org.apache.directory.shared.ldap.filter.ExprNode#get(java.lang.Object) 282 * 283 * @return the annotation value. 284 */ 285 public Object get( Object key ) 286 { 287 if ( null == annotations ) 288 { 289 return null; 290 } 291 292 return annotations.get( key ); 293 } 294 295 296 /** 297 * @see org.apache.directory.shared.ldap.filter.ExprNode#set(java.lang.Object, 298 * java.lang.Object) 299 */ 300 public void set( String key, Object value ) 301 { 302 if ( null == annotations ) 303 { 304 annotations = new HashMap<String, Object>( 2 ); 305 } 306 307 annotations.put( key, value ); 308 } 309 310 311 /** 312 * Gets the annotations as a Map. 313 * 314 * @return the annotation map. 315 */ 316 protected Map<String, Object> getAnnotations() 317 { 318 return annotations; 319 } 320 321 /** 322 * Default implementation for this method : just throw an exception. 323 * 324 * @param buf the buffer to append to. 325 * @return The buffer in which the refinement has been appended 326 * @throws UnsupportedOperationException if this node isn't a part of a refinement. 327 */ 328 public StringBuilder printRefinementToBuffer( StringBuilder buf ) 329 { 330 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04144 ) ); 331 } 332 333 334 /** 335 * Clone the object 336 */ 337 @Override public ExprNode clone() 338 { 339 try 340 { 341 ExprNode clone = (ExprNode)super.clone(); 342 343 if ( annotations != null ) 344 { 345 for ( String key:annotations.keySet() ) 346 { 347 Object value = annotations.get( key ); 348 349 // Note : the value aren't cloned ! 350 ((AbstractExprNode)clone).annotations.put( key, value ); 351 } 352 } 353 354 return clone; 355 } 356 catch ( CloneNotSupportedException cnse ) 357 { 358 return null; 359 } 360 } 361 362 363 /** 364 * @see Object#toString() 365 */ 366 public String toString() 367 { 368 if ( ( null != annotations ) && annotations.containsKey( "count" ) ) 369 { 370 return ":[" + annotations.get( "count" ) + "]"; 371 } 372 else 373 { 374 return ""; 375 } 376 } 377 }