Beispiel #1
0
        public RecipeField CopyTo(RecipeRecord record)
        {
            RecipeField field = CloneTo(record);

            record.PutField(field);
            return(field);
        }
Beispiel #2
0
 public void PutField(RecipeField field)
 {
     if (field.Record != this)
     {
         throw new ArgumentException("Cannot add a field from a different record to this record");
     }
     fields[field.Name] = field;
 }
Beispiel #3
0
        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");
            }
        }
Beispiel #4
0
        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);
        }