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.ldap.entry.Value;
027 import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
028 import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
029 import org.apache.directory.shared.ldap.util.StringTools;
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: 798550 $
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( ((ClientBinaryValue)value).getReference().length * 3 );
160
161 for ( byte b:((ClientBinaryValue)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 ClientStringValue( sb.toString() );
206 }
207
208 val = ( ( ClientStringValue ) 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 ClientStringValue( 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( "ScopeNode can't be part of a refinement" );
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 }