internal Recipe(Stream stream, RecipeContext context) { XmlDocument doc = new XmlDocument(); doc.Load(stream); this.doc = context.Canonicalize(doc); }
public RecipeRecord CopyTo(RecipeContext context) { RecipeRecord record = CloneTo(context); context.PutRecordRaw(record); return(record); }
public RecipeRecord(RecipeContext context, string tableName, string originalTableName, string nrdoId) { this.context = context; this.originalTableName = originalTableName; this.tableName = tableName; this.nrdoId = nrdoId ?? ("." + Guid.NewGuid()); }
public RecipeContext Clone() { RecipeContext context = new RecipeContext(Lookup); foreach (RecipeRecord record in records.Values) { record.CopyTo(context); } return(context); }
private RecipeRecord CloneTo(RecipeContext context) { RecipeRecord record = new RecipeRecord(context, TableName, OriginalTableName, NrdoId); foreach (RecipeField field in fields.Values) { field.CopyTo(record); } return(record); }
public static RecipeContext FromXml(ILookupAssemblies lookup, XmlDocument doc) { XmlElement root = doc.DocumentElement; if (root.LocalName != "nrdo.context") { throw new ArgumentException("Cannot create a context from a <" + root.LocalName + "> element"); } RecipeContext result = new RecipeContext(lookup); foreach (XmlElement element in Recipe.elementChildren(root)) { result.PutRecordRaw(RecipeRecord.FromXmlElement(result, element)); } return(result); }
public RecipeLoader(RecipeContext context) { Context = context; }
internal void Run(RecipeContext context) { DateTime now = DateTime.Now; XmlElement recipeElement = doc.DocumentElement; if (recipeElement.LocalName != "nrdo.recipe") { throw new InvalidDataException("Root element of a recipe must be <nrdo.recipe>"); } foreach (XmlElement recordElement in elementChildren(recipeElement)) { debug(recordElement, "Starting..."); if (recordElement.LocalName == "nrdo.id.changed") { context.ChangeNrdoId(recordElement.GetAttribute("from"), recordElement.GetAttribute("to")); continue; } // Verify that the element corresponds to a table we know about NrdoTable table = NrdoTable.GetTable(context.Lookup, recordElement.LocalName.Replace('.', ':')); if (table == null) { throw new ArgumentException("No table called " + recordElement.LocalName + " could be found."); } // Scan for eligible references on the table, and break any corresponding values down into individual attributes // of the :nrdoid.fieldname variety. // NOTE: It's inefficient to just translate them into :nrdoid.fieldname rather than looking up the value at this // point, because we have the object already and don't need to figure everything out again when we come to parse // the attribute, but since we're storing the values into an XML attribute, we need something we can stringify. // In future we could implement a cache on :nrdoid.fieldname strings, in which case we'd prepopulate that cache // at this point. Recipe loading is not considered perf-critical though. Fortunately! foreach (NrdoReference reference in table.References) { if (isEligibleReference(reference) && recordElement.HasAttribute(reference.Name)) { string targetId = XmlUtil.GetAttr(recordElement, reference.Name); RecipeRecord target = context.GetRecord(targetId); recordElement.RemoveAttribute(reference.Name); string raction = XmlUtil.GetAttr(recordElement, "nrdo.action"); if (target == null && raction != "none" && raction != "delete") { throw new ArgumentException("Could not find record with nrdo.id " + targetId + " in context (processing " + recordElement.LocalName + " " + XmlUtil.GetAttr(recordElement, "nrdo.id") + " " + raction + ")"); } if (target != null) { if (target.TableName != reference.TargetTable.Name) { throw new ArgumentException("Target of reference " + reference.Name + " on " + table.Name + " is " + reference.TargetTable.Name + ", but nrdo.id " + targetId + " refers to a " + target.TableName); } foreach (NrdoJoin join in reference.Joins) { if (recordElement.HasAttribute(join.From.Field.Name)) { throw new ArgumentException("Cannot specify field " + join.From.Field.Name + " at the same time as reference " + reference.Name + " which uses that field"); } recordElement.SetAttribute(join.From.Field.Name, ":" + targetId + "." + join.To.Field.Name); } } } } // Construct a key for looking up the value based on primary key RecordKey key = new RecordKey(table, delegate(NrdoField field) { if (recordElement.HasAttribute(field.Name)) { return(context.evaluate(field.Type, XmlUtil.GetAttr(recordElement, field.Name))); } else { return(Undefined.Value); } }); // Validate the nrdo.id attribute // nrdo.id is required unless the pkey is not sequenced, AND all the // pkey fields are specified (which is equivalent to key.IsDefined) string nrdoId = null; if (recordElement.HasAttribute("nrdo.id")) { nrdoId = XmlUtil.GetAttr(recordElement, "nrdo.id"); if (!Regex.IsMatch(nrdoId, "^[A-Za-z_][A-Za-z0-9_-]*$")) { throw new ArgumentException("Illegal nrdo.id value " + nrdoId); } } else { if (table.IsPkeySequenced || !key.IsDefined) { throw new ArgumentException("Must specify nrdo.id attribute on " + table.Name); } } // Verify that only attributes that are supposed to exist on the element actually do. foreach (XmlAttribute attr in recordElement.Attributes) { switch (attr.Name) { case "nrdo.find.by": case "nrdo.find.where": if (!recordElement.HasAttribute("nrdo.id")) { throw new ArgumentException(attr.Name + " can only be specified if nrdo.id is present on " + table.Name); } if (key.IsDefined) { throw new ArgumentException(attr.Name + " cannot be specified if all primary keys are given on " + table.Name + " (" + recordElement.GetAttribute("nrdo.id") + ")"); } break; case "nrdo.id": case "nrdo.exists": case "nrdo.action": // These are always legal. Nothing to see here. Move along. break; default: if (table.GetField(attr.Name) == null) { throw new ArgumentException("No such field as " + attr.Name + " defined on " + table.Name); } break; } } // Go and look up the record. If found, check its type matches the element, then clone it. ITableObject data; RecipeRecord record = context.GetRecord(nrdoId, key); if (record != null) { if (record.TableName != table.Name) { throw new ArgumentException("nrdo.id " + nrdoId + " refers to a " + record.TableName + " record in the context, but is used on a " + table.Name + " record here."); } data = record.GetData(); record = record.Clone(); } else { record = new RecipeRecord(context, table, nrdoId); data = null; if (recordElement.HasAttribute("nrdo.find.by") && recordElement.HasAttribute("nrdo.find.where")) { throw new ArgumentException("Cannot specify both nrdo.find.by and nrdo.find.where"); } if (recordElement.HasAttribute("nrdo.find.where")) { throw new ArgumentException("nrdo.find.where is not implemented"); } else if (recordElement.HasAttribute("nrdo.find.by")) { NrdoSingleGet get = null; foreach (NrdoGet aGet in table.Gets) { if (aGet is NrdoSingleGet && aGet.Name == XmlUtil.GetAttr(recordElement, "nrdo.find.by")) { get = (NrdoSingleGet)aGet; break; } } if (get == null) { throw new ArgumentException("No single get by " + XmlUtil.GetAttr(recordElement, "nrdo.find.by") + " found on " + table.Name); } data = invokeGet(get, delegate(NrdoField field) { if (recordElement.HasAttribute(field.Name)) { return(context.evaluate(field.NullableType, XmlUtil.GetAttr(recordElement, field.Name))); } else { return(null); } }); } if (data != null) { foreach (NrdoField field in table.Fields) { record.PutField(new RecipeField(record, field.Name)); } foreach (NrdoFieldRef field in table.PkeyGet.Fields) { record.PutField(new RecipeValueField(record, field.Field.Name, field.Field.Get(data))); } } } // Validate against the nrdo.exists attribute, if present. if (recordElement.HasAttribute("nrdo.exists")) { string val = XmlUtil.GetAttr(recordElement, "nrdo.exists"); switch (val) { case "required": if (data == null) { throw new ArgumentException("The record " + nrdoId + " is specified as 'required' in the recipe, but does not exist"); } break; case "permitted": break; case "error": if (data != null) { throw new ArgumentException("The record " + nrdoId + " exists, but the recipe specifies that it must not"); } break; default: throw new ArgumentException("Illegal value " + val + " for nrdo.exists attribute, legal values are required, permitted and error"); } } // Determine what needs to be done based on the nrdo.action attribute string action = "update"; bool setFields = true; bool overwriteFields = false; if (recordElement.HasAttribute("nrdo.action")) { action = XmlUtil.GetAttr(recordElement, "nrdo.action"); } switch (action) { case "none": setFields = false; break; case "ensure": if (data != null) { setFields = false; } // Otherwise equivalent to "update", which is the default case, nothing to do. break; case "update": // Nothing to do; this is the default case. break; case "replace": overwriteFields = true; break; case "delete": if (data != null) { data.Delete(); context.RemoveRecord(record); } continue; default: throw new ArgumentException("Illegal value " + action + " for the nrdo.action attribute, legal values are none, ensure, update, replace and delete"); } // If the record doesn't correspond to an existing item in the database, create it. if (data == null) { // If the record wasn't found AND isn't being created, then we don't need to add it to the database or // to the context. if (!setFields) { continue; } List <object> ctorArgs = new List <object>(); foreach (NrdoField field in table.CtorParams) { object value = Undefined.Value; if (recordElement.HasAttribute(field.Name)) { value = context.evaluate(field.Type, XmlUtil.GetAttr(recordElement, field.Name)); } if (value is Undefined) { value = defaultValue(field.Type, field.IsNullable); } if (value is Now) { ctorArgs.Add(now); record.PutField(new RecipeField(record, field.Name)); } else { ctorArgs.Add(value); record.PutField(new RecipeValueField(record, field.Name, value)); } } data = table.Create(ctorArgs.ToArray()); } // Set the appropriate fields based on what's already known in the context and whether overwriteFields is true foreach (NrdoField field in table.Fields) { // Figure out whether this particular field should get set bool setIt = false; if (field.IsWritable && recordElement.HasAttribute(field.Name)) { if (data.IsNew || overwriteFields) { setIt = setFields; } else { RecipeField rfield = record.GetField(field.Name); if (rfield == null) { // Wish I could figure out something smarter to do here setIt = false; } else if (rfield is RecipeValueField) { object value = ((RecipeValueField)rfield).Value; if (object.Equals(field.Get(data), value)) { setIt = setFields; } } } } // Set the field if (setIt) { object value = context.evaluate(field.Type, XmlUtil.GetAttr(recordElement, field.Name)); if (value is Now) { field.Set(data, now); record.PutField(new RecipeField(record, field.Name)); } else { field.Set(data, value); record.PutField(new RecipeValueField(record, field.Name, value)); } } // Save the fact that we know the field, but not the value. But not if it's a readonly field, // because that would have been set when the ctor was being processed. else if (record.GetField(field.Name) == null) { // If the record was newly created, we know the value is the default for the type. Otherwise, // we store that we just don't know the value. if (data.IsNew) { if (field.IsWritable) { record.PutField(new RecipeValueField(record, field.Name, defaultValue(field.Type, field.IsNullable))); } else { // must either have been picked up by the constructor, or // be the sequenced pkey and will be picked up below } } else { record.PutField(new RecipeField(record, field.Name)); } } } // Save the newly created object into the database and into the context if (setFields) { data.Update(); } // This covers sequenced pkeys and also covers the case where // a DateTime field in the pkey was set to :now. If it's part of // the pkey we can't change it (so storing the value is harmless) // but we need to know it for future reference. foreach (NrdoFieldRef pkeyField in table.PkeyGet.Fields) { record.PutField(new RecipeValueField(record, pkeyField.Field.Name, pkeyField.Field.Get(data))); } context.PutRecord(record); debug(recordElement, "Finished"); } }
internal static RecipeRecord FromXmlElement(RecipeContext context, XmlElement element) { var origTableName = element.LocalName.Replace('.', ':'); var tableName = context.GetRenameMappedTableName(origTableName); if (tableName == null) { return(null); } RecipeRecord result = new RecipeRecord(context, tableName, origTableName, XmlUtil.GetAttr(element, "nrdo.id")); foreach (XmlAttribute attr in element.Attributes) { if (attr.LocalName == "nrdo.id") { continue; } RecipeField field; string[] parts = attr.Value.Split(new char[] { ':' }, 2); if (parts.Length != 2) { throw new ArgumentException("attribute value " + attr.Name + "='" + attr.Value + "' does not contain a ':'"); } string type = parts[0]; string value = parts[1]; if (type == "") { switch (value) { case "undefined": field = new RecipeField(result, attr.LocalName); break; case "null": field = new RecipeValueField(result, attr.LocalName, null); break; default: throw new ArgumentException("attribute value " + attr.Name + "='" + attr.Value + "' starts with : but is neither :null nor :undefined"); } } else { if (!type.StartsWith("System.")) { throw new ArgumentException("Illegal context value type " + type); } object valueObject; if (type == "System.DateTime") { valueObject = DateTime.FromBinary(long.Parse(value)); } else { Type valueType = Type.GetType(type); valueObject = TypeDescriptor.GetConverter(valueType).ConvertFromString(value); } field = new RecipeValueField(result, attr.LocalName, valueObject); } result.PutField(field); } return(result); }
public RecipeRecord(RecipeContext context, NrdoTable table, string nrdoId) : this(context, table.Name, table.Name, nrdoId) { this.table = table; }