View Javadoc
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.HashSet;
24  import java.util.Set;
25  
26  import org.apache.directory.api.ldap.model.cursor.Cursor;
27  import org.apache.directory.api.ldap.model.cursor.CursorException;
28  import org.apache.directory.api.ldap.model.entry.Entry;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
31  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
32  import org.apache.directory.api.ldap.model.filter.AndNode;
33  import org.apache.directory.api.ldap.model.filter.ExprNode;
34  import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
35  import org.apache.directory.api.ldap.model.filter.ScopeNode;
36  import org.apache.directory.api.ldap.model.message.AliasDerefMode;
37  import org.apache.directory.api.ldap.model.message.SearchScope;
38  import org.apache.directory.api.ldap.model.name.Dn;
39  import org.apache.directory.api.ldap.model.schema.SchemaManager;
40  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
41  import org.apache.directory.server.core.api.partition.Partition;
42  import org.apache.directory.server.core.api.partition.PartitionTxn;
43  import org.apache.directory.server.core.partition.impl.btree.IndexCursorAdaptor;
44  import org.apache.directory.server.i18n.I18n;
45  import org.apache.directory.server.xdbm.IndexEntry;
46  import org.apache.directory.server.xdbm.Store;
47  import org.apache.directory.server.xdbm.search.Evaluator;
48  import org.apache.directory.server.xdbm.search.Optimizer;
49  import org.apache.directory.server.xdbm.search.PartitionSearchResult;
50  import org.apache.directory.server.xdbm.search.SearchEngine;
51  import org.apache.directory.server.xdbm.search.evaluator.BaseLevelScopeEvaluator;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * Given a search filter and a scope the search engine identifies valid
58   * candidate entries returning their ids.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   */
62  public class DefaultSearchEngine implements SearchEngine
63  {
64      /** The logger */
65      private static final Logger LOG = LoggerFactory.getLogger( DefaultSearchEngine.class );
66  
67      /** the Optimizer used by this DefaultSearchEngine */
68      private final Optimizer optimizer;
69  
70      /** the Database this DefaultSearchEngine operates on */
71      private final Store db;
72  
73      /** creates Cursors over entries satisfying filter expressions */
74      private final CursorBuilder cursorBuilder;
75  
76      /** creates evaluators which check to see if candidates satisfy a filter expression */
77      private final EvaluatorBuilder evaluatorBuilder;
78  
79  
80      // ------------------------------------------------------------------------
81      // C O N S T R U C T O R S
82      // ------------------------------------------------------------------------
83  
84      /**
85       * Creates a DefaultSearchEngine for searching a Database without setting
86       * up the database.
87       * @param db the btree based partition
88       * @param cursorBuilder an expression cursor builder
89       * @param evaluatorBuilder an expression evaluator builder
90       * @param optimizer an optimizer to use during search
91       */
92      public DefaultSearchEngine( Store db, CursorBuilder cursorBuilder,
93          EvaluatorBuilder evaluatorBuilder, Optimizer optimizer )
94      {
95          this.db = db;
96          this.optimizer = optimizer;
97          this.cursorBuilder = cursorBuilder;
98          this.evaluatorBuilder = evaluatorBuilder;
99      }
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         PartitionSearchResultPartitionSearchResult.html#PartitionSearchResult">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 }