aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
diff options
context:
space:
mode:
authorDr Scofield2008-08-20 10:11:11 +0000
committerDr Scofield2008-08-20 10:11:11 +0000
commit5e83a758157520d48b15c725f5be2b196d2414e3 (patch)
tree42ab07bde2a984d06632abd30e6ad59877021f3f /OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
parentAdding unit test for issue 2006, though it's commented out as those compiler (diff)
downloadopensim-SC-5e83a758157520d48b15c725f5be2b196d2414e3.zip
opensim-SC-5e83a758157520d48b15c725f5be2b196d2414e3.tar.gz
opensim-SC-5e83a758157520d48b15c725f5be2b196d2414e3.tar.bz2
opensim-SC-5e83a758157520d48b15c725f5be2b196d2414e3.tar.xz
From: Alan Webb <alan_webb@us.ibm.com>
cleanups of the REST inventory code.
Diffstat (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs141
1 files changed, 109 insertions, 32 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
index 9853f16..cb80846 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using System; 29using System;
@@ -34,8 +35,27 @@ using OpenSim.ApplicationPlugins.Rest;
34 35
35namespace OpenSim.ApplicationPlugins.Rest.Inventory 36namespace OpenSim.ApplicationPlugins.Rest.Inventory
36{ 37{
37 public class RestHandler : RestPlugin, IHttpAgentHandler 38
39 /// <remarks>
40 /// The class signature reveals the roles that RestHandler plays.
41 ///
42 /// [1] It is a sub-class of RestPlugin. It inherits and extends
43 /// the functionality of this class, constraining it to the
44 /// specific needs of this REST implementation. This relates
45 /// to the plug-in mechanism supported by OpenSim, the specifics
46 /// of which are mostly hidden by RestPlugin.
47 /// [2] IRestHandler describes the interface that this class
48 /// exports to service implementations. This is the services
49 /// management interface.
50 /// [3] IHttpAgentHandler describes the interface that is exported
51 /// to the BaseHttpServer in support of this particular HTTP
52 /// processing model. This is the request interface of the
53 /// handler.
54 /// </remarks>
55
56 public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
38 { 57 {
58
39 /// <remarks> 59 /// <remarks>
40 /// The handler delegates are not noteworthy. The allocator allows 60 /// The handler delegates are not noteworthy. The allocator allows
41 /// a given handler to optionally subclass the base RequestData 61 /// a given handler to optionally subclass the base RequestData
@@ -43,8 +63,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
43 /// needed. 63 /// needed.
44 /// </remarks> 64 /// </remarks>
45 65
46 internal delegate void RestMethodHandler(RequestData rdata); 66 // internal delegate void RestMethodHandler(RequestData rdata);
47 internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); 67 // internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
48 68
49 // Handler tables: both stream and REST are supported. The path handlers and their 69 // Handler tables: both stream and REST are supported. The path handlers and their
50 // respective allocators are stored in separate tables. 70 // respective allocators are stored in separate tables.
@@ -64,10 +84,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
64 /// <summary> 84 /// <summary>
65 /// This static initializer scans the ASSEMBLY for classes that 85 /// This static initializer scans the ASSEMBLY for classes that
66 /// export the IRest interface and builds a list of them. These 86 /// export the IRest interface and builds a list of them. These
67 /// are later activated by the handler. To add a new handler it 87 /// are later activated by the handler. To add a new handler it
68 /// is only necessary to create a new services class that implements 88 /// is only necessary to create a new services class that implements
69 /// the IRest interface, and recompile the handler. This gives 89 /// the IRest interface, and recompile the handler. This gives
70 /// all of the build-time flexibility of a modular approach 90 /// all of the build-time flexibility of a modular approach
71 /// while not introducing yet-another module loader. Note that 91 /// while not introducing yet-another module loader. Note that
72 /// multiple assembles can still be built, each with its own set 92 /// multiple assembles can still be built, each with its own set
73 /// of handlers. Examples of services classes are RestInventoryServices 93 /// of handlers. Examples of services classes are RestInventoryServices
@@ -76,12 +96,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
76 96
77 static RestHandler() 97 static RestHandler()
78 { 98 {
99
79 Module[] mods = Assembly.GetExecutingAssembly().GetModules(); 100 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
80 101
81 foreach (Module m in mods) 102 foreach (Module m in mods)
82 { 103 {
83 Type[] types = m.GetTypes(); 104 Type[] types = m.GetTypes();
84 foreach (Type t in types) 105 foreach (Type t in types)
85 { 106 {
86 try 107 try
87 { 108 {
@@ -97,6 +118,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
97 } 118 }
98 } 119 }
99 } 120 }
121
100 } 122 }
101 123
102 #endregion local static state 124 #endregion local static state
@@ -105,13 +127,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
105 127
106 /// <summary> 128 /// <summary>
107 /// This routine loads all of the handlers discovered during 129 /// This routine loads all of the handlers discovered during
108 /// instance initialization. 130 /// instance initialization.
109 /// A table of all loaded and successfully constructed handlers 131 /// A table of all loaded and successfully constructed handlers
110 /// is built, and this table is then used by the constructor to 132 /// is built, and this table is then used by the constructor to
111 /// initialize each of the handlers in turn. 133 /// initialize each of the handlers in turn.
112 /// NOTE: The loading process does not automatically imply that 134 /// NOTE: The loading process does not automatically imply that
113 /// the handler has registered any kind of an interface, that 135 /// the handler has registered any kind of an interface, that
114 /// may be (optionally) done by the handler either during 136 /// may be (optionally) done by the handler either during
115 /// construction, or during initialization. 137 /// construction, or during initialization.
116 /// 138 ///
117 /// I was not able to make this code work within a constructor 139 /// I was not able to make this code work within a constructor
@@ -124,6 +146,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
124 { 146 {
125 if (!handlersLoaded) 147 if (!handlersLoaded)
126 { 148 {
149
127 ConstructorInfo ci; 150 ConstructorInfo ci;
128 Object ht; 151 Object ht;
129 152
@@ -154,8 +177,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
154 177
155 // Name is used to differentiate the message header. 178 // Name is used to differentiate the message header.
156 179
157 public override string Name 180 public override string Name
158 { 181 {
159 get { return "HANDLER"; } 182 get { return "HANDLER"; }
160 } 183 }
161 184
@@ -168,15 +191,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
168 191
169 // We have to rename these because we want 192 // We have to rename these because we want
170 // to be able to share the values with other 193 // to be able to share the values with other
171 // classes in our assembly and the base 194 // classes in our assembly and the base
172 // names are protected. 195 // names are protected.
173 196
174 internal string MsgId 197 public string MsgId
175 { 198 {
176 get { return base.MsgID; } 199 get { return base.MsgID; }
177 } 200 }
178 201
179 internal string RequestId 202 public string RequestId
180 { 203 {
181 get { return base.RequestID; } 204 get { return base.RequestID; }
182 } 205 }
@@ -198,6 +221,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
198 { 221 {
199 try 222 try
200 { 223 {
224
201 // This plugin will only be enabled if the broader 225 // This plugin will only be enabled if the broader
202 // REST plugin mechanism is enabled. 226 // REST plugin mechanism is enabled.
203 227
@@ -208,7 +232,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
208 // IsEnabled is implemented by the base class and 232 // IsEnabled is implemented by the base class and
209 // reflects an overall RestPlugin status 233 // reflects an overall RestPlugin status
210 234
211 if (!IsEnabled) 235 if (!IsEnabled)
212 { 236 {
213 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); 237 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
214 return; 238 return;
@@ -221,7 +245,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
221 245
222 Rest.main = openSim; 246 Rest.main = openSim;
223 Rest.Plugin = this; 247 Rest.Plugin = this;
224 Rest.Comms = App.CommunicationsManager; 248 Rest.Comms = Rest.main.CommunicationsManager;
225 Rest.UserServices = Rest.Comms.UserService; 249 Rest.UserServices = Rest.Comms.UserService;
226 Rest.InventoryServices = Rest.Comms.InventoryService; 250 Rest.InventoryServices = Rest.Comms.InventoryService;
227 Rest.AssetServices = Rest.Comms.AssetCache; 251 Rest.AssetServices = Rest.Comms.AssetCache;
@@ -234,7 +258,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
234 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); 258 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
235 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); 259 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
236 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); 260 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
261 Rest.Fill = Rest.Config.GetBoolean("path-fill",true);
237 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); 262 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
263 Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true);
238 264
239 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, 265 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
240 (Rest.Authenticate ? "" : "not ")); 266 (Rest.Authenticate ? "" : "not "));
@@ -248,6 +274,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
248 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, 274 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
249 (Rest.DumpAsset ? "" : "not ")); 275 (Rest.DumpAsset ? "" : "not "));
250 276
277 // The supplied prefix MUST be absolute
278
279 if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
280 Rest.Prefix = Rest.UrlPathSeparator+Rest.Prefix;
281
251 // If data dumping is requested, report on the chosen line 282 // If data dumping is requested, report on the chosen line
252 // length. 283 // length.
253 284
@@ -257,15 +288,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
257 Rest.DumpLineSize); 288 Rest.DumpLineSize);
258 } 289 }
259 290
260 // Load all of the handlers present in the 291 // Load all of the handlers present in the
261 // assembly 292 // assembly
262 293
263 // In principle, as we're an application plug-in, 294 // In principle, as we're an application plug-in,
264 // most of what needs to be done could be done using 295 // most of what needs to be done could be done using
265 // static resources, however the Open Sim plug-in 296 // static resources, however the Open Sim plug-in
266 // model makes this an instance, so that's what we 297 // model makes this an instance, so that's what we
267 // need to be. 298 // need to be.
268 // There is only one Communications manager per 299 // There is only one Communications manager per
269 // server, and by inference, only one each of the 300 // server, and by inference, only one each of the
270 // user, asset, and inventory servers. So we can cache 301 // user, asset, and inventory servers. So we can cache
271 // those using a static initializer. 302 // those using a static initializer.
@@ -308,12 +339,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
308 { 339 {
309 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); 340 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
310 } 341 }
342
311 } 343 }
312 344
313 /// <summary> 345 /// <summary>
314 /// In the interests of efficiency, and because we cannot determine whether 346 /// In the interests of efficiency, and because we cannot determine whether
315 /// or not this instance will actually be harvested, we clobber the only 347 /// or not this instance will actually be harvested, we clobber the only
316 /// anchoring reference to the working state for this plug-in. What the 348 /// anchoring reference to the working state for this plug-in. What the
317 /// call to close does is irrelevant to this class beyond knowing that it 349 /// call to close does is irrelevant to this class beyond knowing that it
318 /// can nullify the reference when it returns. 350 /// can nullify the reference when it returns.
319 /// To make sure everything is copacetic we make sure the primary interface 351 /// To make sure everything is copacetic we make sure the primary interface
@@ -322,6 +354,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
322 354
323 public override void Close() 355 public override void Close()
324 { 356 {
357
325 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); 358 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
326 359
327 try 360 try
@@ -329,11 +362,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
329 RemoveAgentHandler(Rest.Name, this); 362 RemoveAgentHandler(Rest.Name, this);
330 } 363 }
331 catch (KeyNotFoundException){} 364 catch (KeyNotFoundException){}
332 365
333 foreach (IRest handler in handlers) 366 foreach (IRest handler in handlers)
334 { 367 {
335 handler.Close(); 368 handler.Close();
336 } 369 }
370
337 } 371 }
338 372
339 #endregion overriding methods 373 #endregion overriding methods
@@ -352,25 +386,57 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
352 { 386 {
353 string path = request.RawUrl; 387 string path = request.RawUrl;
354 388
389 Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
390
355 try 391 try
356 { 392 {
357 foreach (string key in pathHandlers.Keys) 393 foreach (string key in pathHandlers.Keys)
358 { 394 {
395 Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
396
397 // Note that Match will not necessarily find the handler that will
398 // actually be used - it does no test for the "closest" fit. It
399 // simply reflects that at least one possible handler exists.
400
359 if (path.StartsWith(key)) 401 if (path.StartsWith(key))
360 { 402 {
361 return (path.Length == key.Length || 403 Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
362 path.Substring(key.Length, 1) == Rest.UrlPathSeparator); 404
405 // This apparently odd evaluation is needed to prevent a match
406 // on anything other than a URI token boundary. Otherwise we
407 // may match on URL's that were not intended for this handler.
408
409 return ( path.Length == key.Length ||
410 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
411
363 } 412 }
364 } 413 }
365 414
366 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); 415 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
416
367 foreach (string key in streamHandlers.Keys) 417 foreach (string key in streamHandlers.Keys)
368 { 418 {
419
420 Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
421
422 // Note that Match will not necessarily find the handler that will
423 // actually be used - it does no test for the "closest" fit. It
424 // simply reflects that at least one possible handler exists.
425
369 if (path.StartsWith(key)) 426 if (path.StartsWith(key))
370 { 427 {
371 return true; 428 Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
429
430 // This apparently odd evaluation is needed to prevent a match
431 // on anything other than a URI token boundary. Otherwise we
432 // may match on URL's that were not intended for this handler.
433
434 return ( path.Length == key.Length ||
435 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
436
372 } 437 }
373 } 438 }
439
374 } 440 }
375 catch (Exception e) 441 catch (Exception e)
376 { 442 {
@@ -404,7 +470,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
404 470
405 for (int i = 0; i < request.Headers.Count; i++) 471 for (int i = 0; i < request.Headers.Count; i++)
406 { 472 {
407 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", 473 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
408 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); 474 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
409 } 475 }
410 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); 476 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
@@ -415,8 +481,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
415 481
416 try 482 try
417 { 483 {
418 handled = FindPathHandler(request, response) || 484 handled = ( FindPathHandler(request, response) ||
419 FindStreamHandler(request, response); 485 FindStreamHandler(request, response) );
420 } 486 }
421 catch (Exception e) 487 catch (Exception e)
422 { 488 {
@@ -430,6 +496,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
430 Rest.Log.DebugFormat("{0} EXIT", MsgId); 496 Rest.Log.DebugFormat("{0} EXIT", MsgId);
431 497
432 return handled; 498 return handled;
499
433 } 500 }
434 501
435 #endregion interface methods 502 #endregion interface methods
@@ -477,6 +544,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
477 } 544 }
478 545
479 return rdata.handled; 546 return rdata.handled;
547
480 } 548 }
481 549
482 /// <summary> 550 /// <summary>
@@ -489,12 +557,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
489 557
490 public void AddStreamHandler(string httpMethod, string path, RestMethod method) 558 public void AddStreamHandler(string httpMethod, string path, RestMethod method)
491 { 559 {
560
492 if (!IsEnabled) 561 if (!IsEnabled)
493 { 562 {
494 return; 563 return;
495 } 564 }
496 565
497 if (!path.StartsWith(Rest.Prefix)) 566 if (!path.StartsWith(Rest.Prefix))
498 { 567 {
499 path = String.Format("{0}{1}", Rest.Prefix, path); 568 path = String.Format("{0}{1}", Rest.Prefix, path);
500 } 569 }
@@ -512,6 +581,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
512 { 581 {
513 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); 582 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
514 } 583 }
584
515 } 585 }
516 586
517 /// <summary> 587 /// <summary>
@@ -526,9 +596,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
526 596
527 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) 597 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
528 { 598 {
599
529 RequestData rdata = null; 600 RequestData rdata = null;
530 string bestMatch = null; 601 string bestMatch = null;
531 602
532 if (!IsEnabled) 603 if (!IsEnabled)
533 { 604 {
534 return false; 605 return false;
@@ -551,6 +622,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
551 622
552 if (!String.IsNullOrEmpty(bestMatch)) 623 if (!String.IsNullOrEmpty(bestMatch))
553 { 624 {
625
554 rdata = pathAllocators[bestMatch](request, response); 626 rdata = pathAllocators[bestMatch](request, response);
555 627
556 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); 628 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
@@ -559,7 +631,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
559 { 631 {
560 pathHandlers[bestMatch](rdata); 632 pathHandlers[bestMatch](rdata);
561 } 633 }
562 634
563 // A plugin generated error indicates a request-related error 635 // A plugin generated error indicates a request-related error
564 // that has been handled by the plugin. 636 // that has been handled by the plugin.
565 637
@@ -567,9 +639,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
567 { 639 {
568 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); 640 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
569 } 641 }
642
570 } 643 }
571 644
572 return (rdata == null) ? false : rdata.handled; 645 return (rdata == null) ? false : rdata.handled;
646
573 } 647 }
574 648
575 /// <summary> 649 /// <summary>
@@ -577,8 +651,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
577 /// path as a key. If an entry already exists, it is replaced by the new one. 651 /// path as a key. If an entry already exists, it is replaced by the new one.
578 /// </summary> 652 /// </summary>
579 653
580 internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) 654 public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
581 { 655 {
656
582 if (!IsEnabled) 657 if (!IsEnabled)
583 { 658 {
584 return; 659 return;
@@ -600,6 +675,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
600 675
601 pathHandlers.Add(path, mh); 676 pathHandlers.Add(path, mh);
602 pathAllocators.Add(path, ra); 677 pathAllocators.Add(path, ra);
678
603 } 679 }
604 } 680 }
681
605} 682}