You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by ak...@apache.org on 2005/08/24 03:21:59 UTC

svn commit: r239501 - in /directory/apacheds/trunk/core/src: main/java/org/apache/ldap/server/schema/ test/org/apache/ldap/server/schema/ test/resources/

Author: akarasulu
Date: Tue Aug 23 18:21:51 2005
New Revision: 239501

URL: http://svn.apache.org/viewcvs?rev=239501&view=rev
Log:
changes ...

 o added some schema checking for 2251 compliance to the SchemaService which 
   is an interceptor responsible for schema checks
 o added satelite class for actually doing the checks called SchemaChecker
   which I suspect can be split into several other classes for the specific
   nature of the schema checks it performs
 o added test case to unit test the operation of functions in the SchemaChecker
 o added log4j configuration for testing only which is not packaged into the
   core jar 

todos ...

 o this stuff needs serious refactoring because of crazy amounts of code 
   duplication - it was easier (quick and dirty) to get the code working 
   as separate functions for now

This commit resolves the following issue, DIREVE-230, filed by Stefan:

	http://issues.apache.org/jira/browse/DIREVE-230


Added:
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaChecker.java
    directory/apacheds/trunk/core/src/test/org/apache/ldap/server/schema/SchemaCheckerTest.java
    directory/apacheds/trunk/core/src/test/resources/
    directory/apacheds/trunk/core/src/test/resources/log4j.properties
Modified:
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaService.java

