//******************************************************************************************************
// CategorizedSettingsElement.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), 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.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 04/11/2006 - Pinal C. Patel
// Generated original version of source code.
// 05/25/2006 - J. Ritchie Carroll
// Added Try/Catch safety wrapper around GetTypedValue implementation.
// 06/01/2006 - J. Ritchie Carroll
// Added GetTypedValue overload to handle boolean types as a special case.
// 08/17/2007 - Darrell Zuercher
// Edited code comments.
// 09/17/2008 - Pinal C. Patel
// Converted code to C#.
// 09/22/2008 - J. Ritchie Carroll
// Made boolean types a special case (i.e., using ParseBoolean extension).
// 09/29/2008 - Pinal C. Patel
// Reviewed code comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 04/15/2010 - Pinal C. Patel
// Modified property setters to update the internal property bag only if values have changed.
// 04/20/2010 - Pinal C. Patel
// Added new Category property for the purpose of managing user scope setting.
// Removed publicly accessible constructors for manageability.
// Added Scope property as a way of identifying user scope settings.
// 04/21/2010 - Pinal C. Patel
// Removed unnecessary overloads of Update() for manageability.
// 04/22/2010 - Pinal C. Patel
// Fixed encryption related bug introduced when adding support for user scope settings.
// 12/05/2010 - Pinal C. Patel
// Modified Update() and ValueAs() to specify CultureInfo for the conversion.
// 01/11/2011 - Pinal C. Patel
// Added support for use of Eval() in Value to reference other setting values and static members
// of .NET types.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header,
//
//******************************************************************************************************
using System;
using System.Configuration;
using System.Reflection;
using System.Text.RegularExpressions;
using GSF.Security.Cryptography;
namespace GSF.Configuration
{
#region [ Enumerations ]
///
/// Specifies the scope of a setting represented by .
///
public enum SettingScope
{
///
/// Settings is intended for user specific use.
///
User,
///
/// Settings is intended for application wide use.
///
Application
}
#endregion
///
/// Represents a settings entry in the config file.
///
public class CategorizedSettingsElement : ConfigurationElement
{
#region [ Members ]
// Constants
///
/// Specifies the default value for the property.
///
public const string DefaultValue = "";
///
/// Specifies the default value for the property.
///
public const string DefaultDescription = "";
///
/// Specifies the default value for the property.
///
public const bool DefaultEncrypted = false;
///
/// Specifies the default value for the property.
///
public const SettingScope DefaultScope = SettingScope.Application;
private const string DefaultCryptoKey = "0679d9ae-aca5-4702-a3f5-604415096987";
private const string EvalRegex = @"Eval\((?(?[\w\d]+\.?)+)\.(?[\w\d]+)\)";
// Fields
private string m_cryptoKey;
private CategorizedSettingsElementCollection m_category;
#endregion
#region [ Constructors ]
///
/// Required by the configuration API and is for internal use only.
///
internal CategorizedSettingsElement(CategorizedSettingsElementCollection category)
: this(category, "")
{
}
///
/// Required by the configuration API and is for internal use only.
///
internal CategorizedSettingsElement(CategorizedSettingsElementCollection category, string name)
{
m_category = category;
this.Name = name;
m_cryptoKey = DefaultCryptoKey;
}
#endregion
#region [ Properties ]
///
/// Gets or sets the to which this belongs.
///
public CategorizedSettingsElementCollection Category
{
get
{
return m_category;
}
internal set
{
m_category = value;
}
}
///
/// Gets or sets the identifier of the setting.
///
/// The identifier of the setting.
[ConfigurationProperty("name", IsKey = true, IsRequired = true)]
public string Name
{
get
{
return (string)base["name"];
}
set
{
base["name"] = value;
Modified = true;
}
}
///
/// Gets or sets the value of the setting.
///
/// The value of the setting.
///
/// can reference the value of another setting using Eval([Section].[Setting]) syntax or the value of a known .NET
/// type's static member (field, non-indexed property or parameterless method) using Eval([Type].[Member]) syntax as shown in the example below:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
[ConfigurationProperty("value", IsRequired = true, DefaultValue = DefaultValue)]
public string Value
{
get
{
return EvaluateValue(DecryptValue(GetRawValue()));
}
set
{
// Continue only if values are different.
string currentValue;
try
{
currentValue = DecryptValue(GetRawValue());
}
catch (ConfigurationErrorsException)
{
// Clear value if it fails to decrypt, updating anyway
currentValue = string.Empty;
}
if (value.ToNonNullString().Equals(currentValue))
return;
// Ensure only Eval() can replace Eval().
if (Regex.IsMatch(currentValue, EvalRegex, RegexOptions.IgnoreCase) &&
!Regex.IsMatch(value, EvalRegex, RegexOptions.IgnoreCase))
return;
value = EncryptValue(value);
if (Scope == SettingScope.Application || (object)Category[Name] == null)
{
// Setting is application wide or is being added for the first time.
base["value"] = value;
Modified = true;
}
else
{
// Setting is user specific so update setting in user settings store.
Category.Section.File.UserSettings.Write(Category.Name, Name, value);
}
}
}
///
/// Gets or sets the description of the setting.
///
/// The description of the setting.
[ConfigurationProperty("description", IsRequired = true, DefaultValue = DefaultDescription)]
public string Description
{
get
{
return (string)base["description"];
}
set
{
// Continue only if values are different.
if (value.ToNonNullString().Equals(Description))
return;
base["description"] = value;
Modified = true;
}
}
///
/// Gets or sets a boolean value that indicates whether the setting value is to be encrypted.
///
/// true, if the setting value is to be encrypted; otherwise false.
[ConfigurationProperty("encrypted", IsRequired = true, DefaultValue = DefaultEncrypted)]
public bool Encrypted
{
get
{
return (bool)base["encrypted"];
}
set
{
// Continue only if values are different.
if (value.Equals(Encrypted))
return;
base["encrypted"] = value;
Modified = true;
}
}
///
/// Gets or sets the .
///
[ConfigurationProperty("scope", IsRequired = false, DefaultValue = DefaultScope)]
public SettingScope Scope
{
get
{
return (SettingScope)base["scope"];
}
set
{
// Continue only if values are different.
if (value.Equals(Scope))
return;
base["scope"] = value;
Modified = true;
}
}
///
/// Gets value that will actually be serialized to the configuration file.
///
public string SerializedValue
{
get
{
return EncryptValue((string)base["value"]);
}
}
internal bool Modified
{
set
{
if ((object)m_category != null)
m_category.Modified = value;
}
}
#endregion
#region [ Methods ]
///
/// Sets the key to be used for encrypting and decrypting the .
///
/// New crypto key.
public void SetCryptoKey(string cryptoKey)
{
if (!string.IsNullOrEmpty(cryptoKey))
{
// Re-encrypt the existing value with the new key. This is done because the value gets encrypted,
// if specified, with the default crypto key when the value is set during instantiation.
string decryptedValue;
try
{
decryptedValue = Value;
}
catch (ConfigurationErrorsException)
{
// Clear value if it fails to decrypt
decryptedValue = string.Empty;
}
m_cryptoKey = cryptoKey;
Value = decryptedValue;
Modified = true;
}
}
///
/// Updates setting information.
///
/// New setting value.
public void Update(object value)
{
Update(value, Description, Encrypted, Scope);
}
///
/// Updates setting information.
///
/// New setting value.
/// New setting description.
public void Update(object value, string description)
{
Update(value, description, Encrypted, Scope);
}
///
/// Updates setting information.
///
/// New setting value.
/// New setting description.
/// A boolean value that indicated whether the new setting value is to be encrypted.
public void Update(object value, string description, bool encrypted)
{
Update(value, description, encrypted, Scope);
}
///
/// Updates setting information.
///
/// New setting value.
/// New setting description.
/// A boolean value that indicated whether the new setting value is to be encrypted.
/// One of the values.
public void Update(object value, string description, bool encrypted, SettingScope scope)
{
this.Scope = scope;
this.Encrypted = encrypted;
this.Value = Common.TypeConvertToString(value, m_category.Section.File.Culture);
this.Description = description;
}
///
/// Gets the setting value as the specified type.
///
/// Type to which the setting value is to be converted.
/// The type-coerced value of the setting.
///
/// If this function fails to properly coerce value to specified type, the default value is returned.
///
public T ValueAs()
{
return this.ValueAs(default(T));
}
///
/// Gets the setting value as the specified type.
///
/// Type to which the setting value is to be converted.
/// The default value to return if the setting value is empty.
/// The type-coerced value of the setting.
///
/// If this function fails to properly coerce value to specified type, the default value is returned.
///
public T ValueAs(T defaultValue)
{
try
{
string value = Value;
// Value is an empty string - use default value.
if (string.IsNullOrEmpty(value))
return defaultValue;
return value.ConvertToType(m_category.Section.File.Culture);
}
catch
{
// Conversion to target type failed so use the default value.
return defaultValue;
}
}
///
/// Gets the setting value as a string.
///
/// Value as string.
public string ValueAsString()
{
return ValueAsString(string.Empty);
}
///
/// Gets the setting value as a string.
///
/// The default value to return if the setting value is empty.
/// Value as string.
public string ValueAsString(string defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a boolean.
///
/// Value as boolean.
public bool ValueAsBoolean()
{
return ValueAsBoolean(default(bool));
}
///
/// Gets the setting value as a boolean.
///
/// The default value to return if the setting value is empty.
/// Value as boolean.
public bool ValueAsBoolean(bool defaultValue)
{
try
{
string value = Value;
// Value is an empty string - use default value.
if (string.IsNullOrEmpty(value))
return defaultValue;
// Value is not empty string - convert to boolean.
return value.ParseBoolean();
}
catch
{
// Conversion to target type failed so use the default value.
return defaultValue;
}
}
///
/// Gets the setting value as a byte.
///
/// Value as byte.
public byte ValueAsByte()
{
return ValueAsByte(default(byte));
}
///
/// Gets the setting value as a byte.
///
/// The default value to return if the setting value is empty.
/// Value as byte.
public byte ValueAsByte(byte defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a signed byte.
///
/// Value as signed byte.
public sbyte ValueAsSByte()
{
return ValueAsSByte(default(sbyte));
}
///
/// Gets the setting value as a signed byte.
///
/// The default value to return if the setting value is empty.
/// Value as signed byte.
public sbyte ValueAsSByte(sbyte defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a char.
///
/// Value as char.
public char ValueAsChar()
{
return ValueAsChar(default(char));
}
///
/// Gets the setting value as a char.
///
/// The default value to return if the setting value is empty.
/// Value as char.
public char ValueAsChar(char defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a short.
///
/// Value as short.
public short ValueAsInt16()
{
return ValueAsInt16(default(short));
}
///
/// Gets the setting value as a short.
///
/// The default value to return if the setting value is empty.
/// Value as short.
public short ValueAsInt16(short defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as an int.
///
/// Value as int.
public int ValueAsInt32()
{
return ValueAsInt32(default(int));
}
///
/// Gets the setting value as an int.
///
/// The default value to return if the setting value is empty.
/// Value as int.
public int ValueAsInt32(int defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a long.
///
/// Value as long.
public long ValueAsInt64()
{
return ValueAsInt64(default(long));
}
///
/// Gets the setting value as a long.
///
/// The default value to return if the setting value is empty.
/// Value as long.
public long ValueAsInt64(long defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as an unsigned short.
///
/// Value as unsigned short.
public ushort ValueAsUInt16()
{
return ValueAsUInt16(default(ushort));
}
///
/// Gets the setting value as an unsigned short.
///
/// The default value to return if the setting value is empty.
/// Value as unsigned short.
public ushort ValueAsUInt16(ushort defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as an unsigned int.
///
/// Value as unsigned int.
public uint ValueAsUInt32()
{
return ValueAsUInt32(default(uint));
}
///
/// Gets the setting value as an unsigned int.
///
/// The default value to return if the setting value is empty.
/// Value as unsigned int.
public uint ValueAsUInt32(uint defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as an unsigned long.
///
/// Value as unsigned long.
public ulong ValueAsUInt64()
{
return ValueAsUInt64(default(ulong));
}
///
/// Gets the setting value as an unsigned long.
///
/// The default value to return if the setting value is empty.
/// Value as unsigned long.
public ulong ValueAsUInt64(ulong defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a float.
///
/// Value as float.
public float ValueAsSingle()
{
return ValueAsSingle(default(float));
}
///
/// Gets the setting value as a float.
///
/// The default value to return if the setting value is empty.
/// Value as float.
public float ValueAsSingle(float defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a double.
///
/// Value as double.
public double ValueAsDouble()
{
return ValueAsDouble(default(double));
}
///
/// Gets the setting value as a double.
///
/// The default value to return if the setting value is empty.
/// Value as double.
public double ValueAsDouble(double defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as a decimal.
///
/// Value as decimal.
public decimal ValueAsDecimal()
{
return ValueAsDecimal(default(decimal));
}
///
/// Gets the setting value as a decimal.
///
/// The default value to return if the setting value is empty.
/// Value as decimal.
public decimal ValueAsDecimal(decimal defaultValue)
{
return ValueAs(defaultValue);
}
///
/// Gets the setting value as DateTime.
///
/// Value as DateTime.
public DateTime ValueAsDateTime()
{
return ValueAsDateTime(default(DateTime));
}
///
/// Gets the setting value as DateTime.
///
/// The default value to return if the setting value is empty.
/// Value as DateTime.
public DateTime ValueAsDateTime(DateTime defaultValue)
{
return ValueAs(defaultValue);
}
private string GetRawValue()
{
string value = (string)base["value"];
// If setting is user specific, retrieve value from user settings store.
if (Scope == SettingScope.User)
value = Category.Section.File.UserSettings.Read(Category.Name, Name, value);
return value;
}
private string EncryptValue(string value)
{
if (Encrypted && !string.IsNullOrEmpty(value))
{
try
{
// Encrypts the element's value.
value = value.Encrypt(m_cryptoKey, CipherStrength.Aes256);
}
catch (Exception ex)
{
throw new ConfigurationErrorsException(string.Format("Failed to encrypt '{0}'", value), ex);
}
}
return value;
}
private string DecryptValue(string value)
{
if (Encrypted && !string.IsNullOrEmpty(value))
{
try
{
// Decrypts the element's value.
value = value.Decrypt(m_cryptoKey, CipherStrength.Aes256);
}
catch (Exception ex)
{
throw new ConfigurationErrorsException(string.Format("Failed to decrypt '{0}'", value), ex);
}
}
return value;
}
private string EvaluateValue(string value)
{
ConfigurationFile config = m_category.Section.File;
CategorizedSettingsElement setting;
foreach (Match match in Regex.Matches(value, EvalRegex, RegexOptions.IgnoreCase))
{
// Try replacing Eval() with the actual value.
setting = config.Settings[match.Groups["Container"].Value][match.Groups["Target"].Value];
if ((object)setting != null)
{
// Replacement is the value of another setting.
value = value.Replace(match.Value, setting.Value);
}
else
{
// Replacement could be the value of .NET type's static member.
Type target = Type.GetType(match.Groups["Container"].Value);
if ((object)target != null)
{
// Specified .NET type is found.
MemberInfo[] members = target.GetMember(match.Groups["Target"].Value, BindingFlags.Public | BindingFlags.Static);
if (members.Length > 0)
{
// Specified target member is found in the .NET type.
switch (members[0].MemberType)
{
case MemberTypes.Field:
// Member is a static field.
value = value.Replace(match.Value, ((FieldInfo)members[0]).GetValue(null).ToNonNullString());
break;
case MemberTypes.Method:
// Member is a static method.
value = value.Replace(match.Value, ((MethodInfo)members[0]).Invoke(null, new object[0]).ToNonNullString());
break;
case MemberTypes.Property:
// Member is a static property.
value = value.Replace(match.Value, ((PropertyInfo)members[0]).GetValue(null, new object[0]).ToNonNullString());
break;
}
}
}
}
}
return value;
}
#endregion
}
}