Package org.apache.ldap.server.subtree

Source Code of org.apache.ldap.server.subtree.SubentryService

/*
*   Copyright 2004 The Apache Software Foundation
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*
*/
package org.apache.ldap.server.subtree;


import org.apache.ldap.server.DirectoryServiceConfiguration;
import org.apache.ldap.server.interceptor.BaseInterceptor;
import org.apache.ldap.server.interceptor.NextInterceptor;
import org.apache.ldap.server.enumeration.SearchResultFilter;
import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
import org.apache.ldap.server.invocation.InvocationStack;
import org.apache.ldap.server.invocation.Invocation;
import org.apache.ldap.server.configuration.InterceptorConfiguration;
import org.apache.ldap.server.partition.DirectoryPartitionNexus;
import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
import org.apache.ldap.common.message.SubentryRequestControl;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.message.LockableAttributesImpl;
import org.apache.ldap.common.message.LockableAttributeImpl;
import org.apache.ldap.common.filter.*;
import org.apache.ldap.common.subtree.SubtreeSpecificationParser;
import org.apache.ldap.common.subtree.SubtreeSpecification;
import org.apache.ldap.common.name.DnParser;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.exception.LdapNoSuchAttributeException;
import org.apache.ldap.common.exception.LdapInvalidAttributeValueException;
import org.apache.ldap.common.exception.LdapSchemaViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.directory.*;
import javax.naming.NamingException;
import javax.naming.NamingEnumeration;
import javax.naming.Name;
import java.util.*;