Added: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaChecker.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaChecker.java?rev=239501&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaChecker.java (added)
+++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaChecker.java Tue Aug 23 18:21:51 2005
@@ -0,0 +1,658 @@
+/*
+ *   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.schema;
+
+
+import org.apache.ldap.common.util.NamespaceTools;
+import org.apache.ldap.common.exception.LdapSchemaViolationException;
+import org.apache.ldap.common.message.ResultCodeEnum;
+import org.apache.ldap.common.schema.ObjectClass;
+import org.apache.ldap.common.schema.ObjectClassTypeEnum;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.NamingException;
+import javax.naming.Name;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.Attribute;
+import java.util.Set;
+import java.util.HashSet;
+
+
+/**
+ * Performs schema checks on behalf of the SchemaService.
+ *
+ * @todo we really need to refactor this code since there's much duplication
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class SchemaChecker
+{
+    /** the SLF4J logger for this class */
+    private static Logger log = LoggerFactory.getLogger( SchemaChecker.class );
+
+
+    /**
+     * Makes sure modify operations do not leave the entry without a STRUCTURAL
+     * objectClass.  At least one STRUCTURAL objectClass must be specified for
+     * the entry after modifications take effect.
+     *
+     * @param registry the objectClass registry to lookup ObjectClass specifications
+     * @param name the name of the entry being modified
+     * @param mod the type of modification operation being performed (should be
+     * REMOVE_ATTRIBUTE)
+     * @param attribute the attribute being modified
+     * @throws NamingException if modify operations leave the entry inconsistent
+     * without a STRUCTURAL objectClass
+     */
+    public static void preventStructuralClassRemovalOnModifyReplace( ObjectClassRegistry registry, Name name, int mod,
+                                                                     Attribute attribute )
+            throws NamingException
+    {
+        if ( mod != DirContext.REPLACE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        if ( ! "objectclass".equalsIgnoreCase( attribute.getID() ) )
+        {
+            return;
+        }
+
+        // whoever issued the modify operation is insane they want to delete
+        // all the objectClass values in which case we must throw an exception
+        if ( attribute.size() == 0 )
+        {
+            String msg = "Modify operation leaves no structural objectClass for entry " + name;
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ".  Raising LdapSchemaViolationException." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // check that there is at least one structural objectClass in the replacement set
+        for ( int ii = 0; ii < attribute.size(); ii++ )
+        {
+            ObjectClass ocType = registry.lookup( ( String ) attribute.get( ii ) );
+            if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
+            {
+                return;
+            }
+        }
+
+        // no structural object classes exist for the entry in the replacement
+        // set for the objectClass attribute so we need to complain about that
+        String msg = "Modify operation leaves no structural objectClass for entry " + name;
+        if ( log.isInfoEnabled() )
+        {
+            log.info( msg + ".  Raising LdapSchemaViolationException." );
+        }
+        throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+    }
+
+
+    /**
+     * Makes sure modify operations do not leave the entry without a STRUCTURAL
+     * objectClass.  At least one STRUCTURAL objectClass must be specified for
+     * the entry after modifications take effect.
+     *
+     * @param registry the objectClass registry to lookup ObjectClass specifications
+     * @param name the name of the entry being modified
+     * @param mod the type of modification operation being performed (should be
+     * REMOVE_ATTRIBUTE)
+     * @param attributes the attributes being modified
+     * @throws NamingException if modify operations leave the entry inconsistent
+     * without a STRUCTURAL objectClass
+     */
+    public static void preventStructuralClassRemovalOnModifyReplace( ObjectClassRegistry registry, Name name, int mod,
+                                                                     Attributes attributes )
+            throws NamingException
+    {
+        if ( mod != DirContext.REPLACE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Attribute objectClass = attributes.get( "objectClass" );
+        if ( objectClass == null )
+        {
+            return;
+        }
+
+        // whoever issued the modify operation is insane they want to delete
+        // all the objectClass values in which case we must throw an exception
+        if ( objectClass.size() == 0 )
+        {
+            String msg = "Modify operation leaves no structural objectClass for entry " + name;
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ".  Raising LdapSchemaViolationException." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // check that there is at least one structural objectClass in the replacement set
+        for ( int ii = 0; ii < objectClass.size(); ii++ )
+        {
+            ObjectClass ocType = registry.lookup( ( String ) objectClass.get( ii ) );
+            if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
+            {
+                return;
+            }
+        }
+
+        // no structural object classes exist for the entry in the replacement
+        // set for the objectClass attribute so we need to complain about that
+        String msg = "Modify operation leaves no structural objectClass for entry " + name;
+        if ( log.isInfoEnabled() )
+        {
+            log.info( msg + ".  Raising LdapSchemaViolationException." );
+        }
+        throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+    }
+
+
+    /**
+     * Makes sure modify operations do not leave the entry without a STRUCTURAL
+     * objectClass.  At least one STRUCTURAL objectClass must be specified for
+     * the entry after modifications take effect.
+     *
+     * @param registry the objectClass registry to lookup ObjectClass specifications
+     * @param name the name of the entry being modified
+     * @param mod the type of modification operation being performed (should be
+     * REMOVE_ATTRIBUTE)
+     * @param attribute the attribute being modified
+     * @param entryObjectClasses the entry being modified
+     * @throws NamingException if modify operations leave the entry inconsistent
+     * without a STRUCTURAL objectClass
+     */
+    public static void preventStructuralClassRemovalOnModifyRemove( ObjectClassRegistry registry, Name name, int mod,
+                                                                    Attribute attribute, Attribute entryObjectClasses )
+            throws NamingException
+    {
+        if ( mod != DirContext.REMOVE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        if ( ! "objectclass".equalsIgnoreCase( attribute.getID() ) )
+        {
+            return;
+        }
+
+        // whoever issued the modify operation is insane they want to delete
+        // all the objectClass values in which case we must throw an exception
+        if ( attribute.size() == 0 )
+        {
+            String msg = "Modify operation leaves no structural objectClass for entry " + name;
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ".  Raising LdapSchemaViolationException." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // remove all the objectClass attribute values from a cloned copy and then
+        // we can analyze what remains in this attribute to make sure a structural
+        // objectClass is present for the entry
+
+        Attribute cloned = ( Attribute ) entryObjectClasses.clone();
+        for ( int ii = 0; ii < attribute.size(); ii++ )
+        {
+            cloned.remove( attribute.get( ii ) );
+        }
+
+        // check resultant set of objectClass values for a structural objectClass
+        for ( int ii = 0; ii < cloned.size(); ii++ )
+        {
+            ObjectClass ocType = registry.lookup( ( String ) cloned.get( ii ) );
+            if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
+            {
+                return;
+            }
+        }
+
+        // no structural object classes exist for the entry after the modifications
+        // to the objectClass attribute so we need to complain about that
+        String msg = "Modify operation leaves no structural objectClass for entry " + name;
+        if ( log.isInfoEnabled() )
+        {
+            log.info( msg + ".  Raising LdapSchemaViolationException." );
+        }
+        throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+    }
+
+
+    /**
+     * Makes sure modify operations do not leave the entry without a STRUCTURAL
+     * objectClass.  At least one STRUCTURAL objectClass must be specified for
+     * the entry after modifications take effect.
+     *
+     * @param registry the objectClass registry to lookup ObjectClass specifications
+     * @param name the name of the entry being modified
+     * @param mod the type of modification operation being performed (should be
+     * REMOVE_ATTRIBUTE)
+     * @param attributes the attributes being modified
+     * @param entryObjectClasses the entry being modified
+     * @throws NamingException if modify operations leave the entry inconsistent
+     * without a STRUCTURAL objectClass
+     */
+    public static void preventStructuralClassRemovalOnModifyRemove( ObjectClassRegistry registry, Name name, int mod,
+                                                                    Attributes attributes, Attribute entryObjectClasses )
+            throws NamingException
+    {
+        if ( mod != DirContext.REMOVE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Attribute objectClass = attributes.get( "objectClass" );
+        if ( objectClass == null )
+        {
+            return;
+        }
+
+        // whoever issued the modify operation is insane they want to delete
+        // all the objectClass values in which case we must throw an exception
+        if ( objectClass.size() == 0 )
+        {
+            String msg = "Modify operation leaves no structural objectClass for entry " + name;
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ".  Raising LdapSchemaViolationException." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // remove all the objectClass attribute values from a cloned copy and then
+        // we can analyze what remains in this attribute to make sure a structural
+        // objectClass is present for the entry
+
+        Attribute cloned = ( Attribute ) entryObjectClasses.clone();
+        for ( int ii = 0; ii < objectClass.size(); ii++ )
+        {
+            cloned.remove( objectClass.get( ii ) );
+        }
+
+        // check resultant set of objectClass values for a structural objectClass
+        for ( int ii = 0; ii < cloned.size(); ii++ )
+        {
+            ObjectClass ocType = registry.lookup( ( String ) cloned.get( ii ) );
+            if ( ocType.getType() == ObjectClassTypeEnum.STRUCTURAL )
+            {
+                return;
+            }
+        }
+
+        // no structural object classes exist for the entry after the modifications
+        // to the objectClass attribute so we need to complain about that
+        String msg = "Modify operation leaves no structural objectClass for entry " + name;
+        if ( log.isInfoEnabled() )
+        {
+            log.info( msg + ".  Raising LdapSchemaViolationException." );
+        }
+        throw new LdapSchemaViolationException( msg, ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+    }
+
+
+    /**
+     * Makes sure a modify operation does not replace RDN attributes or their value.
+     * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
+     * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
+     * seen below:
+     * <p/>
+     * <pre>
+     *     The Modify Operation cannot be used to remove from an entry any of
+     *     its distinguished values, those values which form the entry's
+     *     relative distinguished name.  An attempt to do so will result in the
+     *     server returning the error notAllowedOnRDN.  The Modify DN Operation
+     *     described in section 4.9 is used to rename an entry.
+     * </pre>
+     *
+     * @param name the distinguished name of the attribute being modified
+     * @param mod the modification operation being performed (should be REPLACE_ATTRIBUTE )
+     * @param attribute the attribute being modified
+     * @throws NamingException if the modify operation is removing an Rdn attribute
+     */
+    public static void preventRdnChangeOnModifyReplace( Name name, int mod, Attribute attribute )
+            throws NamingException
+    {
+        if ( mod != DirContext.REPLACE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Set rdnAttributes = getRdnAttributes( name );
+        String id = ( String ) attribute.getID();
+
+        if ( ! rdnAttributes.contains( id ) )
+        {
+            return;
+        }
+
+        // if the attribute values to delete are not specified then all values
+        // for the attribute are to be deleted in which case we must just throw
+        // a schema violation exception with the notAllowedOnRdn result code
+        if ( attribute.size() == 0 )
+        {
+            String msg = "Modify operation attempts to delete RDN attribute ";
+            msg += id + " on entry " + name + " violates schema constraints";
+
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+        }
+
+        // from here on the modify operation replaces specific values
+        // of the Rdn attribute so we must check to make sure all the old
+        // rdn attribute values are present in the replacement set
+        String rdnValue = getRdnValue( id, name );
+        for ( int ii = 0; ii < attribute.size(); ii++ )
+        {
+            // if the old rdn value is not in the rdn attribute then
+            // we must complain with a schema violation
+            if ( ! attribute.contains( rdnValue ) )
+            {
+                String msg = "Modify operation attempts to delete RDN attribute values in use for ";
+                msg += id + " on entry " + name + " and violates schema constraints";
+
+                if ( log.isInfoEnabled() )
+                {
+                    log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                }
+                throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+            }
+        }
+    }
+
+
+    /**
+     * Makes sure a modify operation does not replace RDN attributes or their value.
+     * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
+     * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
+     * seen below:
+     * <p/>
+     * <pre>
+     *     The Modify Operation cannot be used to remove from an entry any of
+     *     its distinguished values, those values which form the entry's
+     *     relative distinguished name.  An attempt to do so will result in the
+     *     server returning the error notAllowedOnRDN.  The Modify DN Operation
+     *     described in section 4.9 is used to rename an entry.
+     * </pre>
+     *
+     * @param name the distinguished name of the attribute being modified
+     * @param mod the modification operation being performed (should be REPLACE_ATTRIBUTE )
+     * @param attributes the attributes being modified
+     * @throws NamingException if the modify operation is removing an Rdn attribute
+     */
+    public static void preventRdnChangeOnModifyReplace( Name name, int mod, Attributes attributes )
+            throws NamingException
+    {
+        if ( mod != DirContext.REPLACE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Set rdnAttributes = getRdnAttributes( name );
+        NamingEnumeration list = attributes.getIDs();
+        while ( list.hasMore() )
+        {
+            String id = ( String ) list.next();
+
+            if ( rdnAttributes.contains( id ) )
+            {
+                // if the attribute values to delete are not specified then all values
+                // for the attribute are to be deleted in which case we must just throw
+                // a schema violation exception with the notAllowedOnRdn result code
+                if ( attributes.get( id ).size() == 0 )
+                {
+                    String msg = "Modify operation attempts to delete RDN attribute ";
+                    msg += id + " on entry " + name + " violates schema constraints";
+
+                    if ( log.isInfoEnabled() )
+                    {
+                        log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                    }
+                    throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+                }
+
+                // from here on the modify operation replaces specific values
+                // of the Rdn attribute so we must check to make sure all the old
+                // rdn attribute values are present in the replacement set
+                String rdnValue = getRdnValue( id, name );
+                Attribute rdnAttr = attributes.get( id );
+                for ( int ii = 0; ii < rdnAttr.size(); ii++ )
+                {
+                    // if the old rdn value is not in the rdn attribute then
+                    // we must complain with a schema violation
+                    if ( ! rdnAttr.contains( rdnValue ) )
+                    {
+                        String msg = "Modify operation attempts to delete RDN attribute values in use for ";
+                        msg += id + " on entry " + name + " and violates schema constraints";
+
+                        if ( log.isInfoEnabled() )
+                        {
+                            log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                        }
+                        throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Makes sure a modify operation does not delete RDN attributes or their value.
+     * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
+     * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
+     * seen below:
+     * <p/>
+     * <pre>
+     *     The Modify Operation cannot be used to remove from an entry any of
+     *     its distinguished values, those values which form the entry's
+     *     relative distinguished name.  An attempt to do so will result in the
+     *     server returning the error notAllowedOnRDN.  The Modify DN Operation
+     *     described in section 4.9 is used to rename an entry.
+     * </pre>
+     *
+     * @param name the distinguished name of the attribute being modified
+     * @param mod the modification operation being performed (should be REMOVE_ATTRIBUTE )
+     * @param attribute the attribute being modified
+     * @throws NamingException if the modify operation is removing an Rdn attribute
+     */
+    public static void preventRdnChangeOnModifyRemove( Name name, int mod, Attribute attribute )
+            throws NamingException
+    {
+        if ( mod != DirContext.REMOVE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Set rdnAttributes = getRdnAttributes( name );
+        String id = attribute.getID();
+
+        if ( ! rdnAttributes.contains( id ) )
+        {
+            return;
+        }
+
+        // if the attribute values to delete are not specified then all values
+        // for the attribute are to be deleted in which case we must just throw
+        // a schema violation exception with the notAllowedOnRdn result code
+        if ( attribute.size() == 0 )
+        {
+            String msg = "Modify operation attempts to delete RDN attribute ";
+            msg += id + " on entry " + name + " violates schema constraints";
+
+            if ( log.isInfoEnabled() )
+            {
+                log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+            }
+            throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+        }
+
+        // from here on the modify operation only deletes specific values
+        // of the Rdn attribute so we must check if one of those values
+        // are used by the Rdn attribute value pair for the name of the entry
+        String rdnValue = getRdnValue( id, name );
+        for ( int ii = 0; ii < attribute.size(); ii++ )
+        {
+            if ( rdnValue.equals( attribute.get( ii ) ) )
+            {
+                String msg = "Modify operation attempts to delete RDN attribute values in use for ";
+                msg += id + " on entry " + name + " and violates schema constraints";
+
+                if ( log.isInfoEnabled() )
+                {
+                    log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                }
+                throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+            }
+        }
+    }
+
+
+    /**
+     * Makes sure a modify operation does not delete RDN attributes or their value.
+     * According to section 4.6 of <a href="http://rfc.net/rfc2251.html#s4.6.">
+     * RFC 2251</a> a modify operation cannot be used to remove Rdn attributes as
+     * seen below:
+     * <p/>
+     * <pre>
+     *     The Modify Operation cannot be used to remove from an entry any of
+     *     its distinguished values, those values which form the entry's
+     *     relative distinguished name.  An attempt to do so will result in the
+     *     server returning the error notAllowedOnRDN.  The Modify DN Operation
+     *     described in section 4.9 is used to rename an entry.
+     * </pre>
+     *
+     * @param name the distinguished name of the attribute being modified
+     * @param mod the modification operation being performed (should be REMOVE_ATTRIBUTE )
+     * @param attributes the attributes being modified
+     * @throws NamingException if the modify operation is removing an Rdn attribute
+     */
+    public static void preventRdnChangeOnModifyRemove( Name name, int mod, Attributes attributes )
+            throws NamingException
+    {
+        if ( mod != DirContext.REMOVE_ATTRIBUTE )
+        {
+            return;
+        }
+
+        Set rdnAttributes = getRdnAttributes( name );
+        NamingEnumeration list = attributes.getIDs();
+        while ( list.hasMore() )
+        {
+            String id = ( String ) list.next();
+
+            if ( rdnAttributes.contains( id ) )
+            {
+                // if the attribute values to delete are not specified then all values
+                // for the attribute are to be deleted in which case we must just throw
+                // a schema violation exception with the notAllowedOnRdn result code
+                if ( attributes.get( id ).size() == 0 )
+                {
+                    String msg = "Modify operation attempts to delete RDN attribute ";
+                    msg += id + " on entry " + name + " violates schema constraints";
+
+                    if ( log.isInfoEnabled() )
+                    {
+                        log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                    }
+                    throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+                }
+
+                // from here on the modify operation only deletes specific values
+                // of the Rdn attribute so we must check if one of those values
+                // are used by the Rdn attribute value pair for the name of the entry
+                String rdnValue = getRdnValue( id, name );
+                Attribute rdnAttr = attributes.get( id );
+                for ( int ii = 0; ii < rdnAttr.size(); ii++ )
+                {
+                    if ( rdnValue.equals( rdnAttr.get( ii ) ) )
+                    {
+                        String msg = "Modify operation attempts to delete RDN attribute values in use for ";
+                        msg += id + " on entry " + name + " and violates schema constraints";
+
+                        if ( log.isInfoEnabled() )
+                        {
+                            log.info( msg + ". SchemaChecker is throwing a schema violation exception." );
+                        }
+                        throw new LdapSchemaViolationException( msg, ResultCodeEnum.NOTALLOWEDONRDN );
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Gets the Rdn attribute value. This method works even if the Rdn is
+     * composed of multiple attributes.
+     *
+     * @param id the attribute id of the Rdn attribute to return
+     * @param name the distinguished name of the entry
+     * @return the Rdn attribute value corresponding to the id, or null if the
+     * attribute is not an rdn attribute
+     * @throws NamingException if the name is malformed in any way
+     */
+    private static String getRdnValue( String id, Name name ) throws NamingException
+    {
+        String [] comps = NamespaceTools.getCompositeComponents( name.get( name.size() - 1 ) );
+
+        for ( int ii = 0; ii < comps.length; ii++ )
+        {
+            String rdnAttrId = NamespaceTools.getRdnAttribute( comps[ii] );
+
+            if ( rdnAttrId.equalsIgnoreCase( id ) )
+            {
+                return NamespaceTools.getRdnValue( comps[ii] );
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Collects the set of Rdn attributes whether or not the Rdn is based on a
+     * single attribute or multiple attributes.
+     *
+     * @param name the distinguished name of an entry
+     * @return the set of attributes composing the Rdn for the name
+     * @throws NamingException if the syntax of the Rdn is incorrect
+     */
+    private static Set getRdnAttributes( Name name ) throws NamingException
+    {
+        String [] comps = NamespaceTools.getCompositeComponents( name.get( name.size() - 1 ) );
+        Set attributes = new HashSet();
+
+        for ( int ii = 0; ii < comps.length; ii++ )
+        {
+            attributes.add( NamespaceTools.getRdnAttribute( comps[ii] ) );
+        }
+
+        return attributes;
+    }
+}

Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaService.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaService.java?rev=239501&r1=239500&r2=239501&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaService.java (original)
+++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/schema/SchemaService.java Tue Aug 23 18:21:51 2005
@@ -26,10 +26,7 @@
 import javax.naming.Name;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
+import javax.naming.directory.*;
 import javax.naming.ldap.LdapContext;
 
 import org.apache.ldap.common.filter.ExprNode;
@@ -56,6 +53,8 @@
 import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
 import org.apache.ldap.server.jndi.ServerLdapContext;
 import org.apache.ldap.server.partition.ContextPartitionNexus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 /**
@@ -69,6 +68,9 @@
 {
     private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
 
+    /** The LoggerFactory used by this Interceptor */
+    private static Logger log = LoggerFactory.getLogger( SchemaService.class );
+
     /**
      * the root nexus to all database partitions
      */
@@ -127,8 +129,8 @@
 
 
     public NamingEnumeration search( NextInterceptor nextInterceptor,
-            Name base, Map env, ExprNode filter,
-            SearchControls searchCtls ) throws NamingException
+                                     Name base, Map env, ExprNode filter,
+                                     SearchControls searchCtls ) throws NamingException
     {
         // check to make sure the DN searched for is a subentry
         if ( !subentryDn.equals( base.toString() ) )
@@ -328,12 +330,60 @@
     }
 
 
+    public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
+    {
+        ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
+
+        if ( modOp == DirContext.REMOVE_ATTRIBUTE )
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, mods );
+            Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
+            SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, mods, ocAttr );
+        }
+
+        if ( modOp == DirContext.REPLACE_ATTRIBUTE )
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, mods );
+            SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, mods );
+        }
+
+        next.modify( name, modOp, mods );
+    }
+
+
+    public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
+    {
+        ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
+
+        for ( int ii = 0; ii < mods.length; ii++ )
+        {
+            int modOp = mods[ii].getModificationOp();
+            Attribute change = mods[ii].getAttribute();
+
+            if ( modOp == DirContext.REMOVE_ATTRIBUTE )
+            {
+                SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, change );
+                Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
+                SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change, ocAttr );
+            }
+
+            if ( modOp == DirContext.REPLACE_ATTRIBUTE )
+            {
+                SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change );
+                SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
+            }
+        }
+
+        next.modify( name, mods );
+    }
+
+
     private void doFilter( LdapContext ctx, Attributes entry )
             throws NamingException
     {
         // set of AttributeType objects that are to behave as binaries
         Set binaries;
-        
+
         // construct the set for fast lookups while filtering
         String binaryIds = ( String ) ctx.getEnvironment().get( BINARY_KEY );
 
@@ -354,11 +404,11 @@
                 binaries.add( type );
             }
         }
-        
+
         /*
-         * start converting values of attributes to byte[]s which are not
-         * human readable and those that are in the binaries set
-         */
+        * start converting values of attributes to byte[]s which are not
+        * human readable and those that are in the binaries set
+        */
         NamingEnumeration list = entry.getIDs();
 
         while ( list.hasMore() )

Added: directory/apacheds/trunk/core/src/test/org/apache/ldap/server/schema/SchemaCheckerTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/org/apache/ldap/server/schema/SchemaCheckerTest.java?rev=239501&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/test/org/apache/ldap/server/schema/SchemaCheckerTest.java (added)
+++ directory/apacheds/trunk/core/src/test/org/apache/ldap/server/schema/SchemaCheckerTest.java Tue Aug 23 18:21:51 2005
@@ -0,0 +1,577 @@
+/*
+ *   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.schema;
+
+
+import junit.framework.TestCase;
+
+import javax.naming.directory.*;
+import javax.naming.Name;
+import javax.naming.NamingException;
+
+import org.apache.ldap.common.name.LdapName;
+import org.apache.ldap.common.exception.LdapSchemaViolationException;
+import org.apache.ldap.common.message.ResultCodeEnum;
+import org.apache.ldap.server.schema.bootstrap.*;
+
+import java.util.Set;
+import java.util.HashSet;
+
+
+/**
+ * Tests to make sure the schema checker is operating correctly.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class SchemaCheckerTest extends TestCase
+{
+    GlobalRegistries registries = null;
+
+
+    public SchemaCheckerTest() throws NamingException
+    {
+        this( "SchemaCheckerTest" );
+    }
+
+
+    public SchemaCheckerTest( String name ) throws NamingException
+    {
+        super( name );
+
+        BootstrapRegistries bootstrapRegistries = new BootstrapRegistries();
+
+        BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
+        Set schemas = new HashSet();
+        schemas.add( new SystemSchema() );
+        schemas.add( new ApacheSchema() );
+        schemas.add( new CoreSchema() );
+        schemas.add( new CosineSchema() );
+        schemas.add( new InetorgpersonSchema() );
+        schemas.add( new JavaSchema() );
+        loader.load( schemas, bootstrapRegistries );
+
+        java.util.List errors = bootstrapRegistries.checkRefInteg();
+        if ( !errors.isEmpty() )
+        {
+            NamingException e = new NamingException();
+            e.setRootCause( ( Throwable ) errors.get( 0 ) );
+            throw e;
+        }
+
+        registries = new GlobalRegistries( bootstrapRegistries );
+    }
+
+
+    private GlobalRegistries getGlobalRegistries() throws NamingException
+    {
+        BootstrapRegistries bootstrapRegistries = new BootstrapRegistries();
+
+        BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
+        Set schemas = new HashSet();
+        schemas.add( new SystemSchema() );
+        schemas.add( new ApacheSchema() );
+        schemas.add( new CoreSchema() );
+        schemas.add( new CosineSchema() );
+        schemas.add( new InetorgpersonSchema() );
+        schemas.add( new JavaSchema() );
+        loader.load( schemas, bootstrapRegistries );
+
+        java.util.List errors = bootstrapRegistries.checkRefInteg();
+        if ( !errors.isEmpty() )
+        {
+            NamingException e = new NamingException();
+            e.setRootCause( ( Throwable ) errors.get( 0 ) );
+            throw e;
+        }
+
+        return new GlobalRegistries( bootstrapRegistries );
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations replace objectClasses.
+     */
+    public void testPreventStructuralClassRemovalOnModifyReplace() throws Exception
+    {
+        Name name = new LdapName( "uid=akarasulu,ou=users,dc=example,dc=com" );
+        int mod = DirContext.REPLACE_ATTRIBUTE;
+        Attributes modifyAttributes = new BasicAttributes( true );
+        modifyAttributes.put( new BasicAttribute( "cn" ) );
+
+        ObjectClassRegistry ocRegistry = registries.getObjectClassRegistry();
+
+        // this should pass
+        SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, modifyAttributes );
+
+        // this should succeed since person is still in replaced set and is structural
+        modifyAttributes.remove( "cn" );
+        BasicAttribute objectClassesReplaced = new BasicAttribute( "objectClass" );
+        objectClassesReplaced.add( "top" );
+        objectClassesReplaced.add( "person" );
+        modifyAttributes.put( objectClassesReplaced );
+        SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, modifyAttributes );
+
+        // this should fail since only top is left
+        objectClassesReplaced = new BasicAttribute( "objectClass" );
+        objectClassesReplaced.add( "top" );
+        modifyAttributes.put( objectClassesReplaced );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, modifyAttributes );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // this should fail since the modify operation tries to delete all
+        // objectClass attribute values
+        modifyAttributes.remove( "cn" );
+        objectClassesReplaced = new BasicAttribute( "objectClass" );
+        modifyAttributes.put( objectClassesReplaced );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, modifyAttributes );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations remove objectClasses.
+     */
+    public void testPreventStructuralClassRemovalOnModifyRemove() throws Exception
+    {
+        Name name = new LdapName( "uid=akarasulu,ou=users,dc=example,dc=com" );
+        int mod = DirContext.REMOVE_ATTRIBUTE;
+        Attributes modifyAttributes = new BasicAttributes( true );
+        Attribute entryObjectClasses = new BasicAttribute( "objectClass" );
+        entryObjectClasses.add( "top" );
+        entryObjectClasses.add( "person" );
+        entryObjectClasses.add( "organizationalPerson" );
+        modifyAttributes.put( new BasicAttribute( "cn" ) );
+
+        ObjectClassRegistry ocRegistry = registries.getObjectClassRegistry();
+
+        // this should pass
+        SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, modifyAttributes,
+                entryObjectClasses );
+
+        // this should succeed since person is left and is structural
+        modifyAttributes.remove( "cn" );
+        BasicAttribute objectClassesRemoved = new BasicAttribute( "objectClass" );
+        objectClassesRemoved.add( "person" );
+        modifyAttributes.put( objectClassesRemoved );
+        SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, modifyAttributes,
+                entryObjectClasses );
+
+        // this should fail since only top is left
+        modifyAttributes.remove( "cn" );
+        objectClassesRemoved = new BasicAttribute( "objectClass" );
+        objectClassesRemoved.add( "person" );
+        objectClassesRemoved.add( "organizationalPerson" );
+        modifyAttributes.put( objectClassesRemoved );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, modifyAttributes,
+                    entryObjectClasses );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // this should fail since the modify operation tries to delete all
+        // objectClass attribute values
+        modifyAttributes.remove( "cn" );
+        objectClassesRemoved = new BasicAttribute( "objectClass" );
+        modifyAttributes.put( objectClassesRemoved );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, modifyAttributes,
+                    entryObjectClasses );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations remove RDN attributes.
+     */
+    public void testPreventRdnChangeOnModifyRemove() throws Exception
+    {
+        int mod = DirContext.REMOVE_ATTRIBUTE;
+        Name name = new LdapName( "ou=user,dc=example,dc=com" );
+        Attributes attributes = new BasicAttributes( true );
+        attributes.put( "cn", "does not matter" );
+
+        // postive test which should pass
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+
+        // test should fail since we are removing the ou attribute
+        attributes.put( new BasicAttribute( "ou" ) );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // test success using more than one attribute for the Rdn but not modifying rdn attribute
+        name = new LdapName( "ou=users+cn=system users,dc=example,dc=com" );
+        attributes = new BasicAttributes( true );
+        attributes.put( "sn", "does not matter" );
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+
+        // test for failure when modifying Rdn attribute in multi attribute Rdn
+        attributes.put( new BasicAttribute( "cn" ) );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // should succeed since the value being deleted from the rdn attribute is
+        // is not used when composing the Rdn
+        attributes = new BasicAttributes( true );
+        attributes.put( "ou", "container" );
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+
+        // now let's make it fail again just by providing the right value for ou (users)
+        attributes = new BasicAttributes( true );
+        attributes.put( "ou", "users" );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations replace RDN attributes.
+     */
+    public void testPreventRdnChangeOnModifyReplace() throws Exception
+    {
+        int mod = DirContext.REPLACE_ATTRIBUTE;
+        Name name = new LdapName( "ou=user,dc=example,dc=com" );
+        Attributes attributes = new BasicAttributes( true );
+        attributes.put( "cn", "does not matter" );
+
+        // postive test which should pass
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+
+        // test should fail since we are removing the ou attribute
+        attributes.put( new BasicAttribute( "ou" ) );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // test success using more than one attribute for the Rdn but not modifying rdn attribute
+        name = new LdapName( "ou=users+cn=system users,dc=example,dc=com" );
+        attributes = new BasicAttributes( true );
+        attributes.put( "sn", "does not matter" );
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+
+        // test for failure when modifying Rdn attribute in multi attribute Rdn
+        attributes.put( new BasicAttribute( "cn" ) );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // should succeed since the values being replaced from the rdn attribute is
+        // is includes the old Rdn attribute value
+        attributes = new BasicAttributes( true );
+        attributes.put( "ou", "container" );
+        attributes.put( "ou", "users" );
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+
+        // now let's make it fail by not including the old value for ou (users)
+        attributes = new BasicAttributes( true );
+        attributes.put( "ou", "container" );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attributes );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Single Attribute Test Cases
+    // ------------------------------------------------------------------------
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations replace objectClasses.
+     */
+    public void testPreventStructuralClassRemovalOnModifyReplaceAttribute() throws Exception
+    {
+        ObjectClassRegistry ocRegistry = registries.getObjectClassRegistry();
+
+        // this should pass
+        Name name = new LdapName( "uid=akarasulu,ou=users,dc=example,dc=com" );
+        int mod = DirContext.REPLACE_ATTRIBUTE;
+        SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, new BasicAttribute( "cn" ) );
+
+        // this should succeed since person is still in replaced set and is structural
+        BasicAttribute objectClassesReplaced = new BasicAttribute( "objectClass" );
+        objectClassesReplaced.add( "top" );
+        objectClassesReplaced.add( "person" );
+        SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, objectClassesReplaced );
+
+        // this should fail since only top is left
+        objectClassesReplaced = new BasicAttribute( "objectClass" );
+        objectClassesReplaced.add( "top" );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, objectClassesReplaced );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // this should fail since the modify operation tries to delete all
+        // objectClass attribute values
+        objectClassesReplaced = new BasicAttribute( "objectClass" );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, mod, objectClassesReplaced );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations remove objectClasses.
+     */
+    public void testPreventStructuralClassRemovalOnModifyRemoveAttribute() throws Exception
+    {
+        Name name = new LdapName( "uid=akarasulu,ou=users,dc=example,dc=com" );
+        int mod = DirContext.REMOVE_ATTRIBUTE;
+        Attribute entryObjectClasses = new BasicAttribute( "objectClass" );
+        entryObjectClasses.add( "top" );
+        entryObjectClasses.add( "person" );
+        entryObjectClasses.add( "organizationalPerson" );
+
+        ObjectClassRegistry ocRegistry = registries.getObjectClassRegistry();
+
+        // this should pass
+        SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, new BasicAttribute( "cn" ),
+                entryObjectClasses );
+
+        // this should succeed since person is left and is structural
+        BasicAttribute objectClassesRemoved = new BasicAttribute( "objectClass" );
+        objectClassesRemoved.add( "person" );
+        SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, objectClassesRemoved,
+                entryObjectClasses );
+
+        // this should fail since only top is left
+        objectClassesRemoved = new BasicAttribute( "objectClass" );
+        objectClassesRemoved.add( "person" );
+        objectClassesRemoved.add( "organizationalPerson" );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, objectClassesRemoved,
+                    entryObjectClasses );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+
+        // this should fail since the modify operation tries to delete all
+        // objectClass attribute values
+        objectClassesRemoved = new BasicAttribute( "objectClass" );
+        try
+        {
+            SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, mod, objectClassesRemoved,
+                    entryObjectClasses );
+            fail( "should never get here due to an LdapSchemaViolationException" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( e.getResultCode(), ResultCodeEnum.OBJECTCLASSMODSPROHIBITED );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations remove RDN attributes.
+     */
+    public void testPreventRdnChangeOnModifyRemoveAttribute() throws Exception
+    {
+        int mod = DirContext.REMOVE_ATTRIBUTE;
+        Name name = new LdapName( "ou=user,dc=example,dc=com" );
+
+        // postive test which should pass
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "cn", "does not matter" ) );
+
+        // test should fail since we are removing the ou attribute
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "ou" ) );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // test success using more than one attribute for the Rdn but not modifying rdn attribute
+        name = new LdapName( "ou=users+cn=system users,dc=example,dc=com" );
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "sn", "does not matter" ) );
+
+        // test for failure when modifying Rdn attribute in multi attribute Rdn
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "cn" ) );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // should succeed since the value being deleted from the rdn attribute is
+        // is not used when composing the Rdn
+        SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "ou", "container" ) );
+
+        // now let's make it fail again just by providing the right value for ou (users)
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyRemove( name, mod, new BasicAttribute( "ou", "users" ) );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+    }
+
+
+    /**
+     * Test case to check the schema checker operates correctly when modify
+     * operations replace RDN attributes.
+     */
+    public void testPreventRdnChangeOnModifyReplaceAttribute() throws Exception
+    {
+        int mod = DirContext.REPLACE_ATTRIBUTE;
+        Name name = new LdapName( "ou=user,dc=example,dc=com" );
+
+        // postive test which should pass
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, new BasicAttribute( "cn", "does not matter" ) );
+
+        // test should fail since we are removing the ou attribute
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, new BasicAttribute( "ou" ) );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // test success using more than one attribute for the Rdn but not modifying rdn attribute
+        name = new LdapName( "ou=users+cn=system users,dc=example,dc=com" );
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, new BasicAttribute( "sn", "does not matter" ) );
+
+        // test for failure when modifying Rdn attribute in multi attribute Rdn
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, new BasicAttribute( "cn" ) );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+
+        // should succeed since the values being replaced from the rdn attribute is
+        // is includes the old Rdn attribute value
+        Attribute attribute = new BasicAttribute( "ou" );
+        attribute.add( "container" );
+        attribute.add( "users" );
+        SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attribute );
+
+        // now let's make it fail by not including the old value for ou (users)
+        attribute = new BasicAttribute( "ou" );
+        attribute.add( "container" );
+        try
+        {
+            SchemaChecker.preventRdnChangeOnModifyReplace( name, mod, attribute );
+            fail( "should never get here due to a LdapSchemaViolationException being thrown" );
+        }
+        catch ( LdapSchemaViolationException e )
+        {
+            assertEquals( ResultCodeEnum.NOTALLOWEDONRDN, e.getResultCode() );
+        }
+    }
+}

Added: directory/apacheds/trunk/core/src/test/resources/log4j.properties
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/resources/log4j.properties?rev=239501&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/test/resources/log4j.properties (added)
+++ directory/apacheds/trunk/core/src/test/resources/log4j.properties Tue Aug 23 18:21:51 2005
@@ -0,0 +1,6 @@
+log4j.rootCategory=DEBUG, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n
+