// // WebBackEnd.cs // // Copyright (C) 2005 Novell, Inc. // // Authors: // Vijay K. Nanjundaswamy (knvijay@novell.com) // // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // using System; using System.Text; using System.Collections; using System.Diagnostics; using System.Threading; using System.Reflection; using System.IO; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using Beagle; using Beagle.Util; using BT = Beagle.Tile; using Beagle.Daemon; namespace Beagle.WebService { [Serializable()] public struct webArgs { public string sessId; public string searchString; public string searchSource; public bool isLocalReq; public bool globalSearch; } public class WebBackEnd: MarshalByRefObject{ static WebBackEnd instance = null; static Logger log = Logger.Get ("WebBackEnd"); private Hashtable result; private Hashtable sessionResp; public WebBackEnd() { result = Hashtable.Synchronized(new Hashtable()); sessionResp = Hashtable.Synchronized(new Hashtable()); } ~WebBackEnd() { result.Clear(); sessionResp.Clear(); } public bool allowGlobalAccess { get { return WebServiceBackEnd.web_global; } } public string HostName { get { return WebServiceBackEnd.hostname; } } static TcpChannel tch1 = null; public static void init() { if (instance == null) { instance = new WebBackEnd(); if (tch1 == null) { tch1 = new TcpChannel(8347); //Register TCP Channel Listener ChannelServices.RegisterChannel(tch1); WellKnownServiceTypeEntry WKSTE = new WellKnownServiceTypeEntry(typeof(WebBackEnd), "WebBackEnd.rem", WellKnownObjectMode.Singleton); RemotingConfiguration.ApplicationName="beagled"; RemotingConfiguration.RegisterWellKnownServiceType(WKSTE); } } } public static void cleanup() { if (tch1 != null) { tch1.StopListening(null); ChannelServices.UnregisterChannel(tch1); tch1 = null; } instance = null; } void OnHitsAdded (QueryResult qres, ICollection hits) { if (result.Contains(qres)) { Resp resp = ((Resp) result[qres]); BT.SimpleRootTile root = resp.resultPair.rootTile; ArrayList hitsCopy = resp.resultPair.hitsCopy; lock (root) { if (resp.isLocalReq) { root.Add(hits); lock (hitsCopy.SyncRoot) hitsCopy.AddRange(hits); } else { foreach (Hit h in hits) if (h.UriAsString.StartsWith(NetworkedBeagle.BeagleNetPrefix) || WebServiceBackEnd.AccessFilter.FilterHit(h)) { root.Add(h); lock (hitsCopy.SyncRoot) hitsCopy.Add(h); } } } } } void removeUris(ArrayList res, ICollection uris) { foreach(Uri u in uris) foreach(Hit h in res) if (h.Uri.Equals (u) && h.Uri.Fragment == u.Fragment) { lock (res.SyncRoot) { res.Remove(h); } break; } } void OnHitsSubtracted (QueryResult qres, ICollection uris) { if (result.Contains(qres)) { BT.SimpleRootTile root = ((Resp) result[qres]).resultPair.rootTile; lock (root) { root.Subtract (uris); removeUris(((Resp) result[qres]).resultPair.hitsCopy, uris); } } } void OnFinished (QueryResult qres) { if (result.Contains(qres)) log.Info("WebBackEnd:OnFinished() - Got {0} results from beagled QueryDriver", ((Resp) result[qres]).resultPair.rootTile.HitCollection.NumResults); DetachQueryResult(qres); } void OnCancelled (QueryResult qres) { DetachQueryResult(qres); } private void AttachQueryResult (QueryResult qres, Resp resp) { if (qres != null) { qres.HitsAddedEvent += OnHitsAdded; qres.HitsSubtractedEvent += OnHitsSubtracted; qres.FinishedEvent += OnFinished; qres.CancelledEvent += OnCancelled; result.Add(qres, resp); } } private void DetachQueryResult (QueryResult qres) { if (qres != null) { if (result.Contains(qres)) { Resp resp = ((Resp) result[qres]); ArrayList hitsCopy = resp.resultPair.hitsCopy; if (hitsCopy != null) hitsCopy.Sort(); resp.bufferContext.maxDisplayed = 0; result.Remove(qres); } qres.HitsAddedEvent -= OnHitsAdded; qres.HitsSubtractedEvent -= OnHitsSubtracted; qres.FinishedEvent -= OnFinished; qres.CancelledEvent -= OnCancelled; qres.Dispose (); } } const string NO_RESULTS = "No results."; private string getResultsLabel(BT.SimpleRootTile root) { string label; if (root.HitCollection.NumResults == 0) label = NO_RESULTS; else if (root.HitCollection.FirstDisplayed == 0) label = String.Format ("{0} results of {1} are shown.", root.HitCollection.LastDisplayed + 1, root.HitCollection.NumResults); else label = String.Format ("Results {0} through {1} of {2} are shown.", root.HitCollection.FirstDisplayed + 1, root.HitCollection.LastDisplayed + 1, root.HitCollection.NumResults); return label; } public bool canForward(string sessId) { Resp resp = (Resp) sessionResp[sessId]; if (resp == null) return false; BT.SimpleRootTile root = resp.resultPair.rootTile; return (root != null)? root.HitCollection.CanPageForward:false; } public string doForward(string sessId) { Resp resp = (Resp) sessionResp[sessId]; if (!canForward(sessId) || (resp == null)) return NO_RESULTS; BT.SimpleRootTile root = resp.resultPair.rootTile; if (root != null) { lock (root) { root.HitCollection.PageForward (); bufferRenderContext bctx = resp.bufferContext; bctx.init(); root.Render(bctx); return (getResultsLabel(root) + (resp.isLocalReq ? bctx.buffer:bctx.bufferForExternalQuery)); } } return NO_RESULTS; } public bool canBack(string sessId) { Resp resp = (Resp) sessionResp[sessId]; if (resp == null) return false; BT.SimpleRootTile root = resp.resultPair.rootTile; return (root != null) ? root.HitCollection.CanPageBack:false; } public string doBack(string sessId) { Resp resp = (Resp) sessionResp[sessId]; if (!canBack(sessId) || (resp == null)) return NO_RESULTS; BT.SimpleRootTile root = resp.resultPair.rootTile; if (root != null) { lock (root) { root.HitCollection.PageBack(); bufferRenderContext bctx = resp.bufferContext; bctx.init(); root.Render(bctx); return (getResultsLabel(root) + (resp.isLocalReq ? bctx.buffer:bctx.bufferForExternalQuery)); } } return NO_RESULTS; } public bool NetworkBeagleActive { get {return NetworkedBeagle.NetBeagleListActive;} } public string doQuery(webArgs wargs) { if (wargs.sessId == null || wargs.searchString == null || wargs.searchString == "") return NO_RESULTS; log.Debug("WebBackEnd: Got Search String: " + wargs.searchString); Query query = new Query(); query.AddText (wargs.searchString); if (wargs.searchSource != null && wargs.searchSource != "") { query.AddSource(wargs.searchSource); query.AddDomain(QueryDomain.System); } else query.AddDomain (wargs.globalSearch ? QueryDomain.Global:QueryDomain.System); QueryResult qres = new QueryResult (); //Note: QueryDriver.DoQuery() local invocation is used. //The root tile is used only for adding hits and generating html. BT.SimpleRootTile root = new BT.SimpleRootTile (); root.Query = query; //root.SetSource (searchSource); Do not SetSource on root! ResultPair rp = new ResultPair(root); bufferRenderContext bctx = new bufferRenderContext(rp); Resp resp = new Resp(rp, bctx, wargs.isLocalReq); AttachQueryResult (qres, resp); //Add sessionId-Resp mapping if (sessionResp.Contains(wargs.sessId)) sessionResp[wargs.sessId] = resp; else sessionResp.Add(wargs.sessId, resp); log.Info("WebBackEnd: Starting Query for string \"{0}\"", wargs.searchString); QueryDriver.DoQueryLocal (query, qres); //Wait only till we have enough results to display while ((result.Contains(qres)) && (root.HitCollection.NumResults < 10)) Thread.Sleep(100); if (root.HitCollection.IsEmpty) return NO_RESULTS; lock (root) { root.Render(bctx); return (getResultsLabel(root) + (wargs.isLocalReq ? bctx.buffer:bctx.bufferForExternalQuery)); } } public void dispatchAction (string sessId, string actionString) { string tile_id = null, action = null; bool actionDone = false; //if (actionString.StartsWith ("dynaction:")) { bufferRenderContext b = ((Resp)sessionResp[sessId]).bufferContext; if (b != null) actionDone = b.DoAction(actionString); //} if (actionDone) return; if (actionString.StartsWith ("action:")) { int pos1 = "action:".Length; int pos2 = actionString.IndexOf ("!"); if (pos2 <= 0) return; tile_id = actionString.Substring (pos1, pos2 - pos1); action = actionString.Substring (pos2 + 1); log.Debug("WebBackEnd tile_id: {0}, action: {1}", tile_id, action); BT.Tile t = ((Resp)sessionResp[sessId]).GetTile (tile_id); if (t == null) return; MethodInfo info = t.GetType().GetMethod (action, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, CallingConventions.Any, new Type[] {}, null); if (info == null) { log.Warn ("WebBackEnd:dispatchAction couldn't find method called {0}", action); return; } object[] attrs = info.GetCustomAttributes (false); foreach (object attr in attrs) { if (attr is BT.TileActionAttribute) { info.Invoke (t, null); return; } } log.Warn ("WebBackEnd:dispatchAction {0} does not have the TileAction attribute", t); } string command = null; string commandArgs = null; if (actionString.StartsWith ("http://") || actionString.StartsWith ("file://")) { command = "gnome-open"; commandArgs = "'" + actionString + "'"; } else if (actionString.StartsWith ("mailto:")) { command = "evolution"; commandArgs = actionString; } if (command != null) { Process p = new Process (); p.StartInfo.UseShellExecute = false; p.StartInfo.FileName = command; if (commandArgs != null) //if (args != null) p.StartInfo.Arguments = commandArgs; try { p.Start (); } catch { } } } ////////////////////////////////////////////////////////////////////////// private class ResultPair { private BT.SimpleRootTile _rootTile; private ArrayList _hitsCopy; public ResultPair(BT.SimpleRootTile rootTile) { this._rootTile = rootTile; _hitsCopy = ArrayList.Synchronized(new ArrayList()); } public BT.SimpleRootTile rootTile { get { return _rootTile; } } public ArrayList hitsCopy { get { return _hitsCopy; } } } private class Resp { private ResultPair _rp; private bufferRenderContext bufCtx = null; private bool _localRequest; private Hashtable tileTab = null; public Resp(ResultPair rp, bufferRenderContext bCtx, bool isLocalReq) { this._rp = rp; this.bufCtx = bCtx; this._localRequest = isLocalReq; this.tileTab = bCtx.table; } public ResultPair resultPair { get { return _rp; } } public bufferRenderContext bufferContext { get { return bufCtx; } } public bool isLocalReq { get { return _localRequest; } } public BT.Tile GetTile (string key) { if (key == "") return resultPair.rootTile; return (Beagle.Tile.Tile) tileTab[key]; } } ////////////////////////////////////////////////////////////////////////// private class bufferRenderContext : BT.TileRenderContext { private ResultPair _rp; private Hashtable tileTable = null; private Hashtable actionTable = null; int actionId = 1; private System.Text.StringBuilder sb; private bool renderStylesDone = false; public bufferRenderContext (ResultPair rp) { this._rp = rp; this.tileTable = Hashtable.Synchronized(new Hashtable()); this.actionTable = new Hashtable (); init(); } public string buffer { get { return sb.ToString(); } } public Hashtable table { get { return tileTable; } } public string bufferForExternalQuery { get { //Substitute "action:_tile_id!Open" with "http://host:port/beagle?xxxx" string s; string[] list = sb.ToString().Split('\"'); for (int i = 0; i < list.Length; i++) { s = list[i]; if (s.StartsWith("action") && s.EndsWith("!Open")) { string[] s1 = s.Split(':'); if (s1.Length > 1) { string[] s2 = s1[1].Split('!'); if (s2.Length > 1) { BT.Tile t = (BT.Tile) table[s2[0]]; list[i] = WebServiceBackEnd.AccessFilter.TranslateHit(t.Hit); t.Uri = new Uri(list[i]); } } } } return String.Join ("\"", list); } } public void init() { lock (this) { sb = new StringBuilder(4096); renderStylesDone = false; tileTable.Clear(); ClearActions(); tileTable[_rp.rootTile.UniqueKey] = _rp.rootTile; } } ///////////////////////////////////////////////// public void ClearActions () { actionTable.Clear(); actionId = 1; } private string AddAction (BT.TileActionHandler handler) { if (handler == null) return "dynaction:NULL"; string key = "dynaction:" + actionId.ToString (); ++actionId; actionTable [key] = handler; return key; } public bool DoAction (string key) { BT.TileActionHandler handler = (BT.TileActionHandler) actionTable [key]; if (handler != null) { handler (); return true; } return false; } ///////////////////////////////////////////////// override public void Write (string markup) { sb.Append(markup); } override public void Link (string label, BT.TileActionHandler handler) { string key = AddAction (handler); Write ("{1}", key, label); } override public void Tile (BT.Tile tile) { tileTable [tile.UniqueKey] = tile; if (!renderStylesDone) { //KNV: Using static_stylesheet for now. Replace with TileCanvas logic later: Write(static_stylesheet); /* Write (""); */ renderStylesDone = true; } if (tile != null) { if (tile is BT.TileHitCollection) PrefetchSnippetsForNetworkHits((BT.TileHitCollection)tile); tile.Render (this); } } ///////////////////////////////////////////////// // Code to scan forward through result set & prefetch/cache Snippets for Network Hits public int maxDisplayed = 0; const int MAX_HIT_IDS_PER_REQ = 20; //Max no. of hits snippets to seek at a time const int MAX_HITS_AHEAD = 40; //No. of hits ahead of lastDisplayed to scan private bool tenHits = false; //Flag to do Prefetch check only every 10 hits private void PrefetchSnippetsForNetworkHits(BT.TileHitCollection thc) { int lastDisplayed = 0; if (maxDisplayed != 0) lastDisplayed = thc.LastDisplayed + 1; //We have cached snippets for network hits upto maxDisplayed if (lastDisplayed < maxDisplayed) return; maxDisplayed = thc.LastDisplayed + 1; //Do Prefetch check once every ten hits tenHits = !tenHits; if (!tenHits) return; if (lastDisplayed < thc.NumResults) { int limit = 0; ArrayList networkHits = new ArrayList(); if ((thc.NumResults - lastDisplayed) > MAX_HITS_AHEAD) limit = lastDisplayed + MAX_HITS_AHEAD; else limit = thc.NumResults; ArrayList hits = _rp.hitsCopy; lock (hits.SyncRoot) { if (limit > hits.Count) limit = hits.Count; log.Debug("PrefetchSnippets: Scanning result set for Network Hits from {0} to {1}", lastDisplayed, limit); //Get all NetworkHits with snippets field not initialized: for (int si = lastDisplayed; si < limit ; si++) { if ((hits[si] is NetworkHit) && (((NetworkHit)hits[si]).snippet == null)) networkHits.Add((NetworkHit)hits[si]); } } log.Debug("PrefetchSnippets: Found {0} NetworkHits without snippets", networkHits.Count); while (networkHits.Count > 0) { ArrayList nwHitsPerNode = new ArrayList(); string hostnamePort = GetHostnamePort((NetworkHit)networkHits[0]); //Gather NetworkHits from a specific target Networked Beagle foreach (NetworkHit nh in networkHits) { string hnp = GetHostnamePort(nh); if (hnp == null) continue; if (hnp.Equals(hostnamePort)) { if (nwHitsPerNode.Count < MAX_HIT_IDS_PER_REQ) nwHitsPerNode.Add(nh); else break; } } //Remove NetworkHits for this Networked Beagle int i = networkHits.Count; while (--i >= 0) { string hnp = GetHostnamePort((NetworkHit)networkHits[i]); if ((hnp == null) || hnp.Equals(hostnamePort)) networkHits.RemoveAt(i); } if (nwHitsPerNode.Count > 0) { string[] f3 = hostnamePort.Split(':'); if (f3.Length < 2) { log.Warn("PrefetchSnippets: Invalid format netBeagle URI in NetworkHit"); continue; } BeagleWebService wsp = new BeagleWebService(f3[0], f3[1]); string searchToken = GetSearchToken((NetworkHit)nwHitsPerNode[0]); if (searchToken.Equals("beagle")) //Check if it is Older version of Beagle networking searchToken = null; if (searchToken != null) { int[] hitHashCodes = new int [nwHitsPerNode.Count]; for (int j = 0; j < hitHashCodes.Length; j++) hitHashCodes[j] = ((NetContext) ((NetworkHit)nwHitsPerNode[j]).context).hashCode; log.Debug("PrefetchSnippets: Invoking GetSnippets on {0} for {1} hits", wsp.Hostname, nwHitsPerNode.Count); GetSnippetsRequest sreq = new GetSnippetsRequest(); sreq.searchToken = searchToken; sreq.hitHashCodes = hitHashCodes; ReqContext2 rc = new ReqContext2(wsp, nwHitsPerNode, thc); wsp.BeginGetSnippets(sreq, PrefetchSnippetsResponseHandler, rc); } //Signal change in TileHitCollection due to addition of snippets: //_rp.rootTile.HitCollection.ClearSources(null); } } //end while } //end if } private static void PrefetchSnippetsResponseHandler(IAsyncResult ar) { ReqContext2 rc = (ReqContext2)ar.AsyncState; ArrayList nwHits = rc.GetNwHits; BeagleWebService wsp = rc.GetProxy; try { Beagle.Daemon.HitSnippet[] hslist = wsp.EndGetSnippets(ar); int j = 0; if (hslist.Length > 0) { log.Debug("PrefetchSnippetsResponseHandler: Got {0} snippet responses from {1}", hslist.Length, wsp.Hostname); foreach (Beagle.Daemon.HitSnippet hs in hslist) { int i, hitHashCode; string snippet; try { hitHashCode = hs.hashCode; snippet = hs.snippet; } catch (Exception ex2) { log.Warn ("Exception in WebBackEnd: PrefetchSnippetsResponseHandler(), while getting snippet from {1}\n Reason: {2} ", wsp.Hostname + ":" + wsp.Port, ex2.Message); continue; } if (snippet.StartsWith(WebServiceBackEnd.InvalidHitSnippetError)) continue; for (i = 0; i < nwHits.Count; i++) if ( ((NetContext) ((NetworkHit)nwHits[i]).context).hashCode == hitHashCode) { ((NetworkHit)nwHits[i]).snippet = snippet; //log.Debug("\nPrefetchSnippetsResponseHandler: URI" + j++ + "=" + ((NetworkHit)nwHits[i]).UriAsString + "\n Snippet=" + snippet); break; } if (i < nwHits.Count) nwHits.RemoveAt(i); } //end foreach } } catch (Exception ex) { log.Error ("Exception in WebBackEnd: PrefetchSnippetsResponseHandler() - {0} - for {1} ", ex.Message, wsp.Hostname + ":" + wsp.Port); } if (nwHits.Count > 0) { //Possible Error in getting snippets for these hitIds log.Warn("WebBackEnd/PrefetchSnippetsResponseHandler(): Didn't get Snippets for some network Hits"); foreach (NetworkHit nh in nwHits) nh.snippet = ""; } //Signal change in TileHitCollection due to addition of snippets: rc.GetHitCollection.ClearSources(null); } private class ReqContext2 { BT.TileHitCollection _thc; BeagleWebService _wsp; ArrayList _nwHits; public ReqContext2(BeagleWebService wsp, ArrayList nwHits, BT.TileHitCollection thc) { this._thc = thc; this._wsp = wsp; this._nwHits = nwHits; } public BT.TileHitCollection GetHitCollection { get { return _thc; } } public BeagleWebService GetProxy { get { return _wsp; } } public ArrayList GetNwHits { get { return _nwHits; } } } private string GetSearchToken(NetworkHit nh) { if (nh == null) return null; string netUri = nh.UriAsString; //netbeagle://164.99.153.134:8888/searchToken?http:///.... string[] f1, f2 = netUri.Split('?'); if (f2.Length > 1) { f1 = f2[0].Split ('/'); if (f1.Length > 1) return (f1[f1.Length - 1]); } return null; } private string GetHostnamePort(NetworkHit nh) { if (nh == null) return null; string netUri = nh.UriAsString; //netbeagle://164.99.153.134:8888/searchToken?http:///.... string[] f1, f2 = netUri.Split('?'); if (f2.Length > 1) { f1 = f2[0].Split ('/'); if (f1.Length > 1) return (f1[2]); } return null; } ////////////////////////////////////////////////////////////////////////// //string static_stylesheet = ""; string static_stylesheet = ""; } } }