Esempio n. 1
0
File: Recipe.cs Progetto: sab39/nrdo
        internal static ITableObject invokeGet(NrdoSingleGet get, FieldValueGetter getFieldValue)
        {
            if (get.Params.Count > 0 || get.ParamTables.Count > 0)
            {
                throw new ArgumentException("Get by " + get.Name + " is not eligible for use in nrdo.find.by since it has parameters that are not fields");
            }
            object[] args = new object[get.Fields.Count];
            int      i    = 0;

            foreach (NrdoFieldRef field in get.Fields)
            {
                if (!field.Table.IsSelf)
                {
                    throw new ArgumentException("Get by " + get.Name + " is not eligible for use in nrdo.find.by since it uses fields not on the current table");
                }
                object value = getFieldValue(field.Field);
                args[i++] = value;
                if (value is Undefined)
                {
                    return(null);                    // We skip attempts to call gets on Undefined values because the value is inherently unknown, the get couldn't succeed.
                }
            }
            return(get.Call(args));
        }
Esempio n. 2
0
File: Recipe.cs Progetto: sab39/nrdo
        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");
            }
        }