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 * http://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.server.xdbm.search.impl;
21
22
23 import java.util.Iterator;
24 import java.util.regex.Pattern;
25
26 import org.apache.directory.server.schema.registries.Registries;
27 import org.apache.directory.server.xdbm.Index;
28 import org.apache.directory.server.xdbm.IndexEntry;
29 import org.apache.directory.server.xdbm.Store;
30 import org.apache.directory.server.xdbm.search.Evaluator;
31 import org.apache.directory.server.core.cursor.Cursor;
32 import org.apache.directory.server.core.entry.ServerEntry;
33 import org.apache.directory.server.core.entry.ServerAttribute;
34 import org.apache.directory.shared.ldap.filter.SubstringNode;
35 import org.apache.directory.shared.ldap.schema.AttributeType;
36 import org.apache.directory.shared.ldap.schema.MatchingRule;
37 import org.apache.directory.shared.ldap.schema.Normalizer;
38 import org.apache.directory.shared.ldap.schema.NoOpNormalizer;
39 import org.apache.directory.shared.ldap.entry.Value;
40
41
42 /**
43 * Evaluates substring filter assertions on an entry.
44 *
45 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
46 * @version $Rev: 658798 $
47 */
48 public class SubstringEvaluator implements Evaluator<SubstringNode, ServerEntry>
49 {
50 /** Database used while evaluating candidates */
51 private final Store<ServerEntry> db;
52
53 /** Oid Registry used to translate attributeIds to OIDs */
54 private final Registries registries;
55
56 /** The Substring expression */
57 private final SubstringNode node;
58
59 /** The regular expression generated for the SubstringNode pattern */
60 private final Pattern regex;
61
62 private final AttributeType type;
63
64 private final Normalizer normalizer;
65
66 private final Index<String,ServerEntry> idx;
67
68
69 /**
70 * Creates a new SubstringEvaluator for substring expressions.
71 *
72 * @param node the substring expression node
73 * @param db the database this evaluator uses
74 * @param registries the set of registries
75 * @throws Exception if there are failures accessing resources and the db
76 */
77 public SubstringEvaluator( SubstringNode node, Store<ServerEntry> db, Registries registries ) throws Exception
78 {
79 this.db = db;
80 this.node = node;
81 this.registries = registries;
82
83 String oid = registries.getOidRegistry().getOid( node.getAttribute() );
84 type = registries.getAttributeTypeRegistry().lookup( oid );
85
86 MatchingRule rule = type.getSubstr();
87
88 if ( rule == null )
89 {
90 rule = type.getEquality();
91 }
92
93 if ( rule != null )
94 {
95 normalizer = rule.getNormalizer();
96 }
97 else
98 {
99 normalizer = new NoOpNormalizer();
100 }
101
102 // compile the regular expression to search for a matching attribute
103 regex = node.getRegex( normalizer );
104
105 if ( db.hasUserIndexOn( node.getAttribute() ) )
106 {
107 //noinspection unchecked
108 idx = ( Index<String,ServerEntry> ) db.getUserIndex( node.getAttribute() );
109 }
110 else
111 {
112 idx = null;
113 }
114 }
115
116
117 public boolean evaluate( IndexEntry<?,ServerEntry> indexEntry ) throws Exception
118 {
119
120 if ( idx == null )
121 {
122 //noinspection unchecked
123 return evaluateWithoutIndex( ( IndexEntry<String,ServerEntry> ) indexEntry );
124 }
125 else
126 {
127 return evaluateWithIndex( indexEntry );
128 }
129 }
130
131
132 public boolean evaluate( Long id ) throws Exception
133 {
134
135 if ( idx == null )
136 {
137 //noinspection unchecked
138 return evaluateWithoutIndex( id );
139 }
140 else
141 {
142 return evaluateWithIndex( id );
143 }
144 }
145
146
147 public boolean evaluate( ServerEntry entry ) throws Exception
148 {
149
150 if ( idx == null )
151 {
152 //noinspection unchecked
153 return evaluateWithoutIndex( entry );
154 }
155 else
156 {
157 return evaluateWithIndex( entry );
158 }
159 }
160
161
162 public Pattern getPattern()
163 {
164 return regex;
165 }
166
167
168 public SubstringNode getExpression()
169 {
170 return node;
171 }
172
173
174 private boolean evaluateWithIndex( IndexEntry<?,ServerEntry> indexEntry ) throws Exception
175 {
176 /*
177 * Note that this is using the reverse half of the index giving a
178 * considerable performance improvement on this kind of operation.
179 * Otherwise we would have to scan the entire index if there were
180 * no reverse lookups.
181 */
182 Cursor<IndexEntry<String,ServerEntry>> entries = idx.reverseCursor( indexEntry.getId() );
183
184 // cycle through the attribute values testing for a match
185 while ( entries.next() )
186 {
187 IndexEntry rec = entries.get();
188
189 // once match is found cleanup and return true
190 if ( regex.matcher( ( String ) rec.getValue() ).matches() )
191 {
192 entries.close();
193 return true;
194 }
195 }
196
197 // we fell through so a match was not found - assertion was false.
198 return false;
199 }
200
201
202 @SuppressWarnings( { "UnusedDeclaration" } )
203 private boolean evaluateWithIndex( ServerEntry entry ) throws Exception
204 {
205 throw new UnsupportedOperationException( "This is too inefficient without getId() on ServerEntry" );
206 }
207
208
209 private boolean evaluateWithIndex( Long 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>> entries = idx.reverseCursor( id );
218
219 // cycle through the attribute values testing for a match
220 while ( entries.next() )
221 {
222 IndexEntry 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( Long 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 ServerAttribute attr = ( ServerAttribute ) 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 ( registries.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 =
285 registries.getAttributeTypeRegistry().descendants( node.getAttribute() );
286
287 while ( descendants.hasNext() )
288 {
289 AttributeType descendant = descendants.next();
290
291 attr = ( ServerAttribute ) entry.get( descendant );
292
293 if ( null != attr )
294 {
295
296
297 /*
298 * Cycle through the attribute values testing normalized version
299 * obtained from using the substring matching rule's normalizer.
300 * The test uses the comparator obtained from the appropriate
301 * substring matching rule.
302 */
303 for ( Value value : attr )
304 {
305 value.normalize( normalizer );
306 String strValue = ( String ) value.getNormalizedValue();
307
308 // Once match is found cleanup and return true
309 if ( regex.matcher( strValue ).matches() )
310 {
311 return true;
312 }
313 }
314 }
315 }
316 }
317
318 // we fell through so a match was not found - assertion was false.
319 return false;
320 }
321
322
323 // TODO - determine if comaparator and index entry should have the Value
324 // wrapper or the raw normalized value
325 private boolean evaluateWithoutIndex( IndexEntry<String,ServerEntry> indexEntry ) throws Exception
326 {
327 ServerEntry entry = indexEntry.getObject();
328
329 // resuscitate the entry if it has not been and set entry in IndexEntry
330 if ( null == entry )
331 {
332 entry = db.lookup( indexEntry.getId() );
333 indexEntry.setObject( entry );
334 }
335
336 /*
337 * Don't make a call here to evaluateWithoutIndex( ServerEntry ) for
338 * code reuse since we do want to set the value on the indexEntry on
339 * matches.
340 */
341
342 // get the attribute
343 ServerAttribute attr = ( ServerAttribute ) entry.get( type );
344
345 // if the attribute exists and the pattern matches return true
346 if ( attr != null )
347 {
348 /*
349 * Cycle through the attribute values testing normalized version
350 * obtained from using the substring matching rule's normalizer.
351 * The test uses the comparator obtained from the appropriate
352 * substring matching rule.
353 */
354 for ( Value value : attr )
355 {
356 value.normalize( normalizer );
357 String strValue = ( String ) value.getNormalizedValue();
358
359 // Once match is found cleanup and return true
360 if ( regex.matcher( strValue ).matches() )
361 {
362 // before returning we set the normalized value
363 indexEntry.setValue( strValue );
364 return true;
365 }
366 }
367
368 // Fall through as we didn't find any matching value for this attribute.
369 // We will have to check in the potential descendant, if any.
370 }
371
372 // If we do not have the attribute, loop through the descendant
373 // May be the node Attribute has descendant ?
374 if ( registries.getAttributeTypeRegistry().hasDescendants( node.getAttribute() ) )
375 {
376 // TODO check to see if descendant handling is necessary for the
377 // index so we can match properly even when for example a name
378 // attribute is used instead of more specific commonName
379 Iterator<AttributeType> descendants =
380 registries.getAttributeTypeRegistry().descendants( node.getAttribute() );
381
382 while ( descendants.hasNext() )
383 {
384 AttributeType descendant = descendants.next();
385
386 attr = ( ServerAttribute ) entry.get( descendant );
387
388 if ( null != attr )
389 {
390
391
392 /*
393 * Cycle through the attribute values testing normalized version
394 * obtained from using the substring matching rule's normalizer.
395 * The test uses the comparator obtained from the appropriate
396 * substring matching rule.
397 */
398 for ( Value value : attr )
399 {
400 value.normalize( normalizer );
401 String strValue = ( String ) value.getNormalizedValue();
402
403 // Once match is found cleanup and return true
404 if ( regex.matcher( strValue ).matches() )
405 {
406 // before returning we set the normalized value
407 indexEntry.setValue( strValue );
408 return true;
409 }
410 }
411 }
412 }
413 }
414
415 // we fell through so a match was not found - assertion was false.
416 return false;
417 }
418 }