| /* |
| * 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. |
| */ |
| |
| namespace Apache.Ignite.Core.Impl.Common |
| { |
| using System; |
| using System.Collections; |
| using System.Collections.Generic; |
| using System.ComponentModel; |
| using System.Configuration; |
| using System.Diagnostics; |
| using System.Diagnostics.CodeAnalysis; |
| using System.IO; |
| using System.Linq; |
| using System.Reflection; |
| using System.Text; |
| using System.Xml; |
| using System.Xml.Serialization; |
| using Apache.Ignite.Core.Events; |
| using Apache.Ignite.Core.Impl.Binary; |
| using Apache.Ignite.Core.Impl.Events; |
| |
| /// <summary> |
| /// Serializes and deserializes Ignite configurations to and from XML. |
| /// </summary> |
| internal static class IgniteConfigurationXmlSerializer |
| { |
| /** Attribute that specifies a type for abstract properties, such as IpFinder. */ |
| private const string TypNameAttribute = "type"; |
| |
| /** Value for TypNameAttribute that denotes null. */ |
| private const string TypNameNull = "null"; |
| |
| /** Xmlns. */ |
| private const string XmlnsAttribute = "xmlns"; |
| |
| /** Xmlns. */ |
| private const string KeyValPairElement = "pair"; |
| |
| /** Schema. */ |
| private const string Schema = "http://ignite.apache.org/schema/dotnet/{0}Section"; |
| |
| /// <summary> |
| /// Deserializes configuration of specified type from XML string. |
| /// </summary> |
| /// <param name="xml">Xml string.</param> |
| /// <returns>Resulting configuration.</returns> |
| [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] |
| [SuppressMessage("Microsoft.Usage", "CA2202: Do not call Dispose more than one time on an object")] |
| public static T Deserialize<T>(string xml) where T : new() |
| { |
| IgniteArgumentCheck.NotNullOrEmpty(xml, "xml"); |
| |
| using (var stringReader = new StringReader(xml)) |
| using (var xmlReader = XmlReader.Create(stringReader)) |
| { |
| // Skip XML header. |
| xmlReader.MoveToContent(); |
| |
| return Deserialize<T>(xmlReader); |
| } |
| } |
| |
| /// <summary> |
| /// Deserializes configuration of specified type from specified <see cref="XmlReader"/>. |
| /// </summary> |
| /// <param name="reader">The reader.</param> |
| /// <returns>Resulting configuration.</returns> |
| public static T Deserialize<T>(XmlReader reader) where T : new() |
| { |
| IgniteArgumentCheck.NotNull(reader, "reader"); |
| |
| var cfg = new T(); |
| |
| if (reader.NodeType == XmlNodeType.Element || reader.Read()) |
| ReadElement(reader, cfg, new TypeResolver()); |
| |
| return cfg; |
| } |
| |
| /// <summary> |
| /// Serializes specified configuration to <see cref="XmlWriter" />. |
| /// </summary> |
| /// <param name="configuration">The configuration.</param> |
| /// <param name="writer">The writer.</param> |
| /// <param name="rootElementName">Name of the root element.</param> |
| public static void Serialize(object configuration, XmlWriter writer, string rootElementName) |
| { |
| IgniteArgumentCheck.NotNull(configuration, "configuration"); |
| IgniteArgumentCheck.NotNull(writer, "writer"); |
| IgniteArgumentCheck.NotNullOrEmpty(rootElementName, "rootElementName"); |
| |
| WriteElement(configuration, writer, rootElementName, configuration.GetType(), writeSchema: true); |
| } |
| |
| /// <summary> |
| /// Serializes specified configuration to <see cref="XmlWriter" />. |
| /// </summary> |
| /// <param name="configuration">The configuration.</param> |
| /// <param name="rootElementName">Name of the root element.</param> |
| /// <returns>XML string.</returns> |
| public static string Serialize(object configuration, string rootElementName) |
| { |
| var sb = new StringBuilder(); |
| |
| var settings = new XmlWriterSettings |
| { |
| Indent = true |
| }; |
| |
| using (var xmlWriter = XmlWriter.Create(sb, settings)) |
| { |
| Serialize(configuration, xmlWriter, rootElementName); |
| } |
| |
| return sb.ToString(); |
| } |
| |
| /// <summary> |
| /// Writes new element. |
| /// </summary> |
| private static void WriteElement(object obj, XmlWriter writer, string rootElementName, Type valueType, |
| PropertyInfo property = null, bool writeSchema = false) |
| { |
| if (property != null) |
| { |
| if (!property.CanWrite && !IsKeyValuePair(property.DeclaringType)) |
| return; |
| |
| if (IsIgnored(property)) |
| return; |
| } |
| |
| if (writeSchema) |
| { |
| // Write xmlns for the root element. |
| writer.WriteStartElement(rootElementName, string.Format(Schema, valueType.Name)); |
| } |
| else |
| { |
| writer.WriteStartElement(rootElementName); |
| } |
| |
| if (IsBasicType(valueType)) |
| WriteBasicProperty(obj, writer, valueType, property); |
| else if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (ICollection<>)) |
| WriteCollectionProperty(obj, writer, valueType, property); |
| else if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (IDictionary<,>)) |
| WriteDictionaryProperty(obj, writer, valueType, property); |
| else |
| WriteComplexProperty(obj, writer, valueType); |
| |
| writer.WriteEndElement(); |
| } |
| |
| /// <summary> |
| /// Writes the property of a basic type (primitives, strings, types). |
| /// </summary> |
| private static void WriteBasicProperty(object obj, XmlWriter writer, Type valueType, PropertyInfo property) |
| { |
| var converter = GetConverter(property, valueType); |
| |
| var stringValue = converter.ConvertToInvariantString(obj); |
| |
| writer.WriteString(stringValue ?? ""); |
| } |
| |
| /// <summary> |
| /// Writes the collection property. |
| /// </summary> |
| private static void WriteCollectionProperty(object obj, XmlWriter writer, Type valueType, PropertyInfo property) |
| { |
| var elementType = valueType.GetGenericArguments().Single(); |
| |
| var elementTypeName = PropertyNameToXmlName(elementType.Name); |
| |
| foreach (var element in (IEnumerable)obj) |
| WriteElement(element, writer, elementTypeName, elementType, property); |
| } |
| |
| /// <summary> |
| /// Writes the dictionary property. |
| /// </summary> |
| private static void WriteDictionaryProperty(object obj, XmlWriter writer, Type valueType, PropertyInfo property) |
| { |
| var elementType = typeof (KeyValuePair<,>).MakeGenericType(valueType.GetGenericArguments()); |
| |
| foreach (var element in (IEnumerable)obj) |
| WriteElement(element, writer, KeyValPairElement, elementType, property); |
| } |
| |
| /// <summary> |
| /// Writes the complex property (nested object). |
| /// </summary> |
| private static void WriteComplexProperty(object obj, XmlWriter writer, Type valueType) |
| { |
| if (obj == null) |
| { |
| // Happens with reference-type properties that have non-null default value. |
| // Example: IgniteClientConfiguration.Logger |
| writer.WriteAttributeString(TypNameAttribute, TypNameNull); |
| return; |
| } |
| |
| var props = GetNonDefaultProperties(obj).OrderBy(x => x.Name).ToList(); |
| |
| var realType = obj.GetType(); |
| |
| // Specify type when it differs from declared type. |
| if (valueType != realType) |
| { |
| writer.WriteAttributeString(TypNameAttribute, TypeStringConverter.Convert(obj.GetType())); |
| } |
| |
| if (IsBasicType(obj.GetType())) |
| { |
| WriteBasicProperty(obj, writer, realType, null); |
| return; |
| } |
| |
| // Write attributes |
| foreach (var prop in props.Where(p => IsBasicType(p.PropertyType) && !IsIgnored(p))) |
| { |
| var converter = GetConverter(prop, prop.PropertyType); |
| var stringValue = converter.ConvertToInvariantString(prop.GetValue(obj, null)); |
| writer.WriteAttributeString(PropertyNameToXmlName(prop.Name), stringValue ?? ""); |
| } |
| |
| // Write elements |
| foreach (var prop in props.Where(p => !IsBasicType(p.PropertyType))) |
| WriteElement(prop.GetValue(obj, null), writer, PropertyNameToXmlName(prop.Name), |
| prop.PropertyType, prop); |
| } |
| |
| /// <summary> |
| /// Reads the element. |
| /// </summary> |
| private static void ReadElement(XmlReader reader, object target, TypeResolver resolver) |
| { |
| var targetType = target.GetType(); |
| |
| // Read attributes |
| while (reader.MoveToNextAttribute()) |
| { |
| if (reader.Name == TypNameAttribute || reader.Name == XmlnsAttribute) |
| continue; |
| |
| var prop = GetPropertyOrThrow(reader.Name, reader.Value, target.GetType()); |
| |
| var value = ConvertBasicValue(reader.Value, prop, prop.PropertyType); |
| |
| prop.SetValue(target, value, null); |
| } |
| |
| // Read content |
| reader.MoveToElement(); |
| |
| while (reader.Read()) |
| { |
| if (reader.NodeType != XmlNodeType.Element) |
| continue; |
| |
| var prop = GetPropertyOrThrow(reader.Name, reader.Value, targetType); |
| |
| var value = ReadPropertyValue(reader, resolver, prop, targetType); |
| |
| prop.SetValue(target, value, null); |
| } |
| } |
| |
| /// <summary> |
| /// Reads the property value. |
| /// </summary> |
| private static object ReadPropertyValue(XmlReader reader, TypeResolver resolver, |
| PropertyInfo prop, Type targetType) |
| { |
| var propType = prop.PropertyType; |
| |
| if (propType == typeof(object)) |
| { |
| propType = ResolvePropertyType(reader, propType, prop.Name, targetType, resolver); |
| } |
| |
| if (propType == null) |
| { |
| return null; |
| } |
| |
| if (IsBasicType(propType)) |
| { |
| // Regular property in xmlElement form. |
| return ConvertBasicValue(reader.ReadString(), prop, propType); |
| } |
| |
| if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(ICollection<>)) |
| { |
| // Collection. |
| return ReadCollectionProperty(reader, prop, targetType, resolver); |
| } |
| |
| if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) |
| { |
| // Dictionary. |
| return ReadDictionaryProperty(reader, prop, resolver); |
| } |
| |
| // Nested object (complex property). |
| return ReadComplexProperty(reader, propType, prop.Name, targetType, resolver); |
| } |
| |
| /// <summary> |
| /// Reads the complex property (nested object). |
| /// </summary> |
| private static object ReadComplexProperty(XmlReader reader, Type propType, string propName, Type targetType, |
| TypeResolver resolver) |
| { |
| propType = ResolvePropertyType(reader, propType, propName, targetType, resolver); |
| |
| if (propType == null) |
| { |
| return null; |
| } |
| |
| var nestedVal = Activator.CreateInstance(propType); |
| |
| using (var subReader = reader.ReadSubtree()) |
| { |
| subReader.Read(); // read first element |
| |
| ReadElement(subReader, nestedVal, resolver); |
| } |
| |
| return nestedVal; |
| } |
| |
| /// <summary> |
| /// Resolves the type of the property. |
| /// </summary> |
| private static Type ResolvePropertyType(XmlReader reader, Type propType, string propName, Type targetType, |
| TypeResolver resolver) |
| { |
| var typeName = reader.GetAttribute(TypNameAttribute); |
| |
| if (typeName == TypNameNull) |
| return null; |
| |
| if (!propType.IsAbstract && typeName == null) |
| return propType; |
| |
| var res = typeName == null |
| ? null |
| : resolver.ResolveType(typeName) ?? |
| GetConcreteDerivedTypes(propType).FirstOrDefault(x => x.Name == typeName); |
| |
| if (res != null) |
| return res; |
| |
| var message = string.Format("'type' attribute is required for '{0}.{1}' property", targetType.Name, |
| propName); |
| |
| var derivedTypes = GetConcreteDerivedTypes(propType); |
| |
| |
| if (typeName != null) |
| { |
| message += ", specified type cannot be resolved: " + typeName; |
| } |
| else if (derivedTypes.Any()) |
| { |
| message += ", possible values are: " + string.Join(", ", derivedTypes.Select(x => x.Name)); |
| } |
| |
| throw new ConfigurationErrorsException(message); |
| } |
| |
| /// <summary> |
| /// Reads the collection. |
| /// </summary> |
| private static IList ReadCollectionProperty(XmlReader reader, PropertyInfo prop, Type targetType, |
| TypeResolver resolver) |
| { |
| var elementType = prop.PropertyType.GetGenericArguments().Single(); |
| |
| var listType = typeof (List<>).MakeGenericType(elementType); |
| |
| var list = (IList) Activator.CreateInstance(listType); |
| |
| var converter = IsBasicType(elementType) ? GetConverter(prop, elementType) : null; |
| |
| using (var subReader = reader.ReadSubtree()) |
| { |
| subReader.Read(); // skip list head |
| while (subReader.Read()) |
| { |
| if (subReader.NodeType != XmlNodeType.Element) |
| continue; |
| |
| if (subReader.Name != PropertyNameToXmlName(elementType.Name)) |
| throw new ConfigurationErrorsException( |
| string.Format("Invalid list element in IgniteConfiguration: expected '{0}', but was '{1}'", |
| PropertyNameToXmlName(elementType.Name), subReader.Name)); |
| |
| list.Add(converter != null |
| ? converter.ConvertFromInvariantString(subReader.ReadString()) |
| : ReadComplexProperty(subReader, elementType, prop.Name, targetType, resolver)); |
| } |
| } |
| |
| return list; |
| } |
| |
| /// <summary> |
| /// Reads the dictionary. |
| /// </summary> |
| private static IDictionary ReadDictionaryProperty(XmlReader reader, PropertyInfo prop, TypeResolver resolver) |
| { |
| var keyValTypes = prop.PropertyType.GetGenericArguments(); |
| |
| var dictType = typeof (Dictionary<,>).MakeGenericType(keyValTypes); |
| |
| var pairType = typeof(Pair<,>).MakeGenericType(keyValTypes); |
| |
| var dict = (IDictionary) Activator.CreateInstance(dictType); |
| |
| using (var subReader = reader.ReadSubtree()) |
| { |
| subReader.Read(); // skip list head |
| while (subReader.Read()) |
| { |
| if (subReader.NodeType != XmlNodeType.Element) |
| continue; |
| |
| if (subReader.Name != PropertyNameToXmlName(KeyValPairElement)) |
| throw new ConfigurationErrorsException( |
| string.Format("Invalid dictionary element in IgniteConfiguration: expected '{0}', " + |
| "but was '{1}'", KeyValPairElement, subReader.Name)); |
| |
| var pair = (IPair) Activator.CreateInstance(pairType); |
| |
| var pairReader = subReader.ReadSubtree(); |
| |
| pairReader.Read(); |
| |
| ReadElement(pairReader, pair, resolver); |
| |
| dict[pair.Key] = pair.Value; |
| } |
| } |
| |
| return dict; |
| } |
| |
| /// <summary> |
| /// Reads the basic value. |
| /// </summary> |
| private static object ConvertBasicValue(string propVal, PropertyInfo property, Type propertyType) |
| { |
| var converter = GetConverter(property, propertyType); |
| |
| return converter.ConvertFromInvariantString(propVal); |
| } |
| |
| /// <summary> |
| /// Gets concrete derived types. |
| /// </summary> |
| private static List<Type> GetConcreteDerivedTypes(Type type) |
| { |
| return TypeResolver.GetAssemblyTypesSafe(typeof(IIgnite).Assembly) |
| .Where(t => t.IsClass && !t.IsAbstract && type.IsAssignableFrom(t)).ToList(); |
| } |
| |
| /// <summary> |
| /// Gets specified property from a type or throws an exception. |
| /// </summary> |
| private static PropertyInfo GetPropertyOrThrow(string propName, object propVal, Type type) |
| { |
| var property = type.GetProperty(XmlNameToPropertyName(propName)); |
| |
| if (property == null) |
| { |
| throw new ConfigurationErrorsException( |
| string.Format( |
| "Invalid IgniteConfiguration attribute '{0}={1}', there is no such property on '{2}'", |
| propName, propVal, type)); |
| } |
| |
| if (!property.CanWrite) |
| { |
| throw new ConfigurationErrorsException(string.Format( |
| "Invalid IgniteConfiguration attribute '{0}={1}', property '{2}.{3}' is not writeable", |
| propName, propVal, type, property.Name)); |
| } |
| |
| return property; |
| } |
| |
| /// <summary> |
| /// Converts an XML name to CLR name. |
| /// </summary> |
| private static string XmlNameToPropertyName(string name) |
| { |
| Debug.Assert(name.Length > 0); |
| |
| if (name == "int") |
| return "Int32"; // allow aliases |
| |
| return char.ToUpperInvariant(name[0]) + name.Substring(1); |
| } |
| |
| /// <summary> |
| /// Converts a CLR name to XML name. |
| /// </summary> |
| private static string PropertyNameToXmlName(string name) |
| { |
| Debug.Assert(name.Length > 0); |
| |
| if (name == "Int32") |
| return "int"; // allow aliases |
| |
| return char.ToLowerInvariant(name[0]) + name.Substring(1); |
| } |
| |
| /// <summary> |
| /// Determines whether specified type is a basic built-in type. |
| /// </summary> |
| private static bool IsBasicType(Type propertyType) |
| { |
| Debug.Assert(propertyType != null); |
| |
| if (IsKeyValuePair(propertyType)) |
| return false; |
| |
| return propertyType.IsValueType || propertyType == typeof (string) || propertyType == typeof (Type); |
| } |
| |
| /// <summary> |
| /// Determines whether specified type is KeyValuePair. |
| /// </summary> |
| private static bool IsKeyValuePair(Type propertyType) |
| { |
| Debug.Assert(propertyType != null); |
| |
| return propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof (KeyValuePair<,>); |
| } |
| |
| /// <summary> |
| /// Gets converter for a property. |
| /// </summary> |
| private static TypeConverter GetConverter(PropertyInfo property, Type propertyType) |
| { |
| Debug.Assert(propertyType != null); |
| |
| if (propertyType.IsEnum) |
| return new GenericEnumConverter(propertyType); |
| |
| if (propertyType == typeof (Type)) |
| return TypeStringConverter.Instance; |
| |
| if (propertyType == typeof(bool)) |
| return BooleanLowerCaseConverter.Instance; |
| |
| if (property != null && |
| property.DeclaringType == typeof (IgniteConfiguration) && property.Name == "IncludedEventTypes") |
| return EventTypeConverter.Instance; |
| |
| if (property != null && |
| property.DeclaringType == typeof (LocalEventListener) && property.Name == "EventTypes") |
| return EventTypeConverter.Instance; |
| |
| if (propertyType == typeof (object)) |
| return ObjectStringConverter.Instance; |
| |
| var converter = TypeDescriptor.GetConverter(propertyType); |
| |
| if (converter == null || !converter.CanConvertFrom(typeof(string)) || |
| !converter.CanConvertTo(typeof(string))) |
| throw new ConfigurationErrorsException("No converter for type " + propertyType); |
| |
| return converter; |
| } |
| |
| /// <summary> |
| /// Gets properties with non-default value. |
| /// </summary> |
| private static IEnumerable<PropertyInfo> GetNonDefaultProperties(object obj) |
| { |
| if (obj == null) |
| { |
| Debug.Assert(obj != null); |
| } |
| |
| return obj.GetType().GetProperties() |
| .Where(p => p.GetIndexParameters().Length == 0 && // Skip indexed properties. |
| !Equals(p.GetValue(obj, null), GetDefaultValue(p))); |
| } |
| |
| /// <summary> |
| /// Gets the default value for a property. |
| /// </summary> |
| private static object GetDefaultValue(PropertyInfo property) |
| { |
| var attr = property.GetCustomAttributes(true).OfType<DefaultValueAttribute>().FirstOrDefault(); |
| |
| if (attr != null) |
| { |
| return attr.Value; |
| } |
| |
| var declType = property.DeclaringType; |
| if (declType != null && !declType.IsAbstract && declType.GetConstructor(new Type[0]) != null) |
| { |
| return property.GetValue(Activator.CreateInstance(declType), null); |
| } |
| |
| var propertyType = property.PropertyType; |
| |
| if (propertyType.IsValueType) |
| { |
| return Activator.CreateInstance(propertyType); |
| } |
| |
| return null; |
| } |
| |
| /// <summary> |
| /// Determines whether the specified property is marked with XmlIgnore. |
| /// </summary> |
| private static bool IsIgnored(PropertyInfo property) |
| { |
| Debug.Assert(property != null); |
| |
| return property.GetCustomAttributes(typeof(XmlIgnoreAttribute), true).Any(); |
| } |
| |
| /// <summary> |
| /// Non-generic Pair accessor. |
| /// </summary> |
| private interface IPair |
| { |
| /// <summary> |
| /// Gets the key. |
| /// </summary> |
| object Key { get; } |
| |
| /// <summary> |
| /// Gets the value. |
| /// </summary> |
| object Value { get; } |
| } |
| |
| /// <summary> |
| /// Surrogate dictionary entry to overcome immutable KeyValuePair. |
| /// </summary> |
| private class Pair<TK, TV> : IPair |
| { |
| // ReSharper disable once UnusedAutoPropertyAccessor.Local |
| // ReSharper disable once MemberCanBePrivate.Local |
| public TK Key { get; set; } |
| |
| // ReSharper disable once UnusedAutoPropertyAccessor.Local |
| // ReSharper disable once MemberCanBePrivate.Local |
| public TV Value { get; set; } |
| |
| /** <inheritdoc /> */ |
| object IPair.Key |
| { |
| get { return Key; } |
| } |
| |
| /** <inheritdoc /> */ |
| object IPair.Value |
| { |
| get { return Value; } |
| } |
| } |
| } |
| } |