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.core.api.subtree;
021
022
023import org.apache.directory.api.ldap.model.entry.Entry;
024import org.apache.directory.api.ldap.model.exception.LdapException;
025import org.apache.directory.api.ldap.model.name.Dn;
026import org.apache.directory.api.ldap.model.schema.SchemaManager;
027import org.apache.directory.api.ldap.model.subtree.SubtreeSpecification;
028import org.apache.directory.server.core.api.event.Evaluator;
029import org.apache.directory.server.core.api.event.ExpressionEvaluator;
030
031
032/**
033 * An evaluator used to determine if an entry is included in the collection
034 * represented by a subtree specification.
035 *
036 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
037 */
038public class SubtreeEvaluator
039{
040    /** A refinement filter evaluator */
041    private final Evaluator evaluator;
042
043
044    /**
045     * Creates a subtreeSpecification evaluatior which can be used to determine
046     * if an entry is included within the collection of a subtree.
047     *
048     * @param schemaManager The server schemaManager
049     */
050    public SubtreeEvaluator( SchemaManager schemaManager )
051    {
052        evaluator = new ExpressionEvaluator( schemaManager );
053    }
054
055
056    /**
057     * Determines if an entry is selected by a subtree specification.
058     *
059     * @param subtree the subtree specification
060     * @param apDn the distinguished name of the administrative point containing the subentry
061     * @param entryDn the distinguished name of the candidate entry
062     * @param entry The entry to evaluate
063     * @return true if the entry is selected by the specification, false if it is not
064     * @throws LdapException if errors are encountered while evaluating selection
065     */
066    public boolean evaluate( SubtreeSpecification subtree, Dn apDn, Dn entryDn, Entry entry )
067        throws LdapException
068    {
069        /* =====================================================================
070         * NOTE: Regarding the overall approach, we try to narrow down the
071         * possibilities by slowly pruning relative names off of the entryDn.
072         * For example we check first if the entry is a descendant of the AP.
073         * If so we use the relative name thereafter to calculate if it is
074         * a descendant of the base. This means shorter names to compare and
075         * less work to do while we continue to deduce inclusion by the subtree
076         * specification.
077         * =====================================================================
078         */
079        // First construct the subtree base, which is the concatenation of the
080        // AP Dn and the subentry base
081        Dn subentryBaseDn = apDn;
082        subentryBaseDn = subentryBaseDn.add( subtree.getBase() );
083
084        if ( !entryDn.isDescendantOf( subentryBaseDn ) )
085        {
086            // The entry Dn is not part of the subtree specification, get out
087            return false;
088        }
089
090        /*
091         * Evaluate based on minimum and maximum chop values.  Here we simply
092         * need to compare the distances respectively with the size of the
093         * baseRelativeRdn.  For the max distance entries with a baseRelativeRdn
094         * size greater than the max distance are rejected.  For the min distance
095         * entries with a baseRelativeRdn size less than the minimum distance
096         * are rejected.
097         */
098        int entryRelativeDnSize = entryDn.size() - subentryBaseDn.size();
099
100        if ( ( subtree.getMaxBaseDistance() != SubtreeSpecification.UNBOUNDED_MAX )
101            && ( entryRelativeDnSize > subtree.getMaxBaseDistance() ) )
102        {
103            return false;
104        }
105
106        if ( ( subtree.getMinBaseDistance() > 0 ) && ( entryRelativeDnSize < subtree.getMinBaseDistance() ) )
107        {
108            return false;
109        }
110
111        /*
112         * For specific exclusions we must iterate through the set and check
113         * if the baseRelativeRdn is a descendant of the exclusion.  The
114         * isDescendant() function will return true if the compared names
115         * are equal so for chopAfter exclusions we must check for equality
116         * as well and reject if the relative names are equal.
117         */
118        // Now, get the entry's relative part
119
120        if ( !subtree.getChopBeforeExclusions().isEmpty() || !subtree.getChopAfterExclusions().isEmpty() )
121        {
122            Dn entryRelativeDn = entryDn.getDescendantOf( apDn ).getDescendantOf( subtree.getBase() );
123
124            for ( Dn chopBeforeDn : subtree.getChopBeforeExclusions() )
125            {
126                if ( entryRelativeDn.isDescendantOf( chopBeforeDn ) )
127                {
128                    return false;
129                }
130            }
131
132            for ( Dn chopAfterDn : subtree.getChopAfterExclusions() )
133            {
134                if ( entryRelativeDn.isDescendantOf( chopAfterDn ) && !chopAfterDn.equals( entryRelativeDn ) )
135                {
136                    return false;
137                }
138            }
139        }
140
141        /*
142         * The last remaining step is to check and see if the refinement filter
143         * selects the entry candidate based on objectClass attribute values.
144         * To do this we invoke the refinement evaluator members evaluate() method.
145         */
146        if ( subtree.getRefinement() != null )
147        {
148            return evaluator.evaluate( subtree.getRefinement(), entryDn, entry );
149        }
150
151        /*
152         * If nothing has rejected the candidate entry and there is no refinement
153         * filter then the entry is included in the collection represented by the
154         * subtree specification so we return true.
155         */
156        return true;
157    }
158}