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 *    https://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 org.apache.directory.api.i18n.I18n;
024import org.apache.directory.api.ldap.model.schema.AttributeType;
025import org.apache.directory.api.util.Strings;
026
027
028/**
029 * Abstract base class for leaf nodes within the expression filter tree.
030 * 
031 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
032 */
033public abstract class LeafNode extends AbstractExprNode
034{
035    /** attributeType on which this leaf is based */
036    protected AttributeType attributeType;
037
038    /** attribute on which this leaf is based */
039    protected String attribute;
040
041
042    /**
043     * Creates a leaf node.
044     * 
045     * @param attributeType the attribute this node is based on
046     * @param assertionType the type of this leaf node
047     */
048    protected LeafNode( AttributeType attributeType, AssertionType assertionType )
049    {
050        super( assertionType );
051        this.attributeType = attributeType;
052
053        if ( attributeType != null )
054        {
055            this.attribute = attributeType.getName();
056        }
057        else
058        {
059            throw new NullPointerException( I18n.err( I18n.ERR_13302_CANNOT_CREATE_NODE_NULL_ATTR ) );
060        }
061    }
062
063
064    /**
065     * Creates a leaf node.
066     * 
067     * @param attribute the attribute this node is based on
068     * @param assertionType the type of this leaf node
069     */
070    protected LeafNode( String attribute, AssertionType assertionType )
071    {
072        super( assertionType );
073        this.attributeType = null;
074        this.attribute = attribute;
075    }
076
077
078    /**
079     * Gets whether this node is a leaf - the answer is always true here.
080     * 
081     * @return true always
082     */
083    @Override
084    public final boolean isLeaf()
085    {
086        return true;
087    }
088
089
090    /**
091     * Gets the attributeType this leaf node is based on.
092     * 
093     * @return the attributeType asserted
094     */
095    public final AttributeType getAttributeType()
096    {
097        return attributeType;
098    }
099
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
166public 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}