4 // Copyright (C) 2005 Novell, Inc.
7 // Vijay K. Nanjundaswamy (knvijay@novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining a
12 // copy of this software and associated documentation files (the "Software"),
13 // to deal in the Software without restriction, including without limitation
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 // and/or sell copies of the Software, and to permit persons to whom the
16 // Software is furnished to do so, subject to the following conditions:
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 // DEALINGS IN THE SOFTWARE.
32 using System
.Collections
;
33 using System
.Threading
;
35 using System
.Runtime
.Remoting
;
36 using System
.Runtime
.Remoting
.Channels
;
37 using System
.Runtime
.Remoting
.Channels
.Tcp
;
44 namespace Beagle
.WebService
{
46 public class WebServiceBackEnd
: MarshalByRefObject
{
48 public static string hostname
= "localhost";
49 public static string DEFAULT_XSP_ROOT
= Path
.Combine (ExternalStringsHack
.PkgDataDir
, "xsp");
50 public static string DEFAULT_XSP_PORT
= "8888";
51 public static string web_rootDir
= DEFAULT_XSP_ROOT
;
52 public static string web_port
= DEFAULT_XSP_PORT
;
53 public static bool web_start
= true;
54 public static bool web_global
= false;
56 static Mono
.ASPNET
.ApplicationServer appServer
= null;
57 //Both "/" and "/beagle" aliased to DEFAULT_XSP_ROOT only for BeagleXSP server
58 static string DEFAULT_APP_MAPPINGS
= "/:" + DEFAULT_XSP_ROOT
+ ",/beagle:" + DEFAULT_XSP_ROOT
;
59 static string[] xsp_param
= {"--port", DEFAULT_XSP_PORT
,
60 "--root", DEFAULT_XSP_ROOT
,
61 "--applications", DEFAULT_APP_MAPPINGS
,
64 //static Logger log = Logger.Get ("WebServiceBackEnd");
65 static string BeagleHttpUriBase
;
66 static string[] reserved_suffixes
;
68 public static ExternalAccessFilter AccessFilter
;
70 public static void Start()
73 Logger
.Log
.Warn("Beagle running with WebServices DISABLED\n");
78 IPHostEntry hostInfo
= Dns
.GetHostByName(Dns
.GetHostName());
79 //Fully qualified DNS name of host:
80 hostname
= hostInfo
.HostName
;
81 Logger
.Log
.Info("This Computer Hostname: " + hostname
);
85 Logger
.Log
.Error("Caught exception {0} in Dns.GetHostName: ", ex
.Message
);
86 Logger
.Log
.Error("Resetting hostname to \"localhost\"");
87 hostname
= "localhost";
91 web_global
= Conf
.WebServices
.AllowGlobalAccess
;
93 //start web-access server first
94 Logger
.Log
.Debug ("Starting WebBackEnd");
97 //Next start web-service server
98 Logger
.Log
.Info ("Starting WebServiceBackEnd");
99 WebServiceBackEnd
.init ();
101 Logger
.Log
.Debug ("Global WebServicesAccess {0}", web_global
? "Enabled" : "Disabled");
103 xsp_param
[1] = web_port
;
104 xsp_param
[3] = web_rootDir
;
106 //Check if web_rootDir_changed:
107 if (String
.Compare(web_rootDir
, DEFAULT_XSP_ROOT
, true) != 0)
108 //Assuming "/beagle" exists as an explicit sub-folder under user specified xsp root directory:
109 xsp_param
[5] = "/:" + web_rootDir
+ ",/beagle:" + web_rootDir
+ "/beagle";
112 // Mapping /beagle/local to ExternalStringsHack.Prefix
113 if (Directory
.Exists(ExternalStringsHack
.Prefix
))
114 xsp_param
[5] += ",/beagle/local:" + ExternalStringsHack
.Prefix
;
116 //Mapping /beagle/gnome to ExternalStringsHack.GnomePrefix
117 if (Directory
.Exists(ExternalStringsHack
.GnomePrefix
))
118 xsp_param
[5] += ",/beagle/gnome:" + ExternalStringsHack
.GnomePrefix
;
120 //Mapping /beagle/kde3 to ExternalStringsHack.KdePrefix
121 if (Directory
.Exists(ExternalStringsHack
.KdePrefix
))
122 xsp_param
[5] += ",/beagle/kde3:" + ExternalStringsHack
.KdePrefix
;
124 //if (!hostname.Equals("localhost")) {
126 reserved_suffixes
= new string[] {"beagle", "local", "gnome", "kde3"}
;
127 BeagleHttpUriBase
= "http://" + hostname
+ ":" + xsp_param
[1] + "/beagle/";
129 AccessFilter
= new ExternalAccessFilter(BeagleHttpUriBase
, reserved_suffixes
);
131 ArrayList matchers
= AccessFilter
.Matchers
;
132 foreach (SimpleMatcher sm
in matchers
)
133 xsp_param
[5] += ",/beagle/" + sm
.Rewrite
+":" + sm
.Match
;
135 AccessFilter
.Initialize();
140 xsp_param
[5] = DEFAULT_APP_MAPPINGS
;
143 Logger
.Log
.Debug ("Starting Internal Web Server");
147 //Start beagled internal web server (BeagleXsp)
148 retVal
= Mono
.ASPNET
.Server
.initXSP(xsp_param
, out appServer
);
150 catch (ArgumentException e
) {
151 //Retry with default application mappings:
152 xsp_param
[5] = DEFAULT_APP_MAPPINGS
;
153 retVal
= Mono
.ASPNET
.Server
.initXSP(xsp_param
, out appServer
);
157 Logger
.Log
.Warn ("Error starting Internal Web Server (retVal={0})", retVal
);
158 Logger
.Log
.Warn ("Check if there is another instance of Beagle running");
161 Logger
.Log
.Debug("BeagleXSP Applications list: " + xsp_param
[5]);
164 public static void Stop()
166 Logger
.Log
.Info ("Stopping WebServiceBackEnd");
167 if (appServer
!= null) {
173 /////////////////////////////////////////////////////////////////////////////////////////
175 private void WebServicesConfigurationChanged (Conf
.Section section
)
177 Logger
.Log
.Info("WebServicesConfigurationChanged EventHandler invoked");
178 if (! (section
is Conf
.WebServicesConfig
))
181 Conf
.WebServicesConfig wsc
= (Conf
.WebServicesConfig
) section
;
183 if (web_global
!= wsc
.AllowGlobalAccess
) {
184 // Update AllowGlobalAccess configuration:
185 web_global
= wsc
.AllowGlobalAccess
;
186 Logger
.Log
.Info("WebServicesBackEnd: Global WebServicesAccess {0}", web_global
? "Enabled" : "Disabled");
189 if (wsc
.PublicFolders
.Count
== 0)
192 Logger
.Log
.Warn("WebServicesBackEnd: Changes in PublicFolders configuration doesn't take effect until Beagle daemon is restarted!");
194 /* Note: ExternalAccessFilter Matchers can be updated,
195 but app mapping changes in BeagleXsp Server require it to be restarted !
197 ArrayList newList = new ArrayList();
198 foreach (string pf in wsc.PublicFolders) {
199 if (pf == null || pf == "")
202 newList.Add (new NetBeagleHandler (host, port, this));
205 if (usingPublicFoldersDotCfgFile) {
206 usingPublicFoldersDotCfgFile = false;
207 log.Warn("NetBeagleQueryable: Duplicate configuration of PublicFolders in '~/.beagle/publicfolders.cfg' and '~/.beagle/config/webservices.xml' !");
208 log.Info("NetBeagleQueryable: Remove '~/.beagle/publicfolders.cfg' file. Use 'beagle-config' instead to setup public folders.");
209 log.Info("NetBeagleQueryable: Replacing PublicFoldersList with new list from \"webservices.xml\" having {0} node(s)", newList.Count);
212 AccessFilter.ReplaceAccessFilter(newList);
216 /////////////////////////////////////////////////////////////////////////////////////////
218 //KNV: If needed, we can convert this to a Singleton, adding a
219 // static Factory method to get the singleton instance reference,
220 // so that front-end code always gets hold of same instance.
222 static WebServiceBackEnd instance
= null;
224 private Hashtable resultTable
;
225 private Hashtable sessionTable
;
227 public WebServiceBackEnd() {
229 resultTable
= Hashtable
.Synchronized(new Hashtable());
230 sessionTable
= Hashtable
.Synchronized(new Hashtable());
232 Conf
.Subscribe (typeof (Conf
.WebServicesConfig
), new Conf
.ConfigUpdateHandler (WebServicesConfigurationChanged
));
235 ~
WebServiceBackEnd() {
237 sessionTable
.Clear();
240 public bool allowGlobalAccess
{
241 get { return web_global; }
244 public static void init()
246 if (instance
== null) {
248 instance
= new WebServiceBackEnd();
250 //TCP Channel Listener registered in beagledWeb:init()
251 //ChannelServices.RegisterChannel(new TcpChannel(8347));
252 WellKnownServiceTypeEntry WKSTE
=
253 new WellKnownServiceTypeEntry(typeof(WebServiceBackEnd
),
254 "WebServiceBackEnd.rem", WellKnownObjectMode
.Singleton
);
255 RemotingConfiguration
.ApplicationName
="beagled";
256 RemotingConfiguration
.RegisterWellKnownServiceType(WKSTE
);
260 void OnHitsAdded (QueryResult qres
, ICollection hits
)
262 if (resultTable
.Contains(qres
)) {
264 SessionData sdata
= ((SessionData
) resultTable
[qres
]);
265 ArrayList results
= sdata
.results
;
266 bool localReq
= sdata
.localRequest
;
269 lock (results
.SyncRoot
)
270 results
.AddRange(hits
);
273 //Query query = sdata.query;
274 lock (results
.SyncRoot
) {
275 foreach (Hit h
in hits
)
276 if (h
.Uri
.ToString().StartsWith(NetworkedBeagle
.BeagleNetPrefix
) ||
277 AccessFilter
.FilterHit(h
))
284 void removeUris(ArrayList results
, ICollection uris
)
286 foreach(Uri u
in uris
)
287 foreach(Hit h
in results
)
288 if (h
.Uri
.Equals (u
) && h
.Uri
.Fragment
== u
.Fragment
) {
289 lock (results
.SyncRoot
) {
296 void OnHitsSubtracted (QueryResult qres
, ICollection uris
)
298 if (resultTable
.Contains(qres
)) {
299 SessionData sdata
= ((SessionData
) resultTable
[qres
]);
300 ArrayList results
= sdata
.results
;
301 removeUris(results
, uris
);
305 void OnFinished (QueryResult qres
)
307 DetachQueryResult (qres
);
310 void OnCancelled (QueryResult qres
)
312 DetachQueryResult (qres
);
315 void AttachQueryResult (QueryResult qres
, SessionData sdata
)
319 qres
.HitsAddedEvent
+= OnHitsAdded
;
320 qres
.HitsSubtractedEvent
+= OnHitsSubtracted
;
321 qres
.FinishedEvent
+= OnFinished
;
322 qres
.CancelledEvent
+= OnCancelled
;
324 resultTable
.Add(qres
, sdata
);
328 void DetachQueryResult (QueryResult qres
)
332 if (resultTable
.Contains(qres
)) {
333 SessionData sdata
= ((SessionData
) resultTable
[qres
]);
334 sdata
.results
.Sort();
336 qres
.HitsAddedEvent
-= OnHitsAdded
;
337 qres
.HitsSubtractedEvent
-= OnHitsSubtracted
;
338 qres
.FinishedEvent
-= OnFinished
;
339 qres
.CancelledEvent
-= OnCancelled
;
341 resultTable
.Remove(qres
);
346 private class SessionData
{
348 private bool _localRequest
;
349 private ArrayList _results
;
350 private Query _query
;
352 public SessionData (Query _query
, ArrayList _results
, bool _localRequest
)
354 this._localRequest
= _localRequest
;
355 this._results
= _results
;
356 this._query
= _query
;
359 public bool localRequest
{
360 get { return _localRequest; }
363 public ArrayList results
{
364 get { return _results; }
369 get { return _query; }
373 public string[] ICollection2StringList(ICollection il
)
376 return new string[0] ;
378 string[] sl
= new string[il
.Count
];
385 private const int MAX_RESULTS_PER_CALL
= 20;
387 public const int SC_QUERY_SUCCESS
= 0;
388 public const int SC_INVALID_QUERY
= -1;
389 public const int SC_UNAUTHORIZED_ACCESS
= -2;
390 public const int SC_INVALID_SEARCH_TOKEN
= -3;
391 public const int SC_DUPLICATE_QUERY
= -4;
394 public SearchResult
doQuery(SearchRequest sreq
, bool isLocalReq
)
396 SearchResult sr
= null;
398 if (sreq
.text
== null || sreq
.text
.Length
== 0 ||
399 (sreq
.text
.Length
== 1 && sreq
.text
[0].Trim() == "") ) {
401 sr
= new SearchResult();
402 sr
.statusCode
= SC_INVALID_QUERY
;
403 sr
.statusMsg
= "Error: No search terms specified";
407 Query query
= new Query();
409 foreach (string text
in sreq
.text
)
412 Logger
.Log
.Info("WebServiceBackEnd: Received {0} WebService Query with search term: {1}", isLocalReq
? "Local":"External", query
.QuotedText
);
414 if (sreq
.mimeType
!= null && sreq
.mimeType
[0] != null)
415 foreach (string mtype
in sreq
.mimeType
)
416 query
.AddMimeType(mtype
);
418 if (sreq
.searchSources
!= null && sreq
.searchSources
[0] != null)
419 foreach (string src
in sreq
.searchSources
)
420 query
.AddSource(src
);
422 //If needed, check to restrict queries to System or Neighborhood domain, can be added here
423 if (sreq
.qdomain
> 0)
424 query
.AddDomain(sreq
.qdomain
);
426 if (!isLocalReq
) { //External Request, check if this Node is already processing it
429 if ((sreq
.searchId
!= 0) && NetworkedBeagle
.IsCachedRequest(sreq
.searchId
)) {
431 sr
= new SearchResult();
432 sr
.numResults
= sr
.totalResults
= sr
.firstResultIndex
= 0;
433 sr
.hitResults
= new HitResult
[sr
.numResults
];
436 sr
.statusCode
= SC_DUPLICATE_QUERY
;
437 sr
.statusMsg
= "Error: Duplicate Query loopback";
438 Logger
.Log
.Warn("WebServiceBackEnd: Received duplicate Query for a query already in process!");
439 Logger
.Log
.Warn("WebServiceBackEnd: Check NetBeagle configuration on all nodes to remove possible loops");
442 if (sreq
.hopCount
>= 5) {
443 //If request has traversed 5 nodes in reaching here, stop cascading.
444 //Make it a Local Query.
445 query
.RemoveDomain(sreq
.qdomain
);
446 query
.AddDomain(QueryDomain
.System
);
449 if ((sr
== null) && (sreq
.searchId
!= 0) )
450 NetworkedBeagle
.CacheRequest(query
, sreq
.searchId
, sreq
.hopCount
+ 1);
456 //Logger.Log.Info("New external Query: searchId = {0}", sreq.searchId);
459 ArrayList results
= ArrayList
.Synchronized(new ArrayList());
461 QueryResult qres
= new QueryResult ();
463 string searchToken
= TokenGenerator();
465 SessionData sdata
= new SessionData(query
, results
, isLocalReq
);
467 AttachQueryResult (qres
, sdata
);
469 /* Include this code, if sessionID passed from front-end:
470 if (sessionTable.Contains(searchToken))
471 sessionTable[searchToken] = sdata;
474 sessionTable
.Add(searchToken
, sdata
);
476 QueryDriver
.DoQuery (query
, qres
);
478 while (resultTable
.Contains(qres
) && (results
.Count
< MAX_RESULTS_PER_CALL
) )
481 //Console.WriteLine("WebServiceBackEnd: Got {0} results from beagled", results.Count);
482 sr
= new SearchResult();
484 if (results
.Count
> 0)
486 lock (results
.SyncRoot
) { //Lock results ArrayList to prevent more Hits added till we've processed doQuery
488 sr
.numResults
= results
.Count
< MAX_RESULTS_PER_CALL
? results
.Count
: MAX_RESULTS_PER_CALL
;
489 sr
.hitResults
= new HitResult
[sr
.numResults
];
492 for (int i
= 0; i
< sr
.numResults
; i
++) {
494 Hit h
= (Hit
) results
[i
];
498 //Queryable queryable = h.SourceObject as Queryable;
499 Queryable queryable
= QueryDriver
.GetQueryable (h
.SourceObjectName
);
501 if (queryable
== null)
502 snippet
= "ERROR: hit.SourceObject is null, uri=" + h
.Uri
;
504 snippet
= queryable
.GetSnippet (ICollection2StringList(query
.Text
), h
);
506 sr
.hitResults
[i
] = new HitResult();
507 sr
.hitResults
[i
].id
= h
.Id
;
509 hitUri
= h
.Uri
.ToString();
510 if (isLocalReq
|| hitUri
.StartsWith(NetworkedBeagle
.BeagleNetPrefix
))
511 sr
.hitResults
[i
].uri
= hitUri
;
513 sr
.hitResults
[i
].uri
= AccessFilter
.TranslateHit(h
);
515 sr
.hitResults
[i
].resourceType
= h
.Type
;
516 sr
.hitResults
[i
].mimeType
= h
.MimeType
;
517 sr
.hitResults
[i
].source
= h
.Source
;
518 sr
.hitResults
[i
].scoreRaw
= h
.ScoreRaw
;
519 sr
.hitResults
[i
].scoreMultiplier
= h
.ScoreMultiplier
;
521 int plen
= h
.Properties
.Count
;
522 sr
.hitResults
[i
].properties
= new HitProperty
[plen
];
523 for (int j
= 0; j
< plen
; j
++) {
524 Property p
= (Property
) h
.Properties
[j
];
525 sr
.hitResults
[i
].properties
[j
] = new HitProperty();
526 sr
.hitResults
[i
].properties
[j
].PKey
= p
.Key
;
527 sr
.hitResults
[i
].properties
[j
].PVal
= p
.Value
;
528 sr
.hitResults
[i
].properties
[j
].IsMutable
= p
.IsMutable
;
529 sr
.hitResults
[i
].properties
[j
].IsSearched
= p
.IsSearched
;
533 sr
.hitResults
[i
].snippet
= snippet
.Trim();
540 sr
.hitResults
= new HitResult
[sr
.numResults
];
543 sr
.totalResults
= results
.Count
;
545 sr
.firstResultIndex
= 0;
548 if (sr
.totalResults
> 0)
549 sr
.searchToken
= searchToken
;
551 sr
.statusCode
= SC_QUERY_SUCCESS
;
552 sr
.statusMsg
= "Success";
553 Logger
.Log
.Info("WebServiceBackEnd: Total Results = " + sr
.totalResults
);
557 public SearchResult
getMoreResults(string searchToken
, int startIndex
, bool isLocalReq
)
560 SearchResult sr
= new SearchResult();
563 if (!sessionTable
.ContainsKey(searchToken
)) {
564 sr
.statusCode
= SC_INVALID_SEARCH_TOKEN
;
565 sr
.statusMsg
= "Error: Invalid Search Token";
566 Logger
.Log
.Warn("GetMoreResults: Invalid Search Token received ");
570 ArrayList results
= ((SessionData
)sessionTable
[searchToken
]).results
;
571 if (results
== null) {
572 sr
.statusCode
= SC_INVALID_SEARCH_TOKEN
;
573 sr
.statusMsg
= "Error: Invalid Search Token";
574 Logger
.Log
.Warn("GetMoreResults: Invalid Search Token received ");
578 lock (results
.SyncRoot
) { //Lock results ArrayList to prevent more Hits getting added till we've processed doQuery
582 if (startIndex
< results
.Count
)
583 sr
.numResults
= (results
.Count
< startIndex
+ MAX_RESULTS_PER_CALL
) ? (results
.Count
- startIndex
): MAX_RESULTS_PER_CALL
;
585 sr
.hitResults
= new HitResult
[sr
.numResults
];
588 for (int k
= startIndex
; (i
< sr
.numResults
) && (k
< results
.Count
); k
++) {
590 Hit h
= (Hit
) results
[k
];
592 sr
.hitResults
[i
] = new HitResult();
594 // GetMoreResults will NOT return Snippets by default. Client must make explicit GetSnippets request to get snippets for these hits.
595 // Not initializing sr.hitResults[i].snippet implies there is no <snippets> element in HitResult XML response.
597 sr
.hitResults
[i
].id
= h
.Id
;
599 hitUri
= h
.Uri
.ToString();
600 if (isLocalReq
|| hitUri
.StartsWith(NetworkedBeagle
.BeagleNetPrefix
))
601 sr
.hitResults
[i
].uri
= hitUri
;
603 sr
.hitResults
[i
].uri
= AccessFilter
.TranslateHit(h
);
605 sr
.hitResults
[i
].resourceType
= h
.Type
;
606 sr
.hitResults
[i
].mimeType
= h
.MimeType
;
607 sr
.hitResults
[i
].source
= h
.Source
;
608 sr
.hitResults
[i
].scoreRaw
= h
.ScoreRaw
;
609 sr
.hitResults
[i
].scoreMultiplier
= h
.ScoreMultiplier
;
611 int plen
= h
.Properties
.Count
;
612 sr
.hitResults
[i
].properties
= new HitProperty
[plen
];
613 for (int j
= 0; j
< plen
; j
++) {
614 Property p
= (Property
) h
.Properties
[j
];
615 sr
.hitResults
[i
].properties
[j
] = new HitProperty();
616 sr
.hitResults
[i
].properties
[j
].PKey
= p
.Key
;
617 sr
.hitResults
[i
].properties
[j
].PVal
= p
.Value
;
618 sr
.hitResults
[i
].properties
[j
].IsMutable
= p
.IsMutable
;
619 sr
.hitResults
[i
].properties
[j
].IsSearched
= p
.IsSearched
;
626 sr
.totalResults
= results
.Count
;
628 sr
.firstResultIndex
= startIndex
;
631 if (sr
.totalResults
> 0)
632 sr
.searchToken
= searchToken
;
634 sr
.statusCode
= SC_QUERY_SUCCESS
;
635 sr
.statusMsg
= "Success";
636 //Console.WriteLine("WebServiceQuery: Total Results = " + sr.totalResults);
640 public static string InvalidHitSnippetError
= "ERROR: Invalid or Duplicate Hit Id";
641 public HitSnippet
[] getSnippets(string searchToken
, int[] hitIds
)
643 HitSnippet
[] response
;
645 if (!sessionTable
.ContainsKey(searchToken
)) {
647 response
= new HitSnippet
[0];
648 Logger
.Log
.Warn("GetSnippets: Invalid Search Token received ");
652 ArrayList results
= ((SessionData
)sessionTable
[searchToken
]).results
;
653 if ((results
== null) || (results
.Count
== 0)) {
655 response
= new HitSnippet
[0];
656 Logger
.Log
.Warn("GetSnippets: Invalid Search Token received ");
661 ArrayList IdList
= new ArrayList();
662 IdList
.AddRange(hitIds
);
663 response
= new HitSnippet
[hitIds
.Length
];
664 Logger
.Log
.Debug("GetSnippets invoked with {0} hitIds", hitIds
.Length
);
666 Query query
= ((SessionData
)sessionTable
[searchToken
]).query
;
668 lock (results
.SyncRoot
) {
670 string snippet
= null;
671 foreach (Hit h
in results
) {
673 if (IdList
.Contains(h
.Id
)) {
677 //Queryable queryable = h.SourceObject as Queryable;
678 Queryable queryable
= QueryDriver
.GetQueryable (h
.SourceObjectName
);
680 if (queryable
== null)
681 snippet
= "ERROR: hit.SourceObject is null, uri=" + h
.Uri
;
683 snippet
= queryable
.GetSnippet (ICollection2StringList(query
.Text
), h
);
685 //GetSnippets always invoked on Target Beagle Node where hits originate:
689 HitSnippet hs
= new HitSnippet();
691 hs
.snippet
= snippet
.Trim();
694 if (i
== hitIds
.Length
)
700 foreach (int hitId
in IdList
) {
701 HitSnippet hs
= new HitSnippet();
703 hs
.snippet
= InvalidHitSnippetError
;
706 if (i
== hitIds
.Length
)
709 Logger
.Log
.Warn("GetSnippets invoked some invalid hitIds");
714 //Returns a 15-char random alpha-numeric string similar to ASP.NET sessionId
715 private string TokenGenerator()
717 const int TOKEN_LEN
= 15;
719 Random r
= new Random();
720 string token
= ((Char
)((int)((26 * r
.NextDouble()) + 'a')) + System
.Guid
.NewGuid().ToString()).Substring (0, TOKEN_LEN
);
722 char alpha
= (Char
)((int)((26 * r
.NextDouble()) + 'a'));
724 return (token
.Replace('-', alpha
));
728 ////////////////////////////////////////////////////////////////////////////////////////////
729 ///////////// WebService Request-Response Data Structures
730 ////////////////////////////////////////////////////////////////////////////////////////////
733 public class SearchRequest
{
735 public string[] text
;
736 public string[] mimeType
;
737 public string[] searchSources
;
738 public QueryDomain qdomain
;
739 //Unique searchId across network
745 public class HitProperty
{
747 private string pKey
="";
753 private string pVal
="";
759 private bool isKeyword
;
760 public bool IsMutable
{
761 get { return isKeyword; }
762 set { isKeyword = value; }
765 private bool isSearched
;
766 public bool IsSearched
{
767 get { return isSearched; }
768 set { isSearched = value; }
773 public class HitResult
{
777 public string resourceType
;
778 public string mimeType
;
779 public string source
;
780 public double scoreRaw
;
781 public double scoreMultiplier
;
782 public HitProperty
[] properties
;
783 //FIXME: public xxx[] data;
784 public string snippet
;
788 public class SearchResult
{
790 public int statusCode
; //ReturnCode for programmatic processing
791 public string statusMsg
; //User-friendly return message
793 public string searchToken
; //Token identifying the query,
794 //to enable follow-up queries
795 public int firstResultIndex
; //Index of first result in this response
796 public int numResults
; //No. of results in this response
797 public int totalResults
; //Total no. of results from the query
798 public HitResult
[] hitResults
;
802 public class HitSnippet
{
804 public string snippet
;