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.impl; 021 022 023import java.util.HashSet; 024import java.util.Set; 025 026import net.sf.ehcache.Element; 027 028import org.apache.directory.api.ldap.model.cursor.Cursor; 029import org.apache.directory.api.ldap.model.cursor.CursorException; 030import org.apache.directory.api.ldap.model.entry.Entry; 031import org.apache.directory.api.ldap.model.exception.LdapException; 032import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException; 033import org.apache.directory.api.ldap.model.exception.LdapOtherException; 034import org.apache.directory.api.ldap.model.filter.AndNode; 035import org.apache.directory.api.ldap.model.filter.ExprNode; 036import org.apache.directory.api.ldap.model.filter.ObjectClassNode; 037import org.apache.directory.api.ldap.model.filter.ScopeNode; 038import org.apache.directory.api.ldap.model.message.AliasDerefMode; 039import org.apache.directory.api.ldap.model.message.SearchScope; 040import org.apache.directory.api.ldap.model.name.Dn; 041import org.apache.directory.api.ldap.model.schema.SchemaManager; 042import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 043import org.apache.directory.server.core.api.partition.Partition; 044import org.apache.directory.server.core.api.partition.PartitionTxn; 045import org.apache.directory.server.core.partition.impl.btree.IndexCursorAdaptor; 046import org.apache.directory.server.i18n.I18n; 047import org.apache.directory.server.xdbm.IndexEntry; 048import org.apache.directory.server.xdbm.Store; 049import org.apache.directory.server.xdbm.search.Evaluator; 050import org.apache.directory.server.xdbm.search.Optimizer; 051import org.apache.directory.server.xdbm.search.PartitionSearchResult; 052import org.apache.directory.server.xdbm.search.SearchEngine; 053import org.apache.directory.server.xdbm.search.evaluator.BaseLevelScopeEvaluator; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057 058/** 059 * Given a search filter and a scope the search engine identifies valid 060 * candidate entries returning their ids. 061 * 062 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 063 */ 064public class DefaultSearchEngine implements SearchEngine 065{ 066 /** The logger */ 067 private static final Logger LOG = LoggerFactory.getLogger( DefaultSearchEngine.class ); 068 069 /** the Optimizer used by this DefaultSearchEngine */ 070 private final Optimizer optimizer; 071 072 /** the Database this DefaultSearchEngine operates on */ 073 private final Store db; 074 075 /** creates Cursors over entries satisfying filter expressions */ 076 private final CursorBuilder cursorBuilder; 077 078 /** creates evaluators which check to see if candidates satisfy a filter expression */ 079 private final EvaluatorBuilder evaluatorBuilder; 080 081 082 // ------------------------------------------------------------------------ 083 // C O N S T R U C T O R S 084 // ------------------------------------------------------------------------ 085 086 /** 087 * Creates a DefaultSearchEngine for searching a Database without setting 088 * up the database. 089 * @param db the btree based partition 090 * @param cursorBuilder an expression cursor builder 091 * @param evaluatorBuilder an expression evaluator builder 092 * @param optimizer an optimizer to use during search 093 */ 094 public DefaultSearchEngine( Store db, CursorBuilder cursorBuilder, 095 EvaluatorBuilder evaluatorBuilder, Optimizer optimizer ) 096 { 097 this.db = db; 098 this.optimizer = optimizer; 099 this.cursorBuilder = cursorBuilder; 100 this.evaluatorBuilder = evaluatorBuilder; 101 } 102 103 104 /** 105 * Gets the optimizer for this DefaultSearchEngine. 106 * 107 * @return the optimizer 108 */ 109 @Override 110 public Optimizer getOptimizer() 111 { 112 return optimizer; 113 } 114 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override 120 public PartitionSearchResult computeResult( PartitionTxn partitionTxn, SchemaManager schemaManager, 121 SearchOperationContext searchContext ) throws LdapException 122 { 123 SearchScope scope = searchContext.getScope(); 124 Dn baseDn = searchContext.getDn(); 125 AliasDerefMode aliasDerefMode = searchContext.getAliasDerefMode(); 126 ExprNode filter = searchContext.getFilter(); 127 128 // Compute the UUID of the baseDN entry 129 String baseId = db.getEntryId( partitionTxn, baseDn ); 130 131 // Prepare the instance containing the search result 132 PartitionSearchResult searchResult = new PartitionSearchResult( schemaManager ); 133 Set<IndexEntry<String, String>> resultSet = new HashSet<>(); 134 135 // Check that we have an entry, otherwise we can immediately get out 136 if ( baseId == null ) 137 { 138 if ( ( ( Partition ) db ).getSuffixDn().equals( baseDn ) ) 139 { 140 // The context entry is not created yet, return an empty result 141 searchResult.setResultSet( resultSet ); 142 143 return searchResult; 144 } 145 else 146 { 147 // The search base doesn't exist 148 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_648, baseDn ) ); 149 } 150 } 151 152 // -------------------------------------------------------------------- 153 // Determine the effective base with aliases 154 // -------------------------------------------------------------------- 155 Dn aliasedBase = null; 156 157 158 if ( db.getAliasCache() != null ) 159 { 160 Element aliasBaseElement = db.getAliasCache().get( baseId ); 161 162 if ( aliasBaseElement != null ) 163 { 164 aliasedBase = ( Dn ) ( aliasBaseElement ).getObjectValue(); 165 } 166 } 167 else 168 { 169 aliasedBase = db.getAliasIndex().reverseLookup( partitionTxn, baseId ); 170 } 171 172 Dn effectiveBase = baseDn; 173 String effectiveBaseId = baseId; 174 175 if ( ( aliasedBase != null ) && aliasDerefMode.isDerefFindingBase() ) 176 { 177 /* 178 * If the base is an alias and alias dereferencing does occur on 179 * finding the base, or always then we set the effective base to the alias target 180 * got from the alias index. 181 */ 182 if ( !aliasedBase.isSchemaAware() ) 183 { 184 effectiveBase = new Dn( schemaManager, aliasedBase ); 185 } 186 else 187 { 188 effectiveBase = aliasedBase; 189 } 190 191 effectiveBaseId = db.getEntryId( partitionTxn, effectiveBase ); 192 } 193 194 // -------------------------------------------------------------------- 195 // Specifically Handle Object Level Scope 196 // -------------------------------------------------------------------- 197 if ( scope == SearchScope.OBJECT ) 198 { 199 IndexEntry<String, String> indexEntry = new IndexEntry<>(); 200 indexEntry.setId( effectiveBaseId ); 201 202 // Fetch the entry, as we have only one 203 Entry entry = db.fetch( partitionTxn, indexEntry.getId(), effectiveBase ); 204 205 Evaluator<? extends ExprNode> evaluator; 206 207 if ( filter instanceof ObjectClassNode ) 208 { 209 ScopeNode node = new ScopeNode( aliasDerefMode, effectiveBase, effectiveBaseId, scope ); 210 evaluator = new BaseLevelScopeEvaluator<>( db, node ); 211 } 212 else 213 { 214 optimizer.annotate( partitionTxn, filter ); 215 evaluator = evaluatorBuilder.build( partitionTxn, filter ); 216 217 // Special case if the filter selects no candidate 218 if ( evaluator == null ) 219 { 220 ScopeNode node = new ScopeNode( aliasDerefMode, effectiveBase, effectiveBaseId, scope ); 221 evaluator = new BaseLevelScopeEvaluator<>( db, node ); 222 } 223 } 224 225 indexEntry.setEntry( entry ); 226 resultSet.add( indexEntry ); 227 228 searchResult.setEvaluator( evaluator ); 229 searchResult.setResultSet( resultSet ); 230 231 return searchResult; 232 } 233 234 // This is not a BaseObject scope search. 235 236 // Add the scope node using the effective base to the filter 237 ExprNode root; 238 239 if ( filter instanceof ObjectClassNode ) 240 { 241 root = new ScopeNode( aliasDerefMode, effectiveBase, effectiveBaseId, scope ); 242 } 243 else 244 { 245 root = new AndNode(); 246 ( ( AndNode ) root ).getChildren().add( filter ); 247 ExprNode node = new ScopeNode( aliasDerefMode, effectiveBase, effectiveBaseId, scope ); 248 ( ( AndNode ) root ).getChildren().add( node ); 249 } 250 251 // Annotate the node with the optimizer and return search enumeration. 252 optimizer.annotate( partitionTxn, root ); 253 Evaluator<? extends ExprNode> evaluator = evaluatorBuilder.build( partitionTxn, root ); 254 255 Set<String> uuidSet = new HashSet<>(); 256 searchResult.setAliasDerefMode( aliasDerefMode ); 257 searchResult.setCandidateSet( uuidSet ); 258 259 long nbResults = cursorBuilder.build( partitionTxn, root, searchResult ); 260 261 LOG.debug( "Nb results : {} for filter : {}", nbResults, root ); 262 263 if ( nbResults < Long.MAX_VALUE ) 264 { 265 for ( String uuid : uuidSet ) 266 { 267 IndexEntry<String, String> indexEntry = new IndexEntry<>(); 268 indexEntry.setId( uuid ); 269 resultSet.add( indexEntry ); 270 } 271 } 272 else 273 { 274 // Full scan : use the MasterTable 275 Cursor<IndexEntry<String, String>> cursor = new IndexCursorAdaptor( partitionTxn, db.getMasterTable().cursor(), true ); 276 277 try 278 { 279 while ( cursor.next() ) 280 { 281 IndexEntry<String, String> indexEntry = cursor.get(); 282 283 // Here, the indexEntry contains a <UUID, Entry> tuple. Convert it to <UUID, UUID> 284 IndexEntry<String, String> forwardIndexEntry = new IndexEntry<>(); 285 forwardIndexEntry.setKey( indexEntry.getKey() ); 286 forwardIndexEntry.setId( indexEntry.getKey() ); 287 forwardIndexEntry.setEntry( null ); 288 289 resultSet.add( forwardIndexEntry ); 290 } 291 } 292 catch ( CursorException ce ) 293 { 294 throw new LdapOtherException( ce.getMessage(), ce ); 295 } 296 } 297 298 searchResult.setEvaluator( evaluator ); 299 searchResult.setResultSet( resultSet ); 300 301 return searchResult; 302 } 303 304 305 /** 306 * {@inheritDoc} 307 */ 308 @Override 309 public Evaluator<? extends ExprNode> evaluator( PartitionTxn partitionTxn, ExprNode filter ) throws LdapException 310 { 311 return evaluatorBuilder.build( partitionTxn, filter ); 312 } 313}