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
;
34 using System
.Diagnostics
;
36 using System
.Runtime
.Remoting
;
37 using System
.Runtime
.Remoting
.Channels
;
38 using System
.Runtime
.Remoting
.Channels
.Tcp
;
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
,
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()
74 Logger
.Log
.Warn("Beagle running with WebServices DISABLED\n");
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
);
86 Logger
.Log
.Error("Caught exception {0} in Dns.GetHostName: ", ex
.Message
);
87 Logger
.Log
.Error("Resetting hostname to \"localhost\"");
88 hostname
= "localhost";
92 web_global
= Conf
.WebServices
.AllowGlobalAccess
;
94 //start web-access server first
95 Logger
.Log
.Debug ("Starting WebBackEnd");
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";
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
;
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();
161 xsp_param
[5] = DEFAULT_APP_MAPPINGS
;
164 Logger
.Log
.Debug ("Starting Internal Web Server");
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");
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) {
194 Process pr
= new Process ();
195 pr
.StartInfo
.UseShellExecute
= true;
196 pr
.StartInfo
.FileName
= "rm";
197 pr
.StartInfo
.Arguments
= " -rf " + PathFinder
.StorageDir
+ "/img/*";
205 catch (Exception e
) { }
207 WebBackEnd
.cleanup();
211 /////////////////////////////////////////////////////////////////////////////////////////
213 private void WebServicesConfigurationChanged (Conf
.Section section
)
215 Logger
.Log
.Info("WebServicesConfigurationChanged EventHandler invoked");
216 if (! (section
is Conf
.WebServicesConfig
))
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)
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 == "")
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() {
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
;
307 lock (results
.SyncRoot
)
308 results
.AddRange(hits
);
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
))
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
) {
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
)
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
)
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
);
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; }
407 get { return _query; }
411 public string[] ICollection2StringList(ICollection il
)
414 return new string[0] ;
416 string[] sl
= new string[il
.Count
];
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;
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";
445 Query query
= new Query();
447 string searchString
= "";
448 foreach (string text
in sreq
.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
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
];
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);
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;
515 sessionTable
.Add(searchToken
, sdata
);
517 QueryDriver
.DoQueryLocal (query
, qres
);
519 while (resultTable
.Contains(qres
) && (results
.Count
< MAX_RESULTS_PER_CALL
) )
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
];
533 for (int i
= 0; i
< sr
.numResults
; i
++) {
535 Hit h
= (Hit
) results
[i
];
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
;
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
;
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 ();
574 sr
.hitResults
[i
].snippet
= snippet
.Trim();
581 sr
.hitResults
= new HitResult
[sr
.numResults
];
584 sr
.totalResults
= results
.Count
;
586 sr
.firstResultIndex
= 0;
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
);
598 public SearchResult
getResults(GetResultsRequest req
, bool isLocalReq
)
600 int startIndex
= req
.startIndex
;
601 string searchToken
= req
.searchToken
;
603 SearchResult sr
= new SearchResult();
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 ");
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 ");
621 lock (results
.SyncRoot
) { //Lock results ArrayList to prevent more Hits getting added till we've processed doQuery
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
];
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
;
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 ();
668 sr
.totalResults
= results
.Count
;
670 sr
.firstResultIndex
= startIndex
;
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);
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 ");
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 ");
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
;
729 snippet
= queryable
.GetSnippet (ICollection2StringList(query
.StemmedText
), h
);
731 //GetSnippets always invoked on Target Beagle Node where hits originate:
735 HitSnippet hs
= new HitSnippet();
736 hs
.hashCode
= hashCode
;
737 hs
.snippet
= snippet
.Trim();
740 if ((hashCodeList
.Count
== 0) || (i
== hitHashCodes
.Length
))
746 foreach (int hashCode
in hashCodeList
) {
747 HitSnippet hs
= new HitSnippet();
748 hs
.hashCode
= hashCode
;
749 hs
.snippet
= InvalidHitSnippetError
;
752 if (i
== hitHashCodes
.Length
)
755 Logger
.Log
.Warn("GetSnippets invoked some invalid hitIds");
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
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
795 public class HitProperty {
797 private string pKey="";
803 private string pVal="";
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; }
823 public class HitResult {
826 //public string parentUri;
827 public string resourceType;
828 public string mimeType;
829 public string source;
831 public HitProperty[] properties;
832 //FIXME: public xxx[] data;
834 public string snippet;
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;
852 public class HitSnippet {
854 public string snippet;