View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    https://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.ldap.model.filter;
21  
22  
23  import org.apache.directory.api.i18n.I18n;
24  import org.apache.directory.api.ldap.model.schema.AttributeType;
25  import org.apache.directory.api.util.Strings;
26  
27  
28  /**
29   * Abstract base class for leaf nodes within the expression filter tree.
30   * 
31   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
32   */
33  public abstract class LeafNode extends AbstractExprNode
34  {
35      /** attributeType on which this leaf is based */
36      protected AttributeType attributeType;
37  
38      /** attribute on which this leaf is based */
39      protected String attribute;
40  
41  
42      /**
43       * Creates a leaf node.
44       * 
45       * @param attributeType the attribute this node is based on
46       * @param assertionType the type of this leaf node
47       */
48      protected LeafNode( AttributeType attributeType, AssertionType assertionType )
49      {
50          super( assertionType );
51          this.attributeType = attributeType;
52  
53          if ( attributeType != null )
54          {
55              this.attribute = attributeType.getName();
56          }
57          else
58          {
59              throw new NullPointerException( I18n.err( I18n.ERR_13302_CANNOT_CREATE_NODE_NULL_ATTR ) );
60          }
61      }
62  
63  
64      /**
65       * Creates a leaf node.
66       * 
67       * @param attribute the attribute this node is based on
68       * @param assertionType the type of this leaf node
69       */
70      protected LeafNode( String attribute, AssertionType assertionType )
71      {
72          super( assertionType );
73          this.attributeType = null;
74          this.attribute = attribute;
75      }
76  
77  
78      /**
79       * Gets whether this node is a leaf - the answer is always true here.
80       * 
81       * @return true always
82       */
83      @Override
84      public final boolean isLeaf()
85      {
86          return true;
87      }
88  
89  
90      /**
91       * Gets the attributeType this leaf node is based on.
92       * 
93       * @return the attributeType asserted
94       */
95      public final AttributeType getAttributeType()
96      {
97          return attributeType;
98      }
99  
100 
101     /**
102      * Gets the attribute this leaf node is based on.
103      * 
104      * @return the attribute asserted
105      */
106     public final String getAttribute()
107     {
108         return attribute;
109     }
110 
111 
112     /**
113      * Sets the attributeType this leaf node is based on.
114      * 
115      * @param attributeType the attributeType that is asserted by this filter node
116      */
117     public void setAttributeType( AttributeType attributeType )
118     {
119         this.attributeType = attributeType;
120 
121         if ( attributeType != null )
122         {
123             attribute = attributeType.getName();
124         }
125     }
126 
127 
128     /**
129      * Sets the attribute this leaf node is based on.
130      * 
131      * @param attribute the attribute that is asserted by this filter node
132      */
133     public void setAttribute( String attribute )
134     {
135         this.attribute = attribute;
136     }
137 
138 
139     /**
140      * @see ExprNode#accept(
141      *FilterVisitor)
142      * 
143      * @param visitor the filter expression tree structure visitor
144      * @return The modified element
145      */
146     @Override
147     public final Object accept( FilterVisitor visitor )
148     {
149         if ( visitor.canVisit( this ) )
150         {
151             return visitor.visit( this );
152         }
153         else
154         {
155             return null;
156         }
157     }
158 
159 
160     /**
161      * Tells if this Node is Schema aware.
162      * 
163      * @return true if the Node is SchemaAware
164      */
165     @Override
166 public boolean isSchemaAware()
167     {
168         return attributeType != null;
169     }
170     
171     
172     /**
173      * Escape a binary value into a String form that is accepted as a Filter
174      * 
175      * @param bytes The data to escape
176      * @return The escaped String
177      */
178     private static String escapeBytes( byte[] bytes )
179     {
180         // We have to escape all the bytes
181         char[] chars = new char[bytes.length * 3];
182         int pos = 0;
183         
184         for ( byte bb : bytes )
185         {
186             chars[pos++] = '\\';
187             chars[pos++] = Strings.dumpHex( ( byte ) ( bb >> 4 ) );
188             chars[pos++] = Strings.dumpHex( ( byte ) ( bb & 0x0F ) );
189         }
190         
191         return new String( chars, 0, pos );
192     }
193     
194     
195     /**
196      * Escape a String value into a String form that is accepted as a Filter
197      * 
198      * @param bytes The data to escape
199      * @return The escaped String
200      */
201     private static String escapeString( byte[] bytes )
202     {
203         StringBuilder sb = new StringBuilder( bytes.length );
204         
205         for ( byte b : bytes )
206         {
207             switch ( b )
208             {
209                 case 0x20 : case 0x21 : case 0x22 : case 0x23 : case 0x24 : case 0x25 : case 0x26 : case 0x27 :
210                     sb.append( ( char ) b );
211                     break;
212                     
213                 case 0x28 : 
214                     // '('
215                     sb.append( "\\28" );
216                     break;
217                     
218                 case 0x29 :
219                     sb.append( "\\29" );
220                     // ')'
221                     break;
222                     
223                 case 0x2A :
224                     // '*'
225                     sb.append( "\\2A" );
226                     break;
227                     
228                 case 0x2B : case 0x2C : case 0x2D : case 0x2E : case 0x2F : 
229                 case 0x30 : case 0x31 : case 0x32 : case 0x33 : case 0x34 : case 0x35 : case 0x36 : case 0x37 : 
230                 case 0x38 : case 0x39 : case 0x3A : case 0x3B : case 0x3C : case 0x3D : case 0x3E : case 0x3F : 
231                 case 0x40 : case 0x41 : case 0x42 : case 0x43 : case 0x44 : case 0x45 : case 0x46 : case 0x47 : 
232                 case 0x48 : case 0x49 : case 0x4A : case 0x4B : case 0x4C : case 0x4D : case 0x4E : case 0x4F : 
233                 case 0x50 : case 0x51 : case 0x52 : case 0x53 : case 0x54 : case 0x55 : case 0x56 : case 0x57 : 
234                 case 0x58 : case 0x59 : case 0x5A : case 0x5B : 
235                     sb.append( ( char ) b );
236                     break;
237 
238                 case 0x5C :
239                     // '\' 
240                     sb.append( "\\5C" );
241                     break;
242                     
243                 case 0x5D : case 0x5E : case 0x5F : 
244                 case 0x60 : case 0x61 : case 0x62 : case 0x63 : case 0x64 : case 0x65 : case 0x66 : case 0x67 : 
245                 case 0x68 : case 0x69 : case 0x6A : case 0x6B : case 0x6C : case 0x6D : case 0x6E : case 0x6F : 
246                 case 0x70 : case 0x71 : case 0x72 : case 0x73 : case 0x74 : case 0x75 : case 0x76 : case 0x77 : 
247                 case 0x78 : case 0x79 : case 0x7A : case 0x7B : case 0x7C : case 0x7D : case 0x7E : case 0x7F :
248                     sb.append( ( char ) b );
249                     break;
250                     
251                 default : 
252                     // This is a binary value
253                     return null;
254             }
255         }
256         
257         return sb.toString();
258     }
259 
260 
261     /**
262      * Handles the escaping of special characters in LDAP search filter assertion values using the
263      * &lt;valueencoding&gt; rule as described in
264      * <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. Needed so that
265      * {@link ExprNode#printRefinementToBuffer(StringBuilder)} results in a valid filter string that can be parsed
266      * again (as a way of cloning filters).
267      *
268      * @param attributeType The associated {@link AttributeType}
269      * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
270      * @return Escaped version of <code>value</code>
271      */
272     protected static String escapeFilterValue( AttributeType attributeType, byte[] value )
273     {
274         if ( value == null )
275         {
276             return null;
277         }
278 
279         if ( attributeType != null )
280         {
281             if ( attributeType.isHR() )
282             {
283                 String result = escapeString( value );
284                 
285                 if ( result == null )
286                 {
287                     return escapeBytes( value );
288                 }
289                 else
290                 {
291                     return result;
292                 }
293             }
294             else
295             {
296                 return escapeBytes( value );
297             }
298         }
299         else
300         {
301             String result = escapeString( value );
302             
303             if ( result == null )
304             {
305                 return escapeBytes( value );
306             }
307             else
308             {
309                 return result;
310             }
311         }
312     }
313 
314 
315     /**
316      * Handles the escaping of special characters in LDAP search filter assertion values using the
317      * &lt;valueencoding&gt; rule as described in
318      * <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. Needed so that
319      * {@link ExprNode#printRefinementToBuffer(StringBuilder)} results in a valid filter string that can be parsed
320      * again (as a way of cloning filters).
321      *
322      * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
323      * @return Escaped version of <code>value</code>
324      */
325     protected static String escapeFilterValue( String value )
326     {
327         if ( value == null )
328         {
329             return null;
330         }
331         
332         StringBuilder sb = new StringBuilder( value.length() );
333         
334         for ( int i = 0; i < value.length(); i++ )
335         {
336             char c = value.charAt( i );
337             
338             switch ( c )
339             {
340                 case 0x00:
341                     sb.append( "\\00" );
342                     break;
343                     
344                 case '(' :
345                     sb.append( "\\28" );
346                     break;
347                     
348                 case ')' :
349                     sb.append( "\\29" );
350                     break;
351                     
352                 case '*' :
353                     sb.append( "\\2A" );
354                     break;
355                     
356                 case '\\' :
357                     sb.append( "\\5C" );
358                     break;
359                     
360                 default :
361                     sb.append( c );
362                     break;
363                     
364             }
365         }
366         
367         return sb.toString();
368     }
369 
370 
371 
372     /**
373      * @see Object#hashCode()
374      * @return the instance's hash code 
375      */
376     @Override
377     public int hashCode()
378     {
379         int h = 37;
380 
381         h = h * 17 + super.hashCode();
382 
383         if ( attributeType != null )
384         {
385             h = h * 17 + attributeType.hashCode();
386         }
387         else
388         {
389             h = h * 17 + attribute.hashCode();
390         }
391 
392         return h;
393     }
394 
395 
396     /**
397      * @see java.lang.Object#equals(java.lang.Object)
398      */
399     @Override
400     public boolean equals( Object other )
401     {
402         if ( this == other )
403         {
404             return true;
405         }
406 
407         if ( !( other instanceof LeafNode ) )
408         {
409             return false;
410         }
411 
412         LeafNode otherNode = ( LeafNode ) other;
413 
414         if ( other.getClass() != this.getClass() )
415         {
416             return false;
417         }
418 
419         if ( attributeType != null )
420         {
421             return attributeType.equals( otherNode.getAttributeType() );
422         }
423         else
424         {
425             return attribute.equalsIgnoreCase( otherNode.getAttribute() );
426         }
427     }
428 }