/**
* The Subentry interceptor service which is responsible for filtering
* out subentries on search operations and injecting operational attributes
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class SubentryService extends BaseInterceptor
{
    /** the subentry control OID */
    private static final String SUBENTRY_CONTROL = "1.3.6.1.4.1.4203.1.10.1";
    /** the objectClass value for a subentry */
    private static final String SUBENTRY_OBJECTCLASS = "subentry";
    /** the objectClass OID for a subentry */
    private static final String SUBENTRY_OBJECTCLASS_OID = "2.5.17.0";

    public static final String AUTONOUMOUS_AREA = "autonomousArea";
    public static final String AUTONOUMOUS_AREA_SUBENTRY = "autonomousAreaSubentry";

    public static final String AC_AREA = "accessControlSpecificArea";
    public static final String AC_INNERAREA = "accessControlInnerArea";
    public static final String AC_SUBENTRY = "accessControlSubentries";

    public static final String SCHEMA_AREA = "subschemaAdminSpecificArea";
    public static final String SCHEMA_AREA_SUBENTRY = "subschemaSubentry";

    public static final String COLLECTIVE_AREA = "collectiveAttributeSpecificArea";
    public static final String COLLECTIVE_ATTRIBUTE_SUBENTRIES = "collectiveAttributeSubentries";

    public static final String COLLECTIVE_INNERAREA = "collectiveAttributeInnerArea";

    public static final String[] SUBENTRY_OPATTRS = {
        AUTONOUMOUS_AREA_SUBENTRY,
        AC_SUBENTRY,
        SCHEMA_AREA_SUBENTRY,
        COLLECTIVE_ATTRIBUTE_SUBENTRIES
    };

    /**
     * the search result filter to filter out subentries based on objectClass values.
     */
    private static final SearchResultFilter SUBENTRY_FILTER = new SearchResultFilter()
    {
        public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
        {
            Attribute objectClasses = result.getAttributes().get( "objectClass" );

            if ( objectClasses == null )
            {
                return true;
            }

            return !( objectClasses.contains(SUBENTRY_OBJECTCLASS) || objectClasses.contains(SUBENTRY_OBJECTCLASS_OID) );
        }
    };

    private static final Logger log = LoggerFactory.getLogger( SubentryService.class );

    /** the hash mapping the DN of a subentry to its SubtreeSpecification */
    private final Map subtrees = new HashMap();
    private DirectoryServiceConfiguration factoryCfg;
    private DnParser dnParser;
    private SubtreeSpecificationParser ssParser;
    private SubtreeEvaluator evaluator;
    private DirectoryPartitionNexus nexus;


    public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
    {
        super.init( factoryCfg, cfg );
        this.nexus = factoryCfg.getPartitionNexus();
        this.factoryCfg = factoryCfg;
        ConcreteNameComponentNormalizer ncn = new ConcreteNameComponentNormalizer(
                factoryCfg.getGlobalRegistries().getAttributeTypeRegistry() );
        ssParser = new SubtreeSpecificationParser( ncn );
        dnParser = new DnParser( ncn );
        evaluator = new SubtreeEvaluator( factoryCfg.getGlobalRegistries().getOidRegistry() );

        // prepare to find all subentries in all namingContexts
        Iterator suffixes = this.nexus.listSuffixes( true );
        ExprNode filter = new SimpleNode( "objectclass", "subentry", LeafNode.EQUALITY );
        SearchControls controls = new SearchControls();
        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        controls.setReturningAttributes( new String[] { "subtreeSpecification" } );

        // search each namingContext for subentries
        while ( suffixes.hasNext() )
        {
            Name suffix = dnParser.parse( ( String ) suffixes.next() );
            NamingEnumeration subentries = nexus.search( suffix, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes subentry = result.getAttributes();
                String dn = result.getName();
                String subtree = ( String ) subentry.get( "subtreeSpecification" ).get();
                SubtreeSpecification ss;

                try
                {
                    ss = ssParser.parse( subtree );
                }
                catch ( Exception e )
                {
                    log.warn( "Failed while parsing subtreeSpecification for " + dn );
                    continue;
                }

                subtrees.put( dnParser.parse( dn ).toString(), ss );
            }
        }
    }


    // -----------------------------------------------------------------------
    // Methods/Code dealing with Subentry Visibility
    // -----------------------------------------------------------------------


    public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
    {
        NamingEnumeration e = nextInterceptor.list( base );
        Invocation invocation = InvocationStack.getInstance().peek();

        if ( ! isSubentryVisible( invocation ) )
        {
            return new SearchResultFilteringEnumeration( e, new SearchControls(), invocation, SUBENTRY_FILTER );
        }

        return e;
    }


    public NamingEnumeration search( NextInterceptor nextInterceptor, Name base, Map env, ExprNode filter,
            SearchControls searchCtls ) throws NamingException
    {
        NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
        Invocation invocation = InvocationStack.getInstance().peek();

        // object scope searches by default return subentries
        if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
        {
            return e;
        }

        // for subtree and one level scope we filter
        if ( ! isSubentryVisible( invocation ) )
        {
            return new SearchResultFilteringEnumeration( e, searchCtls, invocation, SUBENTRY_FILTER );
        }

        return e;
    }


    /**
     * Checks to see if subentries for the search and list operations should be
     * made visible based on the availability of the search request control
     *
     * @param invocation
     * @return true if subentries should be visible, false otherwise
     * @throws NamingException if there are problems accessing request controls
     */
    private boolean isSubentryVisible( Invocation invocation ) throws NamingException
    {
        Control[] reqControls = ( ( LdapContext ) invocation.getCaller() ).getRequestControls();

        if ( reqControls == null || reqControls.length <= 0 )
        {
            return false;
        }

        // check all request controls to see if subentry control is present
        for ( int ii = 0; ii < reqControls.length; ii++ )
        {
            // found the subentry request control so we return its value
            if ( reqControls[ii].getID().equals( SUBENTRY_CONTROL ) )
            {
                SubentryRequestControl subentryControl = ( SubentryRequestControl ) reqControls[ii];
                return subentryControl.getSubentryVisibility();
            }
        }

        return false;
    }


    // -----------------------------------------------------------------------
    // Methods dealing with entry and subentry addition
    // -----------------------------------------------------------------------


    /**
     * Evaluates the set of subentry subtrees upon an entry and returns the
     * operational subentry attributes that will be added to the entry if
     * added at the dn specified.
     *
     * @param dn the normalized distinguished name of the entry
     * @param entryAttrs the entry attributes are generated for
     * @return the set of subentry op attrs for an entry
     * @throws NamingException if there are problems accessing entry information
     */
    public Attributes getSubentryAttributes( Name dn, Attributes entryAttrs ) throws NamingException
    {
        Attributes subentryAttrs = new LockableAttributesImpl();
        Attribute objectClasses = entryAttrs.get( "objectClass" );
        Iterator list = subtrees.keySet().iterator();
        while ( list.hasNext() )
        {
            String subentryDnStr = ( String ) list.next();
            Name subentryDn = new LdapName( subentryDnStr );
            Name apDn = ( Name ) subentryDn.clone();
            apDn.remove( apDn.size() - 1 );
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );

            if ( evaluator.evaluate( ss, apDn, dn, objectClasses ) )
            {
                Attribute administrativeRole = nexus.lookup( apDn ).get( "administrativeRole" );
                NamingEnumeration roles = administrativeRole.getAll();
                while ( roles.hasMore() )
                {
                    Attribute operational;
                    String role = ( String ) roles.next();

                    if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
                    {
                        operational = subentryAttrs.get( AUTONOUMOUS_AREA_SUBENTRY );
                        if ( operational == null )
                        {
                            operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
                            subentryAttrs.put( operational );
                        }
                    }
                    else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
                    {
                        operational = subentryAttrs.get( AC_SUBENTRY );
                        if ( operational == null )
                        {
                            operational = new LockableAttributeImpl( AC_SUBENTRY );
                            subentryAttrs.put( operational );
                        }
                    }
                    else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
                    {
                        operational = subentryAttrs.get( SCHEMA_AREA_SUBENTRY );
                        if ( operational == null )
                        {
                            operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
                            subentryAttrs.put( operational );
                        }
                    }
                    else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
                              role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
                    {
                        operational = subentryAttrs.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
                        if ( operational == null )
                        {
                            operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
                            subentryAttrs.put( operational );
                        }
                    }
                    else
                    {
                        throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
                                + role + "' in administrative point of subentry " + subentryDnStr + ". The values of this attribute"
                                + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
                                + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
                                + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
                    }

                    operational.add( subentryDn.toString() );
                }
            }
        }

        return subentryAttrs;
    }


    public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
    {
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) )
        {
            // get the name of the administrative point and its administrativeRole attributes
            Name apName = ( Name ) normName.clone();
            apName.remove( normName.size() - 1 );
            Attributes ap = nexus.lookup( apName );
            Attribute administrativeRole = ap.get( "administrativeRole" );

            // check that administrativeRole has something valid in it for us
            if ( administrativeRole == null || administrativeRole.size() <= 0 )
            {
                throw new LdapNoSuchAttributeException( "Administration point " + apName
                        + " does not contain an administrativeRole attribute! An"
                        + " administrativeRole attribute in the administrative point is"
                        + " required to add a subordinate subentry." );
            }

            /* ----------------------------------------------------------------
             * Build the set of operational attributes to be injected into
             * entries that are contained within the subtree repesented by this
             * new subentry.  In the process we make sure the proper roles are
             * supported by the administrative point to allow the addition of
             * this new subentry.
             * ----------------------------------------------------------------
             */
            Attributes operational = getSubentryOperatationalAttributes( normName, administrativeRole );

            /* ----------------------------------------------------------------
             * Parse the subtreeSpecification of the subentry and add it to the
             * SubtreeSpecification cache.  If the parse succeeds we continue
             * to add the entry to the DIT.  Thereafter we search out entries
             * to modify the subentry operational attributes of.
             * ----------------------------------------------------------------
             */
            String subtree = ( String ) entry.get( "subtreeSpecification" ).get();
            SubtreeSpecification ss;
            try
            {
                ss = ssParser.parse( subtree );
            }
            catch ( Exception e )
            {
                String msg = "Failed while parsing subtreeSpecification for " + upName;
                log.warn( msg );
                throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
            }
            subtrees.put( normName.toString(), ss );
            next.add( upName, normName, entry );

            /* ----------------------------------------------------------------
             * Find the baseDn for the subentry and use that to search the tree
             * while testing each entry returned for inclusion within the
             * subtree of the subentry's subtreeSpecification.  All included
             * entries will have their operational attributes merged with the
             * operational attributes calculated above.
             * ----------------------------------------------------------------
             */
            Name baseDn = ( Name ) apName.clone();
            baseDn.addAll( ss.getBase() );

            ExprNode filter = new PresenceNode( "objectclass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );

            NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
                }
            }
        }
        else
        {
            Iterator list = subtrees.keySet().iterator();
            while ( list.hasNext() )
            {
                String subentryDnStr = ( String ) list.next();
                Name subentryDn = new LdapName( subentryDnStr );
                Name apDn = ( Name ) subentryDn.clone();
                apDn.remove( apDn.size() - 1 );
                SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );

                if ( evaluator.evaluate( ss, apDn, normName, objectClasses ) )
                {
                    Attribute administrativeRole = nexus.lookup( apDn ).get( "administrativeRole" );
                    NamingEnumeration roles = administrativeRole.getAll();
                    while ( roles.hasMore() )
                    {
                        Attribute operational;
                        String role = ( String ) roles.next();

                        if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
                        {
                            operational = entry.get( AUTONOUMOUS_AREA_SUBENTRY );
                            if ( operational == null )
                            {
                                operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
                                entry.put( operational );
                            }
                        }
                        else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
                        {
                            operational = ( Attribute ) entry.get( AC_SUBENTRY );
                            if ( operational == null )
                            {
                                operational = new LockableAttributeImpl( AC_SUBENTRY );
                                entry.put( operational );
                            }
                        }
                        else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
                        {
                            operational = ( Attribute ) entry.get( SCHEMA_AREA_SUBENTRY );
                            if ( operational == null )
                            {
                                operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
                                entry.put( operational );
                            }
                        }
                        else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
                                  role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
                        {
                            operational = ( Attribute ) entry.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
                            if ( operational == null )
                            {
                                operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
                                entry.put( operational );
                            }
                        }
                        else
                        {
                            throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
                                    + role + "' in administrative point of subentry " + subentryDnStr + ". The values of this attribute"
                                    + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
                                    + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
                                    + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
                        }

                        operational.add( subentryDn.toString() );
                    }
                }
            }

            next.add( upName, normName, entry );
        }
    }


    // -----------------------------------------------------------------------
    // Methods dealing subentry deletion
    // -----------------------------------------------------------------------


    public void delete( NextInterceptor next, Name name ) throws NamingException
    {
        Attributes entry = nexus.lookup( name );
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) )
        {
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( name.toString() );
            subtrees.remove( ss );
            next.delete( name );

            /* ----------------------------------------------------------------
             * Find the baseDn for the subentry and use that to search the tree
             * for all entries included by the subtreeSpecification.  Then we
             * check the entry for subentry operational attribute that contain
             * the DN of the subentry.  These are the subentry operational
             * attributes we remove from the entry in a modify operation.
             * ----------------------------------------------------------------
             */
            Name apName = ( Name ) name.clone();
            apName.remove( name.size() - 1 );
            Name baseDn = ( Name ) apName.clone();
            baseDn.addAll( ss.getBase() );

            ExprNode filter = new PresenceNode( "objectclass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );

            NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
                }
            }
        }
        else
        {
            next.delete( name );
        }
    }


    // -----------------------------------------------------------------------
    // Methods dealing subentry name changes
    // -----------------------------------------------------------------------


    /**
     * Checks to see if an entry being renamed has a descendant that is an
     * administrative point.
     *
     * @param name the name of the entry which is used as the search base
     * @return true if name is an administrative point or one of its descendants
     * are, false otherwise
     * @throws NamingException if there are errors while searching the directory
     */
    private boolean hasAdministrativeDescendant( Name name ) throws NamingException
    {
        ExprNode filter = new PresenceNode( "administrativeRole" );
        SearchControls controls = new SearchControls();
        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
        NamingEnumeration aps = nexus.search( name, factoryCfg.getEnvironment(), filter, controls );
        if ( aps.hasMore() )
        {
            aps.close();
            return true;
        }

        return false;
    }


    private ModificationItem[] getModsOnEntryRdnChange( Name oldName, Name newName, Attributes entry )
        throws NamingException
    {
        Attribute objectClasses = entry.get( "objectClass" );
        List modList = new ArrayList();

        /*
         * There are two different situations warranting action.  Firt if
         * an ss evalutating to true with the old name no longer evalutates
         * to true with the new name.  This would be caused by specific chop
         * exclusions that effect the new name but did not effect the old
         * name. In this case we must remove subentry operational attribute
         * values associated with the dn of that subentry.
         *
         * In the second case an ss selects the entry with the new name when
         * it did not previously with the old name.  Again this situation
         * would be caused by chop exclusions. In this case we must add subentry
         * operational attribute values with the dn of this subentry.
         */
        Iterator subentries = subtrees.keySet().iterator();
        while ( subentries.hasNext() )
        {
            String subentryDn = ( String ) subentries.next();
            Name apDn = new LdapName( subentryDn );
            apDn.remove( apDn.size() - 1 );
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( subentryDn );
            boolean isOldNameSelected = evaluator.evaluate( ss, apDn, oldName, objectClasses );
            boolean isNewNameSelected = evaluator.evaluate( ss, apDn, newName, objectClasses );

            if ( isOldNameSelected == isNewNameSelected )
            {
                continue;
            }

            // need to remove references to the subentry
            if ( isOldNameSelected && ! isNewNameSelected )
            {
                for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
                {
                    int op = DirContext.REPLACE_ATTRIBUTE;
                    Attribute opAttr = entry.get( SUBENTRY_OPATTRS[ii] );
                    if ( opAttr != null )
                    {
                        opAttr = ( Attribute ) opAttr.clone();
                        opAttr.remove( subentryDn );

                        if ( opAttr.size() < 1 )
                        {
                            op = DirContext.REMOVE_ATTRIBUTE;
                        }

                        modList.add( new ModificationItem( op, opAttr ) );
                    }
                }
            }
            // need to add references to the subentry
            else if ( isNewNameSelected && ! isOldNameSelected )
            {
                for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
                {
                    int op = DirContext.REPLACE_ATTRIBUTE;
                    Attribute opAttr = entry.get( SUBENTRY_OPATTRS[ii] );
                    if ( opAttr != null )
                    {
                        opAttr = ( Attribute ) opAttr.clone();
                    }
                    else
                    {
                        op = DirContext.ADD_ATTRIBUTE;
                        opAttr = new LockableAttributeImpl( SUBENTRY_OPATTRS[ii] );
                    }

                    opAttr.add( subentryDn );
                    modList.add( new ModificationItem( op, opAttr ) );
                }
            }
        }

        ModificationItem[] mods = new ModificationItem[modList.size()];
        mods = ( ModificationItem[] ) modList.toArray( mods );
        return mods;
    }


    public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
    {
        Attributes entry = nexus.lookup( name );
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) )
        {
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( name.toString() );
            Name apName = ( Name ) name.clone();
            apName.remove( apName.size() - 1 );
            Name baseDn = ( Name ) apName.clone();
            baseDn.addAll( ss.getBase() );
            Name newName = ( Name ) name.clone();
            newName.remove( newName.size() - 1 );
            Name rdn = dnParser.parse( newRn );
            newName.addAll( rdn );

            subtrees.put( newName.toString(), ss );
            next.modifyRn( name, newRn, deleteOldRn );

            Attributes apAttrs = nexus.lookup( apName );
            Attribute administrativeRole = apAttrs.get( "administrativeRole" );
            ExprNode filter = new PresenceNode( "objectclass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );
            NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForReplace( name, newName, administrativeRole, candidate ) );
                }
            }
        }
        else
        {
            if ( hasAdministrativeDescendant( name ) )
            {
                String msg = "Will not allow rename operation on entries with administrative descendants.";
                log.warn( msg );
                throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
            }
            next.modifyRn( name, newRn, deleteOldRn );

            // calculate the new DN now for use below to modify subentry operational
            // attributes contained within this regular entry with name changes
            Name newName = ( Name ) name.clone();
            newName.remove( newName.size() - 1 );
            newName.add( newRn );
            ModificationItem[] mods = getModsOnEntryRdnChange( name, newName, entry );

            if ( mods.length > 0 )
            {
                nexus.modify( newName, mods );
            }
        }
    }


    public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn )
            throws NamingException
    {
        Attributes entry = nexus.lookup( oriChildName );
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) )
        {
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( oriChildName.toString() );
            Name apName = ( Name ) oriChildName.clone();
            apName.remove( apName.size() - 1 );
            Name baseDn = ( Name ) apName.clone();
            baseDn.addAll( ss.getBase() );
            Name newName = ( Name ) newParentName.clone();
            newName.remove( newName.size() - 1 );
            Name rdn = dnParser.parse( newRn );
            newName.addAll( rdn );

            subtrees.put( newName.toString(), ss );
            next.move( oriChildName, newParentName, newRn, deleteOldRn );

            Attributes apAttrs = nexus.lookup( apName );
            Attribute administrativeRole = apAttrs.get( "administrativeRole" );

            ExprNode filter = new PresenceNode( "objectclass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );
            NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForReplace( oriChildName, newName, administrativeRole, candidate ) );
                }
            }
        }
        else
        {
            if ( hasAdministrativeDescendant( oriChildName ) )
            {
                String msg = "Will not allow rename operation on entries with administrative descendants.";
                log.warn( msg );
                throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
            }
            next.move( oriChildName, newParentName, newRn, deleteOldRn );

            // calculate the new DN now for use below to modify subentry operational
            // attributes contained within this regular entry with name changes
            Name newName = ( Name ) newParentName.clone();
            newName.add( newRn );
            ModificationItem[] mods = getModsOnEntryRdnChange( oriChildName, newName, entry );

            if ( mods.length > 0 )
            {
                nexus.modify( newName, mods );
            }
        }
    }


    public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
    {
        Attributes entry = nexus.lookup( oriChildName );
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) )
        {
            SubtreeSpecification ss = ( SubtreeSpecification ) subtrees.get( oriChildName.toString() );
            Name apName = ( Name ) oriChildName.clone();
            apName.remove( apName.size() - 1 );
            Name baseDn = ( Name ) apName.clone();
            baseDn.addAll( ss.getBase() );
            Name newName = ( Name ) newParentName.clone();
            newName.remove( newName.size() - 1 );
            newName.add( newParentName.get( newParentName.size() - 1 ) );

            subtrees.put( newName.toString(), ss );
            next.move( oriChildName, newParentName );

            Attributes apAttrs = nexus.lookup( apName );
            Attribute administrativeRole = apAttrs.get( "administrativeRole" );

            ExprNode filter = new PresenceNode( "objectclass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );
            NamingEnumeration subentries = nexus.search( baseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ss, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForReplace( oriChildName, newName, administrativeRole, candidate ) );
                }
            }
        }
        else
        {
            if ( hasAdministrativeDescendant( oriChildName ) )
            {
                String msg = "Will not allow rename operation on entries with administrative descendants.";
                log.warn( msg );
                throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
            }
            next.move( oriChildName, newParentName );

            // calculate the new DN now for use below to modify subentry operational
            // attributes contained within this regular entry with name changes
            Name newName = ( Name ) newParentName.clone();
            newName.add( oriChildName.get( oriChildName.size() - 1 ) );
            ModificationItem[] mods = getModsOnEntryRdnChange( oriChildName, newName, entry );

            if ( mods.length > 0 )
            {
                nexus.modify( newName, mods );
            }
        }
    }


    // -----------------------------------------------------------------------
    // Methods dealing subentry modification
    // -----------------------------------------------------------------------


    public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
    {
        Attributes entry = nexus.lookup( name );
        Attribute objectClasses = entry.get( "objectClass" );

        if ( objectClasses.contains( "subentry" ) && mods.get( "subtreeSpecification" ) != null )
        {
            SubtreeSpecification ssOld = ( SubtreeSpecification ) subtrees.remove( name.toString() );
            SubtreeSpecification ssNew;

            try
            {
                ssNew = ssParser.parse( ( String ) mods.get( "subtreeSpecification" ).get() );
            }
            catch ( Exception e )
            {
                String msg = "failed to parse the new subtreeSpecification";
                log.error( msg, e );
                throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
            }

            subtrees.put( name.toString(), ssNew );
            next.modify( name, modOp, mods );

            // search for all entries selected by the old SS and remove references to subentry
            Name apName = ( Name ) name.clone();
            apName.remove( apName.size() - 1 );
            Name oldBaseDn = ( Name ) apName.clone();
            oldBaseDn.addAll( ssOld.getBase() );
            ExprNode filter = new PresenceNode( "objectClass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );
            NamingEnumeration subentries = nexus.search( oldBaseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ssOld, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
                }
            }

            // search for all selected entries by the new SS and add references to subentry
            Attributes apAttrs = nexus.lookup( apName );
            Attribute administrativeRole = apAttrs.get( "administrativeRole" );
            Attributes operational = getSubentryOperatationalAttributes( name, administrativeRole );
            Name newBaseDn = ( Name ) apName.clone();
            newBaseDn.addAll( ssNew.getBase() );
            subentries = nexus.search( newBaseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ssNew, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
                }
            }
        }
        else
        {
            next.modify( name, modOp, mods );
        }
    }


    public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
    {
        Attributes entry = nexus.lookup( name );
        Attribute objectClasses = entry.get( "objectClass" );
        boolean isSubtreeSpecificationModification = false;
        ModificationItem subtreeMod = null;

        for ( int ii = 0; ii < mods.length; ii++ )
        {
            if ( "subtreeSpecification".equalsIgnoreCase( mods[ii].getAttribute().getID() ) )
            {
                isSubtreeSpecificationModification = true;
                subtreeMod = mods[ii];
            }
        }

        if ( objectClasses.contains( "subentry" ) && isSubtreeSpecificationModification )
        {
            SubtreeSpecification ssOld = ( SubtreeSpecification ) subtrees.remove( name.toString() );
            SubtreeSpecification ssNew;

            try
            {
                ssNew = ssParser.parse( ( String ) subtreeMod.getAttribute().get() );
            }
            catch ( Exception e )
            {
                String msg = "failed to parse the new subtreeSpecification";
                log.error( msg, e );
                throw new LdapInvalidAttributeValueException( msg, ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
            }

            subtrees.put( name.toString(), ssNew );
            next.modify( name, mods );

            // search for all entries selected by the old SS and remove references to subentry
            Name apName = ( Name ) name.clone();
            apName.remove( apName.size() - 1 );
            Name oldBaseDn = ( Name ) apName.clone();
            oldBaseDn.addAll( ssOld.getBase() );
            ExprNode filter = new PresenceNode( "objectClass" );
            SearchControls controls = new SearchControls();
            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
            controls.setReturningAttributes( new String[] { "+", "*" } );
            NamingEnumeration subentries = nexus.search( oldBaseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ssOld, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForRemove( name, candidate ) );
                }
            }

            // search for all selected entries by the new SS and add references to subentry
            Attributes apAttrs = nexus.lookup( apName );
            Attribute administrativeRole = apAttrs.get( "administrativeRole" );
            Attributes operational = getSubentryOperatationalAttributes( name, administrativeRole );
            Name newBaseDn = ( Name ) apName.clone();
            newBaseDn.addAll( ssNew.getBase() );
            subentries = nexus.search( newBaseDn, factoryCfg.getEnvironment(), filter, controls );
            while ( subentries.hasMore() )
            {
                SearchResult result = ( SearchResult ) subentries.next();
                Attributes candidate = result.getAttributes();
                Name dn = dnParser.parse( result.getName() );

                if ( evaluator.evaluate( ssNew, apName, dn, candidate.get( "objectClass" ) ) )
                {
                    nexus.modify( dn, getOperationalModsForAdd( candidate, operational ) );
                }
            }
        }
        else
        {
            next.modify( name, mods );
        }
    }


    // -----------------------------------------------------------------------
    // Utility Methods
    // -----------------------------------------------------------------------


    private ModificationItem[] getOperationalModsForReplace( Name oldName, Name newName, Attribute administrativeRole,
                                                             Attributes entry ) throws NamingException
    {
        List modList = new ArrayList();
        NamingEnumeration roles = administrativeRole.getAll();
        while ( roles.hasMore() )
        {
            Attribute operational;
            String role = ( String ) roles.next();

            if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
            {
                operational = ( Attribute ) entry.get( AUTONOUMOUS_AREA_SUBENTRY ).clone();
                if ( operational == null )
                {
                    operational = new LockableAttributeImpl( AUTONOUMOUS_AREA_SUBENTRY );
                    operational.add( newName.toString() );
                }
                else
                {
                    operational.remove( oldName.toString() );
                    operational.add( newName.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
            {
                operational = ( Attribute ) entry.get( AC_SUBENTRY ).clone();
                if ( operational == null )
                {
                    operational = new LockableAttributeImpl( AC_SUBENTRY );
                    operational.add( newName.toString() );
                }
                else
                {
                    operational.remove( oldName.toString() );
                    operational.add( newName.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
            {
                operational = ( Attribute ) entry.get( SCHEMA_AREA_SUBENTRY ).clone();
                if ( operational == null )
                {
                    operational = new LockableAttributeImpl( SCHEMA_AREA_SUBENTRY );
                    operational.add( newName.toString() );
                }
                else
                {
                    operational.remove( oldName.toString() );
                    operational.add( newName.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
                      role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
            {
                operational = ( Attribute ) entry.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ).clone();
                if ( operational == null )
                {
                    operational = new LockableAttributeImpl( COLLECTIVE_ATTRIBUTE_SUBENTRIES );
                    operational.add( newName.toString() );
                }
                else
                {
                    operational.remove( oldName.toString() );
                    operational.add( newName.toString() );
                }
            }
            else
            {
                throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
                        + role + "' in administrative point of subentry " + oldName + ". The values of this attribute"
                        + " are constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
                        + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
                        + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
            }

            modList.add( new ModificationItem( DirContext.REPLACE_ATTRIBUTE, operational ) );
        }

        ModificationItem[] mods = new ModificationItem[modList.size()];
        return ( ModificationItem[] ) modList.toArray( mods );
    }


    /**
     * Gets the subschema operational attributes to be added to or removed from
     * an entry selected by a subentry's subtreeSpecification.
     *
     * @param name the normalized distinguished name of the subentry (the value of op attrs)
     * @param administrativeRole the roles the administrative point participates in
     * @return the set of attributes to be added or removed from entries
     * @throws NamingException if there are problems accessing attributes
     */
    private Attributes getSubentryOperatationalAttributes( Name name, Attribute administrativeRole )
            throws NamingException
    {
        Attributes operational = new LockableAttributesImpl();
        NamingEnumeration roles = administrativeRole.getAll();
        while ( roles.hasMore() )
        {
            String role = ( String ) roles.next();

            if ( role.equalsIgnoreCase( AUTONOUMOUS_AREA ) )
            {
                if ( operational.get( AUTONOUMOUS_AREA_SUBENTRY ) == null )
                {
                    operational.put( AUTONOUMOUS_AREA_SUBENTRY, name.toString() );
                }
                else
                {
                    operational.get( AUTONOUMOUS_AREA_SUBENTRY ).add( name.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( AC_AREA ) || role.equalsIgnoreCase( AC_INNERAREA ) )
            {
                if ( operational.get( AC_SUBENTRY ) == null )
                {
                    operational.put( AC_SUBENTRY, name.toString() );
                }
                else
                {
                    operational.get( AC_SUBENTRY ).add( name.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( SCHEMA_AREA ) )
            {
                if ( operational.get( SCHEMA_AREA_SUBENTRY ) == null )
                {
                    operational.put( SCHEMA_AREA_SUBENTRY, name.toString() );
                }
                else
                {
                    operational.get( SCHEMA_AREA_SUBENTRY ).add( name.toString() );
                }
            }
            else if ( role.equalsIgnoreCase( COLLECTIVE_AREA ) ||
                      role.equalsIgnoreCase( COLLECTIVE_INNERAREA ) )
            {
                if ( operational.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ) == null )
                {
                    operational.put( COLLECTIVE_ATTRIBUTE_SUBENTRIES, name.toString() );
                }
                else
                {
                    operational.get( COLLECTIVE_ATTRIBUTE_SUBENTRIES ).add( name.toString() );
                }
            }
            else
            {
                throw new LdapInvalidAttributeValueException( "Encountered invalid administrativeRole '"
                        + role + "' in administrative point of subentry " + name + ". The values of this attribute are"
                        + " constrained to autonomousArea, accessControlSpecificArea, accessControlInnerArea,"
                        + " subschemaAdminSpecificArea, collectiveAttributeSpecificArea, and"
                        + " collectiveAttributeInnerArea.", ResultCodeEnum.CONSTRAINTVIOLATION );
            }
        }

        return operational;
    }


    /**
     * Calculates the subentry operational attributes to remove from a candidate
     * entry selected by a subtreeSpecification.  When we remove a subentry we
     * must remove the operational attributes in the entries that were once selected
     * by the subtree specification of that subentry.  To do so we must perform
     * a modify operation with the set of modifications to perform.  This method
     * calculates those modifications.
     *
     * @param subentryDn the distinguished name of the subentry
     * @param candidate the candidate entry to removed from the
     * @return the set of modifications required to remove an entry's reference to
     * a subentry
     */
    private ModificationItem[] getOperationalModsForRemove( Name subentryDn, Attributes candidate )
    {
        List modList = new ArrayList();
        String dn = subentryDn.toString();

        for ( int ii = 0; ii < SUBENTRY_OPATTRS.length; ii++ )
        {
            String opAttrId = SUBENTRY_OPATTRS[ii];
            Attribute opAttr = candidate.get( opAttrId );

            if ( opAttr != null && opAttr.contains( dn ) )
            {
                Attribute attr = new LockableAttributeImpl( SUBENTRY_OPATTRS[ii] );
                attr.add( dn );
                modList.add( new ModificationItem( DirContext.REMOVE_ATTRIBUTE, attr ) );
            }
        }

        ModificationItem[] mods = new ModificationItem[modList.size()];
        return ( ModificationItem[] ) modList.toArray( mods );
    }


    /**
     * Calculates the subentry operational attributes to add or replace from
     * a candidate entry selected by a subtree specification.  When a subentry
     * is added or it's specification is modified some entries must have new
     * operational attributes added to it to point back to the associated
     * subentry.  To do so a modify operation must be performed on entries
     * selected by the subtree specification.  This method calculates the
     * modify operation to be performed on the entry.
     *
     * @param entry the entry being modified
     * @param operational the set of operational attributes supported by the AP
     * of the subentry
     * @return the set of modifications needed to update the entry
     */
    public ModificationItem[] getOperationalModsForAdd( Attributes entry, Attributes operational ) throws NamingException
    {
        List modList = new ArrayList();

        NamingEnumeration opAttrIds = operational.getIDs();
        while ( opAttrIds.hasMore() )
        {
            int op = DirContext.REPLACE_ATTRIBUTE;
            String opAttrId = ( String ) opAttrIds.next();
            Attribute result = new LockableAttributeImpl( opAttrId );
            Attribute opAttrAdditions = operational.get( opAttrId );
            Attribute opAttrInEntry = entry.get( opAttrId );

            for ( int ii = 0; ii < opAttrAdditions.size(); ii++ )
            {
                result.add( opAttrAdditions.get( ii ) );
            }

            if ( opAttrInEntry != null && opAttrInEntry.size() > 0 )
            {
                for ( int ii = 0; ii < opAttrInEntry.size(); ii++ )
                {
                    result.add( opAttrInEntry.get( ii ) );
                }
            }
            else
            {
                op = DirContext.ADD_ATTRIBUTE;
            }

            modList.add( new ModificationItem( op, result ) );
        }

        ModificationItem[] mods = new ModificationItem[modList.size()];
        mods = ( ModificationItem[] ) modList.toArray( mods );
        return mods;
    }
}
TOP

Related Classes of org.apache.ldap.server.subtree.SubentryService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.