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