/** * 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. */ using System; using System.Collections.Generic; using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json; namespace Avro { /// /// Base class for all schema types /// public abstract class Schema { /// /// Enum for schema types /// public enum Type { Null, Boolean, Int, Long, Float, Double, Bytes, String, Record, Enumeration, Array, Map, Union, Fixed, Error } /// /// Schema type property /// public Type Tag { get; private set; } /// /// Additional JSON attributes apart from those defined in the AVRO spec /// internal PropertyMap Props { get; private set; } /// /// Constructor for schema class /// /// protected Schema(Type type, PropertyMap props) { this.Tag = type; this.Props = props; } /// /// The name of this schema. If this is a named schema such as an enum, it returns the fully qualified /// name for the schema. For other schemas, it returns the type of the schema. /// public abstract string Name { get; } /// /// Static class to return new instance of schema object /// /// JSON object /// list of named schemas already read /// enclosing namespace of the schema /// new Schema object internal static Schema ParseJson(JToken jtok, SchemaNames names, string encspace) { if (null == jtok) throw new ArgumentNullException("j", "j cannot be null."); if (jtok.Type == JTokenType.String) // primitive schema with no 'type' property or primitive or named type of a record field { string value = (string)jtok; PrimitiveSchema ps = PrimitiveSchema.NewInstance(value); if (null != ps) return ps; NamedSchema schema = null; if (names.TryGetValue(value, null, encspace, out schema)) return schema; throw new SchemaParseException("Undefined name: " + value); } if (jtok is JArray) // union schema with no 'type' property or union type for a record field return UnionSchema.NewInstance(jtok as JArray, null, names, encspace); if (jtok is JObject) // JSON object with open/close parenthesis, it must have a 'type' property { JObject jo = jtok as JObject; JToken jtype = jo["type"]; if (null == jtype) throw new SchemaParseException("Property type is required"); var props = Schema.GetProperties(jtok); if (jtype.Type == JTokenType.String) { string type = (string)jtype; if (type.Equals("array")) return ArraySchema.NewInstance(jtok, props, names, encspace); if (type.Equals("map")) return MapSchema.NewInstance(jtok, props, names, encspace); Schema schema = PrimitiveSchema.NewInstance((string)type, props); if (null != schema) return schema; return NamedSchema.NewInstance(jo, props, names, encspace); } else if (jtype.Type == JTokenType.Array) return UnionSchema.NewInstance(jtype as JArray, props, names, encspace); } throw new AvroTypeException("Invalid JSON for schema: " + jtok); } /// /// Parses a given JSON string to create a new schema object /// /// JSON string /// new Schema object public static Schema Parse(string json) { if (string.IsNullOrEmpty(json)) throw new ArgumentNullException("json", "json cannot be null."); return Parse(json.Trim(), new SchemaNames(), null); // standalone schema, so no enclosing namespace } /// /// Parses a JSON string to create a new schema object /// /// JSON string /// list of named schemas already read /// enclosing namespace of the schema /// new Schema object internal static Schema Parse(string json, SchemaNames names, string encspace) { Schema sc = PrimitiveSchema.NewInstance(json); if (null != sc) return sc; try { bool IsArray = json.StartsWith("[") && json.EndsWith("]"); JContainer j = IsArray ? (JContainer)JArray.Parse(json) : (JContainer)JObject.Parse(json); return ParseJson(j, names, encspace); } catch (Newtonsoft.Json.JsonSerializationException ex) { throw new SchemaParseException("Could not parse. " + ex.Message + Environment.NewLine + json); } } /// /// Static function to parse custom properties (not defined in the Avro spec) from the given JSON object /// /// JSON object to parse /// Property map if custom properties were found, null if no custom properties found internal static PropertyMap GetProperties(JToken jtok) { var props = new PropertyMap(); props.Parse(jtok); if (props.Count > 0) return props; else return null; } /// /// Returns the canonical JSON representation of this schema. /// /// The canonical JSON representation of this schema. public override string ToString() { System.IO.StringWriter sw = new System.IO.StringWriter(); Newtonsoft.Json.JsonTextWriter writer = new Newtonsoft.Json.JsonTextWriter(sw); if (this is PrimitiveSchema || this is UnionSchema) { writer.WriteStartObject(); writer.WritePropertyName("type"); } WriteJson(writer, new SchemaNames(), null); // stand alone schema, so no enclosing name space if (this is PrimitiveSchema || this is UnionSchema) writer.WriteEndObject(); return sw.ToString(); } /// /// Writes opening { and 'type' property /// /// JSON writer private void writeStartObject(JsonTextWriter writer) { writer.WriteStartObject(); writer.WritePropertyName("type"); writer.WriteValue(GetTypeString(this.Tag)); } /// /// Returns symbol name for the given schema type /// /// schema type /// symbol name public static string GetTypeString(Type type) { if (type != Type.Enumeration) return type.ToString().ToLower(); return "enum"; } /// /// Default implementation for writing schema properties in JSON format /// /// JSON writer /// list of named schemas already written /// enclosing namespace of the schema protected internal virtual void WriteJsonFields(JsonTextWriter writer, SchemaNames names, string encspace) { } /// /// Writes schema object in JSON format /// /// JSON writer /// list of named schemas already written /// enclosing namespace of the schema protected internal virtual void WriteJson(JsonTextWriter writer, SchemaNames names, string encspace) { writeStartObject(writer); WriteJsonFields(writer, names, encspace); if (null != this.Props) Props.WriteJson(writer); writer.WriteEndObject(); } /// /// Returns the schema's custom property value given the property name /// /// custom property name /// custom property value public string GetProperty(string key) { if (null == this.Props) return null; string v; return (this.Props.TryGetValue(key, out v)) ? v : null; } /// /// Hash code function /// /// public override int GetHashCode() { return Tag.GetHashCode() + getHashCode(Props); } /// /// Returns true if and only if data written using writerSchema can be read using the current schema /// according to the Avro resolution rules. /// /// The writer's schema to match against. /// True if and only if the current schema matches the writer's. public virtual bool CanRead(Schema writerSchema) { return Tag == writerSchema.Tag; } /// /// Compares two objects, null is equal to null /// /// first object /// second object /// true if two objects are equal, false otherwise protected static bool areEqual(object o1, object o2) { return o1 == null ? o2 == null : o1.Equals(o2); } /// /// Hash code helper function /// /// /// protected static int getHashCode(object obj) { return obj == null ? 0 : obj.GetHashCode(); } } }