/// <summary>
        /// In the case of the inventory, and probably in general,
        /// the distinction between PUT and POST is not always
        /// easy to discern. The standard is badly worded in places,
        /// and adding a node to a hierarchy can be viewed as
        /// an addition, or as a modification to the inventory as
        /// a whole. This is exacerbated by an unjustified lack of
        /// consistency across different implementations.
        ///
        /// For OpenSim PUT is an update and POST is an addition. This
        /// is the behavior required by the HTTP specification and
        /// therefore as required by REST.
        ///
        /// The best way to explain the distinction is to
        /// consider the relationship between the URI and the
        /// enclosed entity. For PUT, the URI identifies the
        /// actual entity to be modified or replaced, i.e. the
        /// enclosed entity.
        ///
        /// If the operation is POST,then the URI describes the
        /// context into which the new entity will be added.
        ///
        /// As an example, suppose the URI contains:
        ///      /admin/inventory/Clothing
        ///
        /// A PUT request will normally result in some modification of
        /// the folder or item named "Clothing". Whereas a POST
        /// request will normally add some new information into the
        /// content identified by Clothing. It follows from this
        /// that for POST, the element identified by the URI MUST
        /// be a folder.
        /// </summary>

        /// <summary>
        /// POST adds new information to the inventory in the
        /// context identified by the URI.
        /// </summary>
        /// <param name=rdata>HTTP service request work area</param>
        private void DoExtend(InventoryRequestData rdata)
        {
            bool  created  = false;
            bool  modified = false;
            string newnode = String.Empty;

            // Resolve the context node specified in the URI. Entity
            // data will be ADDED beneath this node. rdata already contains
            // information about the current content of the user's
            // inventory.

            Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);

            // Processing depends upon the type of inventory node
            // identified in the URI. This is the CONTEXT for the
            // change. We either got a context or we threw an
            // exception.

            // It follows that we can only add information if the URI
            // has identified a folder. So only a type of folder is supported
            // in this case.

            if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
                typeof(InventoryFolderImpl) == InventoryNode.GetType())
            {
                // Cast the context node appropriately.

                InventoryFolderBase context    = (InventoryFolderBase) InventoryNode;

                Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
                                     MsgId, rdata.method, rdata.path);

                // Reconstitute the inventory sub-tree from the XML supplied in the entity.
                // The result is a stand-alone inventory subtree, not yet integrated into the
                // existing tree. An inventory collection consists of three components:
                // [1] A (possibly empty) set of folders.
                // [2] A (possibly empty) set of items.
                // [3] A (possibly empty) set of assets.
                // If all of these are empty, then the POST is a harmless no-operation.

                XmlInventoryCollection entity = ReconstituteEntity(rdata);

                // Inlined assets can be included in entity. These must be incorporated into
                // the asset database before we attempt to update the inventory. If anything
                // fails, return a failure to requestor.

                if (entity.Assets.Count > 0)
                {
                    Rest.Log.DebugFormat("{0} Adding {1} assets to server",
                                         MsgId, entity.Assets.Count);

                    foreach (AssetBase asset in entity.Assets)
                    {
                        Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
                                             MsgId, asset.ID, asset.Type, asset.Name);
                        Rest.AssetServices.Store(asset);

                        created = true;
                        rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>",
                                        asset.Name, asset.ID));

                        if (Rest.DEBUG && Rest.DumpAsset)
                        {
                            Rest.Dump(asset.Data);
                        }
                    }
                }

                // Modify the context using the collection of folders and items
                // returned in the XmlInventoryCollection.

                foreach (InventoryFolderBase folder in entity.Folders)
                {
                    InventoryFolderBase found;

                    // If the parentID is zero, then this folder is going
                    // into the root folder identified by the URI. The requestor
                    // may have already set the parent ID explicitly, in which
                    // case we don't have to do it here.

                    if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID)
                    {
                        if (newnode != String.Empty)
                        {
                            Rest.Log.DebugFormat("{0} Too many resources", MsgId);
                            rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed");
                        }
                        folder.ParentID = context.ID;
                        newnode = folder.Name;
                    }

                    // Search the existing inventory for an existing entry. If
                    // we have one, we need to decide if it has really changed.
                    // It could just be present as (unnecessary) context, and we
                    // don't want to waste time updating the database in that
                    // case, OR, it could be being moved from another location
                    // in which case an update is most certainly necessary.

                    found = null;

                    foreach (InventoryFolderBase xf in rdata.folders)
                    {
                        // Compare identifying attribute
                        if (xf.ID == folder.ID)
                        {
                            found = xf;
                            break;
                        }
                    }

                    if (found != null && FolderHasChanged(folder,found))
                    {
                        Rest.Log.DebugFormat("{0} Updating existing folder", MsgId);
                        Rest.InventoryServices.MoveFolder(folder);

                        modified = true;
                        rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1} <p>",
                                                         folder.Name, folder.ID));
                    }
                    else
                    {
                        Rest.Log.DebugFormat("{0} Adding new folder", MsgId);
                        Rest.InventoryServices.AddFolder(folder);

                        created = true;
                        rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1} <p>",
                                                         folder.Name, folder.ID));
                    }
                }

                // Now we repeat a similar process for the items included
                // in the entity.

                foreach (InventoryItemBase item in entity.Items)
                {
                    InventoryItemBase found = null;

                    // If the parentID is zero, then this is going
                    // directly into the root identified by the URI.

                    if (item.Folder == UUID.Zero)
                    {
                        item.Folder = context.ID;
                    }

                    // Determine whether this is a new item or a
                    // replacement definition.

                    foreach (InventoryItemBase xi in rdata.items)
                    {
                        // Compare identifying attribute
                        if (xi.ID == item.ID)
                        {
                            found = xi;
                            break;
                        }
                    }

                    if (found != null && ItemHasChanged(item, found))
                    {
                        Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}",
                                             MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
                        Rest.InventoryServices.UpdateItem(item);
                        modified = true;
                        rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1} <p>", item.Name, item.ID));
                    }
                    else
                    {
                        Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}",
                                             MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
                        Rest.InventoryServices.AddItem(item);
                        created = true;
                        rdata.appendStatus(String.Format("<p> Created item {0}, UUID {1} <p>", item.Name, item.ID));
                    }
                }

                if (created)
                {
                    // Must include a location header with a URI that identifies the new resource.
                    rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}",
                             rdata.hostname, rdata.port,rdata.path,newnode));
                    rdata.Complete(Rest.HttpStatusCodeCreated);
                }
                else
                {
                    if (modified)
                    {
                        rdata.Complete(Rest.HttpStatusCodeOK);
                    }
                    else
                    {
                        rdata.Complete(Rest.HttpStatusCodeNoContent);
                    }
                }

                rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
            }
            else
            {
                Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
                                     MsgId, rdata.method, rdata.path, InventoryNode.GetType());
                rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context");
            }
        }