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}