// 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.getString() );
}
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 ).clone() );
}
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();
// TODO/ handle http://issues.apache.org/jira/browse/DIRSERVER-1198
if ( ( change.getAttributeType() == null ) && !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 = change.getAttributeType();
if ( attributeType == null )
{
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( attributeType.getName() );
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:
if ( tmpEntry.get( change.getId() ) == null )
{
LOG.error( "Trying to remove an non-existant attribute: " + 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 );
}
attr = tmpEntry.get( change.getUpId() );
if ( attr != null )
{
tmpEntry.removeAttributes( change.getUpId() );
}
}
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.getId() ).get( 0 );
// And inject back the values except the ones to remove
for ( Value<?> value : change )
{
if ( modified.contains( value ) )
{
modified.remove( value );
}
else if ( !subSchemaModification )
{
// We are trying to remove an not existing value : error
throw new LdapNoSuchAttributeException( "Value " + value + " does not exist in the " + modified + " AT" );
}
}
// 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.getId(), 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 );