blob: 22c946e4c7ef61bbb3fd607b3c9930740f631a9c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.directory.server.core.schema;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.directory.InvalidAttributeValueException;
import javax.naming.directory.SearchControls;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.entry.DefaultServerAttribute;
import org.apache.directory.server.core.entry.ServerAttribute;
import org.apache.directory.server.core.entry.ServerBinaryValue;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerEntryUtils;
import org.apache.directory.server.core.entry.ServerSearchResult;
import org.apache.directory.server.core.entry.ServerStringValue;
import org.apache.directory.server.core.enumeration.SearchResultFilter;
import org.apache.directory.server.core.enumeration.SearchResultFilteringEnumeration;
import org.apache.directory.server.core.interceptor.BaseInterceptor;
import org.apache.directory.server.core.interceptor.NextInterceptor;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.interceptor.context.ListOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.invocation.Invocation;
import org.apache.directory.server.core.invocation.InvocationStack;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
import org.apache.directory.server.schema.registries.ObjectClassRegistry;
import org.apache.directory.server.schema.registries.OidRegistry;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
import org.apache.directory.shared.ldap.exception.LdapAttributeInUseException;
import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeIdentifierException;
import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
import org.apache.directory.shared.ldap.filter.ApproximateNode;
import org.apache.directory.shared.ldap.filter.AssertionNode;
import org.apache.directory.shared.ldap.filter.BranchNode;
import org.apache.directory.shared.ldap.filter.EqualityNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.ExtensibleNode;
import org.apache.directory.shared.ldap.filter.GreaterEqNode;
import org.apache.directory.shared.ldap.filter.LessEqNode;
import org.apache.directory.shared.ldap.filter.PresenceNode;
import org.apache.directory.shared.ldap.filter.ScopeNode;
import org.apache.directory.shared.ldap.filter.SimpleNode;
import org.apache.directory.shared.ldap.filter.SubstringNode;
import org.apache.directory.shared.ldap.message.CascadeControl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.AttributeTypeAndValue;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.name.Rdn;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.schema.ObjectClass;
import org.apache.directory.shared.ldap.schema.SchemaUtils;
import org.apache.directory.shared.ldap.schema.UsageEnum;
import org.apache.directory.shared.ldap.schema.syntax.AcceptAllSyntaxChecker;
import org.apache.directory.shared.ldap.schema.syntax.SyntaxChecker;
import org.apache.directory.shared.ldap.util.EmptyEnumeration;
import org.apache.directory.shared.ldap.util.SingletonEnumeration;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An {@link org.apache.directory.server.core.interceptor.Interceptor} that manages and enforces schemas.
*
* @todo Better interceptor description required.
* @org.apache.xbean.XBean
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class SchemaInterceptor extends BaseInterceptor
{
/** The LoggerFactory used by this Interceptor */
private static Logger LOG = LoggerFactory.getLogger( SchemaInterceptor.class );
private static final String[] SCHEMA_SUBENTRY_RETURN_ATTRIBUTES = new String[]
{ SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES };
/** Speedup for logs */
private static final boolean IS_DEBUG = LOG.isDebugEnabled();
/**
* the root nexus to all database partitions
*/
private PartitionNexus nexus;
/**
* a binary attribute tranforming filter: String -> byte[]
*/
private BinaryAttributeFilter binaryAttributeFilter;
private TopFilter topFilter;
private List<SearchResultFilter> filters = new ArrayList<SearchResultFilter>();
/**
* the global schema object registries
*/
private Registries registries;
/** A global reference to the ObjectClass attributeType */
private AttributeType OBJECT_CLASS;
/**
* the global attributeType registry
*/
private AttributeTypeRegistry atRegistry;
/** A normalized form for the SubschemaSubentry DN */
private String subschemaSubentryDnNorm;
/**
* the normalized name for the schema modification attributes
*/
private LdapDN schemaModificationAttributesDN;
private SchemaOperationControl schemaManager;
private SchemaService schemaService;
// the base DN (normalized) of the schema partition
private LdapDN schemaBaseDN;
/** A map used to store all the objectClasses superiors */
private Map<String, List<ObjectClass>> superiors;
/** A map used to store all the objectClasses may attributes */
private Map<String, List<AttributeType>> allMay;
/** A map used to store all the objectClasses must */
private Map<String, List<AttributeType>> allMust;
/** A map used to store all the objectClasses allowed attributes (may + must) */
private Map<String, List<AttributeType>> allowed;
/**
* Initialize the Schema Service
*
* @param directoryService the directory service core
* @throws NamingException if there are problems during initialization
*/
public void init( DirectoryService directoryService ) throws NamingException
{
if ( IS_DEBUG )
{
LOG.debug( "Initializing SchemaInterceptor..." );
}
nexus = directoryService.getPartitionNexus();
registries = directoryService.getRegistries();
atRegistry = registries.getAttributeTypeRegistry();
OBJECT_CLASS = atRegistry.lookup( SchemaConstants.OBJECT_CLASS_AT );
binaryAttributeFilter = new BinaryAttributeFilter();
topFilter = new TopFilter();
filters.add( binaryAttributeFilter );
filters.add( topFilter );
schemaBaseDN = new LdapDN( ServerDNConstants.OU_SCHEMA_DN );
schemaBaseDN.normalize( atRegistry.getNormalizerMapping() );
schemaService = directoryService.getSchemaService();
schemaManager = directoryService.getSchemaService().getSchemaControl();
// stuff for dealing with subentries (garbage for now)
Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
LdapDN subschemaSubentryDn = new LdapDN( ( String ) ( subschemaSubentry.get() ) );
subschemaSubentryDn.normalize( atRegistry.getNormalizerMapping() );
subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
schemaModificationAttributesDN = new LdapDN( "cn=schemaModifications,ou=schema" );
schemaModificationAttributesDN.normalize( atRegistry.getNormalizerMapping() );
computeSuperiors();
if ( IS_DEBUG )
{
LOG.debug( "SchemaInterceptor Initialized !" );
}
}
/**
* Compute the MUST attributes for an objectClass. This method gather all the
* MUST from all the objectClass and its superors.
*
* @param atSeen ???
* @param objectClass the object class to gather MUST attributes for
* @throws NamingException if there are problems resolving schema entitites
*/
private void computeMustAttributes( ObjectClass objectClass, Set<String> atSeen ) throws NamingException
{
List<ObjectClass> parents = superiors.get( objectClass.getOid() );
List<AttributeType> mustList = new ArrayList<AttributeType>();
List<AttributeType> allowedList = new ArrayList<AttributeType>();
Set<String> mustSeen = new HashSet<String>();
allMust.put( objectClass.getOid(), mustList );
allowed.put( objectClass.getOid(), allowedList );
for ( ObjectClass parent : parents )
{
AttributeType[] mustParent = parent.getMustList();
if ( ( mustParent != null ) && ( mustParent.length != 0 ) )
{
for ( AttributeType attributeType : mustParent )
{
String oid = attributeType.getOid();
if ( !mustSeen.contains( oid ) )
{
mustSeen.add( oid );
mustList.add( attributeType );
allowedList.add( attributeType );
atSeen.add( attributeType.getOid() );
}
}
}
}
}
/**
* Compute the MAY attributes for an objectClass. This method gather all the
* MAY from all the objectClass and its superors.
*
* The allowed attributes is also computed, it's the union of MUST and MAY
*
* @param atSeen ???
* @param objectClass the object class to get all the MAY attributes for
* @throws NamingException with problems accessing registries
*/
private void computeMayAttributes( ObjectClass objectClass, Set<String> atSeen ) throws NamingException
{
List<ObjectClass> parents = superiors.get( objectClass.getOid() );
List<AttributeType> mayList = new ArrayList<AttributeType>();
Set<String> maySeen = new HashSet<String>();
List<AttributeType> allowedList = allowed.get( objectClass.getOid() );
allMay.put( objectClass.getOid(), mayList );
for ( ObjectClass parent : parents )
{
AttributeType[] mustParent = parent.getMustList();
if ( ( mustParent != null ) && ( mustParent.length != 0 ) )
{
for ( AttributeType attributeType : mustParent )
{
String oid = attributeType.getOid();
if ( !maySeen.contains( oid ) )
{
maySeen.add( oid );
mayList.add( attributeType );
if ( !atSeen.contains( oid ) )
{
allowedList.add( attributeType );
}
}
}
}
}
}
/**
* Recursively compute all the superiors of an object class. For instance, considering
* 'inetOrgPerson', it's direct superior is 'organizationalPerson', which direct superior
* is 'Person', which direct superior is 'top'.
*
* As a result, we will gather all of these three ObjectClasses in 'inetOrgPerson' ObjectClasse
* superiors.
*/
private void computeOCSuperiors( ObjectClass objectClass, List<ObjectClass> superiors, Set<String> ocSeen )
throws NamingException
{
ObjectClass[] parents = objectClass.getSuperClasses();
// Loop on all the objectClass superiors
if ( ( parents != null ) && ( parents.length != 0 ) )
{
for ( ObjectClass parent : parents )
{
// Top is not added
if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
{
continue;
}
// For each one, recurse
computeOCSuperiors( parent, superiors, ocSeen );
String oid = parent.getOid();
if ( !ocSeen.contains( oid ) )
{
superiors.add( parent );
ocSeen.add( oid );
}
}
}
}
/**
* Compute all ObjectClasses superiors, MAY and MUST attributes.
* @throws NamingException
*/
private void computeSuperiors() throws NamingException
{
Iterator<ObjectClass> objectClasses = registries.getObjectClassRegistry().iterator();
superiors = new HashMap<String, List<ObjectClass>>();
allMust = new HashMap<String, List<AttributeType>>();
allMay = new HashMap<String, List<AttributeType>>();
allowed = new HashMap<String, List<AttributeType>>();
while ( objectClasses.hasNext() )
{
List<ObjectClass> ocSuperiors = new ArrayList<ObjectClass>();
ObjectClass objectClass = objectClasses.next();
superiors.put( objectClass.getOid(), ocSuperiors );
computeOCSuperiors( objectClass, ocSuperiors, new HashSet<String>() );
Set<String> atSeen = new HashSet<String>();
computeMustAttributes( objectClass, atSeen );
computeMayAttributes( objectClass, atSeen );
superiors.put( objectClass.getName(), ocSuperiors );
}
}
/**
*
*/
public NamingEnumeration<ServerSearchResult> list( NextInterceptor nextInterceptor, ListOperationContext opContext )
throws NamingException
{
NamingEnumeration<ServerSearchResult> result = nextInterceptor.list( opContext );
Invocation invocation = InvocationStack.getInstance().peek();
return new SearchResultFilteringEnumeration( result, new SearchControls(), invocation, binaryAttributeFilter,
"List Schema Filter" );
}
/**
* Remove all unknown attributes from the searchControls, to avoid an exception.
*
* RFC 2251 states that :
* " Attributes MUST be named at most once in the list, and are returned "
* " at most once in an entry. "
* " If there are attribute descriptions in "
* " the list which are not recognized, they are ignored by the server."
*
* @param searchCtls The SearchControls we will filter
*/
private void filterAttributesToReturn( SearchControls searchCtls )
{
String[] attributes = searchCtls.getReturningAttributes();
if ( ( attributes == null ) || ( attributes.length == 0 ) )
{
// We have no attributes, that means "*" (all users attributes)
searchCtls.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
return;
}
Map<String, String> filteredAttrs = new HashMap<String, String>();
boolean hasNoAttribute = false;
boolean hasAttributes = false;
for ( String attribute : attributes )
{
// Skip special attributes
if ( ( SchemaConstants.ALL_USER_ATTRIBUTES.equals( attribute ) )
|| ( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES.equals( attribute ) )
|| ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) ) )
{
if ( !filteredAttrs.containsKey( attribute ) )
{
filteredAttrs.put( attribute, attribute );
}
if ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) )
{
hasNoAttribute = true;
}
else
{
hasAttributes = true;
}
continue;
}
try
{
// Deal with the special ;binary
if ( attribute.endsWith( ";binary" ) )
{
attribute = attribute.substring( 0, attribute.length() - ";binary".length() );
}
// Check that the attribute is declared
if ( registries.getOidRegistry().hasOid( attribute ) )
{
String oid = registries.getOidRegistry().getOid( attribute );
// The attribute must be an AttributeType
if ( atRegistry.hasAttributeType( oid ) )
{
if ( !filteredAttrs.containsKey( oid ) )
{
// Ok, we can add the attribute to the list of filtered attributes
filteredAttrs.put( oid, attribute );
}
}
}
hasAttributes = true;
}
catch ( NamingException ne )
{
/* Do nothing, the attribute does not exist */
}
}
// Treat a special case : if we have an attribute and "1.1", then discard "1.1"
if ( hasAttributes && hasNoAttribute )
{
filteredAttrs.remove( SchemaConstants.NO_ATTRIBUTE );
}
// If we still have the same attribute number, then we can just get out the method
if ( filteredAttrs.size() == attributes.length )
{
return;
}
// Deal with the special case where the attribute list is now empty
if ( filteredAttrs.size() == 0 )
{
// We just have to pass the special 1.1 attribute,
// as we don't want to return any attribute
searchCtls.setReturningAttributes( SchemaConstants.NO_ATTRIBUTE_ARRAY );
return;
}
// Some attributes have been removed. let's modify the searchControl
String[] newAttributesList = new String[filteredAttrs.size()];
int pos = 0;
for ( String key : filteredAttrs.keySet() )
{
newAttributesList[pos++] = filteredAttrs.get( key );
}
searchCtls.setReturningAttributes( newAttributesList );
}
private Value<?> convert( String id, Object value ) throws NamingException
{
AttributeType at = atRegistry.lookup( id );
if ( at.getSyntax().isHumanReadable() )
{
if ( value instanceof byte[] )
{
try
{
return new ClientStringValue( new String( ( byte[] ) value, "UTF-8" ) );
}
catch ( UnsupportedEncodingException uee )
{
String message = "The value stored in an Human Readable attribute as a byte[] should be convertible to a String";
LOG.error( message );
throw new NamingException( message );
}
}
}
else
{
if ( value instanceof String )
{
try
{
return new ClientBinaryValue( ( ( String ) value ).getBytes( "UTF-8" ) );
}
catch ( UnsupportedEncodingException uee )
{
String message = "The value stored in a non Human Readable attribute as a String should be convertible to a byte[]";
LOG.error( message );
throw new NamingException( message );
}
}
}
return null;
}
/**
* Check that the filter values are compatible with the AttributeType. Typically,
* a HumanReadible filter should have a String value. The substring filter should
* not be used with binary attributes.
*/
private void checkFilter( ExprNode filter ) throws NamingException
{
if ( filter == null )
{
String message = "A filter should not be null";
LOG.error( message );
throw new NamingException( message );
}
if ( filter.isLeaf() )
{
if ( filter instanceof EqualityNode )
{
EqualityNode node = ( ( EqualityNode ) filter );
Object value = node.getValue();
Value<?> newValue = convert( node.getAttribute(), value );
if ( newValue != null )
{
node.setValue( newValue );
}
}
else if ( filter instanceof SubstringNode )
{
SubstringNode node = ( ( SubstringNode ) filter );
if ( !atRegistry.lookup( node.getAttribute() ).getSyntax().isHumanReadable() )
{
String message = "A Substring filter should be used only on Human Readable attributes";
LOG.error( message );
throw new NamingException( message );
}
}
else if ( filter instanceof PresenceNode )
{
// Nothing to do
}
else if ( filter instanceof GreaterEqNode )
{
GreaterEqNode node = ( ( GreaterEqNode ) filter );
Object value = node.getValue();
Value<?> newValue = convert( node.getAttribute(), value );
if ( newValue != null )
{
node.setValue( newValue );
}
}
else if ( filter instanceof LessEqNode )
{
LessEqNode node = ( ( LessEqNode ) filter );
Object value = node.getValue();
Value<?> newValue = convert( node.getAttribute(), value );
if ( newValue != null )
{
node.setValue( newValue );
}
}
else if ( filter instanceof ExtensibleNode )
{
ExtensibleNode node = ( ( ExtensibleNode ) filter );
if ( !atRegistry.lookup( node.getAttribute() ).getSyntax().isHumanReadable() )
{
String message = "A Extensible filter should be used only on Human Readable attributes";
LOG.error( message );
throw new NamingException( message );
}
}
else if ( filter instanceof ApproximateNode )
{
ApproximateNode node = ( ( ApproximateNode ) filter );
Object value = node.getValue();
Value<?> newValue = convert( node.getAttribute(), value );
if ( newValue != null )
{
node.setValue( newValue );
}
}
else if ( filter instanceof AssertionNode )
{
// Nothing to do
return;
}
else if ( filter instanceof ScopeNode )
{
// Nothing to do
return;
}
}
else
{
// Recursively iterate through all the children.
for ( ExprNode child : ( ( BranchNode ) filter ).getChildren() )
{
checkFilter( child );
}
}
}
/**
*
*/
public NamingEnumeration<ServerSearchResult> search( NextInterceptor nextInterceptor,
SearchOperationContext opContext ) throws NamingException
{
LdapDN base = opContext.getDn();
SearchControls searchCtls = opContext.getSearchControls();
ExprNode filter = opContext.getFilter();
// We have to eliminate bad attributes from the request, accordingly
// to RFC 2251, chap. 4.5.1. Basically, all unknown attributes are removed
// from the list
filterAttributesToReturn( searchCtls );
// We also have to check the H/R flag for the filter attributes
checkFilter( filter );
String baseNormForm = ( base.isNormalized() ? base.getNormName() : base.toNormName() );
// Deal with the normal case : searching for a normal value (not subSchemaSubEntry)
if ( !subschemaSubentryDnNorm.equals( baseNormForm ) )
{
NamingEnumeration<ServerSearchResult> result = nextInterceptor.search( opContext );
Invocation invocation = InvocationStack.getInstance().peek();
if ( searchCtls.getReturningAttributes() != null )
{
return new SearchResultFilteringEnumeration( result, new SearchControls(), invocation, topFilter,
"Search Schema Filter top" );
}
return new SearchResultFilteringEnumeration( result, searchCtls, invocation, filters,
"Search Schema Filter" );
}
// The user was searching into the subSchemaSubEntry
// This kind of search _must_ be limited to OBJECT scope (the subSchemaSubEntry
// does not have any sub level)
if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
{
// The filter can be an equality or a presence, but nothing else
if ( filter instanceof SimpleNode )
{
// We should get the value for the filter.
// only 'top' and 'subSchema' are valid values
SimpleNode node = ( SimpleNode ) filter;
String objectClass;
if ( node.getValue() instanceof ClientStringValue )
{
objectClass = ( String ) node.getValue().get();
}
else
{
objectClass = node.getValue().get().toString();
}
String objectClassOid = null;
if ( registries.getObjectClassRegistry().hasObjectClass( objectClass ) )
{
objectClassOid = registries.getObjectClassRegistry().lookup( objectClass ).getOid();
}
else
{
return new EmptyEnumeration<ServerSearchResult>();
}
String nodeOid = registries.getOidRegistry().getOid( node.getAttribute() );
// see if node attribute is objectClass
if ( nodeOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID )
&& ( objectClassOid.equals( SchemaConstants.TOP_OC_OID ) || objectClassOid
.equals( SchemaConstants.SUBSCHEMA_OC_OID ) ) && ( node instanceof EqualityNode ) )
{
// call.setBypass( true );
ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
ServerSearchResult result = new ServerSearchResult( base, null, serverEntry );
return new SingletonEnumeration<ServerSearchResult>( result );
}
else
{
return new EmptyEnumeration<ServerSearchResult>();
}
}
else if ( filter instanceof PresenceNode )
{
PresenceNode node = ( PresenceNode ) filter;
// see if node attribute is objectClass
if ( node.getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
{
// call.setBypass( true );
ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
ServerSearchResult result = new ServerSearchResult( base, null, serverEntry, false );
return new SingletonEnumeration<ServerSearchResult>( result );
}
}
}
// In any case not handled previously, just return an empty result
return new EmptyEnumeration<ServerSearchResult>();
}
/**
* Search for an entry, using its DN. Binary attributes and ObjectClass attribute are removed.
*/
public ServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext )
throws NamingException
{
ServerEntry result = nextInterceptor.lookup( opContext );
if ( result == null )
{
return null;
}
filterBinaryAttributes( result );
filterObjectClass( result );
return result;
}
private void getSuperiors( ObjectClass oc, Set<String> ocSeen, List<ObjectClass> result ) throws NamingException
{
for ( ObjectClass parent : oc.getSuperClasses() )
{
// Skip 'top'
if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
{
continue;
}
if ( !ocSeen.contains( parent.getOid() ) )
{
ocSeen.add( parent.getOid() );
result.add( parent );
}
// Recurse on the parent
getSuperiors( parent, ocSeen, result );
}
}
/**
* Checks to see if an attribute is required by as determined from an entry's
* set of objectClass attribute values.
*
* @param attrId the attribute to test if required by a set of objectClass values
* @param objectClass the objectClass values
* @return true if the objectClass values require the attribute, false otherwise
* @throws NamingException if the attribute is not recognized
*/
private boolean isRequired( String attrId, EntryAttribute objectClasses ) throws NamingException
{
OidRegistry oidRegistry = registries.getOidRegistry();
ObjectClassRegistry registry = registries.getObjectClassRegistry();
if ( !oidRegistry.hasOid( attrId ) )
{
return false;
}
String attrOid = oidRegistry.getOid( attrId );
for ( Value<?> objectClass : objectClasses )
{
ObjectClass ocSpec = registry.lookup( ( String ) objectClass.get() );
for ( AttributeType must : ocSpec.getMustList() )
{
if ( must.getOid().equals( attrOid ) )
{
return true;
}
}
}
return false;
}
/**
* Checks to see if removing a set of attributes from an entry completely removes
* that attribute's values. If change has zero size then all attributes are
* presumed to be removed.
*
* @param change
* @param entry
* @return
* @throws NamingException
*/
private boolean isCompleteRemoval( ServerAttribute change, ServerEntry entry ) throws NamingException
{
// if change size is 0 then all values are deleted then we're in trouble
if ( change.size() == 0 )
{
return true;
}
// can't do math to figure our if all values are removed since some
// values in the modify request may not be in the entry. we need to
// remove the values from a cloned version of the attribute and see
// if nothing is left.
ServerAttribute changedEntryAttr = ( ServerAttribute ) entry.get( change.getUpId() ).clone();
for ( Value<?> value : change )
{
changedEntryAttr.remove( value );
}
return changedEntryAttr.size() == 0;
}
/**
*
* @param modOp
* @param changes
* @param existing
* @return
* @throws NamingException
*/
private EntryAttribute getResultantObjectClasses( ModificationOperation modOp, EntryAttribute changes,
EntryAttribute existing ) throws NamingException
{
if ( ( changes == null ) && ( existing == null ) )
{
return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
}
if ( changes == null )
{
return existing;
}
if ( ( existing == null ) && ( modOp == ModificationOperation.ADD_ATTRIBUTE ) )
{
return changes;
}
else if ( existing == null )
{
return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
}
switch ( modOp )
{
case ADD_ATTRIBUTE:
for ( Value<?> value : changes )
{
existing.add( value );
}
return existing;
case REPLACE_ATTRIBUTE:
return ( ServerAttribute ) changes.clone();
case REMOVE_ATTRIBUTE:
for ( Value<?> value : changes )
{
existing.remove( value );
}
return existing;
default:
throw new InternalError( "" );
}
}
private boolean getObjectClasses( EntryAttribute objectClasses, List<ObjectClass> result ) throws NamingException
{
Set<String> ocSeen = new HashSet<String>();
ObjectClassRegistry registry = registries.getObjectClassRegistry();
// We must select all the ObjectClasses, except 'top',
// but including all the inherited ObjectClasses
boolean hasExtensibleObject = false;
for ( Value<?> objectClass : objectClasses )
{
String objectClassName = ( String ) objectClass.get();
if ( SchemaConstants.TOP_OC.equals( objectClassName ) )
{
continue;
}
if ( SchemaConstants.EXTENSIBLE_OBJECT_OC.equalsIgnoreCase( objectClassName ) )
{
hasExtensibleObject = true;
}
ObjectClass oc = registry.lookup( objectClassName );
// Add all unseen objectClasses to the list, except 'top'
if ( !ocSeen.contains( oc.getOid() ) )
{
ocSeen.add( oc.getOid() );
result.add( oc );
}
// Find all current OC parents
getSuperiors( oc, ocSeen, result );
}
return hasExtensibleObject;
}
private Set<String> getAllMust( EntryAttribute objectClasses ) throws NamingException
{
Set<String> must = new HashSet<String>();
// Loop on all objectclasses
for ( Value<?> value : objectClasses )
{
String ocName = ( String ) value.get();
ObjectClass oc = registries.getObjectClassRegistry().lookup( ocName );
AttributeType[] types = oc.getMustList();
// For each objectClass, loop on all MUST attributeTypes, if any
if ( ( types != null ) && ( types.length > 0 ) )
{
for ( AttributeType type : types )
{
must.add( type.getOid() );
}
}
}
return must;
}
private Set<String> getAllAllowed( EntryAttribute objectClasses, Set<String> must ) throws NamingException
{
Set<String> allowed = new HashSet<String>( must );
// Add the 'ObjectClass' attribute ID
allowed.add( registries.getOidRegistry().getOid( SchemaConstants.OBJECT_CLASS_AT ) );
// Loop on all objectclasses
for ( Value<?> objectClass : objectClasses )
{
String ocName = ( String ) objectClass.get();
ObjectClass oc = registries.getObjectClassRegistry().lookup( ocName );
AttributeType[] types = oc.getMayList();
// For each objectClass, loop on all MAY attributeTypes, if any
if ( ( types != null ) && ( types.length > 0 ) )
{
for ( AttributeType type : types )
{
String oid = type.getOid();
allowed.add( oid );
}
}
}
return allowed;
}
/**
* Given the objectClasses for an entry, this method adds missing ancestors
* in the hierarchy except for top which it removes. This is used for this
* solution to DIREVE-276. More information about this solution can be found
* <a href="http://docs.safehaus.org:8080/x/kBE">here</a>.
*
* @param objectClassAttr the objectClass attribute to modify
* @throws NamingException if there are problems
*/
private void alterObjectClasses( EntryAttribute objectClassAttr ) throws NamingException
{
Set<String> objectClasses = new HashSet<String>();
Set<String> objectClassesUP = new HashSet<String>();
// Init the objectClass list with 'top'
objectClasses.add( SchemaConstants.TOP_OC );
objectClassesUP.add( SchemaConstants.TOP_OC );
// Construct the new list of ObjectClasses
for ( Value<?> ocValue : objectClassAttr )
{
String ocName = ( String ) ocValue.get();
if ( !ocName.equalsIgnoreCase( SchemaConstants.TOP_OC ) )
{
String ocLowerName = ocName.toLowerCase();
ObjectClass objectClass = registries.getObjectClassRegistry().lookup( ocLowerName );
if ( !objectClasses.contains( ocLowerName ) )
{
objectClasses.add( ocLowerName );
objectClassesUP.add( ocName );
}
List<ObjectClass> ocSuperiors = superiors.get( objectClass.getOid() );
if ( ocSuperiors != null )
{
for ( ObjectClass oc : ocSuperiors )
{
if ( !objectClasses.contains( oc.getName().toLowerCase() ) )
{
objectClasses.add( oc.getName() );
objectClassesUP.add( oc.getName() );
}
}
}
}
}
// Now, reset the ObjectClass attribute and put the new list into it
objectClassAttr.clear();
for ( String attribute : objectClassesUP )
{
objectClassAttr.add( attribute );
}
}
public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws NamingException
{
LdapDN oriChildName = opContext.getDn();
ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, oriChildName ) );
if ( oriChildName.startsWith( schemaBaseDN ) )
{
schemaManager.move( oriChildName, opContext.getParent(), opContext.getNewRdn(), opContext.getDelOldDn(),
entry, opContext.hasRequestControl( CascadeControl.CONTROL_OID ) );
}
next.moveAndRename( opContext );
}
public void move( NextInterceptor next, MoveOperationContext opContext ) throws NamingException
{
LdapDN oriChildName = opContext.getDn();
ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, oriChildName ) );
if ( oriChildName.startsWith( schemaBaseDN ) )
{
schemaManager.replace( oriChildName, opContext.getParent(), entry, opContext
.hasRequestControl( CascadeControl.CONTROL_OID ) );
}
next.move( opContext );
}
public void rename( NextInterceptor next, RenameOperationContext opContext ) throws NamingException
{
LdapDN name = opContext.getDn();
Rdn newRdn = opContext.getNewRdn();
boolean deleteOldRn = opContext.getDelOldDn();
ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, name ) );
if ( deleteOldRn )
{
ServerEntry tmpEntry = ( ServerEntry ) entry.clone();
Rdn oldRDN = name.getRdn();
// Delete the old RDN means we remove some attributes and values.
// We must make sure that after this operation all must attributes
// are still present in the entry.
for ( AttributeTypeAndValue atav : oldRDN )
{
AttributeType type = atRegistry.lookup( atav.getUpType() );
String value = ( String ) atav.getNormValue();
tmpEntry.remove( type, value );
}
for ( AttributeTypeAndValue atav : newRdn )
{
AttributeType type = atRegistry.lookup( atav.getUpType() );
String value = ( String ) atav.getNormValue();
if ( !tmpEntry.contains( type, value ) )
{
tmpEntry.add( new DefaultServerAttribute( type, value ) );
}
}
check( name, tmpEntry );
// Check that no operational attributes are removed
for ( AttributeTypeAndValue atav : oldRDN )
{
AttributeType attributeType = atRegistry.lookup( atav.getUpType() );
if ( !attributeType.isCanUserModify() )
{
throw new NoPermissionException( "Cannot modify the attribute '" + atav.getUpType() + "'" );
}
}
}
if ( name.startsWith( schemaBaseDN ) )
{
schemaManager.modifyRn( name, newRdn, deleteOldRn, entry, opContext
.hasRequestControl( CascadeControl.CONTROL_OID ) );
}
next.rename( opContext );
}
public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws NamingException
{
ServerEntry entry;
LdapDN name = opContext.getDn();
List<Modification> mods = opContext.getModItems();
// handle operations against the schema subentry in the schema service
// and never try to look it up in the nexus below
if ( name.getNormName().equalsIgnoreCase( subschemaSubentryDnNorm ) )
{
entry = schemaService.getSubschemaEntry( SCHEMA_SUBENTRY_RETURN_ATTRIBUTES );
}
else
{
entry = nexus.lookup( new LookupOperationContext( registries, name ) );
}
// First, we get the entry from the backend. If it does not exist, then we throw an exception
ServerEntry targetEntry = ServerEntryUtils.toServerEntry( SchemaUtils.getTargetEntry( ServerEntryUtils
.toModificationItemImpl( mods ), ServerEntryUtils.toAttributesImpl( entry ) ), name, registries );
if ( entry == null )
{
LOG.error( "No entry with this name :{}", name );
throw new LdapNameNotFoundException( "The entry which name is " + name + " is not found." );
}
// We will use this temporary entry to check that the modifications
// can be applied as atomic operations
ServerEntry tmpEntry = ( ServerEntry ) entry.clone();
Set<String> modset = new HashSet<String>();
Modification objectClassMod = null;
// Check that we don't have two times the same modification.
// This is somehow useless, as modification operations are supposed to
// be atomic, so we may have a sucession of Add, DEL, ADD operations
// for the same attribute, and this will be legal.
// @TODO : check if we can remove this test.
for ( Modification mod : mods )
{
if ( mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) )
{
objectClassMod = mod;
}
// Freak out under some weird cases
if ( mod.getAttribute().size() == 0 )
{
// not ok for add but ok for replace and delete
if ( mod.getOperation() == ModificationOperation.ADD_ATTRIBUTE )
{
throw new LdapInvalidAttributeValueException( "No value is not a valid value for an attribute.",
ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX );
}
}
StringBuffer keybuf = new StringBuffer();
keybuf.append( mod.getOperation() );
keybuf.append( mod.getAttribute().getId() );
for ( Value<?> value : ( ServerAttribute ) mod.getAttribute() )
{
keybuf.append( value.get() );
}
if ( !modset.add( keybuf.toString() ) && ( mod.getOperation() == ModificationOperation.ADD_ATTRIBUTE ) )
{
throw new LdapAttributeInUseException( "found two copies of the following modification item: " + mod );
}
}
// Get the objectClass attribute.
EntryAttribute objectClass;
if ( objectClassMod == null )
{
objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
if ( objectClass == null )
{
objectClass = new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
}
}
else
{
objectClass = getResultantObjectClasses( objectClassMod.getOperation(), objectClassMod.getAttribute(),
tmpEntry.get( SchemaConstants.OBJECT_CLASS_AT ) );
}
ObjectClassRegistry ocRegistry = this.registries.getObjectClassRegistry();
// Now, apply the modifications on the cloned entry before applying it on the
// real object.
for ( Modification mod : mods )
{
ModificationOperation modOp = mod.getOperation();
ServerAttribute change = ( ServerAttribute ) mod.getAttribute();
if ( !atRegistry.hasAttributeType( change.getUpId() )
&& !objectClass.contains( SchemaConstants.EXTENSIBLE_OBJECT_OC ) )
{
throw new LdapInvalidAttributeIdentifierException();
}
// We will forbid modification of operational attributes which are not
// user modifiable.
AttributeType attributeType = atRegistry.lookup( change.getUpId() );
if ( !attributeType.isCanUserModify() )
{
throw new NoPermissionException( "Cannot modify the attribute '" + change.getUpId() + "'" );
}
switch ( modOp )
{
case ADD_ATTRIBUTE:
EntryAttribute attr = tmpEntry.get( change.getUpId() );
if ( attr != null )
{
for ( Value<?> value : change )
{
attr.add( value );
}
}
else
{
attr = new DefaultServerAttribute( change.getUpId(), attributeType );
for ( Value<?> value : change )
{
attr.add( value );
}
tmpEntry.put( attr );
}
break;
case REMOVE_ATTRIBUTE:
// First check that the attributeType exists
if ( tmpEntry.get( change.getUpId() ) == null )
{
LOG.error( "Trying to remove an non-existant attribute: " + change.getUpId() );
throw new LdapNoSuchAttributeException();
}
// Then check that all the values exists
/* TODO reactivate this code when ChangeLog is fixed
EntryAttribute toBeRemoved = tmpEntry.get( change.getUpId() );
for ( Value<?> val:change )
{
if ( !toBeRemoved.contains( val ) )
{
LOG.error( "Trying to remove an non-existant value '{}' for attribute '{}': ", val, change.getUpId() );
throw new LdapNoSuchAttributeException();
}
}
*/
// We may have to remove the attribute or only some values
if ( change.size() == 0 )
{
// No value : we have to remove the entire attribute
// Check that we aren't removing a MUST attribute
if ( isRequired( change.getUpId(), objectClass ) )
{
LOG.error( "Trying to remove a required attribute: " + change.getUpId() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
}
else
{
// for required attributes we need to check if all values are removed
// if so then we have a schema violation that must be thrown
if ( isRequired( change.getUpId(), objectClass ) && isCompleteRemoval( change, entry ) )
{
LOG.error( "Trying to remove a required attribute: " + change.getUpId() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
// Now remove the attribute and all its values
EntryAttribute modified = tmpEntry.removeAttributes( change.getUpId() ).get( 0 );
// And inject back the values except the ones to remove
for ( Value<?> value : change )
{
modified.remove( value );
}
// ok, done. Last check : if the attribute does not content any more value;
// and if it's a MUST one, we should thow an exception
if ( ( modified.size() == 0 ) && isRequired( change.getUpId(), objectClass ) )
{
LOG.error( "Trying to remove a required attribute: " + change.getUpId() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
// Put back the attribute in the entry only if it has values left in it
if ( modified.size() > 0 )
{
tmpEntry.put( modified );
}
}
SchemaChecker
.preventRdnChangeOnModifyRemove( name, modOp, change, this.registries.getOidRegistry() );
SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change,
objectClass );
break;
case REPLACE_ATTRIBUTE:
SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change, registries.getOidRegistry() );
SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
attr = tmpEntry.get( change.getUpId() );
if ( attr != null )
{
tmpEntry.removeAttributes( change.getUpId() );
}
attr = new DefaultServerAttribute( change.getUpId(), attributeType );
if ( change.size() != 0 )
{
for ( Value<?> value : change )
{
attr.add( value );
}
tmpEntry.put( attr );
}
break;
}
}
check( name, tmpEntry );
// let's figure out if we need to add or take away from mods to maintain
// the objectClass attribute with it's hierarchy of ancestors
if ( objectClassMod != null )
{
ServerAttribute alteredObjectClass = ( ServerAttribute ) objectClass.clone();
alterObjectClasses( alteredObjectClass );
if ( !alteredObjectClass.equals( objectClass ) )
{
ServerAttribute ocMods = ( ServerAttribute ) objectClassMod.getAttribute();
switch ( objectClassMod.getOperation() )
{
case ADD_ATTRIBUTE:
if ( ocMods.contains( SchemaConstants.TOP_OC ) )
{
ocMods.remove( SchemaConstants.TOP_OC );
}
for ( Value<?> value : alteredObjectClass )
{
if ( !objectClass.contains( value ) )
{
ocMods.add( value );
}
}
break;
case REMOVE_ATTRIBUTE:
for ( Value<?> value : alteredObjectClass )
{
if ( !objectClass.contains( value ) )
{
ocMods.remove( value );
}
}
break;
case REPLACE_ATTRIBUTE:
for ( Value<?> value : alteredObjectClass )
{
if ( !objectClass.contains( value ) )
{
ocMods.add( value );
}
}
break;
default:
}
}
}
if ( name.startsWith( schemaBaseDN ) )
{
LOG.debug( "Modification attempt on schema partition {}: \n{}", name, opContext );
schemaManager.modify( name, mods, entry, targetEntry, opContext
.hasRequestControl( CascadeControl.CONTROL_OID ) );
}
else if ( subschemaSubentryDnNorm.equals( name.getNormName() ) )
{
LOG.debug( "Modification attempt on schema subentry {}: \n{}", name, opContext );
schemaManager.modifySchemaSubentry( name, mods, entry, targetEntry, opContext
.hasRequestControl( CascadeControl.CONTROL_OID ) );
return;
}
next.modify( opContext );
}
private void filterObjectClass( ServerEntry entry ) throws NamingException
{
List<ObjectClass> objectClasses = new ArrayList<ObjectClass>();
EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
if ( oc != null )
{
getObjectClasses( oc, objectClasses );
entry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
ServerAttribute newOc = new DefaultServerAttribute( ( ( ServerAttribute ) oc ).getAttributeType() );
for ( ObjectClass currentOC : objectClasses )
{
newOc.add( currentOC.getName() );
}
newOc.add( SchemaConstants.TOP_OC );
entry.put( newOc );
}
}
private void filterBinaryAttributes( ServerEntry entry ) throws NamingException
{
/*
* start converting values of attributes to byte[]s which are not
* human readable and those that are in the binaries set
*/
for ( EntryAttribute attribute : entry )
{
if ( !( ( ServerAttribute ) attribute ).getAttributeType().getSyntax().isHumanReadable() )
{
List<Value<?>> binaries = new ArrayList<Value<?>>();
for ( Value<?> value : attribute )
{
Object attrValue = value.get();
if ( attrValue instanceof String )
{
binaries.add( new ServerBinaryValue( ( ( ServerAttribute ) attribute ).getAttributeType(),
StringTools.getBytesUtf8( ( String ) attrValue ) ) );
}
else
{
binaries.add( new ServerBinaryValue( ( ( ServerAttribute ) attribute ).getAttributeType(),
( byte[] ) attrValue ) );
}
}
attribute.clear();
attribute.put( binaries );
}
}
}
/**
* A special filter over entry attributes which replaces Attribute String values with their respective byte[]
* representations using schema information and the value held in the JNDI environment property:
* <code>java.naming.ldap.attributes.binary</code>.
*
* @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
* java.naming.ldap.attributes.binary</a>
*/
private class BinaryAttributeFilter implements SearchResultFilter
{
public boolean accept( Invocation invocation, ServerSearchResult result, SearchControls controls )
throws NamingException
{
filterBinaryAttributes( result.getServerEntry() );
return true;
}
}
/**
* Filters objectClass attribute to inject top when not present.
*/
private class TopFilter implements SearchResultFilter
{
public boolean accept( Invocation invocation, ServerSearchResult result, SearchControls controls )
throws NamingException
{
filterObjectClass( result.getServerEntry() );
return true;
}
}
/**
* Check that all the attributes exist in the schema for this entry.
*
* We also check the syntaxes
*/
private void check( LdapDN dn, ServerEntry entry ) throws NamingException
{
// ---------------------------------------------------------------
// First, make sure all attributes are valid schema defined attributes
// ---------------------------------------------------------------
for ( AttributeType attributeType : entry.getAttributeTypes() )
{
if ( !atRegistry.hasAttributeType( attributeType.getName() ) )
{
throw new LdapInvalidAttributeIdentifierException( attributeType.getName()
+ " not found in attribute registry!" );
}
}
// We will check some elements :
// 1) the entry must have all the MUST attributes of all its ObjectClass
// 2) The SingleValued attributes must be SingleValued
// 3) No attributes should be used if they are not part of MUST and MAY
// 3-1) Except if the extensibleObject ObjectClass is used
// 3-2) or if the AttributeType is COLLECTIVE
// 4) We also check that for H-R attributes, we have a valid String in the values
EntryAttribute objectClassAttr = entry.get( SchemaConstants.OBJECT_CLASS_AT );
// Protect the server against a null objectClassAttr
// It can be the case if the user forgot to add it to the entry ...
// In this case, we create an new one, empty
if ( objectClassAttr == null )
{
objectClassAttr = new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
}
List<ObjectClass> ocs = new ArrayList<ObjectClass>();
alterObjectClasses( objectClassAttr );
// Now we can process the MUST and MAY attributes
Set<String> must = getAllMust( objectClassAttr );
Set<String> allowed = getAllAllowed( objectClassAttr, must );
boolean hasExtensibleObject = getObjectClasses( objectClassAttr, ocs );
// As we now have all the ObjectClasses updated, we have
// to check that we don't have conflicting ObjectClasses
assertObjectClasses( dn, ocs );
assertRequiredAttributesPresent( dn, entry, must );
assertNumberOfAttributeValuesValid( entry );
if ( !hasExtensibleObject )
{
assertAllAttributesAllowed( dn, entry, allowed );
}
// Check the attributes values and transform them to String if necessary
assertHumanReadable( entry );
// Now check the syntaxes
assertSyntaxes( entry );
}
/**
* Check that all the attributes exist in the schema for this entry.
*/
public void add( NextInterceptor next, AddOperationContext addContext ) throws NamingException
{
LdapDN name = addContext.getDn();
ServerEntry entry = addContext.getEntry();
check( name, entry );
if ( name.startsWith( schemaBaseDN ) )
{
schemaManager.add( name, entry );
}
next.add( addContext );
}
/**
* Checks to see if an attribute is required by as determined from an entry's
* set of objectClass attribute values.
*
* @return true if the objectClass values require the attribute, false otherwise
* @throws NamingException if the attribute is not recognized
*/
private void assertAllAttributesAllowed( LdapDN dn, ServerEntry entry, Set<String> allowed ) throws NamingException
{
// Never check the attributes if the extensibleObject objectClass is
// declared for this entry
EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
if ( objectClass.contains( SchemaConstants.EXTENSIBLE_OBJECT_OC ) )
{
return;
}
for ( EntryAttribute attribute : entry )
{
String attrOid = ( ( ServerAttribute ) attribute ).getAttributeType().getOid();
AttributeType attributeType = ( ( ServerAttribute ) attribute ).getAttributeType();
if ( !attributeType.isCollective() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
{
if ( !allowed.contains( attrOid ) )
{
throw new LdapSchemaViolationException( "Attribute " + attribute.getUpId()
+ " not declared in objectClasses of entry " + dn.getUpName(),
ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
}
}
}
public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws NamingException
{
LdapDN name = opContext.getDn();
if ( name.startsWith( schemaBaseDN ) )
{
ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, name ) );
schemaManager.delete( name, entry, opContext.hasRequestControl( CascadeControl.CONTROL_OID ) );
}
next.delete( opContext );
}
/**
* Checks to see number of values of an attribute conforms to the schema
*/
private void assertNumberOfAttributeValuesValid( Entry entry ) throws InvalidAttributeValueException,
NamingException
{
for ( EntryAttribute attribute : entry )
{
assertNumberOfAttributeValuesValid( attribute );
}
}
/**
* Checks to see numbers of values of attributes conforms to the schema
*/
private void assertNumberOfAttributeValuesValid( EntryAttribute attribute ) throws InvalidAttributeValueException,
NamingException
{
if ( attribute.size() > 1 && ( ( ServerAttribute ) attribute ).getAttributeType().isSingleValue() )
{
throw new LdapInvalidAttributeValueException( "More than one value has been provided "
+ "for the single-valued attribute: " + attribute.getUpId(), ResultCodeEnum.CONSTRAINT_VIOLATION );
}
}
/**
* Checks to see the presence of all required attributes within an entry.
*/
private void assertRequiredAttributesPresent( LdapDN dn, Entry entry, Set<String> must ) throws NamingException
{
for ( EntryAttribute attribute : entry )
{
must.remove( ( ( ServerAttribute ) attribute ).getAttributeType().getOid() );
}
if ( must.size() != 0 )
{
throw new LdapSchemaViolationException( "Required attributes " + must + " not found within entry "
+ dn.getUpName(), ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
}
/**
* Checck that OC does not conflict :
* - we can't have more than one STRUCTURAL OC unless they are in the same
* inheritance tree
* - we must have at least one STRUCTURAL OC
*/
private void assertObjectClasses( LdapDN dn, List<ObjectClass> ocs ) throws NamingException
{
Set<ObjectClass> structuralObjectClasses = new HashSet<ObjectClass>();
/*
* Since the number of ocs present in an entry is small it's not
* so expensive to take two passes while determining correctness
* since it will result in clear simple code instead of a deep nasty
* for loop with nested loops. Plus after the first pass we can
* quickly know if there are no structural object classes at all.
*/
// --------------------------------------------------------------------
// Extract all structural objectClasses within the entry
// --------------------------------------------------------------------
for ( ObjectClass oc : ocs )
{
if ( oc.isStructural() )
{
structuralObjectClasses.add( oc );
}
}
// --------------------------------------------------------------------
// Throw an error if no STRUCTURAL objectClass are found.
// --------------------------------------------------------------------
if ( structuralObjectClasses.isEmpty() )
{
String message = "Entry " + dn + " does not contain a STRUCTURAL ObjectClass";
LOG.error( message );
throw new LdapSchemaViolationException( message, ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
// --------------------------------------------------------------------
// Put all structural object classes into new remaining container and
// start removing any which are superiors of others in the set. What
// is left in the remaining set will be unrelated structural
/// objectClasses. If there is more than one then we have a problem.
// --------------------------------------------------------------------
Set<ObjectClass> remaining = new HashSet<ObjectClass>( structuralObjectClasses.size() );
remaining.addAll( structuralObjectClasses );
for ( ObjectClass oc : structuralObjectClasses )
{
if ( oc.getSuperClasses() != null )
{
for ( ObjectClass superClass : oc.getSuperClasses() )
{
if ( superClass.isStructural() )
{
remaining.remove( superClass );
}
}
}
}
// Like the highlander there can only be one :).
if ( remaining.size() > 1 )
{
String message = "Entry " + dn + " contains more than one STRUCTURAL ObjectClass: " + remaining;
LOG.error( message );
throw new LdapSchemaViolationException( message, ResultCodeEnum.OBJECT_CLASS_VIOLATION );
}
}
/**
* Check the entry attributes syntax, using the syntaxCheckers
*/
private void assertSyntaxes( Entry entry ) throws NamingException
{
// First, loop on all attributes
for ( EntryAttribute attribute : entry )
{
AttributeType attributeType = ( ( ServerAttribute ) attribute ).getAttributeType();
SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker();
if ( syntaxChecker instanceof AcceptAllSyntaxChecker )
{
// This is a speedup : no need to check the syntax of any value
// if all the syntaxes are accepted...
continue;
}
// Then loop on all values
for ( Value<?> value : attribute )
{
try
{
syntaxChecker.assertSyntax( value.get() );
}
catch ( NamingException ne )
{
String message = "Attribute value '"
+ ( value instanceof ServerStringValue ? value.get() : StringTools.dumpBytes( ( byte[] ) value
.get() ) ) + "' for attribute '" + attribute.getUpId() + "' is syntactically incorrect";
LOG.info( message );
throw new LdapInvalidAttributeValueException( message, ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX );
}
}
}
}
/**
* Check a String attribute to see if there is some byte[] value in it.
*
* If this is the case, try to change it to a String value.
*/
private boolean checkHumanReadable( EntryAttribute attribute ) throws NamingException
{
boolean isModified = false;
// Loop on each values
for ( Value<?> value : attribute )
{
if ( value instanceof ServerStringValue )
{
continue;
}
else if ( value instanceof ServerBinaryValue )
{
// we have a byte[] value. It should be a String UTF-8 encoded
// Let's transform it
try
{
String valStr = new String( ( byte[] ) value.get(), "UTF-8" );
attribute.remove( value );
attribute.add( valStr );
isModified = true;
}
catch ( UnsupportedEncodingException uee )
{
throw new NamingException( "The value is not a valid String" );
}
}
else
{
throw new NamingException( "The value stored in an Human Readable attribute is not a String" );
}
}
return isModified;
}
/**
* Check a binary attribute to see if there is some String value in it.
*
* If this is the case, try to change it to a binary value.
*/
private boolean checkNotHumanReadable( EntryAttribute attribute ) throws NamingException
{
boolean isModified = false;
// Loop on each values
for ( Value<?> value : attribute )
{
if ( value instanceof ServerBinaryValue )
{
continue;
}
else if ( value instanceof ServerStringValue )
{
// We have a String value. It should be a byte[]
// Let's transform it
try
{
byte[] valBytes = ( ( String ) value.get() ).getBytes( "UTF-8" );
attribute.remove( value );
attribute.add( valBytes );
isModified = true;
}
catch ( UnsupportedEncodingException uee )
{
String message = "The value stored in a not Human Readable attribute as a String should be convertible to a byte[]";
LOG.error( message );
throw new NamingException( message );
}
}
else
{
String message = "The value is not valid. It should be a String or a byte[]";
LOG.error( message );
throw new NamingException( message );
}
}
return isModified;
}
/**
* Check that all the attribute's values which are Human Readable can be transformed
* to valid String if they are stored as byte[], and that non Human Readable attributes
* stored as String can be transformed to byte[]
*/
private void assertHumanReadable( ServerEntry entry ) throws NamingException
{
boolean isModified = false;
ServerEntry clonedEntry = null;
// Loops on all attributes
for ( EntryAttribute attribute : entry )
{
AttributeType attributeType = ( ( ServerAttribute ) attribute ).getAttributeType();
// If the attributeType is H-R, check all of its values
if ( attributeType.getSyntax().isHumanReadable() )
{
isModified = checkHumanReadable( attribute );
}
else
{
isModified = checkNotHumanReadable( attribute );
}
// If we have a returned attribute, then we need to store it
// into a new entry
if ( isModified )
{
if ( clonedEntry == null )
{
clonedEntry = ( ServerEntry ) entry.clone();
}
// Switch the attributes
clonedEntry.put( attribute );
isModified = false;
}
}
if ( clonedEntry != null )
{
entry = clonedEntry;
}
}
}