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}