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.evaluator;
021
022
023import net.sf.ehcache.Element;
024
025import org.apache.directory.api.ldap.model.entry.Entry;
026import org.apache.directory.api.ldap.model.exception.LdapException;
027import org.apache.directory.api.ldap.model.filter.ScopeNode;
028import org.apache.directory.api.ldap.model.message.SearchScope;
029import org.apache.directory.api.ldap.model.name.Dn;
030import org.apache.directory.server.core.api.partition.Partition;
031import org.apache.directory.server.core.api.partition.PartitionTxn;
032import org.apache.directory.server.i18n.I18n;
033import org.apache.directory.server.xdbm.IndexEntry;
034import org.apache.directory.server.xdbm.ParentIdAndRdn;
035import org.apache.directory.server.xdbm.Store;
036import org.apache.directory.server.xdbm.search.Evaluator;
037
038
039/**
040 * Evaluates ScopeNode assertions with subtree scope on candidates using an
041 * entry database.
042 * 
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045public class SubtreeScopeEvaluator implements Evaluator<ScopeNode>
046{
047    /** The ScopeNode containing initial search scope constraints */
048    private final ScopeNode node;
049
050    /** The entry identifier of the scope base */
051    private final String baseId;
052
053    /** 
054     * Whether or not to accept all candidates.  If this evaluator's baseId is
055     * set to the context entry's id, then obviously all candidates will be 
056     * subordinate to this root ancestor or in subtree scope.  This check is 
057     * done on  initialization and used there after.  One reason we need do 
058     * this is because the subtree scope index (sub level index) does not map 
059     * the values for the context entry id to it's subordinates since it would 
060     * have to include all entries.  This is a waste of space and lookup time
061     * since we know all entries will be subordinates in this case.
062     */
063    private final boolean baseIsContextEntry;
064
065    /** True if the scope requires alias dereferencing while searching */
066    private final boolean dereferencing;
067
068    /** The entry database/store */
069    private final Store db;
070
071
072    /**
073     * Creates a subtree scope node evaluator for search expressions.
074     *
075     * @param partitionTxn The transaction to use
076     * @param db the database used to evaluate scope node
077     * @param node the scope node
078     * @throws LdapException on db access failure
079     */
080    public SubtreeScopeEvaluator( PartitionTxn partitionTxn, Store db, ScopeNode node ) throws LdapException
081    {
082        this.db = db;
083        this.node = node;
084
085        if ( node.getScope() != SearchScope.SUBTREE )
086        {
087            throw new IllegalStateException( I18n.err( I18n.ERR_727 ) );
088        }
089
090        baseId = node.getBaseId();
091        
092        baseIsContextEntry = db.getSuffixId( partitionTxn ) == baseId;
093
094        dereferencing = node.getDerefAliases().isDerefInSearching() || node.getDerefAliases().isDerefAlways();
095    }
096
097
098    /**
099     * Tells if a candidate is a descendant of the base ID. We have to fetch all 
100     * the parentIdAndRdn up to the baseId. If we terminate on the context entry without 
101     * having found the baseId, then the candidate is not a descendant.
102     */
103    private boolean isDescendant( PartitionTxn partitionTxn, String candidateId ) throws LdapException
104    {
105        String tmp = candidateId;
106
107        while ( true )
108        {
109            ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, tmp );
110
111            if ( parentIdAndRdn == null )
112            {
113                return false;
114            }
115
116            tmp = parentIdAndRdn.getParentId();
117
118            if ( tmp.equals( Partition.ROOT_ID ) )
119            {
120                return false;
121            }
122
123            if ( tmp.equals( baseId ) )
124            {
125                return true;
126            }
127        }
128    }
129
130
131    /**
132     * {@inheritDoc}
133     */
134    @Override
135    public boolean evaluate( PartitionTxn partitionTxn, IndexEntry<?, String> indexEntry ) throws LdapException
136    {
137        String id = indexEntry.getId();
138        Entry entry = indexEntry.getEntry();
139
140        // Fetch the entry
141        if ( null == entry )
142        {
143            entry = db.fetch( partitionTxn, indexEntry.getId() );
144
145            if ( null == entry )
146            {
147                // The entry is not anymore present : get out
148                return false;
149            }
150
151            indexEntry.setEntry( entry );
152        }
153
154        /*
155         * This condition catches situations where the candidate is equal to 
156         * the base entry and when the base entry is the context entry.  Note
157         * we do not store a mapping in the subtree index of the context entry
158         * to all it's subordinates since that would be the entire set of 
159         * entries in the db.
160         */
161        boolean isDescendant = baseIsContextEntry || baseId.equals( id ) || isDescendant( partitionTxn, id );
162
163        /*
164         * The candidate id could be any entry in the db.  If search
165         * dereferencing is not enabled then we return the results of the
166         * descendant test.
167         */
168        if ( !isDereferencing() )
169        {
170            return isDescendant;
171        }
172
173        /*
174         * From here down alias dereferencing is enabled.  We determine if the
175         * candidate id is an alias, if so we reject it since aliases should
176         * not be returned.
177         */
178        if ( db.getAliasCache() != null )
179        {
180            Element element = db.getAliasCache().get( id );
181            
182            if ( ( element != null ) && ( element.getValue() != null ) )
183            {
184                Dn dn = ( Dn ) element.getValue();
185
186                return false;
187            }
188        }
189        else if ( null != db.getAliasIndex().reverseLookup( partitionTxn, id ) )
190        {
191            return false;
192        }
193
194        /*
195         * The candidate is NOT an alias at this point.  So if it is a
196         * descendant we just return true since it is in normal subtree scope.
197         */
198        if ( isDescendant )
199        {
200            return true;
201        }
202
203        /*
204         * At this point the candidate is not a descendant and it is not an
205         * alias.  We need to check if the candidate is in extended subtree
206         * scope by performing a lookup on the subtree alias index.  This index
207         * stores a tuple mapping the baseId to the ids of objects brought
208         * into subtree scope of the base by an alias:
209         *
210         * ( baseId, aliasedObjId )
211         *
212         * If the candidate id is an object brought into subtree scope then
213         * the lookup returns true accepting the candidate.  Otherwise the
214         * candidate is rejected with a false return because it is not in scope.
215         */
216        return db.getSubAliasIndex().forward( partitionTxn, baseId, id );
217    }
218
219
220    /**
221     * {@inheritDoc}
222     */
223    @Override
224    public boolean evaluate( Entry candidate ) throws LdapException
225    {
226        throw new UnsupportedOperationException( I18n.err( I18n.ERR_721 ) );
227    }
228
229
230    /**
231     * {@inheritDoc}
232     */
233    @Override
234    public ScopeNode getExpression()
235    {
236        return node;
237    }
238
239
240    /**
241     * @return The base ID
242     */
243    public String getBaseId()
244    {
245        return baseId;
246    }
247
248
249    /**
250     * @return <tt>true</tt> if dereferencing
251     */
252    public boolean isDereferencing()
253    {
254        return dereferencing;
255    }
256
257
258    /**
259     * @see Object#toString()
260     */
261    public String toString( String tabs )
262    {
263        StringBuilder sb = new StringBuilder();
264
265        sb.append( tabs ).append( "SubstreeScopeEvaluator : " ).append( node ).append( '\n' );
266
267        return sb.toString();
268    }
269
270
271    /**
272     * @see Object#toString()
273     */
274    public String toString()
275    {
276        return toString( "" );
277    }
278}