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 }