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