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.evaluator;
21  
22  
23  import org.apache.directory.api.ldap.model.entry.Entry;
24  import org.apache.directory.api.ldap.model.exception.LdapException;
25  import org.apache.directory.api.ldap.model.filter.ScopeNode;
26  import org.apache.directory.api.ldap.model.message.SearchScope;
27  import org.apache.directory.api.ldap.model.name.Dn;
28  import org.apache.directory.server.core.api.partition.Partition;
29  import org.apache.directory.server.core.api.partition.PartitionTxn;
30  import org.apache.directory.server.i18n.I18n;
31  import org.apache.directory.server.xdbm.IndexEntry;
32  import org.apache.directory.server.xdbm.ParentIdAndRdn;
33  import org.apache.directory.server.xdbm.Store;
34  import org.apache.directory.server.xdbm.search.Evaluator;
35  
36  
37  /**
38   * Evaluates ScopeNode assertions with subtree scope on candidates using an
39   * entry database.
40   * 
41   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
42   */
43  public class SubtreeScopeEvaluator implements Evaluator<ScopeNode>
44  {
45      /** The ScopeNode containing initial search scope constraints */
46      private final ScopeNode node;
47  
48      /** The entry identifier of the scope base */
49      private final String baseId;
50  
51      /** 
52       * Whether or not to accept all candidates.  If this evaluator's baseId is
53       * set to the context entry's id, then obviously all candidates will be 
54       * subordinate to this root ancestor or in subtree scope.  This check is 
55       * done on  initialization and used there after.  One reason we need do 
56       * this is because the subtree scope index (sub level index) does not map 
57       * the values for the context entry id to it's subordinates since it would 
58       * have to include all entries.  This is a waste of space and lookup time
59       * since we know all entries will be subordinates in this case.
60       */
61      private final boolean baseIsContextEntry;
62  
63      /** True if the scope requires alias dereferencing while searching */
64      private final boolean dereferencing;
65  
66      /** The entry database/store */
67      private final Store db;
68  
69  
70      /**
71       * Creates a subtree scope node evaluator for search expressions.
72       *
73       * @param partitionTxn The transaction to use
74       * @param db the database used to evaluate scope node
75       * @param node the scope node
76       * @throws LdapException on db access failure
77       */
78      public SubtreeScopeEvaluator( PartitionTxn partitionTxn, Store db, ScopeNode node ) throws LdapException
79      {
80          this.db = db;
81          this.node = node;
82  
83          if ( node.getScope() != SearchScope.SUBTREE )
84          {
85              throw new IllegalStateException( I18n.err( I18n.ERR_727 ) );
86          }
87  
88          baseId = node.getBaseId();
89          
90          baseIsContextEntry = db.getSuffixId( partitionTxn ) == baseId;
91  
92          dereferencing = node.getDerefAliases().isDerefInSearching() || node.getDerefAliases().isDerefAlways();
93      }
94  
95  
96      /**
97       * Tells if a candidate is a descendant of the base ID. We have to fetch all 
98       * the parentIdAndRdn up to the baseId. If we terminate on the context entry without 
99       * having found the baseId, then the candidate is not a descendant.
100      */
101     private boolean isDescendant( PartitionTxn partitionTxn, String candidateId ) throws LdapException
102     {
103         String tmp = candidateId;
104 
105         while ( true )
106         {
107             ParentIdAndRdn parentIdAndRdn = db.getRdnIndex().reverseLookup( partitionTxn, tmp );
108 
109             if ( parentIdAndRdn == null )
110             {
111                 return false;
112             }
113 
114             tmp = parentIdAndRdn.getParentId();
115 
116             if ( tmp.equals( Partition.ROOT_ID ) )
117             {
118                 return false;
119             }
120 
121             if ( tmp.equals( baseId ) )
122             {
123                 return true;
124             }
125         }
126     }
127 
128 
129     /**
130      * {@inheritDoc}
131      */
132     @Override
133     public boolean evaluate( PartitionTxn partitionTxn, IndexEntry<?, String> indexEntry ) throws LdapException
134     {
135         String id = indexEntry.getId();
136         Entry entry = indexEntry.getEntry();
137 
138         // Fetch the entry
139         if ( null == entry )
140         {
141             entry = db.fetch( partitionTxn, indexEntry.getId() );
142 
143             if ( null == entry )
144             {
145                 // The entry is not anymore present : get out
146                 return false;
147             }
148 
149             indexEntry.setEntry( entry );
150         }
151 
152         /*
153          * This condition catches situations where the candidate is equal to 
154          * the base entry and when the base entry is the context entry.  Note
155          * we do not store a mapping in the subtree index of the context entry
156          * to all it's subordinates since that would be the entire set of 
157          * entries in the db.
158          */
159         boolean isDescendant = baseIsContextEntry || baseId.equals( id ) || entry.getDn().isDescendantOf( node.getBaseDn() );
160 
161         /*
162          * The candidate id could be any entry in the db.  If search
163          * dereferencing is not enabled then we return the results of the
164          * descendant test.
165          */
166         if ( !isDereferencing() )
167         {
168             return isDescendant;
169         }
170 
171         /*
172          * From here down alias dereferencing is enabled.  We determine if the
173          * candidate id is an alias, if so we reject it since aliases should
174          * not be returned.
175          */
176         if ( db.getAliasCache() != null )
177         {
178             Dn dn = db.getAliasCache().getIfPresent( id );
179             
180             if ( dn != null )
181             {
182                 return false;
183             }
184         }
185         else if ( null != db.getAliasIndex().reverseLookup( partitionTxn, id ) )
186         {
187             return false;
188         }
189 
190         /*
191          * The candidate is NOT an alias at this point.  So if it is a
192          * descendant we just return true since it is in normal subtree scope.
193          */
194         if ( isDescendant )
195         {
196             return true;
197         }
198 
199         /*
200          * At this point the candidate is not a descendant and it is not an
201          * alias.  We need to check if the candidate is in extended subtree
202          * scope by performing a lookup on the subtree alias index.  This index
203          * stores a tuple mapping the baseId to the ids of objects brought
204          * into subtree scope of the base by an alias:
205          *
206          * ( baseId, aliasedObjId )
207          *
208          * If the candidate id is an object brought into subtree scope then
209          * the lookup returns true accepting the candidate.  Otherwise the
210          * candidate is rejected with a false return because it is not in scope.
211          */
212         return db.getSubAliasIndex().forward( partitionTxn, baseId, id );
213     }
214 
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     public boolean evaluate( Entry candidate ) throws LdapException
221     {
222         throw new UnsupportedOperationException( I18n.err( I18n.ERR_721 ) );
223     }
224 
225 
226     /**
227      * {@inheritDoc}
228      */
229     @Override
230     public ScopeNode getExpression()
231     {
232         return node;
233     }
234 
235 
236     /**
237      * @return The base ID
238      */
239     public String getBaseId()
240     {
241         return baseId;
242     }
243 
244 
245     /**
246      * @return <tt>true</tt> if dereferencing
247      */
248     public boolean isDereferencing()
249     {
250         return dereferencing;
251     }
252 
253 
254     /**
255      * @see Object#toString()
256      */
257     public String toString( String tabs )
258     {
259         StringBuilder sb = new StringBuilder();
260 
261         sb.append( tabs ).append( "SubstreeScopeEvaluator : " ).append( node ).append( '\n' );
262 
263         return sb.toString();
264     }
265 
266 
267     /**
268      * @see Object#toString()
269      */
270     public String toString()
271     {
272         return toString( "" );
273     }
274 }