Tokenize 001234 as 1234. Include a testing function in NoiseFilter to figure out...
[beagle.git] / beagled / WebServices / WebServiceBackEnd.cs
blob96956f19e173d440c0f95a0b1517b3f0e08db627
1 //
2 //WebServiceBackEnd.cs
3 //
4 // Copyright (C) 2005 Novell, Inc.
5 //
6 // Authors:
7 // Vijay K. Nanjundaswamy (knvijay@novell.com)
8 //
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.
29 using System;
30 using System.IO;
31 using System.Net;
32 using System.Collections;
33 using System.Threading;
34 using System.Diagnostics;
36 using System.Runtime.Remoting;
37 using System.Runtime.Remoting.Channels;
38 using System.Runtime.Remoting.Channels.Tcp;
40 using Beagle.Util;
41 using Beagle.Daemon;
43 using Mono.ASPNET;
45 namespace Beagle.WebService {
47 public class WebServiceBackEnd: MarshalByRefObject {
49 public static string hostname = "localhost";
50 public static string DEFAULT_XSP_ROOT = Path.Combine (ExternalStringsHack.PkgDataDir, "xsp");
51 public static string DEFAULT_XSP_PORT = "8888";
52 public static string web_rootDir = DEFAULT_XSP_ROOT;
53 public static string web_port = DEFAULT_XSP_PORT;
54 public static bool web_start = true;
55 public static bool web_global = false;
57 static Mono.ASPNET.ApplicationServer appServer = null;
58 //Both "/" and "/beagle" aliased to DEFAULT_XSP_ROOT only for BeagleXSP server
59 static string DEFAULT_APP_MAPPINGS = "/:" + DEFAULT_XSP_ROOT + ",/beagle:" + DEFAULT_XSP_ROOT;
60 static string[] xsp_param = {"--port", DEFAULT_XSP_PORT,
61 "--root", DEFAULT_XSP_ROOT,
62 "--applications", DEFAULT_APP_MAPPINGS,
63 "--nonstop"};
65 //static Logger log = Logger.Get ("WebServiceBackEnd");
66 static string BeagleHttpUriBase;
67 static string[] reserved_suffixes;
69 public static ExternalAccessFilter AccessFilter;
71 public static void Start()
73 if (! web_start) {
74 Logger.Log.Warn("Beagle running with WebServices DISABLED\n");
75 return;
78 try {
79 IPHostEntry hostInfo = Dns.GetHostByName(Dns.GetHostName());
80 //Fully qualified DNS name of host:
81 hostname = hostInfo.HostName;
82 Logger.Log.Info("This Computer Hostname: " + hostname);
84 catch (Exception ex)
86 Logger.Log.Error("Caught exception {0} in Dns.GetHostName: ", ex.Message);
87 Logger.Log.Error("Resetting hostname to \"localhost\"");
88 hostname = "localhost";
91 if (! web_global)
92 web_global = Conf.WebServices.AllowGlobalAccess;
94 //start web-access server first
95 Logger.Log.Debug ("Starting WebBackEnd");
96 WebBackEnd.init ();
98 //Next start web-service server
99 Logger.Log.Info ("Starting WebServiceBackEnd");
100 WebServiceBackEnd.init ();
102 Logger.Log.Debug ("Global WebServicesAccess {0}", web_global ? "Enabled" : "Disabled");
104 xsp_param[1] = web_port;
105 xsp_param[3] = web_rootDir;
107 //Check if web_rootDir_changed:
108 if (String.Compare(web_rootDir, DEFAULT_XSP_ROOT, true) != 0)
109 //Assuming "/beagle" exists as an explicit sub-folder under user specified xsp root directory:
110 xsp_param[5] = "/:" + web_rootDir + ",/beagle:" + web_rootDir + "/beagle";
112 try {
113 // Mapping /beagle/local to ExternalStringsHack.Prefix
114 if (Directory.Exists(ExternalStringsHack.Prefix))
115 xsp_param[5] += ",/beagle/local:" + ExternalStringsHack.Prefix;
117 //Mapping /beagle/gnome to ExternalStringsHack.GnomePrefix
118 if (Directory.Exists(ExternalStringsHack.GnomePrefix))
119 xsp_param[5] += ",/beagle/gnome:" + ExternalStringsHack.GnomePrefix;
121 //Mapping /beagle/kde3 to ExternalStringsHack.KdePrefix
122 if (Directory.Exists(ExternalStringsHack.KdePrefix))
123 xsp_param[5] += ",/beagle/kde3:" + ExternalStringsHack.KdePrefix;
125 string imgDir = PathFinder.StorageDir + "/img";
126 if (!Directory.Exists(imgDir))
128 Process pr = new Process ();
129 pr.StartInfo.UseShellExecute = true;
130 pr.StartInfo.FileName = "mkdir";
131 pr.StartInfo.Arguments = imgDir;
133 try {
134 pr.Start ();
135 pr.WaitForExit();
136 pr.Close();
137 pr.Dispose();
139 catch (Exception e) {
140 Logger.Log.Warn("Error creating ~/.beagle/img folder");
143 xsp_param[5] += ",/beagle/img:" + imgDir;
145 //if (!hostname.Equals("localhost")) {
147 reserved_suffixes = new string[] {"beagle", "local", "gnome", "kde3"};
148 BeagleHttpUriBase = "http://" + hostname + ":" + xsp_param[1] + "/beagle/";
150 AccessFilter = new ExternalAccessFilter(BeagleHttpUriBase, reserved_suffixes);
152 ArrayList matchers = AccessFilter.Matchers;
153 foreach (SimpleMatcher sm in matchers)
154 xsp_param[5] += ",/beagle/" + sm.Rewrite +":" + sm.Match;
156 AccessFilter.Initialize();
159 catch (Exception e)
161 xsp_param[5] = DEFAULT_APP_MAPPINGS;
164 Logger.Log.Debug ("Starting Internal Web Server");
166 int retVal = 0;
167 try {
168 //Start beagled internal web server (BeagleXsp)
169 retVal = Mono.ASPNET.Server.initXSP(xsp_param, out appServer);
171 catch (ArgumentException e) {
172 //Retry with default application mappings:
173 xsp_param[5] = DEFAULT_APP_MAPPINGS;
174 retVal = Mono.ASPNET.Server.initXSP(xsp_param, out appServer);
176 catch (System.Net.Sockets.SocketException) {
177 Logger.Log.Error ("Error starting Internal Web Server (retVal={0})", retVal);
178 Logger.Log.Error("There is probably another beagled instance running. "
179 + "Use --replace to replace the running service");
182 if (retVal == 0)
183 Logger.Log.Debug("BeagleXSP Applications list: " + xsp_param[5]);
186 public static void Stop()
188 Logger.Log.Info ("Stopping WebServiceBackEnd");
189 if (appServer != null) {
190 appServer.Stop();
191 appServer = null;
194 Process pr = new Process ();
195 pr.StartInfo.UseShellExecute = true;
196 pr.StartInfo.FileName = "rm";
197 pr.StartInfo.Arguments = " -rf " + PathFinder.StorageDir + "/img/*";
199 try {
200 pr.Start ();
201 pr.WaitForExit();
202 pr.Close();
203 pr.Dispose();
205 catch (Exception e) { }
207 WebBackEnd.cleanup();
208 instance = null;
211 /////////////////////////////////////////////////////////////////////////////////////////
213 private void WebServicesConfigurationChanged (Conf.Section section)
215 Logger.Log.Info("WebServicesConfigurationChanged EventHandler invoked");
216 if (! (section is Conf.WebServicesConfig))
217 return;
219 Conf.WebServicesConfig wsc = (Conf.WebServicesConfig) section;
221 if (web_global != wsc.AllowGlobalAccess) {
222 // Update AllowGlobalAccess configuration:
223 web_global = wsc.AllowGlobalAccess;
224 Logger.Log.Info("WebServicesBackEnd: Global WebServicesAccess {0}", web_global ? "Enabled" : "Disabled");
227 if (wsc.PublicFolders.Count == 0)
228 return;
230 Logger.Log.Warn("WebServicesBackEnd: Changes in PublicFolders configuration doesn't take effect until Beagle daemon is restarted!");
232 /* Note: ExternalAccessFilter Matchers can be updated,
233 but app mapping changes in BeagleXsp Server require it to be restarted !
235 ArrayList newList = new ArrayList();
236 foreach (string pf in wsc.PublicFolders) {
237 if (pf == null || pf == "")
238 continue;
240 newList.Add (new NetBeagleHandler (host, port, this));
243 if (usingPublicFoldersDotCfgFile) {
244 usingPublicFoldersDotCfgFile = false;
245 log.Warn("NetBeagleQueryable: Duplicate configuration of PublicFolders in '~/.beagle/publicfolders.cfg' and '~/.beagle/config/webservices.xml' !");
246 log.Info("NetBeagleQueryable: Remove '~/.beagle/publicfolders.cfg' file. Use 'beagle-config' instead to setup public folders.");
247 log.Info("NetBeagleQueryable: Replacing PublicFoldersList with new list from \"webservices.xml\" having {0} node(s)", newList.Count);
250 AccessFilter.ReplaceAccessFilter(newList);
254 /////////////////////////////////////////////////////////////////////////////////////////
256 //KNV: If needed, we can convert this to a Singleton, adding a
257 // static Factory method to get the singleton instance reference,
258 // so that front-end code always gets hold of same instance.
260 static WebServiceBackEnd instance = null;
262 private Hashtable resultTable;
263 private Hashtable sessionTable;
265 public WebServiceBackEnd() {
267 resultTable = Hashtable.Synchronized(new Hashtable());
268 sessionTable = Hashtable.Synchronized(new Hashtable());
270 Conf.Subscribe (typeof (Conf.WebServicesConfig), new Conf.ConfigUpdateHandler (WebServicesConfigurationChanged));
273 ~WebServiceBackEnd() {
274 resultTable.Clear();
275 sessionTable.Clear();
278 public bool allowGlobalAccess {
279 get { return web_global; }
282 public static void init()
284 if (instance == null) {
286 instance = new WebServiceBackEnd();
288 //TCP Channel Listener registered in beagledWeb:init()
289 //ChannelServices.RegisterChannel(new TcpChannel(8347));
290 WellKnownServiceTypeEntry WKSTE =
291 new WellKnownServiceTypeEntry(typeof(WebServiceBackEnd),
292 "WebServiceBackEnd.rem", WellKnownObjectMode.Singleton);
293 RemotingConfiguration.ApplicationName="beagled";
294 RemotingConfiguration.RegisterWellKnownServiceType(WKSTE);
298 void OnHitsAdded (QueryResult qres, ICollection hits)
300 if (resultTable.Contains(qres)) {
302 SessionData sdata = ((SessionData) resultTable[qres]);
303 ArrayList results = sdata.results;
304 bool localReq = sdata.localRequest;
306 if (localReq){
307 lock (results.SyncRoot)
308 results.AddRange(hits);
310 else {
311 //Query query = sdata.query;
312 lock (results.SyncRoot) {
313 foreach (Hit h in hits)
314 if (h.UriAsString.StartsWith(NetworkedBeagle.BeagleNetPrefix) ||
315 AccessFilter.FilterHit(h))
316 results.Add(h);
322 void removeUris(ArrayList results, ICollection uris)
324 foreach(Uri u in uris)
325 foreach(Hit h in results)
326 if (h.Uri.Equals (u) && h.Uri.Fragment == u.Fragment) {
327 lock (results.SyncRoot) {
328 results.Remove(h);
330 break;
334 void OnHitsSubtracted (QueryResult qres, ICollection uris)
336 if (resultTable.Contains(qres)) {
337 SessionData sdata = ((SessionData) resultTable[qres]);
338 ArrayList results = sdata.results;
339 removeUris(results, uris);
343 void OnFinished (QueryResult qres)
345 DetachQueryResult (qres);
348 void OnCancelled (QueryResult qres)
350 DetachQueryResult (qres);
353 void AttachQueryResult (QueryResult qres, SessionData sdata)
355 if (qres != null) {
357 qres.HitsAddedEvent += OnHitsAdded;
358 qres.HitsSubtractedEvent += OnHitsSubtracted;
359 qres.FinishedEvent += OnFinished;
360 qres.CancelledEvent += OnCancelled;
362 resultTable.Add(qres, sdata);
366 void DetachQueryResult (QueryResult qres)
368 if (qres != null) {
370 if (resultTable.Contains(qres)) {
371 SessionData sdata = ((SessionData) resultTable[qres]);
372 sdata.results.Sort();
374 qres.HitsAddedEvent -= OnHitsAdded;
375 qres.HitsSubtractedEvent -= OnHitsSubtracted;
376 qres.FinishedEvent -= OnFinished;
377 qres.CancelledEvent -= OnCancelled;
379 resultTable.Remove(qres);
380 qres.Dispose ();
384 private class SessionData {
386 private bool _localRequest;
387 private ArrayList _results;
388 private Query _query;
390 public SessionData (Query _query, ArrayList _results, bool _localRequest)
392 this._localRequest = _localRequest;
393 this._results = _results;
394 this._query = _query;
397 public bool localRequest {
398 get { return _localRequest; }
401 public ArrayList results {
402 get { return _results; }
406 public Query query {
407 get { return _query; }
411 public string[] ICollection2StringList(ICollection il)
413 if (il == null)
414 return new string[0] ;
416 string[] sl = new string[il.Count];
418 il.CopyTo(sl, 0);
420 return sl;
423 private const int MAX_RESULTS_PER_CALL = 20;
425 public const int SC_QUERY_SUCCESS = 0;
426 public const int SC_INVALID_QUERY = -1;
427 public const int SC_UNAUTHORIZED_ACCESS = -2;
428 public const int SC_INVALID_SEARCH_TOKEN = -3;
429 public const int SC_DUPLICATE_QUERY = -4;
431 //Full beagledQuery
432 public SearchResult doQuery(SearchRequest sreq, bool isLocalReq)
434 SearchResult sr = null;
436 if (sreq.text == null || sreq.text.Length == 0 ||
437 (sreq.text.Length == 1 && sreq.text[0].Trim() == "") ) {
439 sr = new SearchResult();
440 sr.statusCode = SC_INVALID_QUERY;
441 sr.statusMsg = "Error: No search terms specified";
442 return sr;
445 Query query = new Query();
447 string searchString = "";
448 foreach (string text in sreq.text) {
449 query.AddText(text);
450 searchString += (searchString.Length == 0) ? text:" " + text;
453 Logger.Log.Info("WebServiceBackEnd: Received {0} WebService Query with search term: {1}", isLocalReq ? "Local":"External", searchString.Trim());
455 if (sreq.mimeType != null && sreq.mimeType[0] != null)
456 foreach (string mtype in sreq.mimeType)
457 query.AddMimeType(mtype);
459 if (sreq.searchSources != null && sreq.searchSources[0] != null)
460 foreach (string src in sreq.searchSources)
461 query.AddSource(src);
463 //If needed, check to restrict queries to System or Neighborhood domain, can be added here
464 if (sreq.qdomain > 0)
465 query.AddDomain(sreq.qdomain);
467 if (!isLocalReq) { //External Request, check if this Node is already processing it
469 lock (this) {
470 if ((sreq.searchId != 0) && NetworkedBeagle.IsCachedRequest(sreq.searchId)) {
472 sr = new SearchResult();
473 sr.numResults = sr.totalResults = sr.firstResultIndex = 0;
474 sr.hitResults = new HitResult[sr.numResults];
475 sr.searchToken = "";
477 sr.statusCode = SC_DUPLICATE_QUERY;
478 sr.statusMsg = "Error: Duplicate Query loopback";
479 Logger.Log.Warn("WebServiceBackEnd: Received duplicate Query for a query already in process!");
480 Logger.Log.Warn("WebServiceBackEnd: Check NetBeagle configuration on all nodes to remove possible loops");
483 if (sreq.hopCount >= 5) {
484 //If request has traversed 5 nodes in reaching here, stop cascading.
485 //Make it a Local Query.
486 query.RemoveDomain(sreq.qdomain);
487 query.AddDomain(QueryDomain.System);
490 if ((sr == null) && (sreq.searchId != 0) )
491 NetworkedBeagle.CacheRequest(query, sreq.searchId, sreq.hopCount + 1);
494 if (sr != null)
495 return sr;
497 //Logger.Log.Info("New external Query: searchId = {0}", sreq.searchId);
500 ArrayList results = ArrayList.Synchronized(new ArrayList());
502 QueryResult qres = new QueryResult ();
504 string searchToken = TokenGenerator();
506 SessionData sdata = new SessionData(query, results, isLocalReq);
508 AttachQueryResult (qres, sdata);
510 /* Include this code, if sessionID passed from front-end:
511 if (sessionTable.Contains(searchToken))
512 sessionTable[searchToken] = sdata;
513 else
515 sessionTable.Add(searchToken, sdata);
517 QueryDriver.DoQueryLocal (query, qres);
519 while (resultTable.Contains(qres) && (results.Count < MAX_RESULTS_PER_CALL) )
520 Thread.Sleep(100);
522 //Console.WriteLine("WebServiceBackEnd: Got {0} results from beagled", results.Count);
523 sr = new SearchResult();
525 if (results.Count > 0)
527 lock (results.SyncRoot) { //Lock results ArrayList to prevent more Hits added till we've processed doQuery
529 sr.numResults = results.Count < MAX_RESULTS_PER_CALL ? results.Count: MAX_RESULTS_PER_CALL;
530 sr.hitResults = new HitResult[sr.numResults];
532 string hitUri;
533 for (int i = 0; i < sr.numResults; i++) {
535 Hit h = (Hit) results[i];
537 string snippet;
539 //Queryable queryable = h.SourceObject as Queryable;
540 Queryable queryable = QueryDriver.GetQueryable (h.SourceObjectName);
542 if (queryable == null)
543 snippet = "ERROR: hit.SourceObject is null, uri=" + h.Uri;
544 else
545 snippet = queryable.GetSnippet (ICollection2StringList(query.StemmedText), h);
547 sr.hitResults[i] = new HitResult();
549 hitUri = h.UriAsString;
550 if (isLocalReq || hitUri.StartsWith(NetworkedBeagle.BeagleNetPrefix))
551 sr.hitResults[i].uri = hitUri;
552 else
553 sr.hitResults[i].uri = AccessFilter.TranslateHit(h);
555 sr.hitResults[i].resourceType = h.Type;
556 sr.hitResults[i].mimeType = h.MimeType;
557 sr.hitResults[i].source = h.Source;
558 sr.hitResults[i].score = h.Score;
560 int plen = h.Properties.Count;
561 sr.hitResults[i].properties = new HitProperty[plen];
562 for (int j = 0; j < plen; j++) {
563 Property p = (Property) h.Properties[j];
564 sr.hitResults[i].properties[j] = new HitProperty();
565 sr.hitResults[i].properties[j].PKey = p.Key;
566 sr.hitResults[i].properties[j].PVal = p.Value;
567 sr.hitResults[i].properties[j].IsMutable = p.IsMutable;
568 sr.hitResults[i].properties[j].IsSearched = p.IsSearched;
571 sr.hitResults[i].hashCode = h.GetHashCode ();
573 if (snippet != null)
574 sr.hitResults[i].snippet = snippet.Trim();
576 } //end lock
577 }// end if
578 else {
580 sr.numResults = 0;
581 sr.hitResults = new HitResult[sr.numResults];
584 sr.totalResults = results.Count;
586 sr.firstResultIndex = 0;
587 sr.searchToken = "";
589 if (sr.totalResults > 0)
590 sr.searchToken = searchToken;
592 sr.statusCode = SC_QUERY_SUCCESS;
593 sr.statusMsg = "Success";
594 Logger.Log.Info("WebServiceBackEnd: Total Results = " + sr.totalResults);
595 return sr;
598 public SearchResult getResults(GetResultsRequest req, bool isLocalReq)
600 int startIndex = req.startIndex;
601 string searchToken = req.searchToken;
603 SearchResult sr = new SearchResult();
604 sr.numResults = 0;
606 if (!sessionTable.ContainsKey(searchToken)) {
607 sr.statusCode = SC_INVALID_SEARCH_TOKEN;
608 sr.statusMsg = "Error: Invalid Search Token";
609 Logger.Log.Warn("GetResults: Invalid Search Token received ");
610 return sr;
613 ArrayList results = ((SessionData)sessionTable[searchToken]).results;
614 if (results == null) {
615 sr.statusCode = SC_INVALID_SEARCH_TOKEN;
616 sr.statusMsg = "Error: Invalid Search Token";
617 Logger.Log.Warn("GetResults: Invalid Search Token received ");
618 return sr;
621 lock (results.SyncRoot) { //Lock results ArrayList to prevent more Hits getting added till we've processed doQuery
623 int i = 0;
625 if (startIndex < results.Count)
626 sr.numResults = (results.Count < startIndex + MAX_RESULTS_PER_CALL) ? (results.Count - startIndex): MAX_RESULTS_PER_CALL;
628 sr.hitResults = new HitResult[sr.numResults];
630 string hitUri;
631 for (int k = startIndex; (i < sr.numResults) && (k < results.Count); k++) {
633 Hit h = (Hit) results[k];
635 sr.hitResults[i] = new HitResult();
637 // GetResults will NOT return Snippets by default. Client must make explicit GetSnippets request to get snippets for these hits.
638 // Not initializing sr.hitResults[i].snippet implies there is no <snippets> element in HitResult XML response.
640 hitUri = h.UriAsString;
641 if (isLocalReq || hitUri.StartsWith(NetworkedBeagle.BeagleNetPrefix))
642 sr.hitResults[i].uri = hitUri;
643 else
644 sr.hitResults[i].uri = AccessFilter.TranslateHit(h);
646 sr.hitResults[i].resourceType = h.Type;
647 sr.hitResults[i].mimeType = h.MimeType;
648 sr.hitResults[i].source = h.Source;
649 sr.hitResults[i].score = h.Score;
651 int plen = h.Properties.Count;
652 sr.hitResults[i].properties = new HitProperty[plen];
653 for (int j = 0; j < plen; j++) {
654 Property p = (Property) h.Properties[j];
655 sr.hitResults[i].properties[j] = new HitProperty();
656 sr.hitResults[i].properties[j].PKey = p.Key;
657 sr.hitResults[i].properties[j].PVal = p.Value;
658 sr.hitResults[i].properties[j].IsMutable = p.IsMutable;
659 sr.hitResults[i].properties[j].IsSearched = p.IsSearched;
662 sr.hitResults[i].hashCode = h.GetHashCode ();
664 i++;
666 } //end lock
668 sr.totalResults = results.Count;
670 sr.firstResultIndex = startIndex;
671 sr.searchToken = "";
673 if (sr.totalResults > 0)
674 sr.searchToken = searchToken;
676 sr.statusCode = SC_QUERY_SUCCESS;
677 sr.statusMsg = "Success";
678 //Console.WriteLine("WebServiceQuery: Total Results = " + sr.totalResults);
679 return sr;
682 public static string InvalidHitSnippetError = "ERROR: Invalid or Duplicate Hit Id";
683 public HitSnippet[] getSnippets(GetSnippetsRequest req)
685 HitSnippet[] response;
686 string searchToken = req.searchToken;
687 int[] hitHashCodes = req.hitHashCodes;
689 if (!sessionTable.ContainsKey(searchToken)) {
691 response = new HitSnippet[0];
692 Logger.Log.Warn("GetSnippets: Invalid Search Token received ");
693 return response;
696 ArrayList results = ((SessionData)sessionTable[searchToken]).results;
697 if ((results == null) || (results.Count == 0)) {
699 response = new HitSnippet[0];
700 Logger.Log.Warn("GetSnippets: Invalid Search Token received ");
701 return response;
704 int i = 0;
705 ArrayList hashCodeList = new ArrayList();
706 hashCodeList.AddRange(hitHashCodes);
708 response = new HitSnippet[hitHashCodes.Length];
709 Logger.Log.Debug("GetSnippets invoked with {0} hitHashCodes", hitHashCodes.Length);
711 Query query = ((SessionData)sessionTable[searchToken]).query;
713 lock (results.SyncRoot) {
715 string snippet = null;
716 foreach (Hit h in results) {
718 int hashCode = h.GetHashCode();
719 if (hashCodeList.Contains(hashCode)) {
721 hashCodeList.Remove(hashCode);
723 //Queryable queryable = h.SourceObject as Queryable;
724 Queryable queryable = QueryDriver.GetQueryable (h.SourceObjectName);
726 if (queryable == null)
727 snippet = "ERROR: hit.SourceObject is null, uri=" + h.Uri;
728 else
729 snippet = queryable.GetSnippet (ICollection2StringList(query.StemmedText), h);
731 //GetSnippets always invoked on Target Beagle Node where hits originate:
732 if (snippet == null)
733 snippet = "";
735 HitSnippet hs = new HitSnippet();
736 hs.hashCode = hashCode;
737 hs.snippet = snippet.Trim();
738 response[i++] = hs;
740 if ((hashCodeList.Count == 0) || (i == hitHashCodes.Length))
741 return response;
743 } //end foreach
744 } //end lock
746 foreach (int hashCode in hashCodeList) {
747 HitSnippet hs = new HitSnippet();
748 hs.hashCode = hashCode;
749 hs.snippet = InvalidHitSnippetError;
750 response[i++] = hs;
752 if (i == hitHashCodes.Length)
753 break;
755 Logger.Log.Warn("GetSnippets invoked some invalid hitIds");
757 return response;
760 //Returns a 15-char random alpha-numeric string similar to ASP.NET sessionId
761 private string TokenGenerator()
763 const int TOKEN_LEN = 15;
765 Random r = new Random();
766 string token = ((Char)((int)((26 * r.NextDouble()) + 'a')) + System.Guid.NewGuid().ToString()).Substring (0, TOKEN_LEN);
768 char alpha = (Char)((int)((26 * r.NextDouble()) + 'a'));
770 return (token.Replace('-', alpha));
775 ////////////////////////////////////////////////////////////////////////////////////////////
776 ///////////// WebService Request-Response Data Structures
777 ////////////////////////////////////////////////////////////////////////////////////////////
779 /* These are duplicate definitions to the ones in WebServiceProxy.cs.
780 So, we will define and use these from one central place: WebServiceProxy.cs
782 [Serializable()]
783 public class SearchRequest {
785 public string[] text;
786 public string[] mimeType;
787 public string[] searchSources;
788 public QueryDomain qdomain;
789 //Unique searchId across network
790 public int searchId;
791 public int hopCount;
794 [Serializable()]
795 public class HitProperty {
797 private string pKey="";
798 public string PKey {
799 get {return pKey;}
800 set {pKey = value;}
803 private string pVal="";
804 public string PVal {
805 get {return pVal;}
806 set {pVal = value;}
809 private bool isMutable;
810 public bool IsMutable {
811 get { return isMutable; }
812 set { isMutable = value; }
815 private bool isSearched;
816 public bool IsSearched {
817 get { return isSearched; }
818 set { isSearched = value; }
822 [Serializable()]
823 public class HitResult {
825 public string uri;
826 //public string parentUri;
827 public string resourceType;
828 public string mimeType;
829 public string source;
830 public double score;
831 public HitProperty[] properties;
832 //FIXME: public xxx[] data;
833 public int hashCode;
834 public string snippet;
837 [Serializable()]
838 public class SearchResult {
840 public int statusCode; //ReturnCode for programmatic processing
841 public string statusMsg; //User-friendly return message
843 public string searchToken; //Token identifying the query,
844 //to enable follow-up queries
845 public int firstResultIndex; //Index of first result in this response
846 public int numResults; //No. of results in this response
847 public int totalResults; //Total no. of results from the query
848 public HitResult[] hitResults;
851 [Serializable()]
852 public class HitSnippet {
853 public int hashCode;
854 public string snippet;