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 }