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.server.xdbm.search.evaluator;
021
022
023import java.util.Iterator;
024import java.util.regex.Pattern;
025
026import org.apache.directory.api.ldap.model.entry.Attribute;
027import org.apache.directory.api.ldap.model.entry.Entry;
028import org.apache.directory.api.ldap.model.entry.Value;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.filter.SubstringNode;
031import org.apache.directory.api.ldap.model.schema.AttributeType;
032import org.apache.directory.api.ldap.model.schema.MatchingRule;
033import org.apache.directory.api.ldap.model.schema.Normalizer;
034import org.apache.directory.api.ldap.model.schema.SchemaManager;
035import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer;
036import org.apache.directory.server.core.api.partition.PartitionTxn;
037import org.apache.directory.server.xdbm.IndexEntry;
038import org.apache.directory.server.xdbm.Store;
039import org.apache.directory.server.xdbm.search.Evaluator;
040
041
042/**
043 * Evaluates substring filter assertions on an entry.
044 * 
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class SubstringEvaluator implements Evaluator<SubstringNode>
048{
049    /** Database used while evaluating candidates */
050    private final Store db;
051
052    /** Reference to the SchemaManager */
053    private final SchemaManager schemaManager;
054
055    /** The Substring expression */
056    private final SubstringNode node;
057
058    /** The regular expression generated for the SubstringNode pattern */
059    private final Pattern regex;
060
061    /** The AttributeType we will use for the evaluation */
062    private final AttributeType attributeType;
063
064    /** The associated normalizer */
065    private final Normalizer normalizer;
066
067
068    /**
069     * Creates a new SubstringEvaluator for substring expressions.
070     *
071     * @param node the substring expression node
072     * @param db the database this evaluator uses
073     * @param schemaManager the schema manager
074     * @throws LdapException if there are failures accessing resources and the db
075     */
076    public SubstringEvaluator( SubstringNode node, Store db, SchemaManager schemaManager ) throws LdapException
077    {
078        this.db = db;
079        this.node = node;
080        this.schemaManager = schemaManager;
081        this.attributeType = node.getAttributeType();
082
083        MatchingRule rule = attributeType.getSubstring();
084
085        if ( rule == null )
086        {
087            rule = attributeType.getEquality();
088        }
089
090        if ( rule != null )
091        {
092            normalizer = rule.getNormalizer();
093        }
094        else
095        {
096            normalizer = new NoOpNormalizer( attributeType.getSyntaxOid() );
097        }
098
099        // compile the regular expression to search for a matching attribute
100        // if the attributeType is humanReadable
101        if ( attributeType.getSyntax().isHumanReadable() )
102        {
103            regex = node.getRegex( normalizer );
104        }
105        else
106        {
107            regex = null;
108        }
109    }
110
111
112    /**
113     * {@inheritDoc}
114     */
115    @SuppressWarnings("unchecked")
116    @Override
117    public boolean evaluate( PartitionTxn partitionTxn, IndexEntry<?, String> indexEntryQM ) throws LdapException
118    {
119        IndexEntry<String, String> indexEntry = ( IndexEntry<String, String> ) indexEntryQM;
120
121        Entry entry = indexEntry.getEntry();
122
123        // resuscitate the entry if it has not been and set entry in IndexEntry
124        if ( null == entry )
125        {
126            entry = db.fetch( partitionTxn, indexEntry.getId() );
127
128            if ( null == entry )
129            {
130                // The entry is not anymore present : get out
131                return false;
132            }
133
134            indexEntry.setEntry( entry );
135        }
136
137        /*
138         * Don't make a call here to evaluateWithoutIndex( Entry ) for
139         * code reuse since we do want to set the value on the indexEntry on
140         * matches.
141         */
142
143        // get the attribute
144        Attribute attr = entry.get( attributeType );
145
146        // if the attribute exists and the pattern matches return true
147        if ( attr != null )
148        {
149            /*
150             * Cycle through the attribute values testing normalized version
151             * obtained from using the substring matching rule's normalizer.
152             * The test uses the comparator obtained from the appropriate
153             * substring matching rule.
154             */
155            if ( attr.isHumanReadable() )
156            {
157                for ( Value value : attr )
158                {
159                    String strValue = value.getString();
160                    String normalizedValue = attr.getAttributeType().getEquality().getNormalizer().normalize( strValue );
161
162                    // Once match is found cleanup and return true
163                    if ( regex.matcher( normalizedValue ).matches() )
164                    {
165                        // before returning we set the normalized value
166                        indexEntry.setKey( strValue );
167                        return true;
168                    }
169                }
170            }
171            else
172            {
173                // Slightly more complex. We won't be able to use a regex to check
174                // the value.
175                for ( Value value : attr )
176                {
177                    byte[] byteValue = value.getBytes();
178
179                    // Once match is found cleanup and return true
180                    // @TODO : implement this check.
181                    /*
182                    if ( check( byteValue ) )
183                    {
184                        // before returning we set the normalized value
185                        indexEntry.setKey( byteValue );
186                        return true;
187                    }
188                    */
189                }
190            }
191
192            // Fall through as we didn't find any matching value for this attribute.
193            // We will have to check in the potential descendant, if any.
194        }
195
196        // If we do not have the attribute, loop through the descendant
197        // May be the node Attribute has descendant ?
198        if ( schemaManager.getAttributeTypeRegistry().hasDescendants( attributeType ) )
199        {
200            // TODO check to see if descendant handling is necessary for the
201            // index so we can match properly even when for example a name
202            // attribute is used instead of more specific commonName
203            Iterator<AttributeType> descendants = schemaManager.getAttributeTypeRegistry().descendants( attributeType );
204
205            while ( descendants.hasNext() )
206            {
207                AttributeType descendant = descendants.next();
208
209                attr = entry.get( descendant );
210
211                if ( null != attr )
212                {
213
214                    /*
215                     * Cycle through the attribute values testing normalized version
216                     * obtained from using the substring matching rule's normalizer.
217                     * The test uses the comparator obtained from the appropriate
218                     * substring matching rule.
219                     */
220                    for ( Value value : attr )
221                    {
222                        String strValue = value.getString();
223                        String normalizedValue = attr.getAttributeType().getEquality().getNormalizer().normalize( strValue );
224
225                        // Once match is found cleanup and return true
226                        if ( regex.matcher( normalizedValue ).matches() )
227                        {
228                            // before returning we set the normalized value
229                            indexEntry.setKey( strValue );
230                            return true;
231                        }
232                    }
233                }
234            }
235        }
236
237        // we fell through so a match was not found - assertion was false.
238        return false;
239    }
240
241
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    public boolean evaluate( Entry entry ) throws LdapException
247    {
248        // get the attribute
249        Attribute attr = entry.get( attributeType );
250
251        // if the attribute exists and the pattern matches return true
252        if ( attr != null )
253        {
254            /*
255             * Cycle through the attribute values testing normalized version
256             * obtained from using the substring matching rule's normalizer.
257             * The test uses the comparator obtained from the appropriate
258             * substring matching rule.
259             */
260            for ( Value value : attr )
261            {
262                String strValue = value.getString();
263
264                // Once match is found cleanup and return true
265                if ( regex.matcher( strValue ).matches() )
266                {
267                    return true;
268                }
269            }
270
271            // Fall through as we didn't find any matching value for this attribute.
272            // We will have to check in the potential descendant, if any.
273        }
274
275        // If we do not have the attribute, loop through the descendant
276        // May be the node Attribute has descendant ?
277        if ( schemaManager.getAttributeTypeRegistry().hasDescendants( attributeType ) )
278        {
279            // TODO check to see if descendant handling is necessary for the
280            // index so we can match properly even when for example a name
281            // attribute is used instead of more specific commonName
282            Iterator<AttributeType> descendants = schemaManager.getAttributeTypeRegistry().descendants( attributeType );
283
284            while ( descendants.hasNext() )
285            {
286                AttributeType descendant = descendants.next();
287
288                attr = entry.get( descendant );
289
290                if ( null != attr )
291                {
292
293                    /*
294                     * Cycle through the attribute values testing normalized version
295                     * obtained from using the substring matching rule's normalizer.
296                     * The test uses the comparator obtained from the appropriate
297                     * substring matching rule.
298                     */
299                    for ( Value value : attr )
300                    {
301                        String strValue = value.getString();
302
303                        // Once match is found cleanup and return true
304                        if ( regex.matcher( strValue ).matches() )
305                        {
306                            return true;
307                        }
308                    }
309                }
310            }
311        }
312
313        // we fell through so a match was not found - assertion was false.
314        return false;
315    }
316
317
318    public Pattern getPattern()
319    {
320        return regex;
321    }
322
323
324    /**
325     * {@inheritDoc}
326     */
327    @Override
328    public SubstringNode getExpression()
329    {
330        return node;
331    }
332
333
334    /**
335     * @see Object#toString()
336     */
337    @Override
338    public String toString( String tabs )
339    {
340        StringBuilder sb = new StringBuilder();
341
342        sb.append( tabs ).append( "SubstringEvaluator : " ).append( node ).append( "\n" );
343
344        return sb.toString();
345    }
346
347
348    /**
349     * @see Object#toString()
350     */
351    @Override
352    public String toString()
353    {
354        return toString( "" );
355    }
356}