aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
diff options
context:
space:
mode:
authorDr Scofield2008-07-02 09:02:30 +0000
committerDr Scofield2008-07-02 09:02:30 +0000
commitd40bea4a8e09be1f8e87cf41405aaa60fa8826cb (patch)
tree91ef9356d8b284ac6fa5f0d588fedebe723b69ad /OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
parentMantis#1643. Thank you Melanie for a patch that: (diff)
downloadopensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.zip
opensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.gz
opensim-SC_OLD-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.bz2
opensim-SC_OLD-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 '')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs547
1 files changed, 547 insertions, 0 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
new file mode 100644
index 0000000..0a0bf3f
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
@@ -0,0 +1,547 @@
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
29using System;
30using System.Collections.Generic;
31using System.Reflection;
32using OpenSim.Framework;
33using OpenSim.Framework.Servers;
34using OpenSim.ApplicationPlugins.Rest;
35using Mono.Addins;
36
37[assembly : Addin]
38[assembly : AddinDependency("OpenSim", "0.5")]
39
40namespace OpenSim.ApplicationPlugins.Rest.Inventory
41{
42
43 [Extension("/OpenSim/Startup")]
44
45 public class RestHandler : RestPlugin, IHttpAgentHandler
46 {
47
48 #region local static state
49
50 /// <summary>
51 /// This static initializer scans the assembly for classes that
52 /// export the IRest interface and builds a list of them. These
53 /// are later activated by the handler. To add a new handler it
54 /// is only necessary to create a new services class that implements
55 /// the IRest interface, and recompile the handler. This gives
56 /// all of the build-time flexibility of a modular approach
57 /// while not introducing yet-another module loader. Note that
58 /// multiple assembles can still be built, each with its own set
59 /// of handlers.
60 /// </summary>
61
62 private static bool handlersLoaded = false;
63 private static List<Type> classes = new List<Type>();
64 private static List<IRest> handlers = new List<IRest>();
65 private static Type[] parms = new Type[1];
66 private static Object[] args = new Object[1];
67
68 static RestHandler()
69 {
70 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
71 foreach (Module m in mods)
72 {
73 Type[] types = m.GetTypes();
74 foreach (Type t in types)
75 {
76 if (t.GetInterface("IRest") != null)
77 {
78 classes.Add(t);
79 }
80 }
81 }
82 }
83
84 #endregion local static state
85
86 #region local instance state
87
88 /// <remarks>
89 /// The handler delegate is not noteworthy. The allocator allows
90 /// a given handler to optionally subclass the base RequestData
91 /// structure to carry any locally required per-request state
92 /// needed.
93 /// </remarks>
94 internal delegate void RestMethodHandler(RequestData rdata);
95 internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
96
97 // Handler tables: both stream and REST are supported
98
99 internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
100 internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
101 internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
102
103 /// <summary>
104 /// This routine loads all of the handlers discovered during
105 /// instance initialization. Each handler is responsible for
106 /// registering itself with this handler.
107 /// I was not able to make this code work in a constructor.
108 /// </summary>
109 private void LoadHandlers()
110 {
111 lock(handlers)
112 {
113 if (!handlersLoaded)
114 {
115 parms[0] = this.GetType();
116 args[0] = this;
117
118 ConstructorInfo ci;
119 Object ht;
120
121 foreach (Type t in classes)
122 {
123 ci = t.GetConstructor(parms);
124 ht = ci.Invoke(args);
125 handlers.Add((IRest)ht);
126 }
127 handlersLoaded = true;
128 }
129 }
130 }
131
132 #endregion local instance state
133
134 #region overriding properties
135
136 // Used to differentiate the message header.
137
138 public override string Name
139 {
140 get { return "HANDLER"; }
141 }
142
143 // Used to partition the configuration space.
144
145 public override string ConfigName
146 {
147 get { return "RestHandler"; }
148 }
149
150 // We have to rename these because we want
151 // to be able to share the values with other
152 // classes in our assembly and the base
153 // names are protected.
154
155 internal string MsgId
156 {
157 get { return base.MsgID; }
158 }
159
160 internal string RequestId
161 {
162 get { return base.RequestID; }
163 }
164
165 #endregion overriding properties
166
167 #region overriding methods
168
169 /// <summary>
170 /// This method is called by OpenSimMain immediately after loading the
171 /// plugin and after basic server setup, but before running any server commands.
172 /// </summary>
173 /// <remarks>
174 /// Note that entries MUST be added to the active configuration files before
175 /// the plugin can be enabled.
176 /// </remarks>
177 public override void Initialise(OpenSimBase openSim)
178 {
179 try
180 {
181
182 /// <remarks>
183 /// This plugin will only be enabled if the broader
184 /// REST plugin mechanism is enabled.
185 /// </remarks>
186
187 Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID);
188
189 base.Initialise(openSim);
190
191 if (!IsEnabled)
192 {
193 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID);
194 return;
195 }
196
197 Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgID);
198
199 /// <remarks>
200 /// These are stored in static variables to make
201 /// them easy to reach from anywhere in the assembly.
202 /// </remarks>
203
204 Rest.main = openSim;
205 Rest.Plugin = this;
206 Rest.Comms = App.CommunicationsManager;
207 Rest.UserServices = Rest.Comms.UserService;
208 Rest.InventoryServices = Rest.Comms.InventoryService;
209 Rest.AssetServices = Rest.Comms.AssetCache;
210 Rest.Config = Config;
211 Rest.Prefix = Prefix;
212 Rest.GodKey = GodKey;
213
214 Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true);
215 Rest.Secure = Rest.Config.GetBoolean("secured",true);
216 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
217 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
218 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
219 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
220
221 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
222 (Rest.Authenticate ? "" : "not "));
223
224 Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
225 (Rest.Authenticate ? "" : "not "));
226
227 Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
228 (Rest.ExtendedEscape ? "" : "not "));
229
230 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
231 (Rest.DumpAsset ? "" : "not "));
232
233 if (Rest.DumpAsset)
234 {
235 Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId,
236 Rest.DumpLineSize);
237 }
238
239 // Load all of the handlers present in the
240 // assembly
241
242 // In principle, as we're an application plug-in,
243 // most of what needs to be done could be done using
244 // static resources, however the Open Sim plug-in
245 // model makes this an instance, so that's what we
246 // need to be.
247 // There is only one Communications manager per
248 // server, and by inference, only one each of the
249 // user, asset, and inventory servers. So we can cache
250 // those using a static initializer.
251 // We move all of this processing off to another
252 // services class to minimize overlap between function
253 // and infrastructure.
254
255 LoadHandlers();
256
257 /// <remarks>
258 /// The intention of a post construction initializer
259 /// is to allow for setup that is dependent upon other
260 /// activities outside of the agency. We don't currently
261 /// have any, but the design allows for it.
262 /// </remarks>
263
264 foreach (IRest handler in handlers)
265 {
266 handler.Initialize();
267 }
268
269 /// <remarks>
270 /// Now that everything is setup we can proceed and
271 /// add this agent to the HTTP server's handler list
272 /// </remarks>
273
274 if (!AddAgentHandler(Rest.Name,this))
275 {
276 Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
277 foreach (IRest handler in handlers)
278 {
279 handler.Close();
280 }
281 }
282
283 }
284 catch (Exception e)
285 {
286 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgID, e.Message);
287 }
288
289 }
290
291 /// <summary>
292 /// In the interests of efficiency, and because we cannot determine whether
293 /// or not this instance will actually be harvested, we clobber the only
294 /// anchoring reference to the working state for this plug-in. What the
295 /// call to close does is irrelevant to this class beyond knowing that it
296 /// can nullify the reference when it returns.
297 /// To make sure everything is copacetic we make sure the primary interface
298 /// is disabled by deleting the handler from the HTTP server tables.
299 /// </summary>
300 public override void Close()
301 {
302
303 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID);
304
305 try
306 {
307 RemoveAgentHandler(Rest.Name, this);
308 }
309 catch (KeyNotFoundException){}
310
311 foreach (IRest handler in handlers)
312 {
313 handler.Close();
314 }
315
316 }
317
318 #endregion overriding methods
319
320 #region interface methods
321
322 /// <summary>
323 /// This method is called by the server to match the client, it could
324 /// just return true if we only want one such handler. For now we
325 /// match any explicitly specified client.
326 /// </summary>
327 public bool Match(OSHttpRequest request, OSHttpResponse response)
328 {
329 string path = request.RawUrl;
330 foreach (string key in pathHandlers.Keys)
331 {
332 if (path.StartsWith(key))
333 {
334 return ( path.Length == key.Length ||
335 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
336 }
337 }
338
339 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
340 foreach (string key in streamHandlers.Keys)
341 {
342 if (path.StartsWith(key))
343 {
344 return true;
345 }
346 }
347
348 return false;
349 }
350
351 /// <summary>
352 /// Preconditions:
353 /// [1] request != null and is a valid request object
354 /// [2] response != null and is a valid response object
355 /// Behavior is undefined if preconditions are not satisfied.
356 /// </summary>
357 public bool Handle(OSHttpRequest request, OSHttpResponse response)
358 {
359 bool handled;
360 base.MsgID = base.RequestID;
361
362 if (Rest.DEBUG)
363 {
364 Rest.Log.DebugFormat("{0} ENTRY", MsgId);
365 Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
366 Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
367
368 for (int i = 0; i < request.Headers.Count; i++)
369 {
370 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
371 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
372 }
373 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
374 }
375
376 // If a path handler worked we're done, otherwise try any
377 // available stream handlers too.
378
379 try
380 {
381 handled = FindPathHandler(request, response) ||
382 FindStreamHandler(request, response);
383 }
384 catch (Exception e)
385 {
386 // A raw exception indicates that something we weren't expecting has
387 // happened. This should always reflect a shortcoming in the plugin,
388 // or a failure to satisfy the preconditions.
389 Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
390 handled = true;
391 }
392
393 Rest.Log.DebugFormat("{0} EXIT", MsgId);
394
395 return handled;
396
397 }
398
399 #endregion interface methods
400
401 /// <summary>
402 /// If there is a stream handler registered that can handle the
403 /// request, then fine. If the request is not matched, do
404 /// nothing.
405 /// </summary>
406
407 private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
408 {
409 RequestData rdata = new RequestData(request, response, String.Empty);
410
411 string bestMatch = null;
412 string path = String.Format("{0}:{1}", rdata.method, rdata.path);
413
414 Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
415
416 foreach (string pattern in streamHandlers.Keys)
417 {
418 if (path.StartsWith(pattern))
419 {
420 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
421 {
422 bestMatch = pattern;
423 }
424 }
425 }
426
427 // Handle using the best match available
428
429 if (!String.IsNullOrEmpty(bestMatch))
430 {
431 Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
432 RestStreamHandler handler = streamHandlers[bestMatch];
433 rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
434 rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
435 rdata.Respond("FindStreamHandler Completion");
436 }
437
438 return rdata.handled;
439
440 }
441
442 // Preserves the original handler's semantics
443
444 public new void AddStreamHandler(string httpMethod, string path, RestMethod method)
445 {
446
447 if (!IsEnabled)
448 {
449 return;
450 }
451
452 if (!path.StartsWith(Rest.Prefix))
453 {
454 path = String.Format("{0}{1}", Rest.Prefix, path);
455 }
456
457 path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
458
459 // Conditionally add to the list
460
461 if (!streamHandlers.ContainsKey(path))
462 {
463 streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
464 Rest.Log.DebugFormat("{0} Added handler for {1}", MsgID, path);
465 }
466 else
467 {
468 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgID, path);
469 }
470
471 }
472
473
474 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
475 {
476
477 RequestData rdata = null;
478 string bestMatch = null;
479
480 if (!IsEnabled)
481 {
482 return false;
483 }
484
485 // Conditionally add to the list
486
487 Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
488
489 foreach (string pattern in pathHandlers.Keys)
490 {
491 if (request.RawUrl.StartsWith(pattern))
492 {
493 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
494 {
495 bestMatch = pattern;
496 }
497 }
498 }
499
500 if (!String.IsNullOrEmpty(bestMatch))
501 {
502
503 rdata = pathAllocators[bestMatch](request, response);
504
505 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
506
507 try
508 {
509 pathHandlers[bestMatch](rdata);
510 }
511
512 // A plugin generated error indicates a request-related error
513 // that has been handled by the plugin.
514
515 catch (RestException r)
516 {
517 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
518 }
519
520 }
521
522 return (rdata == null) ? false : rdata.handled;
523
524 }
525
526 internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
527 {
528 if (pathHandlers.ContainsKey(path))
529 {
530 Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
531 pathHandlers.Remove(path);
532 }
533
534 if (pathAllocators.ContainsKey(path))
535 {
536 Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
537 pathAllocators.Remove(path);
538 }
539
540 Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
541
542 pathHandlers.Add(path, mh);
543 pathAllocators.Add(path, ra);
544
545 }
546 }
547}