diff options
author | Dr Scofield | 2008-07-02 09:02:30 +0000 |
---|---|---|
committer | Dr Scofield | 2008-07-02 09:02:30 +0000 |
commit | d40bea4a8e09be1f8e87cf41405aaa60fa8826cb (patch) | |
tree | 91ef9356d8b284ac6fa5f0d588fedebe723b69ad /OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | |
parent | Mantis#1643. Thank you Melanie for a patch that: (diff) | |
download | opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.zip opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.gz opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.bz2 opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.xz |
From: Alan M Webb <awebb@vnet.ibm.com>
This adds REST services for inventory access. It also allows inventory
uploads.
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs')
-rw-r--r-- | OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | 1993 |
1 files changed, 1993 insertions, 0 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs new file mode 100644 index 0000000..0fc10f9 --- /dev/null +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | |||
@@ -0,0 +1,1993 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSim Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | * | ||
27 | */ | ||
28 | |||
29 | using System; | ||
30 | using System.Collections.Generic; | ||
31 | using System.IO; | ||
32 | using System.Threading; | ||
33 | using System.Xml; | ||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Framework.Servers; | ||
36 | using OpenSim.Framework.Communications; | ||
37 | using OpenSim.Framework.Communications.Cache; | ||
38 | using libsecondlife; | ||
39 | using Nini.Config; | ||
40 | |||
41 | namespace OpenSim.ApplicationPlugins.Rest.Inventory | ||
42 | { | ||
43 | |||
44 | public class RestInventoryServices : IRest | ||
45 | { | ||
46 | |||
47 | private string key = "inventory"; | ||
48 | private bool enabled = false; | ||
49 | private string qPrefix = "inventory"; | ||
50 | |||
51 | // A simple constructor is used to handle any once-only | ||
52 | // initialization of working classes. | ||
53 | |||
54 | public RestInventoryServices(RestHandler p_rest) | ||
55 | { | ||
56 | |||
57 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); | ||
58 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | ||
59 | |||
60 | // Update to reflect the full prefix if not absolute | ||
61 | |||
62 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | ||
63 | { | ||
64 | qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; | ||
65 | } | ||
66 | |||
67 | // Authentication domain | ||
68 | |||
69 | Rest.Domains.Add(key, Rest.Config.GetString("inventory-domain",qPrefix)); | ||
70 | |||
71 | // Register interface | ||
72 | |||
73 | Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); | ||
74 | |||
75 | // Activate | ||
76 | |||
77 | enabled = true; | ||
78 | |||
79 | Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); | ||
80 | |||
81 | } | ||
82 | |||
83 | // Post-construction, pre-enabled initialization opportunity | ||
84 | // Not currently exploited. | ||
85 | |||
86 | public void Initialize() | ||
87 | { | ||
88 | } | ||
89 | |||
90 | // Called by the plug-in to halt REST processing. Local processing is | ||
91 | // disabled, and control blocks until all current processing has | ||
92 | // completed. No new processing will be started | ||
93 | |||
94 | public void Close() | ||
95 | { | ||
96 | enabled = false; | ||
97 | Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); | ||
98 | } | ||
99 | |||
100 | // Convenient properties | ||
101 | |||
102 | internal string MsgId | ||
103 | { | ||
104 | get { return Rest.MsgId; } | ||
105 | } | ||
106 | |||
107 | #region Interface | ||
108 | |||
109 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) | ||
110 | { | ||
111 | return (RequestData) new InventoryRequestData(request, response, qPrefix); | ||
112 | } | ||
113 | |||
114 | /// <summary> | ||
115 | /// This method is registered with the handler when this class is | ||
116 | /// initialized. It is called whenever the URI includes this handler's | ||
117 | /// prefix string. | ||
118 | /// It handles all aspects of inventory REST processing. | ||
119 | /// </summary> | ||
120 | |||
121 | private void DoInventory(RequestData hdata) | ||
122 | { | ||
123 | |||
124 | InventoryRequestData rdata = (InventoryRequestData) hdata; | ||
125 | |||
126 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); | ||
127 | |||
128 | // We're disabled | ||
129 | if (!enabled) | ||
130 | { | ||
131 | return; | ||
132 | } | ||
133 | |||
134 | // Now that we know this is a serious attempt to | ||
135 | // access inventory data, we should find out who | ||
136 | // is asking, and make sure they are authorized | ||
137 | // to do so. We need to validate the caller's | ||
138 | // identity before revealing anything about the | ||
139 | // status quo. Authenticate throws an exception | ||
140 | // via Fail if no identity information is present. | ||
141 | // | ||
142 | // With the present HTTP server we can't use the | ||
143 | // builtin authentication mechanisms because they | ||
144 | // would be enforced for all in-bound requests. | ||
145 | // Instead we look at the headers ourselves and | ||
146 | // handle authentication directly. | ||
147 | |||
148 | try | ||
149 | { | ||
150 | if (!rdata.IsAuthenticated) | ||
151 | { | ||
152 | rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); | ||
153 | } | ||
154 | } | ||
155 | catch (RestException e) | ||
156 | { | ||
157 | if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) | ||
158 | { | ||
159 | Rest.Log.WarnFormat("{0} User not authenticated", MsgId); | ||
160 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
161 | } | ||
162 | else | ||
163 | { | ||
164 | Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); | ||
165 | Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); | ||
166 | } | ||
167 | throw (e); | ||
168 | } | ||
169 | |||
170 | Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | ||
171 | |||
172 | // We can only get here if we're authorized | ||
173 | // | ||
174 | // The requestor may have specified an LLUUID or | ||
175 | // a conjoined FirstNameLastName string. We'll | ||
176 | // try both. If we fail with the first, UUID, | ||
177 | // attempt, then we need two nodes to construct | ||
178 | // a valid avatar name. | ||
179 | |||
180 | // Do we have at least a user agent name? | ||
181 | |||
182 | if (rdata.parameters.Length < 1) | ||
183 | { | ||
184 | Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); | ||
185 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | ||
186 | } | ||
187 | |||
188 | // The next parameter MUST be the agent identification, either an LLUUID | ||
189 | // or a space-separated First-name Last-Name specification. | ||
190 | |||
191 | try | ||
192 | { | ||
193 | rdata.uuid = new LLUUID(rdata.parameters[0]); | ||
194 | Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId); | ||
195 | rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); | ||
196 | } | ||
197 | catch | ||
198 | { | ||
199 | string[] names = rdata.parameters[0].Split(Rest.CA_SPACE); | ||
200 | if (names.Length == 2) | ||
201 | { | ||
202 | Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); | ||
203 | rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); | ||
204 | } | ||
205 | else | ||
206 | { | ||
207 | Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | ||
208 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | ||
209 | } | ||
210 | } | ||
211 | |||
212 | if (rdata.userProfile != null) | ||
213 | { | ||
214 | Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", | ||
215 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
216 | } | ||
217 | else | ||
218 | { | ||
219 | Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); | ||
220 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | ||
221 | } | ||
222 | |||
223 | // If we get to here, then we have successfully obtained an inventory | ||
224 | // for the specified user. | ||
225 | |||
226 | rdata.uuid = rdata.userProfile.ID; | ||
227 | |||
228 | if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) | ||
229 | { | ||
230 | |||
231 | rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid); | ||
232 | |||
233 | Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", | ||
234 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
235 | |||
236 | Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory); | ||
237 | |||
238 | Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", | ||
239 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
240 | |||
241 | lock(rdata) | ||
242 | { | ||
243 | if (!rdata.HaveInventory) | ||
244 | { | ||
245 | Monitor.Wait(rdata); | ||
246 | } | ||
247 | } | ||
248 | |||
249 | if (rdata.root == null) | ||
250 | { | ||
251 | Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}", | ||
252 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
253 | rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError); | ||
254 | } | ||
255 | |||
256 | } | ||
257 | else | ||
258 | { | ||
259 | Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}", | ||
260 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | ||
261 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | ||
262 | } | ||
263 | |||
264 | // If we get here, then we have successfully retrieved the user's information | ||
265 | // and inventory information is now available locally. | ||
266 | |||
267 | switch (rdata.method) | ||
268 | { | ||
269 | |||
270 | case Rest.HEAD : // Do the processing, set the status code, suppress entity | ||
271 | DoGet(rdata); | ||
272 | rdata.buffer = null; | ||
273 | break; | ||
274 | |||
275 | case Rest.GET : // Do the processing, set the status code, return entity | ||
276 | DoGet(rdata); | ||
277 | break; | ||
278 | |||
279 | case Rest.PUT : // Add new information | ||
280 | DoPut(rdata); | ||
281 | break; | ||
282 | |||
283 | case Rest.POST : // Update (replace) | ||
284 | DoPost(rdata); | ||
285 | break; | ||
286 | |||
287 | case Rest.DELETE : // Delete information | ||
288 | DoDelete(rdata); | ||
289 | break; | ||
290 | |||
291 | default : | ||
292 | Rest.Log.DebugFormat("{0} Method {1} not supported for {2}", | ||
293 | MsgId, rdata.method, rdata.path); | ||
294 | rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | ||
295 | Rest.HttpStatusDescMethodNotAllowed); | ||
296 | break; | ||
297 | } | ||
298 | |||
299 | } | ||
300 | |||
301 | #endregion Interface | ||
302 | |||
303 | #region method-specific processing | ||
304 | |||
305 | /// <summary> | ||
306 | /// This method implements GET processing for inventory. | ||
307 | /// Any remaining parameters are used to locate the | ||
308 | /// corresponding subtree based upon node name. | ||
309 | /// </summary> | ||
310 | |||
311 | private void DoGet(InventoryRequestData rdata) | ||
312 | { | ||
313 | |||
314 | rdata.initXmlWriter(); | ||
315 | |||
316 | rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); | ||
317 | |||
318 | if (rdata.parameters.Length == 1) | ||
319 | { | ||
320 | formatInventory(rdata, rdata.root, String.Empty); | ||
321 | } | ||
322 | else | ||
323 | { | ||
324 | traverseInventory(rdata, rdata.root, 1); | ||
325 | } | ||
326 | |||
327 | rdata.writer.WriteFullEndElement(); | ||
328 | |||
329 | rdata.Complete(); | ||
330 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
331 | |||
332 | } | ||
333 | |||
334 | /// <summary> | ||
335 | /// In the case of the inventory, and probably much else | ||
336 | /// the distinction between PUT and POST is not always | ||
337 | /// easy to discern. Adding a directory can be viewed as | ||
338 | /// an addition, or as a modification to the inventory as | ||
339 | /// a whole. | ||
340 | /// | ||
341 | /// The best distinction may be the relationship between | ||
342 | /// the entity and the URI. If we view POST as an update, | ||
343 | /// then the enity represents a replacement for the | ||
344 | /// element named by the URI. If the operation is PUT, | ||
345 | /// then the URI describes the context into which the | ||
346 | /// entity will be added. | ||
347 | /// | ||
348 | /// As an example, suppose the URI contains: | ||
349 | /// /admin/inventory/Clothing | ||
350 | /// Suppose the entity represents a Folder, called | ||
351 | /// "Clothes". | ||
352 | /// | ||
353 | /// A POST request will result in the replacement of | ||
354 | /// "Clothing" by "Clothes". Whereas a PUT request | ||
355 | /// would add Clothes as a sub-directory of Clothing. | ||
356 | /// | ||
357 | /// This is the model followed by this implementation. | ||
358 | /// </summary> | ||
359 | |||
360 | /// <summary> | ||
361 | /// PUT adds new information to the inventory at the | ||
362 | /// context identified by the URI. | ||
363 | /// </summary> | ||
364 | |||
365 | private void DoPut(InventoryRequestData rdata) | ||
366 | { | ||
367 | |||
368 | // Resolve the context node specified in the URI. Entity | ||
369 | // data will be ADDED beneath this node. | ||
370 | |||
371 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | ||
372 | |||
373 | // Processing depends upon the type of inventory node | ||
374 | // identified in the URI. This is the CONTEXT for the | ||
375 | // change. We either got a context or we threw an | ||
376 | // exception. | ||
377 | |||
378 | // It follows that we can only add information if the URI | ||
379 | // has identified a folder. So only folder is supported | ||
380 | // in this case. | ||
381 | |||
382 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
383 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
384 | { | ||
385 | |||
386 | // Cast the context node appropriately. | ||
387 | |||
388 | InventoryFolderBase context = (InventoryFolderBase) InventoryNode; | ||
389 | |||
390 | Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", | ||
391 | MsgId, rdata.method, rdata.path); | ||
392 | |||
393 | // Reconstitute inventory sub-tree from the XML supplied in the entity. | ||
394 | // This is a stand-alone inventory subtree, not yet integrated into the | ||
395 | // existing tree. | ||
396 | |||
397 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
398 | |||
399 | // Inlined assest included in entity. If anything fails, | ||
400 | // return failure to requestor. | ||
401 | |||
402 | if (entity.Assets.Count > 0) | ||
403 | { | ||
404 | |||
405 | Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
406 | MsgId, entity.Assets.Count); | ||
407 | |||
408 | foreach (AssetBase asset in entity.Assets) | ||
409 | { | ||
410 | Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | ||
411 | MsgId, asset.ID, asset.Type, asset.Name); | ||
412 | Rest.AssetServices.AddAsset(asset); | ||
413 | if (Rest.DumpAsset) | ||
414 | { | ||
415 | Rest.Dump(asset.Data); | ||
416 | } | ||
417 | } | ||
418 | |||
419 | } | ||
420 | |||
421 | // Modify the context using the collection of folders and items | ||
422 | // returned in the XmlInventoryCollection. | ||
423 | |||
424 | foreach (InventoryFolderBase folder in entity.Folders) | ||
425 | { | ||
426 | |||
427 | InventoryFolderBase found = null; | ||
428 | |||
429 | // If the parentID is zero, then this is going | ||
430 | // into the root identified by the URI. The requestor | ||
431 | // may have already set the parent ID correctly, in which | ||
432 | // case we don't have to do it here. | ||
433 | |||
434 | if (folder.ParentID == LLUUID.Zero) | ||
435 | { | ||
436 | folder.ParentID = context.ID; | ||
437 | } | ||
438 | |||
439 | // Search the existing inventory for an existing entry. If | ||
440 | // we have once, we need to decide if it has really changed. | ||
441 | // It could just be present as (unnecessary) context, and we | ||
442 | // don't want to waste time updating the database in that | ||
443 | // case, OR, it could be being moved from another location | ||
444 | // in which case an update is most certainly necessary. | ||
445 | |||
446 | found = null; | ||
447 | |||
448 | foreach (InventoryFolderBase xf in rdata.folders) | ||
449 | { | ||
450 | // Compare identifying attribute | ||
451 | if (xf.ID == folder.ID) | ||
452 | { | ||
453 | found = xf; | ||
454 | } | ||
455 | } | ||
456 | |||
457 | if (found != null && FolderHasChanged(folder,found)) | ||
458 | { | ||
459 | Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); | ||
460 | Rest.InventoryServices.MoveFolder(folder); | ||
461 | } | ||
462 | else | ||
463 | { | ||
464 | Rest.Log.DebugFormat("{0} Adding new folder", MsgId); | ||
465 | Rest.InventoryServices.AddFolder(folder); | ||
466 | } | ||
467 | |||
468 | } | ||
469 | |||
470 | // Now we repeat a similar process for the items included | ||
471 | // in the entity. | ||
472 | |||
473 | foreach (InventoryItemBase item in entity.Items) | ||
474 | { | ||
475 | |||
476 | InventoryItemBase found = null; | ||
477 | |||
478 | // If the parentID is zero, then this is going | ||
479 | // directly into the root identified by the URI. | ||
480 | |||
481 | if (item.Folder == LLUUID.Zero) | ||
482 | { | ||
483 | item.Folder = context.ID; | ||
484 | } | ||
485 | |||
486 | // Determine whether this is a new item or a | ||
487 | // replacement definition. | ||
488 | |||
489 | foreach (InventoryItemBase xi in rdata.items) | ||
490 | { | ||
491 | // Compare identifying attribute | ||
492 | if (xi.ID == item.ID) | ||
493 | { | ||
494 | found = xi; | ||
495 | } | ||
496 | } | ||
497 | |||
498 | if (found != null && ItemHasChanged(item, found)) | ||
499 | { | ||
500 | Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", | ||
501 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
502 | Rest.InventoryServices.UpdateItem(item); | ||
503 | } | ||
504 | else | ||
505 | { | ||
506 | Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", | ||
507 | MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); | ||
508 | Rest.InventoryServices.AddItem(item); | ||
509 | } | ||
510 | |||
511 | } | ||
512 | |||
513 | } | ||
514 | else | ||
515 | { | ||
516 | Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", | ||
517 | MsgId, rdata.method, rdata.path, InventoryNode.GetType()); | ||
518 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
519 | Rest.HttpStatusDescBadRequest); | ||
520 | } | ||
521 | |||
522 | rdata.Complete(); | ||
523 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
524 | |||
525 | } | ||
526 | |||
527 | /// <summary> | ||
528 | /// POST updates the URI-identified element in the inventory. This | ||
529 | /// is actually far more flexible than it might at first sound. For | ||
530 | /// POST the URI serves two purposes: | ||
531 | /// [1] It identifies the user whose inventory is to be | ||
532 | /// processed. | ||
533 | /// [2] It optionally specifies a subtree of the inventory | ||
534 | /// that is to be used to resolve an relative subtree | ||
535 | /// specifications in the entity. If nothing is specified | ||
536 | /// then the whole inventory is implied. | ||
537 | /// Please note that the subtree specified by the URI is only relevant | ||
538 | /// to an entity containing a URI relative specification, i.e. one or | ||
539 | /// more elements do not specify parent folder information. These | ||
540 | /// elements will be implicitly referenced within the context identified | ||
541 | /// by the URI. | ||
542 | /// If an element in the entity specifies an explicit parent folder, then | ||
543 | /// that parent is effective, regardless of nay value specified in the | ||
544 | /// URI. If the parent does not exist, then the element, and any dependent | ||
545 | /// elements, are ignored. This case is actually detected and handled | ||
546 | /// during the reconstitution process. | ||
547 | /// </summary> | ||
548 | |||
549 | private void DoPost(InventoryRequestData rdata) | ||
550 | { | ||
551 | |||
552 | int count = 0; | ||
553 | |||
554 | // Resolve the inventory node that is to be modified. | ||
555 | |||
556 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | ||
557 | |||
558 | // As long as we have a context, then we have something | ||
559 | // meaningful to do, unlike PUT. So reconstitute the | ||
560 | // subtree before doing anything else. Note that we | ||
561 | // etiher got a context or we threw an exception. | ||
562 | |||
563 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | ||
564 | |||
565 | // Incorporate any inlined assets first | ||
566 | |||
567 | if (entity.Assets.Count != 0) | ||
568 | { | ||
569 | foreach (AssetBase asset in entity.Assets) | ||
570 | { | ||
571 | // Asset was validated during the collection | ||
572 | // process | ||
573 | Rest.AssetServices.AddAsset(asset); | ||
574 | } | ||
575 | } | ||
576 | |||
577 | /// <summary> | ||
578 | /// URI specifies a folder to be updated. | ||
579 | /// </summary> | ||
580 | /// <remarks> | ||
581 | /// The root node in the entity must have the same | ||
582 | /// UUID as the node identified by the URI. The | ||
583 | /// parentID if different indicates that the updated | ||
584 | /// folder is actually being moved too. | ||
585 | /// </remarks> | ||
586 | |||
587 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
588 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
589 | { | ||
590 | |||
591 | InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; | ||
592 | InventoryFolderBase xml = null; | ||
593 | |||
594 | // Scan the set of folders in the entity collection for an | ||
595 | // entry that macthes the context folder. It is assumed that | ||
596 | // the only reliable indicator of this is a zero UUID ( using | ||
597 | // implicit context), or the parent's UUID matches that of the | ||
598 | // URI designated node (explicit context). We don't allow | ||
599 | // ambiguity in this case because this is POST and we are | ||
600 | // supposed to be modifying a specific node. | ||
601 | // We assign any element IDs required as an economy; we don't | ||
602 | // want to iterate over the fodler set again if it can be | ||
603 | // helped. | ||
604 | |||
605 | foreach (InventoryFolderBase folder in entity.Folders) | ||
606 | { | ||
607 | if (folder.ParentID == uri.ParentID || | ||
608 | folder.ParentID == LLUUID.Zero) | ||
609 | { | ||
610 | folder.ParentID = uri.ParentID; | ||
611 | xml = folder; | ||
612 | count++; | ||
613 | } | ||
614 | if (xml.ID == LLUUID.Zero) | ||
615 | { | ||
616 | xml.ID = LLUUID.Random(); | ||
617 | } | ||
618 | } | ||
619 | |||
620 | // More than one entry is ambiguous | ||
621 | |||
622 | if (count > 1) | ||
623 | { | ||
624 | Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", | ||
625 | MsgId, rdata.method, rdata.path); | ||
626 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
627 | Rest.HttpStatusDescBadRequest); | ||
628 | } | ||
629 | |||
630 | // Exactly one entry means we ARE replacing the node | ||
631 | // identified by the URI. So we delete the old folder | ||
632 | // by moving it to the trash and then purging it. | ||
633 | // We then add all of the folders and items we | ||
634 | // included in the entity. The subtree has been | ||
635 | // modified. | ||
636 | |||
637 | if (count == 1) | ||
638 | { | ||
639 | |||
640 | InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
641 | |||
642 | uri.ParentID = TrashCan.ID; | ||
643 | Rest.InventoryServices.MoveFolder(uri); | ||
644 | Rest.InventoryServices.PurgeFolder(TrashCan); | ||
645 | |||
646 | } | ||
647 | |||
648 | // Now, regardelss of what they represent, we | ||
649 | // integrate all of the elements in the entity. | ||
650 | |||
651 | foreach (InventoryFolderBase f in entity.Folders) | ||
652 | { | ||
653 | Rest.InventoryServices.MoveFolder(f); | ||
654 | } | ||
655 | |||
656 | foreach (InventoryItemBase it in entity.Items) | ||
657 | { | ||
658 | Rest.InventoryServices.AddItem(it); | ||
659 | } | ||
660 | |||
661 | } | ||
662 | |||
663 | /// <summary> | ||
664 | /// URI specifies an item to be updated | ||
665 | /// </summary> | ||
666 | /// <remarks> | ||
667 | /// The entity must contain a single item node to be | ||
668 | /// updated. ID and Folder ID must be correct. | ||
669 | /// </remarks> | ||
670 | |||
671 | else | ||
672 | { | ||
673 | |||
674 | InventoryItemBase uri = (InventoryItemBase) InventoryNode; | ||
675 | InventoryItemBase xml = null; | ||
676 | |||
677 | if (entity.Folders.Count != 0) | ||
678 | { | ||
679 | Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", | ||
680 | MsgId, rdata.method, rdata.path); | ||
681 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
682 | Rest.HttpStatusDescBadRequest); | ||
683 | } | ||
684 | |||
685 | if (entity.Items.Count > 1) | ||
686 | { | ||
687 | Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", | ||
688 | MsgId, rdata.method, rdata.path); | ||
689 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | ||
690 | Rest.HttpStatusDescBadRequest); | ||
691 | } | ||
692 | |||
693 | xml = entity.Items[0]; | ||
694 | |||
695 | if (xml.ID == LLUUID.Zero) | ||
696 | { | ||
697 | xml.ID = LLUUID.Random(); | ||
698 | } | ||
699 | |||
700 | // If the folder reference has changed, then this item is | ||
701 | // being moved. Otherwise we'll just delete the old, and | ||
702 | // add in the new. | ||
703 | |||
704 | // Delete the old item | ||
705 | |||
706 | Rest.InventoryServices.DeleteItem(uri); | ||
707 | |||
708 | // Add the new item to the inventory | ||
709 | |||
710 | Rest.InventoryServices.AddItem(xml); | ||
711 | |||
712 | } | ||
713 | |||
714 | rdata.Complete(); | ||
715 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
716 | |||
717 | } | ||
718 | |||
719 | /// <summary> | ||
720 | /// Arguably the most damaging REST interface. It deletes the inventory | ||
721 | /// item or folder identified by the URI. | ||
722 | /// | ||
723 | /// We only process if the URI identified node appears to exist | ||
724 | /// We do not test for success because we either get a context, | ||
725 | /// or an exception is thrown. | ||
726 | /// | ||
727 | /// Folders are deleted by moving them to another folder and then | ||
728 | /// purging that folder. We'll do that by creating a temporary | ||
729 | /// sub-folder in the TrashCan and purging that folder's | ||
730 | /// contents. If we can't can it, we don't delete it... | ||
731 | /// So, if no trashcan is available, the request does nothing. | ||
732 | /// Items are summarily deleted. | ||
733 | /// | ||
734 | /// In the interests of safety, a delete request should normally | ||
735 | /// be performed using UUID, as a name might identify several | ||
736 | /// elements. | ||
737 | /// </summary> | ||
738 | |||
739 | private void DoDelete(InventoryRequestData rdata) | ||
740 | { | ||
741 | |||
742 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | ||
743 | |||
744 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | ||
745 | typeof(InventoryFolderImpl) == InventoryNode.GetType()) | ||
746 | { | ||
747 | |||
748 | InventoryFolderBase TrashCan = GetTrashCan(rdata); | ||
749 | |||
750 | InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; | ||
751 | Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted", | ||
752 | MsgId, rdata.method, rdata.path); | ||
753 | folder.ParentID = TrashCan.ID; | ||
754 | Rest.InventoryServices.MoveFolder(folder); | ||
755 | Rest.InventoryServices.PurgeFolder(TrashCan); | ||
756 | |||
757 | } | ||
758 | |||
759 | // Deleting items is much more straight forward. | ||
760 | |||
761 | else | ||
762 | { | ||
763 | InventoryItemBase item = (InventoryItemBase) InventoryNode; | ||
764 | Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", | ||
765 | MsgId, rdata.method, rdata.path); | ||
766 | Rest.InventoryServices.DeleteItem(item); | ||
767 | } | ||
768 | |||
769 | rdata.Complete(); | ||
770 | rdata.Respond("Inventory " + rdata.method + ": Normal completion"); | ||
771 | |||
772 | } | ||
773 | |||
774 | #endregion method-specific processing | ||
775 | |||
776 | /// <summary> | ||
777 | /// This method is called to obtain the OpenSim inventory object identified | ||
778 | /// by the supplied URI. This may be either an Item or a Folder, so a suitably | ||
779 | /// ambiguous return type is employed (Object). This method recurses as | ||
780 | /// necessary to process the designated hierarchy. | ||
781 | /// | ||
782 | /// If we reach the end of the URI then we return the contextural folder to | ||
783 | /// our caller. | ||
784 | /// | ||
785 | /// If we are not yet at the end of the URI we attempt to find a child folder | ||
786 | /// and if we succeed we recurse. | ||
787 | /// | ||
788 | /// If this is the last node, then we look to see if this is an item. If it is, | ||
789 | /// we return that item. | ||
790 | /// | ||
791 | /// Otherwise we fail the request on the ground of an invalid URI. | ||
792 | /// | ||
793 | /// <note> | ||
794 | /// This mechanism cannot detect the case where duplicate subtrees satisfy a | ||
795 | /// request. In such a case the 1st element gets processed. If this is a | ||
796 | /// problem, then UUID should be used to identify the end-node. This is basic | ||
797 | /// premise of normal inventory processing. The name is an informational, and | ||
798 | /// not a defining, attribute. | ||
799 | /// </note> | ||
800 | /// | ||
801 | /// </summary> | ||
802 | |||
803 | private Object getInventoryNode(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
804 | { | ||
805 | |||
806 | Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
807 | |||
808 | // We have just run off the end of the parameter sequence | ||
809 | |||
810 | if (pi >= rdata.parameters.Length) | ||
811 | { | ||
812 | return folder; | ||
813 | } | ||
814 | |||
815 | // More names in the sequence, look for a folder that might | ||
816 | // get us there. | ||
817 | |||
818 | if (rdata.folders != null) | ||
819 | foreach (InventoryFolderBase f in rdata.folders) | ||
820 | { | ||
821 | // Look for the present node in the directory list | ||
822 | if (f.ParentID == folder.ID && | ||
823 | (f.Name == rdata.parameters[pi] || | ||
824 | f.ID.ToString() == rdata.parameters[pi])) | ||
825 | { | ||
826 | return getInventoryNode(rdata, f, pi+1); | ||
827 | } | ||
828 | } | ||
829 | |||
830 | // No folders that match. Perhaps this parameter identifies an item? If | ||
831 | // it does, then it MUST also be the last name in the sequence. | ||
832 | |||
833 | if (pi == rdata.parameters.Length-1) | ||
834 | { | ||
835 | if (rdata.items != null) | ||
836 | { | ||
837 | int k = 0; | ||
838 | InventoryItemBase li = null; | ||
839 | foreach (InventoryItemBase i in rdata.items) | ||
840 | { | ||
841 | if (i.Folder == folder.ID && | ||
842 | (i.Name == rdata.parameters[pi] || | ||
843 | i.ID.ToString() == rdata.parameters[pi])) | ||
844 | { | ||
845 | li = i; | ||
846 | k++; | ||
847 | } | ||
848 | } | ||
849 | if (k == 1) | ||
850 | { | ||
851 | return li; | ||
852 | } | ||
853 | else | ||
854 | { | ||
855 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | ||
856 | MsgId, rdata.method, rdata.path); | ||
857 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound); | ||
858 | } | ||
859 | } | ||
860 | } | ||
861 | |||
862 | // No, so abandon the request | ||
863 | |||
864 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", | ||
865 | MsgId, rdata.method, rdata.path); | ||
866 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound); | ||
867 | |||
868 | return null; /* Never reached */ | ||
869 | |||
870 | } | ||
871 | |||
872 | /// <summary> | ||
873 | /// This routine traverse the inventory's structure until the end-point identified | ||
874 | /// in the URI is reached, the remainder of the inventory (if any) is then formatted | ||
875 | /// and returned to the requestor. | ||
876 | /// | ||
877 | /// Note that this method is only interested in those folder that match elements of | ||
878 | /// the URI supplied by the requestor, so once a match is fund, the processing does | ||
879 | /// not need to consider any further elements. | ||
880 | /// | ||
881 | /// Only the last element in the URI should identify an item. | ||
882 | /// </summary> | ||
883 | |||
884 | private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) | ||
885 | { | ||
886 | |||
887 | Rest.Log.DebugFormat("{0} Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); | ||
888 | |||
889 | if (rdata.folders != null) | ||
890 | { | ||
891 | foreach (InventoryFolderBase f in rdata.folders) | ||
892 | { | ||
893 | if (f.ParentID == folder.ID && | ||
894 | (f.Name == rdata.parameters[pi] || | ||
895 | f.ID.ToString() == rdata.parameters[pi])) | ||
896 | { | ||
897 | if (pi < rdata.parameters.Length-1) | ||
898 | { | ||
899 | traverseInventory(rdata, f, pi+1); | ||
900 | } | ||
901 | else | ||
902 | { | ||
903 | formatInventory(rdata, f, String.Empty); | ||
904 | } | ||
905 | return; | ||
906 | } | ||
907 | } | ||
908 | } | ||
909 | |||
910 | if (pi == rdata.parameters.Length-1) | ||
911 | { | ||
912 | if (rdata.items != null) | ||
913 | { | ||
914 | foreach (InventoryItemBase i in rdata.items) | ||
915 | { | ||
916 | if (i.Folder == folder.ID && | ||
917 | (i.Name == rdata.parameters[pi] || | ||
918 | i.ID.ToString() == rdata.parameters[pi])) | ||
919 | { | ||
920 | // Fetching an Item has a special significance. In this | ||
921 | // case we also want to fetch the associated asset. | ||
922 | // To make it interesting, we'll d this via redirection. | ||
923 | string asseturl = "http://" + rdata.hostname + ":" + rdata.port + | ||
924 | "/admin/assets" + Rest.UrlPathSeparator + i.AssetID.ToString(); | ||
925 | rdata.Redirect(asseturl,Rest.PERMANENT); | ||
926 | Rest.Log.DebugFormat("{0} Never Reached"); | ||
927 | } | ||
928 | } | ||
929 | } | ||
930 | } | ||
931 | |||
932 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | ||
933 | MsgId, rdata.path); | ||
934 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | ||
935 | |||
936 | } | ||
937 | |||
938 | /// <summary> | ||
939 | /// This method generates XML that describes an instance of InventoryFolderBase. | ||
940 | /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem | ||
941 | /// to generate XML for any items encountered along the way. | ||
942 | /// The indentation parameter is solely for the benefit of trace record | ||
943 | /// formatting. | ||
944 | /// </summary> | ||
945 | |||
946 | private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) | ||
947 | { | ||
948 | |||
949 | if (Rest.DEBUG) | ||
950 | { | ||
951 | Rest.Log.DebugFormat("{0} Folder : {1} {2} {3}", MsgId, folder.ID, indent, folder.Name); | ||
952 | indent += "\t"; | ||
953 | } | ||
954 | |||
955 | // Start folder item | ||
956 | |||
957 | rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); | ||
958 | rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); | ||
959 | rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); | ||
960 | rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); | ||
961 | rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); | ||
962 | rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); | ||
963 | |||
964 | if (rdata.folders != null) | ||
965 | { | ||
966 | foreach (InventoryFolderBase f in rdata.folders) | ||
967 | { | ||
968 | if (f.ParentID == folder.ID) | ||
969 | { | ||
970 | formatInventory(rdata, f, indent); | ||
971 | } | ||
972 | } | ||
973 | } | ||
974 | |||
975 | if (rdata.items != null) | ||
976 | { | ||
977 | foreach (InventoryItemBase i in rdata.items) | ||
978 | { | ||
979 | if (i.Folder == folder.ID) | ||
980 | { | ||
981 | formatItem(rdata, i, indent); | ||
982 | } | ||
983 | } | ||
984 | } | ||
985 | |||
986 | // End folder item | ||
987 | |||
988 | rdata.writer.WriteEndElement(); | ||
989 | |||
990 | } | ||
991 | |||
992 | /// <summary> | ||
993 | /// This method generates XML that describes an instance of InventoryItemBase. | ||
994 | /// </summary> | ||
995 | |||
996 | private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) | ||
997 | { | ||
998 | |||
999 | Rest.Log.DebugFormat("{0} Item : {1} {2} {3}", MsgId, i.ID, indent, i.Name); | ||
1000 | |||
1001 | rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty); | ||
1002 | |||
1003 | rdata.writer.WriteAttributeString("name",String.Empty,i.Name); | ||
1004 | rdata.writer.WriteAttributeString("desc",String.Empty,i.Description); | ||
1005 | rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString()); | ||
1006 | rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString()); | ||
1007 | rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString()); | ||
1008 | rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString()); | ||
1009 | rdata.writer.WriteAttributeString("type",String.Empty,i.InvType.ToString()); | ||
1010 | rdata.writer.WriteAttributeString("assettype",String.Empty,i.AssetType.ToString()); | ||
1011 | rdata.writer.WriteAttributeString("groupowned",String.Empty,i.GroupOwned.ToString()); | ||
1012 | rdata.writer.WriteAttributeString("groupid",String.Empty,i.GroupID.ToString()); | ||
1013 | rdata.writer.WriteAttributeString("saletype",String.Empty,i.SaleType.ToString()); | ||
1014 | rdata.writer.WriteAttributeString("saleprice",String.Empty,i.SalePrice.ToString()); | ||
1015 | rdata.writer.WriteAttributeString("flags",String.Empty,i.Flags.ToString("X")); | ||
1016 | |||
1017 | rdata.writer.WriteStartElement(String.Empty,"Permissions",String.Empty); | ||
1018 | rdata.writer.WriteAttributeString("current",String.Empty,i.CurrentPermissions.ToString("X")); | ||
1019 | rdata.writer.WriteAttributeString("next",String.Empty,i.NextPermissions.ToString("X")); | ||
1020 | rdata.writer.WriteAttributeString("everyone",String.Empty,i.EveryOnePermissions.ToString("X")); | ||
1021 | rdata.writer.WriteAttributeString("base",String.Empty,i.BasePermissions.ToString("X")); | ||
1022 | rdata.writer.WriteEndElement(); | ||
1023 | |||
1024 | rdata.writer.WriteElementString("Asset",i.AssetID.ToString()); | ||
1025 | |||
1026 | rdata.writer.WriteEndElement(); | ||
1027 | |||
1028 | } | ||
1029 | |||
1030 | /// <summary> | ||
1031 | /// This method creates a "trashcan" folder to support folder and item | ||
1032 | /// deletions by this interface. The xisting trash folder is found and | ||
1033 | /// this folder is created within it. It is called "tmp" to indicate to | ||
1034 | /// the client that it is OK to delete this folder. The REST interface | ||
1035 | /// will recreate the folder on an as-required basis. | ||
1036 | /// If the trash can cannot be created, then by implication the request | ||
1037 | /// that required it cannot be completed, and it fails accordingly. | ||
1038 | /// </summary> | ||
1039 | |||
1040 | private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) | ||
1041 | { | ||
1042 | |||
1043 | InventoryFolderBase TrashCan = null; | ||
1044 | |||
1045 | foreach (InventoryFolderBase f in rdata.folders) | ||
1046 | { | ||
1047 | if (f.Name == "Trash") | ||
1048 | { | ||
1049 | foreach (InventoryFolderBase t in rdata.folders) | ||
1050 | { | ||
1051 | if (t.Name == "tmp") | ||
1052 | { | ||
1053 | TrashCan = t; | ||
1054 | } | ||
1055 | } | ||
1056 | if (TrashCan == null) | ||
1057 | { | ||
1058 | TrashCan = new InventoryFolderBase(); | ||
1059 | TrashCan.Name = "tmp"; | ||
1060 | TrashCan.ID = LLUUID.Random(); | ||
1061 | TrashCan.Version = 1; | ||
1062 | TrashCan.Type = (short) AssetType.TrashFolder; | ||
1063 | TrashCan.ParentID = f.ID; | ||
1064 | Rest.InventoryServices.AddFolder(TrashCan); | ||
1065 | } | ||
1066 | } | ||
1067 | } | ||
1068 | |||
1069 | if (TrashCan == null) | ||
1070 | { | ||
1071 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); | ||
1072 | rdata.Fail(Rest.HttpStatusCodeServerError, | ||
1073 | Rest.HttpStatusDescServerError); | ||
1074 | } | ||
1075 | |||
1076 | return TrashCan; | ||
1077 | |||
1078 | } | ||
1079 | |||
1080 | /// <summary> | ||
1081 | /// Make sure that an unchanged folder is not unnecessarily | ||
1082 | /// processed. | ||
1083 | /// </summary> | ||
1084 | |||
1085 | private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) | ||
1086 | { | ||
1087 | return ( newf.Name != oldf.Name | ||
1088 | || newf.ParentID != oldf.ParentID | ||
1089 | || newf.Owner != oldf.Owner | ||
1090 | || newf.Type != oldf.Type | ||
1091 | || newf.Version != oldf.Version | ||
1092 | ); | ||
1093 | } | ||
1094 | |||
1095 | /// <summary> | ||
1096 | /// Make sure that an unchanged item is not unnecessarily | ||
1097 | /// processed. | ||
1098 | /// </summary> | ||
1099 | |||
1100 | private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) | ||
1101 | { | ||
1102 | return ( newf.Name != oldf.Name | ||
1103 | || newf.Folder != oldf.Description | ||
1104 | || newf.Description != oldf.Description | ||
1105 | || newf.Owner != oldf.Owner | ||
1106 | || newf.Creator != oldf.Creator | ||
1107 | || newf.AssetID != oldf.AssetID | ||
1108 | || newf.GroupID != oldf.GroupID | ||
1109 | || newf.GroupOwned != oldf.GroupOwned | ||
1110 | || newf.InvType != oldf.InvType | ||
1111 | || newf.AssetType != oldf.AssetType | ||
1112 | ); | ||
1113 | } | ||
1114 | |||
1115 | /// <summary> | ||
1116 | /// This method is called by PUT and POST to create an XmlInventoryCollection | ||
1117 | /// instance that reflects the content of the entity supplied on the request. | ||
1118 | /// Any elements in the completed collection whose UUID is zero, are | ||
1119 | /// considered to be located relative to the end-point identified int he | ||
1120 | /// URI. In this way, an entire sub-tree can be conveyed in a single REST | ||
1121 | /// PUT or POST request. | ||
1122 | /// | ||
1123 | /// A new instance of XmlInventoryCollection is created and, if the request | ||
1124 | /// has an entity, it is more completely initialized. thus, if no entity was | ||
1125 | /// provided the collection is valid, but empty. | ||
1126 | /// | ||
1127 | /// The entity is then scanned and each tag is processed to produce the | ||
1128 | /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection | ||
1129 | /// will reflect the subtree described by the entity. | ||
1130 | /// | ||
1131 | /// This is a very flexible mechanism, the entity may contain arbitrary, | ||
1132 | /// discontiguous tree fragments, or may contain single element. The caller is | ||
1133 | /// responsible for integrating this collection (and ensuring that any | ||
1134 | /// missing parent IDs are resolved). | ||
1135 | /// </summary> | ||
1136 | |||
1137 | internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) | ||
1138 | { | ||
1139 | |||
1140 | Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); | ||
1141 | |||
1142 | XmlInventoryCollection ic = new XmlInventoryCollection(); | ||
1143 | |||
1144 | if (rdata.request.HasEntityBody) | ||
1145 | { | ||
1146 | |||
1147 | Rest.Log.DebugFormat("{0} Entity present", MsgId); | ||
1148 | |||
1149 | ic.init(rdata); | ||
1150 | |||
1151 | try | ||
1152 | { | ||
1153 | while (ic.xml.Read()) | ||
1154 | { | ||
1155 | switch (ic.xml.NodeType) | ||
1156 | { | ||
1157 | case XmlNodeType.Element : | ||
1158 | Rest.Log.DebugFormat("{0} StartElement: <{1}>", | ||
1159 | MsgId, ic.xml.Name); | ||
1160 | switch (ic.xml.Name) | ||
1161 | { | ||
1162 | case "Folder" : | ||
1163 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1164 | MsgId, ic.xml.Name); | ||
1165 | CollectFolder(ic); | ||
1166 | break; | ||
1167 | case "Item" : | ||
1168 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1169 | MsgId, ic.xml.Name); | ||
1170 | CollectItem(ic); | ||
1171 | break; | ||
1172 | case "Asset" : | ||
1173 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1174 | MsgId, ic.xml.Name); | ||
1175 | CollectAsset(ic); | ||
1176 | break; | ||
1177 | case "Permissions" : | ||
1178 | Rest.Log.DebugFormat("{0} Processing {1} element", | ||
1179 | MsgId, ic.xml.Name); | ||
1180 | CollectPermissions(ic); | ||
1181 | break; | ||
1182 | default : | ||
1183 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1184 | MsgId, ic.xml.Name); | ||
1185 | break; | ||
1186 | } | ||
1187 | // This stinks, but the ReadElement call above not only reads | ||
1188 | // the imbedded data, but also consumes the end tag for Asset | ||
1189 | // and moves the element pointer on to the containing Item's | ||
1190 | // element-end, however, if there was a permissions element | ||
1191 | // following, it would get us to the start of that.. | ||
1192 | if (ic.xml.NodeType == XmlNodeType.EndElement && | ||
1193 | ic.xml.Name == "Item") | ||
1194 | { | ||
1195 | Validate(ic); | ||
1196 | } | ||
1197 | break; | ||
1198 | case XmlNodeType.EndElement : | ||
1199 | switch (ic.xml.Name) | ||
1200 | { | ||
1201 | case "Folder" : | ||
1202 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1203 | MsgId, ic.xml.Name); | ||
1204 | ic.Pop(); | ||
1205 | break; | ||
1206 | case "Item" : | ||
1207 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1208 | MsgId, ic.xml.Name); | ||
1209 | Validate(ic); | ||
1210 | break; | ||
1211 | case "Asset" : | ||
1212 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1213 | MsgId, ic.xml.Name); | ||
1214 | break; | ||
1215 | case "Permissions" : | ||
1216 | Rest.Log.DebugFormat("{0} Completing {1} element", | ||
1217 | MsgId, ic.xml.Name); | ||
1218 | break; | ||
1219 | default : | ||
1220 | Rest.Log.DebugFormat("{0} Ignoring {1} element", | ||
1221 | MsgId, ic.xml.Name); | ||
1222 | break; | ||
1223 | } | ||
1224 | break; | ||
1225 | default : | ||
1226 | Rest.Log.DebugFormat("{0} [0] Ignoring: <{1}>:<2>", | ||
1227 | MsgId, ic.xml.NodeType, ic.xml.Value); | ||
1228 | break; | ||
1229 | } | ||
1230 | } | ||
1231 | } | ||
1232 | catch (XmlException e) | ||
1233 | { | ||
1234 | Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); | ||
1235 | throw e; | ||
1236 | } | ||
1237 | catch (Exception e) | ||
1238 | { | ||
1239 | Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); | ||
1240 | throw e; | ||
1241 | } | ||
1242 | |||
1243 | } | ||
1244 | else | ||
1245 | { | ||
1246 | Rest.Log.DebugFormat("{0} Entity absent", MsgId); | ||
1247 | } | ||
1248 | |||
1249 | if (Rest.DEBUG) | ||
1250 | { | ||
1251 | Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); | ||
1252 | Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); | ||
1253 | Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); | ||
1254 | Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); | ||
1255 | } | ||
1256 | |||
1257 | return ic; | ||
1258 | |||
1259 | } | ||
1260 | |||
1261 | /// <summary> | ||
1262 | /// This method creates an inventory Folder from the | ||
1263 | /// information supplied in the request's entity. | ||
1264 | /// A folder instance is created and initialized to reflect | ||
1265 | /// default values. These values are then overridden | ||
1266 | /// by information supplied in the entity. | ||
1267 | /// If context was not explicitly provided, then the | ||
1268 | /// appropriate ID values are determined. | ||
1269 | /// </summary> | ||
1270 | |||
1271 | private void CollectFolder(XmlInventoryCollection ic) | ||
1272 | { | ||
1273 | |||
1274 | Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); | ||
1275 | |||
1276 | InventoryFolderBase result = new InventoryFolderBase(); | ||
1277 | |||
1278 | // Default values | ||
1279 | |||
1280 | result.Name = String.Empty; | ||
1281 | result.ID = LLUUID.Zero; | ||
1282 | result.Owner = ic.UserID; | ||
1283 | result.ParentID = LLUUID.Zero; // Context | ||
1284 | result.Type = (short) AssetType.Folder; | ||
1285 | result.Version = 1; | ||
1286 | |||
1287 | if (ic.xml.HasAttributes) | ||
1288 | { | ||
1289 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1290 | { | ||
1291 | ic.xml.MoveToAttribute(i); | ||
1292 | switch (ic.xml.Name) | ||
1293 | { | ||
1294 | case "name" : | ||
1295 | result.Name = ic.xml.Value; | ||
1296 | break; | ||
1297 | case "uuid" : | ||
1298 | result.ID = new LLUUID(ic.xml.Value); | ||
1299 | break; | ||
1300 | case "parent" : | ||
1301 | result.ParentID = new LLUUID(ic.xml.Value); | ||
1302 | break; | ||
1303 | case "owner" : | ||
1304 | result.Owner = new LLUUID(ic.xml.Value); | ||
1305 | break; | ||
1306 | case "type" : | ||
1307 | result.Type = Int16.Parse(ic.xml.Value); | ||
1308 | break; | ||
1309 | case "version" : | ||
1310 | result.Version = UInt16.Parse(ic.xml.Value); | ||
1311 | break; | ||
1312 | default : | ||
1313 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", | ||
1314 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1315 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1316 | Rest.HttpStatusDescBadRequest); | ||
1317 | break; | ||
1318 | } | ||
1319 | } | ||
1320 | } | ||
1321 | |||
1322 | ic.xml.MoveToElement(); | ||
1323 | |||
1324 | // The client is relying upon the reconstitution process | ||
1325 | // to determine the parent's UUID based upon context. This | ||
1326 | // is necessary where a new folder may have been | ||
1327 | // introduced. | ||
1328 | |||
1329 | if (result.ParentID == LLUUID.Zero) | ||
1330 | { | ||
1331 | result.ParentID = ic.Parent(); | ||
1332 | } | ||
1333 | else | ||
1334 | { | ||
1335 | |||
1336 | bool found = false; | ||
1337 | |||
1338 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
1339 | { | ||
1340 | if ( parent.ID == result.ParentID ) | ||
1341 | { | ||
1342 | found = true; | ||
1343 | break; | ||
1344 | } | ||
1345 | } | ||
1346 | |||
1347 | if (!found) | ||
1348 | { | ||
1349 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", | ||
1350 | MsgId, ic.Item.Folder, result.ID); | ||
1351 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1352 | Rest.HttpStatusDescBadRequest); | ||
1353 | } | ||
1354 | |||
1355 | } | ||
1356 | |||
1357 | // This is a new folder, so no existing UUID is available | ||
1358 | // or appropriate | ||
1359 | |||
1360 | if (result.ID == LLUUID.Zero) | ||
1361 | { | ||
1362 | result.ID = LLUUID.Random(); | ||
1363 | } | ||
1364 | |||
1365 | // Treat this as a new context. Any other information is | ||
1366 | // obsolete as a consequence. | ||
1367 | |||
1368 | ic.Push(result); | ||
1369 | |||
1370 | } | ||
1371 | |||
1372 | /// <summary> | ||
1373 | /// This method is called to handle the construction of an Item | ||
1374 | /// instance from the supplied request entity. It is called | ||
1375 | /// whenever an Item start tag is detected. | ||
1376 | /// An instance of an Item is created and initialized to default | ||
1377 | /// values. These values are then overridden from values supplied | ||
1378 | /// as attributes to the Item element. | ||
1379 | /// This item is then stored in the XmlInventoryCollection and | ||
1380 | /// will be verified by Validate. | ||
1381 | /// All context is reset whenever the effective folder changes | ||
1382 | /// or an item is successfully validated. | ||
1383 | /// </summary> | ||
1384 | |||
1385 | private void CollectItem(XmlInventoryCollection ic) | ||
1386 | { | ||
1387 | |||
1388 | Rest.Log.DebugFormat("{0} Interpret item element", MsgId); | ||
1389 | |||
1390 | InventoryItemBase result = new InventoryItemBase(); | ||
1391 | |||
1392 | result.Name = String.Empty; | ||
1393 | result.Description = String.Empty; | ||
1394 | result.ID = LLUUID.Zero; | ||
1395 | result.Folder = LLUUID.Zero; | ||
1396 | result.Owner = ic.UserID; | ||
1397 | result.Creator = ic.UserID; | ||
1398 | result.AssetID = LLUUID.Zero; | ||
1399 | result.GroupID = LLUUID.Zero; | ||
1400 | result.GroupOwned = false; | ||
1401 | result.InvType = (int) InventoryType.Unknown; | ||
1402 | result.AssetType = (int) AssetType.Unknown; | ||
1403 | |||
1404 | if (ic.xml.HasAttributes) | ||
1405 | { | ||
1406 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1407 | { | ||
1408 | |||
1409 | ic.xml.MoveToAttribute(i); | ||
1410 | |||
1411 | switch (ic.xml.Name) | ||
1412 | { | ||
1413 | case "name" : | ||
1414 | result.Name = ic.xml.Value; | ||
1415 | break; | ||
1416 | case "desc" : | ||
1417 | result.Description = ic.xml.Value; | ||
1418 | break; | ||
1419 | case "uuid" : | ||
1420 | result.ID = new LLUUID(ic.xml.Value); | ||
1421 | break; | ||
1422 | case "folder" : | ||
1423 | result.Folder = new LLUUID(ic.xml.Value); | ||
1424 | break; | ||
1425 | case "owner" : | ||
1426 | result.Owner = new LLUUID(ic.xml.Value); | ||
1427 | break; | ||
1428 | case "invtype" : | ||
1429 | result.InvType = Int32.Parse(ic.xml.Value); | ||
1430 | break; | ||
1431 | case "creator" : | ||
1432 | result.Creator = new LLUUID(ic.xml.Value); | ||
1433 | break; | ||
1434 | case "assettype" : | ||
1435 | result.AssetType = Int32.Parse(ic.xml.Value); | ||
1436 | break; | ||
1437 | case "groupowned" : | ||
1438 | result.GroupOwned = Boolean.Parse(ic.xml.Value); | ||
1439 | break; | ||
1440 | case "groupid" : | ||
1441 | result.GroupID = new LLUUID(ic.xml.Value); | ||
1442 | break; | ||
1443 | case "flags" : | ||
1444 | result.Flags = UInt32.Parse(ic.xml.Value); | ||
1445 | break; | ||
1446 | case "creationdate" : | ||
1447 | result.CreationDate = Int32.Parse(ic.xml.Value); | ||
1448 | break; | ||
1449 | case "saletype" : | ||
1450 | result.SaleType = Byte.Parse(ic.xml.Value); | ||
1451 | break; | ||
1452 | case "saleprice" : | ||
1453 | result.SalePrice = Int32.Parse(ic.xml.Value); | ||
1454 | break; | ||
1455 | |||
1456 | default : | ||
1457 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", | ||
1458 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1459 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1460 | Rest.HttpStatusDescBadRequest); | ||
1461 | break; | ||
1462 | } | ||
1463 | } | ||
1464 | } | ||
1465 | |||
1466 | ic.xml.MoveToElement(); | ||
1467 | |||
1468 | ic.Push(result); | ||
1469 | |||
1470 | } | ||
1471 | |||
1472 | /// <summary> | ||
1473 | /// This method assembles an asset instance from the | ||
1474 | /// information supplied in the request's entity. It is | ||
1475 | /// called as a result of detecting a start tag for a | ||
1476 | /// type of Asset. | ||
1477 | /// The information is collected locally, and an asset | ||
1478 | /// instance is created only if the basic XML parsing | ||
1479 | /// completes successfully. | ||
1480 | /// Default values for all parts of the asset are | ||
1481 | /// established before overriding them from the supplied | ||
1482 | /// XML. | ||
1483 | /// If an asset has inline=true as an attribute, then | ||
1484 | /// the element contains the data representing the | ||
1485 | /// asset. This is saved as the data component. | ||
1486 | /// inline=false means that the element's payload is | ||
1487 | /// simply the UUID of the asset referenced by the | ||
1488 | /// item being constructed. | ||
1489 | /// An asset, if created is stored in the | ||
1490 | /// XmlInventoryCollection | ||
1491 | /// </summary> | ||
1492 | |||
1493 | private void CollectAsset(XmlInventoryCollection ic) | ||
1494 | { | ||
1495 | |||
1496 | Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); | ||
1497 | |||
1498 | AssetBase asset = null; | ||
1499 | |||
1500 | string name = String.Empty; | ||
1501 | string desc = String.Empty; | ||
1502 | sbyte type = (sbyte) AssetType.Unknown; | ||
1503 | sbyte itype = (sbyte) AssetType.Unknown; | ||
1504 | bool temp = false; | ||
1505 | bool local = false; | ||
1506 | |||
1507 | // This is not a persistent attribute | ||
1508 | bool inline = true; | ||
1509 | |||
1510 | LLUUID uuid = LLUUID.Zero; | ||
1511 | |||
1512 | // Attribute is optional | ||
1513 | if (ic.xml.HasAttributes) | ||
1514 | { | ||
1515 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1516 | { | ||
1517 | ic.xml.MoveToAttribute(i); | ||
1518 | switch (ic.xml.Name) | ||
1519 | { | ||
1520 | |||
1521 | case "name" : | ||
1522 | name = ic.xml.Value; | ||
1523 | break; | ||
1524 | |||
1525 | case "type" : | ||
1526 | type = SByte.Parse(ic.xml.Value); | ||
1527 | break; | ||
1528 | |||
1529 | case "description" : | ||
1530 | desc = ic.xml.Value; | ||
1531 | break; | ||
1532 | |||
1533 | case "temporary" : | ||
1534 | temp = Boolean.Parse(ic.xml.Value); | ||
1535 | break; | ||
1536 | |||
1537 | case "invtype" : | ||
1538 | itype = SByte.Parse(ic.xml.Value); | ||
1539 | break; | ||
1540 | |||
1541 | case "uuid" : | ||
1542 | uuid = new LLUUID(ic.xml.Value); | ||
1543 | break; | ||
1544 | |||
1545 | case "inline" : | ||
1546 | inline = Boolean.Parse(ic.xml.Value); | ||
1547 | break; | ||
1548 | |||
1549 | case "local" : | ||
1550 | local = Boolean.Parse(ic.xml.Value); | ||
1551 | break; | ||
1552 | |||
1553 | default : | ||
1554 | Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", | ||
1555 | MsgId, ic.xml.Name, ic.xml.Value); | ||
1556 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1557 | Rest.HttpStatusDescBadRequest); | ||
1558 | break; | ||
1559 | } | ||
1560 | } | ||
1561 | } | ||
1562 | |||
1563 | ic.xml.MoveToElement(); | ||
1564 | |||
1565 | // If this is a reference to an existing asset, just store the | ||
1566 | // asset ID into the item. | ||
1567 | |||
1568 | if (!inline) | ||
1569 | { | ||
1570 | if (ic.Item != null) | ||
1571 | { | ||
1572 | ic.Item.AssetID = new LLUUID(ic.xml.ReadElementContentAsString()); | ||
1573 | Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); | ||
1574 | } | ||
1575 | else | ||
1576 | { | ||
1577 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); | ||
1578 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1579 | Rest.HttpStatusDescBadRequest); | ||
1580 | } | ||
1581 | } | ||
1582 | |||
1583 | // Otherwise, generate an asset ID, store that into the item, and | ||
1584 | // create an entry in the asset list for the inlined asset. But | ||
1585 | // only if the size is non-zero. | ||
1586 | |||
1587 | else | ||
1588 | { | ||
1589 | |||
1590 | string b64string = null; | ||
1591 | |||
1592 | // Generate a UUID of none were given, and generally none should | ||
1593 | // be. Ever. | ||
1594 | |||
1595 | if (uuid == LLUUID.Zero) | ||
1596 | { | ||
1597 | uuid = LLUUID.Random(); | ||
1598 | } | ||
1599 | |||
1600 | // Create AssetBase entity to hold the inlined asset | ||
1601 | |||
1602 | asset = new AssetBase(uuid, name); | ||
1603 | |||
1604 | asset.Description = desc; | ||
1605 | asset.Type = type; // type == 0 == texture | ||
1606 | asset.InvType = itype; | ||
1607 | asset.Local = local; | ||
1608 | asset.Temporary = temp; | ||
1609 | |||
1610 | b64string = ic.xml.ReadElementContentAsString(); | ||
1611 | |||
1612 | Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); | ||
1613 | Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, | ||
1614 | b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); | ||
1615 | |||
1616 | asset.Data = Convert.FromBase64String(b64string); | ||
1617 | |||
1618 | // Ensure the asset always has some kind of data component | ||
1619 | |||
1620 | if (asset.Data == null) | ||
1621 | { | ||
1622 | asset.Data = new byte[1]; | ||
1623 | } | ||
1624 | |||
1625 | // If this is in the context of an item, establish | ||
1626 | // a link with the item in context. | ||
1627 | |||
1628 | if (ic.Item != null && ic.Item.AssetID == LLUUID.Zero) | ||
1629 | { | ||
1630 | ic.Item.AssetID = uuid; | ||
1631 | } | ||
1632 | |||
1633 | } | ||
1634 | |||
1635 | ic.Push(asset); | ||
1636 | |||
1637 | } | ||
1638 | |||
1639 | /// <summary> | ||
1640 | /// Store any permissions information provided by the request. | ||
1641 | /// This overrides the default permissions set when the | ||
1642 | /// XmlInventoryCollection object was created. | ||
1643 | /// </summary> | ||
1644 | |||
1645 | private void CollectPermissions(XmlInventoryCollection ic) | ||
1646 | { | ||
1647 | |||
1648 | if (ic.xml.HasAttributes) | ||
1649 | { | ||
1650 | for (int i = 0; i < ic.xml.AttributeCount; i++) | ||
1651 | { | ||
1652 | ic.xml.MoveToAttribute(i); | ||
1653 | switch (ic.xml.Name) | ||
1654 | { | ||
1655 | case "current" : | ||
1656 | ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber); | ||
1657 | break; | ||
1658 | case "next" : | ||
1659 | ic.NextPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber); | ||
1660 | break; | ||
1661 | case "everyone" : | ||
1662 | ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber); | ||
1663 | break; | ||
1664 | case "base" : | ||
1665 | ic.BasePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber); | ||
1666 | break; | ||
1667 | default : | ||
1668 | Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", | ||
1669 | MsgId,ic.xml.Name, ic.xml.Value); | ||
1670 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1671 | Rest.HttpStatusDescBadRequest); | ||
1672 | break; | ||
1673 | } | ||
1674 | } | ||
1675 | } | ||
1676 | |||
1677 | ic.xml.MoveToElement(); | ||
1678 | |||
1679 | } | ||
1680 | |||
1681 | /// <summary> | ||
1682 | /// This method is called whenever an Item has been successfully | ||
1683 | /// reconstituted from the request's entity. | ||
1684 | /// It uses the information curren tin the XmlInventoryCollection | ||
1685 | /// to complete the item's specification, including any implied | ||
1686 | /// context and asset associations. | ||
1687 | /// It fails the request if any necessary item or asset information | ||
1688 | /// is missing. | ||
1689 | /// </summary> | ||
1690 | |||
1691 | private void Validate(XmlInventoryCollection ic) | ||
1692 | { | ||
1693 | |||
1694 | // There really should be an item present if we've | ||
1695 | // called validate. So fail if there is not. | ||
1696 | |||
1697 | if (ic.Item == null) | ||
1698 | { | ||
1699 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); | ||
1700 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1701 | Rest.HttpStatusDescBadRequest); | ||
1702 | } | ||
1703 | |||
1704 | // Every item is required to have a name (via REST anyway) | ||
1705 | |||
1706 | if (ic.Item.Name == String.Empty) | ||
1707 | { | ||
1708 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); | ||
1709 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1710 | Rest.HttpStatusDescBadRequest); | ||
1711 | } | ||
1712 | |||
1713 | // An item MUST have an asset ID. AssetID should never be zero | ||
1714 | // here. It should always get set from the information stored | ||
1715 | // when the Asset element was processed. | ||
1716 | |||
1717 | if (ic.Item.AssetID == LLUUID.Zero) | ||
1718 | { | ||
1719 | |||
1720 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); | ||
1721 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); | ||
1722 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1723 | Rest.HttpStatusDescBadRequest); | ||
1724 | |||
1725 | } | ||
1726 | |||
1727 | // If the item is new, then assign it an ID | ||
1728 | |||
1729 | if (ic.Item.ID == LLUUID.Zero) | ||
1730 | { | ||
1731 | ic.Item.ID = LLUUID.Random(); | ||
1732 | } | ||
1733 | |||
1734 | // If the context is being implied, obtain the current | ||
1735 | // folder item's ID. If it was specified explicitly, make | ||
1736 | // sure that theparent folder exists. | ||
1737 | |||
1738 | if (ic.Item.Folder == LLUUID.Zero) | ||
1739 | { | ||
1740 | ic.Item.Folder = ic.Parent(); | ||
1741 | } | ||
1742 | else | ||
1743 | { | ||
1744 | |||
1745 | bool found = false; | ||
1746 | |||
1747 | foreach (InventoryFolderBase parent in ic.rdata.folders) | ||
1748 | { | ||
1749 | if ( parent.ID == ic.Item.Folder ) | ||
1750 | { | ||
1751 | found = true; | ||
1752 | break; | ||
1753 | } | ||
1754 | } | ||
1755 | |||
1756 | if (!found) | ||
1757 | { | ||
1758 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", | ||
1759 | MsgId, ic.Item.Folder, ic.Item.ID); | ||
1760 | ic.Fail(Rest.HttpStatusCodeBadRequest, | ||
1761 | Rest.HttpStatusDescBadRequest); | ||
1762 | } | ||
1763 | |||
1764 | } | ||
1765 | |||
1766 | // If this is an inline asset being constructed in the context | ||
1767 | // of a new Item, then use the itm's name here too. | ||
1768 | |||
1769 | if (ic.Asset != null) | ||
1770 | { | ||
1771 | if (ic.Asset.Name == String.Empty) | ||
1772 | ic.Asset.Name = ic.Item.Name; | ||
1773 | if (ic.Asset.Description == String.Empty) | ||
1774 | ic.Asset.Description = ic.Item.Description; | ||
1775 | } | ||
1776 | |||
1777 | // Assign permissions | ||
1778 | |||
1779 | ic.Item.CurrentPermissions = ic.CurrentPermissions; | ||
1780 | ic.Item.EveryOnePermissions = ic.EveryOnePermissions; | ||
1781 | ic.Item.BasePermissions = ic.BasePermissions; | ||
1782 | ic.Item.NextPermissions = ic.NextPermissions; | ||
1783 | |||
1784 | // If no type was specified for this item, we can attempt to | ||
1785 | // infer something from the file type maybe. This is NOT as | ||
1786 | // good as having type be specified in the XML. | ||
1787 | |||
1788 | if (ic.Item.AssetType == (int) AssetType.Unknown || | ||
1789 | ic.Item.InvType == (int) AssetType.Unknown) | ||
1790 | { | ||
1791 | |||
1792 | Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); | ||
1793 | |||
1794 | string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); | ||
1795 | |||
1796 | if (Rest.DEBUG) | ||
1797 | { | ||
1798 | for (int i = 0; i < parts.Length; i++) | ||
1799 | { | ||
1800 | Rest.Log.DebugFormat("{0} Name part {1} : {2}", | ||
1801 | MsgId, i, parts[i]); | ||
1802 | } | ||
1803 | } | ||
1804 | |||
1805 | // If the associated item name is multi-part, then maybe | ||
1806 | // the last part will indicate the item type - if we're | ||
1807 | // lucky. | ||
1808 | |||
1809 | if (parts.Length > 1) | ||
1810 | { | ||
1811 | Rest.Log.DebugFormat("{0} File type is {1}", | ||
1812 | MsgId, parts[parts.Length - 1]); | ||
1813 | switch (parts[parts.Length - 1]) | ||
1814 | { | ||
1815 | case "jpeg2000" : | ||
1816 | case "jpeg-2000" : | ||
1817 | case "jpg2000" : | ||
1818 | case "jpg-2000" : | ||
1819 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
1820 | MsgId, parts[parts.Length-1]); | ||
1821 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
1822 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
1823 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
1824 | ic.Item.InvType = (int) AssetType.ImageJPEG; | ||
1825 | break; | ||
1826 | case "jpg" : | ||
1827 | case "jpeg" : | ||
1828 | Rest.Log.DebugFormat("{0} Type {1} inferred", | ||
1829 | MsgId, parts[parts.Length - 1]); | ||
1830 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
1831 | ic.Item.AssetType = (int) AssetType.ImageJPEG; | ||
1832 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
1833 | ic.Item.InvType = (int) AssetType.ImageJPEG; | ||
1834 | break; | ||
1835 | default : | ||
1836 | Rest.Log.DebugFormat("{0} Type was not inferred", MsgId); | ||
1837 | break; | ||
1838 | } | ||
1839 | } | ||
1840 | } | ||
1841 | |||
1842 | ic.reset(); | ||
1843 | |||
1844 | } | ||
1845 | |||
1846 | #region Inventory RequestData extension | ||
1847 | |||
1848 | internal class InventoryRequestData : RequestData | ||
1849 | { | ||
1850 | |||
1851 | /// <summary> | ||
1852 | /// These are the inventory specific request/response state | ||
1853 | /// extensions. | ||
1854 | /// </summary> | ||
1855 | |||
1856 | internal bool HaveInventory = false; | ||
1857 | internal ICollection<InventoryFolderImpl> folders = null; | ||
1858 | internal ICollection<InventoryItemBase> items = null; | ||
1859 | internal UserProfileData userProfile = null; | ||
1860 | internal InventoryFolderBase root = null; | ||
1861 | |||
1862 | internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) | ||
1863 | : base(request, response, prefix) | ||
1864 | { | ||
1865 | } | ||
1866 | |||
1867 | /// <summary> | ||
1868 | /// This is the callback method required by inventory services. The | ||
1869 | /// requestor issues an inventory request and then blocks until this | ||
1870 | /// method signals the monitor. | ||
1871 | /// </summary> | ||
1872 | |||
1873 | internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items) | ||
1874 | { | ||
1875 | Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); | ||
1876 | this.folders = folders; | ||
1877 | this.items = items; | ||
1878 | this.HaveInventory = true; | ||
1879 | lock(this) | ||
1880 | { | ||
1881 | Monitor.Pulse(this); | ||
1882 | } | ||
1883 | } | ||
1884 | |||
1885 | } | ||
1886 | |||
1887 | #endregion Inventory RequestData extension | ||
1888 | |||
1889 | /// <summary> | ||
1890 | /// This class is used to record and manage the hierarchy | ||
1891 | /// constructed from the entity supplied in the request for | ||
1892 | /// PUT and POST. | ||
1893 | /// </summary> | ||
1894 | |||
1895 | internal class XmlInventoryCollection : InventoryCollection | ||
1896 | { | ||
1897 | |||
1898 | internal InventoryRequestData rdata; | ||
1899 | private Stack<InventoryFolderBase> stk; | ||
1900 | |||
1901 | internal List<AssetBase> Assets; | ||
1902 | |||
1903 | internal InventoryItemBase Item; | ||
1904 | internal AssetBase Asset; | ||
1905 | internal XmlReader xml; | ||
1906 | |||
1907 | internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF; | ||
1908 | internal /*static*/ const uint DefaultNext = 0x82000; | ||
1909 | internal /*static*/ const uint DefaultBase = 0x7FFFFFFF; | ||
1910 | internal /*static*/ const uint DefaultEveryOne = 0x0; | ||
1911 | |||
1912 | internal uint CurrentPermissions = 0x00; | ||
1913 | internal uint NextPermissions = 0x00; | ||
1914 | internal uint BasePermissions = 0x00; | ||
1915 | internal uint EveryOnePermissions = 0x00; | ||
1916 | |||
1917 | internal XmlInventoryCollection() | ||
1918 | { | ||
1919 | Folders = new List<InventoryFolderBase>(); | ||
1920 | Items = new List<InventoryItemBase>(); | ||
1921 | Assets = new List<AssetBase>(); | ||
1922 | } | ||
1923 | |||
1924 | internal void init(InventoryRequestData p_rdata) | ||
1925 | { | ||
1926 | rdata = p_rdata; | ||
1927 | UserID = rdata.uuid; | ||
1928 | stk = new Stack<InventoryFolderBase>(); | ||
1929 | rdata.initXmlReader(); | ||
1930 | xml = rdata.reader; | ||
1931 | initPermissions(); | ||
1932 | } | ||
1933 | |||
1934 | internal void initPermissions() | ||
1935 | { | ||
1936 | CurrentPermissions = DefaultCurrent; | ||
1937 | NextPermissions = DefaultNext; | ||
1938 | BasePermissions = DefaultBase; | ||
1939 | EveryOnePermissions = DefaultEveryOne; | ||
1940 | } | ||
1941 | |||
1942 | internal LLUUID Parent() | ||
1943 | { | ||
1944 | if (stk.Count != 0) | ||
1945 | { | ||
1946 | return stk.Peek().ID; | ||
1947 | } | ||
1948 | else | ||
1949 | { | ||
1950 | return LLUUID.Zero; | ||
1951 | } | ||
1952 | } | ||
1953 | |||
1954 | internal void Push(InventoryFolderBase folder) | ||
1955 | { | ||
1956 | stk.Push(folder); | ||
1957 | Folders.Add(folder); | ||
1958 | reset(); | ||
1959 | } | ||
1960 | |||
1961 | internal void Push(InventoryItemBase item) | ||
1962 | { | ||
1963 | Item = item; | ||
1964 | Items.Add(item); | ||
1965 | } | ||
1966 | |||
1967 | internal void Push(AssetBase asset) | ||
1968 | { | ||
1969 | Asset = asset; | ||
1970 | Assets.Add(asset); | ||
1971 | } | ||
1972 | |||
1973 | internal void Pop() | ||
1974 | { | ||
1975 | stk.Pop(); | ||
1976 | reset(); | ||
1977 | } | ||
1978 | |||
1979 | internal void reset() | ||
1980 | { | ||
1981 | Item = null; | ||
1982 | Asset = null; | ||
1983 | initPermissions(); | ||
1984 | } | ||
1985 | |||
1986 | internal void Fail(int code, string desc) | ||
1987 | { | ||
1988 | rdata.Fail(code, desc); | ||
1989 | } | ||
1990 | |||
1991 | } | ||
1992 | } | ||
1993 | } | ||