|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
namespace System.DirectoryServices
{
/// <devdoc>
/// Holds a collection of values for a multi-valued property.
/// </devdoc>
public class PropertyValueCollection : CollectionBase
{
internal enum UpdateType
{
Add = 0,
Delete = 1,
Update = 2,
None = 3
}
private readonly DirectoryEntry _entry;
private UpdateType _updateType = UpdateType.None;
private readonly ArrayList _changeList;
private readonly bool _allowMultipleChange;
private readonly bool _needNewBehavior;
internal PropertyValueCollection(DirectoryEntry entry, string propertyName)
{
_entry = entry;
PropertyName = propertyName;
PopulateList();
ArrayList tempList = new ArrayList();
_changeList = ArrayList.Synchronized(tempList);
_allowMultipleChange = entry.allowMultipleChange;
string tempPath = entry.Path;
if (string.IsNullOrEmpty(tempPath))
{
// user does not specify path, so we bind to default naming context using LDAP provider.
_needNewBehavior = true;
}
else
{
if (tempPath.StartsWith("LDAP:", StringComparison.Ordinal))
_needNewBehavior = true;
}
}
public object? this[int index]
{
get => List[index];
set
{
if (_needNewBehavior && !_allowMultipleChange)
throw new NotSupportedException();
else
{
List[index] = value;
}
}
}
public string PropertyName { get; }
public object? Value
{
get
{
if (this.Count == 0)
return null;
else if (this.Count == 1)
return List[0];
else
{
object[] objectArray = new object[this.Count];
List.CopyTo(objectArray, 0);
return objectArray;
}
}
set
{
try
{
this.Clear();
}
catch (System.Runtime.InteropServices.COMException e)
{
if (e.ErrorCode != unchecked((int)0x80004005) || (value == null))
// WinNT provider throws E_FAIL when null value is specified though actually ADS_PROPERTY_CLEAR option is used, need to catch exception
// here. But at the same time we don't want to catch the exception if user explicitly sets the value to null.
throw;
}
if (value == null)
return;
// we could not do Clear and Add, we have to bypass the existing collection cache
_changeList.Clear();
if (value is Array)
{
// byte[] is a special case, we will follow what ADSI is doing, it must be an octet string. So treat it as a single valued attribute
if (value is byte[])
_changeList.Add(value);
else if (value is object[])
_changeList.AddRange((object[])value);
else
{
//Need to box value type array elements.
object[] objArray = new object[((Array)value).Length];
((Array)value).CopyTo(objArray, 0);
_changeList.AddRange((object[])objArray);
}
}
else
_changeList.Add(value);
object?[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
_entry.CommitIfNotCaching();
// populate the new context
PopulateList();
}
}
/// <devdoc>
/// Appends the value to the set of values for this property.
/// </devdoc>
public int Add(object? value) => List.Add(value);
/// <devdoc>
/// Appends the values to the set of values for this property.
/// </devdoc>
public void AddRange(object?[] value)
{
ArgumentNullException.ThrowIfNull(value);
for (int i = 0; ((i) < (value.Length)); i = ((i) + (1)))
{
this.Add(value[i]);
}
}
/// <devdoc>
/// Appends the values to the set of values for this property.
/// </devdoc>
public void AddRange(PropertyValueCollection value)
{
ArgumentNullException.ThrowIfNull(value);
int currentCount = value.Count;
for (int i = 0; i < currentCount; i = ((i) + (1)))
{
this.Add(value[i]);
}
}
public bool Contains(object? value) => List.Contains(value);
/// <devdoc>
/// Copies the elements of this instance into an <see cref='System.Array'/>,
/// starting at a particular index into the given <paramref name="array"/>.
/// </devdoc>
public void CopyTo(object?[] array, int index)
{
List.CopyTo(array, index);
}
public int IndexOf(object? value) => List.IndexOf(value);
public void Insert(int index, object? value) => List.Insert(index, value);
private void PopulateList()
{
//No need to fill the cache here, when GetEx is calles, an implicit
//call to GetInfo will be called against an uninitialized property
//cache. Which is exactly what FillCache does.
//entry.FillCache(propertyName);
object? var;
int unmanagedResult = _entry.AdsObject.GetEx(PropertyName, out var);
if (unmanagedResult != 0)
{
// property not found (IIS provider returns 0x80005006, other provides return 0x8000500D).
if ((unmanagedResult == unchecked((int)0x8000500D)) || (unmanagedResult == unchecked((int)0x80005006)))
{
return;
}
else
{
throw COMExceptionHelper.CreateFormattedComException(unmanagedResult);
}
}
if (var is ICollection)
InnerList.AddRange((ICollection)var);
else
InnerList.Add(var);
}
/// <devdoc>
/// Removes the value from the collection.
/// </devdoc>
public void Remove(object? value)
{
if (_needNewBehavior)
{
try
{
List.Remove(value);
}
catch (ArgumentException)
{
// exception is thrown because value does not exist in the current cache, but it actually might do exist just because it is a very
// large multivalued attribute, the value has not been downloaded yet.
OnRemoveComplete(0, value);
}
}
else
List.Remove(value);
}
protected override void OnClearComplete()
{
if (_needNewBehavior && !_allowMultipleChange && _updateType != UpdateType.None && _updateType != UpdateType.Update)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Clear, PropertyName, null);
_updateType = UpdateType.Update;
try
{
_entry.CommitIfNotCaching();
}
catch (System.Runtime.InteropServices.COMException e)
{
// On ADSI 2.5 if property has not been assigned any value before,
// then IAds::SetInfo() in CommitIfNotCaching returns bad HREsult 0x8007200A, which we ignore.
if (e.ErrorCode != unchecked((int)0x8007200A)) // ERROR_DS_NO_ATTRIBUTE_OR_VALUE
throw;
}
}
protected override void OnInsertComplete(int index, object? value)
{
if (_needNewBehavior)
{
if (!_allowMultipleChange)
{
if (_updateType != UpdateType.None && _updateType != UpdateType.Add)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_changeList.Add(value);
object[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, allValues);
_updateType = UpdateType.Add;
}
else
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, new object?[] { value });
}
}
else
{
object[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
_entry.CommitIfNotCaching();
}
protected override void OnRemoveComplete(int index, object? value)
{
if (_needNewBehavior)
{
if (!_allowMultipleChange)
{
if (_updateType != UpdateType.None && _updateType != UpdateType.Delete)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_changeList.Add(value);
object?[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, allValues);
_updateType = UpdateType.Delete;
}
else
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, new object?[] { value });
}
}
else
{
object?[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
_entry.CommitIfNotCaching();
}
protected override void OnSetComplete(int index, object? oldValue, object? newValue)
{
// no need to consider the not allowing accumulative change case as it does not support Set
if (Count <= 1)
{
_entry.AdsObject.Put(PropertyName, newValue);
}
else
{
if (_needNewBehavior)
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, new object?[] { oldValue });
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, new object?[] { newValue });
}
else
{
object?[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
}
_entry.CommitIfNotCaching();
}
}
}
|