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.server.xdbm.search.impl;
021    
022    
023    import java.util.Iterator;
024    import java.util.regex.Pattern;
025    
026    import org.apache.directory.server.i18n.I18n;
027    import org.apache.directory.server.xdbm.Index;
028    import org.apache.directory.server.xdbm.IndexEntry;
029    import org.apache.directory.server.xdbm.Store;
030    import org.apache.directory.server.xdbm.search.Evaluator;
031    import org.apache.directory.shared.ldap.cursor.Cursor;
032    import org.apache.directory.shared.ldap.entry.EntryAttribute;
033    import org.apache.directory.shared.ldap.entry.ServerEntry;
034    import org.apache.directory.shared.ldap.entry.Value;
035    import org.apache.directory.shared.ldap.filter.SubstringNode;
036    import org.apache.directory.shared.ldap.schema.AttributeType;
037    import org.apache.directory.shared.ldap.schema.MatchingRule;
038    import org.apache.directory.shared.ldap.schema.Normalizer;
039    import org.apache.directory.shared.ldap.schema.SchemaManager;
040    import org.apache.directory.shared.ldap.schema.normalizers.NoOpNormalizer;
041    
042    
043    /**
044     * Evaluates substring filter assertions on an entry.
045     * 
046     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047     * @version $Rev: 927839 $
048     */
049    public class SubstringEvaluator<ID> implements Evaluator<SubstringNode, ServerEntry, ID>
050    {
051        /** Database used while evaluating candidates */
052        private final Store<ServerEntry, ID> db;
053    
054        /** Reference to the SchemaManager */
055        private final SchemaManager schemaManager;
056    
057        /** The Substring expression */
058        private final SubstringNode node;
059    
060        /** The regular expression generated for the SubstringNode pattern */
061        private final Pattern regex;
062    
063        private final AttributeType type;
064    
065        private final Normalizer normalizer;
066    
067        private final Index<String, ServerEntry, ID> idx;
068    
069    
070        /**
071         * Creates a new SubstringEvaluator for substring expressions.
072         *
073         * @param node the substring expression node
074         * @param db the database this evaluator uses
075         * @param registries the set of registries
076         * @throws Exception if there are failures accessing resources and the db
077         */
078        @SuppressWarnings("unchecked")
079        public SubstringEvaluator( SubstringNode node, Store<ServerEntry, ID> db, SchemaManager schemaManager )
080            throws Exception
081        {
082            this.db = db;
083            this.node = node;
084            this.schemaManager = schemaManager;
085    
086            String oid = schemaManager.getAttributeTypeRegistry().getOidByName( node.getAttribute() );
087            type = schemaManager.lookupAttributeTypeRegistry( oid );
088    
089            MatchingRule rule = type.getSubstring();
090    
091            if ( rule == null )
092            {
093                rule = type.getEquality();
094            }
095    
096            if ( rule != null )
097            {
098                normalizer = rule.getNormalizer();
099            }
100            else
101            {
102                normalizer = new NoOpNormalizer( type.getSyntaxOid() );
103            }
104    
105            // compile the regular expression to search for a matching attribute
106            regex = node.getRegex( normalizer );
107    
108            if ( db.hasIndexOn( node.getAttribute() ) )
109            {
110                idx = ( Index<String, ServerEntry, ID> ) db.getIndex( node.getAttribute() );
111            }
112            else
113            {
114                idx = null;
115            }
116        }
117    
118    
119        @SuppressWarnings("unchecked")
120        public boolean evaluate( IndexEntry<?, ServerEntry, ID> indexEntry ) throws Exception
121        {
122    
123            if ( idx == null )
124            {
125                return evaluateWithoutIndex( ( IndexEntry<String, ServerEntry, ID> ) indexEntry );
126            }
127            else
128            {
129                return evaluateWithIndex( indexEntry );
130            }
131        }
132    
133    
134        public boolean evaluateId( ID id ) throws Exception
135        {
136    
137            if ( idx == null )
138            {
139                return evaluateWithoutIndex( id );
140            }
141            else
142            {
143                return evaluateWithIndex( id );
144            }
145        }
146    
147    
148        public boolean evaluateEntry( ServerEntry entry ) throws Exception
149        {
150    
151            if ( idx == null )
152            {
153                //noinspection unchecked
154                return evaluateWithoutIndex( entry );
155            }
156            else
157            {
158                return evaluateWithIndex( entry );
159            }
160        }
161    
162    
163        public Pattern getPattern()
164        {
165            return regex;
166        }
167    
168    
169        public SubstringNode getExpression()
170        {
171            return node;
172        }
173    
174    
175        private boolean evaluateWithIndex( IndexEntry<?, ServerEntry, ID> indexEntry ) throws Exception
176        {
177            /*
178             * Note that this is using the reverse half of the index giving a
179             * considerable performance improvement on this kind of operation.
180             * Otherwise we would have to scan the entire index if there were
181             * no reverse lookups.
182             */
183            Cursor<IndexEntry<String, ServerEntry, ID>> entries = idx.reverseCursor( indexEntry.getId() );
184    
185            // cycle through the attribute values testing for a match
186            while ( entries.next() )
187            {
188                IndexEntry<String, ServerEntry, ID> rec = entries.get();
189    
190                // once match is found cleanup and return true
191                if ( regex.matcher( ( String ) rec.getValue() ).matches() )
192                {
193                    entries.close();
194                    return true;
195                }
196            }
197    
198            // we fell through so a match was not found - assertion was false.
199            return false;
200        }
201    
202    
203        private boolean evaluateWithIndex( ServerEntry entry ) throws Exception
204        {
205            throw new UnsupportedOperationException( I18n.err( I18n.ERR_721 ) );
206        }
207    
208    
209        private boolean evaluateWithIndex( ID id ) throws Exception
210        {
211            /*
212             * Note that this is using the reverse half of the index giving a
213             * considerable performance improvement on this kind of operation.
214             * Otherwise we would have to scan the entire index if there were
215             * no reverse lookups.
216             */
217            Cursor<IndexEntry<String, ServerEntry, ID>> entries = idx.reverseCursor( id );
218    
219            // cycle through the attribute values testing for a match
220            while ( entries.next() )
221            {
222                IndexEntry<String, ServerEntry, ID> rec = entries.get();
223    
224                // once match is found cleanup and return true
225                if ( regex.matcher( ( String ) rec.getValue() ).matches() )
226                {
227                    entries.close();
228                    return true;
229                }
230            }
231    
232            // we fell through so a match was not found - assertion was false.
233            return false;
234        }
235    
236    
237        // TODO - determine if comaparator and index entry should have the Value
238        // wrapper or the raw normalized value
239        private boolean evaluateWithoutIndex( ID id ) throws Exception
240        {
241            return evaluateWithoutIndex( db.lookup( id ) );
242        }
243    
244    
245        // TODO - determine if comaparator and index entry should have the Value
246        // wrapper or the raw normalized value
247        private boolean evaluateWithoutIndex( ServerEntry entry ) throws Exception
248        {
249            // get the attribute
250            EntryAttribute attr = entry.get( type );
251    
252            // if the attribute exists and the pattern matches return true
253            if ( attr != null )
254            {
255                /*
256                 * Cycle through the attribute values testing normalized version
257                 * obtained from using the substring matching rule's normalizer.
258                 * The test uses the comparator obtained from the appropriate
259                 * substring matching rule.
260                 */
261                for ( Value<?> value : attr )
262                {
263                    value.normalize( normalizer );
264                    String strValue = ( String ) value.getNormalizedValue();
265    
266                    // Once match is found cleanup and return true
267                    if ( regex.matcher( strValue ).matches() )
268                    {
269                        return true;
270                    }
271                }
272    
273                // Fall through as we didn't find any matching value for this attribute.
274                // We will have to check in the potential descendant, if any.
275            }
276    
277            // If we do not have the attribute, loop through the descendant
278            // May be the node Attribute has descendant ?
279            if ( schemaManager.getAttributeTypeRegistry().hasDescendants( node.getAttribute() ) )
280            {
281                // TODO check to see if descendant handling is necessary for the
282                // index so we can match properly even when for example a name
283                // attribute is used instead of more specific commonName
284                Iterator<AttributeType> descendants = schemaManager.getAttributeTypeRegistry().descendants(
285                    node.getAttribute() );
286    
287                while ( descendants.hasNext() )
288                {
289                    AttributeType descendant = descendants.next();
290    
291                    attr = entry.get( descendant );
292    
293                    if ( null != attr )
294                    {
295    
296                        /*
297                         * Cycle through the attribute values testing normalized version
298                         * obtained from using the substring matching rule's normalizer.
299                         * The test uses the comparator obtained from the appropriate
300                         * substring matching rule.
301                         */
302                        for ( Value<?> value : attr )
303                        {
304                            value.normalize( normalizer );
305                            String strValue = ( String ) value.getNormalizedValue();
306    
307                            // Once match is found cleanup and return true
308                            if ( regex.matcher( strValue ).matches() )
309                            {
310                                return true;
311                            }
312                        }
313                    }
314                }
315            }
316    
317            // we fell through so a match was not found - assertion was false.
318            return false;
319        }
320    
321    
322        // TODO - determine if comaparator and index entry should have the Value
323        // wrapper or the raw normalized value
324        private boolean evaluateWithoutIndex( IndexEntry<String, ServerEntry, ID> indexEntry ) throws Exception
325        {
326            ServerEntry entry = indexEntry.getObject();
327    
328            // resuscitate the entry if it has not been and set entry in IndexEntry
329            if ( null == entry )
330            {
331                entry = db.lookup( indexEntry.getId() );
332                indexEntry.setObject( entry );
333            }
334    
335            /*
336             * Don't make a call here to evaluateWithoutIndex( ServerEntry ) for
337             * code reuse since we do want to set the value on the indexEntry on
338             * matches.
339             */
340    
341            // get the attribute
342            EntryAttribute attr = entry.get( type );
343    
344            // if the attribute exists and the pattern matches return true
345            if ( attr != null )
346            {
347                /*
348                 * Cycle through the attribute values testing normalized version
349                 * obtained from using the substring matching rule's normalizer.
350                 * The test uses the comparator obtained from the appropriate
351                 * substring matching rule.
352                 */
353                for ( Value<?> value : attr )
354                {
355                    value.normalize( normalizer );
356                    String strValue = ( String ) value.getNormalizedValue();
357    
358                    // Once match is found cleanup and return true
359                    if ( regex.matcher( strValue ).matches() )
360                    {
361                        // before returning we set the normalized value
362                        indexEntry.setValue( strValue );
363                        return true;
364                    }
365                }
366    
367                // Fall through as we didn't find any matching value for this attribute.
368                // We will have to check in the potential descendant, if any.
369            }
370    
371            // If we do not have the attribute, loop through the descendant
372            // May be the node Attribute has descendant ?
373            if ( schemaManager.getAttributeTypeRegistry().hasDescendants( node.getAttribute() ) )
374            {
375                // TODO check to see if descendant handling is necessary for the
376                // index so we can match properly even when for example a name
377                // attribute is used instead of more specific commonName
378                Iterator<AttributeType> descendants = schemaManager.getAttributeTypeRegistry().descendants(
379                    node.getAttribute() );
380    
381                while ( descendants.hasNext() )
382                {
383                    AttributeType descendant = descendants.next();
384    
385                    attr = entry.get( descendant );
386    
387                    if ( null != attr )
388                    {
389    
390                        /*
391                         * Cycle through the attribute values testing normalized version
392                         * obtained from using the substring matching rule's normalizer.
393                         * The test uses the comparator obtained from the appropriate
394                         * substring matching rule.
395                         */
396                        for ( Value<?> value : attr )
397                        {
398                            value.normalize( normalizer );
399                            String strValue = ( String ) value.getNormalizedValue();
400    
401                            // Once match is found cleanup and return true
402                            if ( regex.matcher( strValue ).matches() )
403                            {
404                                // before returning we set the normalized value
405                                indexEntry.setValue( strValue );
406                                return true;
407                            }
408                        }
409                    }
410                }
411            }
412    
413            // we fell through so a match was not found - assertion was false.
414            return false;
415        }
416    }