4 // Copyright (C) 2004 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
29 using System
.Collections
;
30 using System
.Reflection
;
32 using System
.Threading
;
34 namespace Beagle
.Daemon
{
36 public class QueryDriver
{
38 // Enable or Disable specific Queryables by name
40 static ArrayList allowed_queryables
= new ArrayList ();
41 static ArrayList denied_queryables
= new ArrayList ();
43 static public void Allow (string name
)
45 allowed_queryables
.Add (name
.ToLower ());
48 static public void Deny (string name
)
50 denied_queryables
.Add (name
.ToLower ());
53 static private bool UseQueryable (string name
)
55 if (allowed_queryables
.Count
== 0
56 && denied_queryables
.Count
== 0)
59 name
= name
.ToLower ();
61 if (allowed_queryables
.Count
> 0) {
62 foreach (string allowed
in allowed_queryables
)
68 foreach (string denied
in denied_queryables
)
75 //////////////////////////////////////////////////////////////////////////////////////
77 // Paths to static queryables
79 static ArrayList static_queryables
= new ArrayList ();
81 static public void AddStaticQueryable (string path
) {
83 if (! static_queryables
.Contains (path
))
84 static_queryables
.Add (path
);
87 //////////////////////////////////////////////////////////////////////////////////////
89 // Delay before starting the indexing process
91 static int indexing_delay
= 60; // Default to 60 seconds
93 public static int IndexingDelay
{
94 set { indexing_delay = value; }
97 //////////////////////////////////////////////////////////////////////////////////////
99 // Use introspection to find all classes that implement IQueryable, the construct
100 // associated Queryables objects.
102 static ArrayList queryables
= new ArrayList ();
103 static Hashtable iqueryable_to_queryable
= new Hashtable ();
105 static bool ThisApiSoVeryIsBroken (Type m
, object criteria
)
107 return m
== (Type
) criteria
;
110 static bool TypeImplementsInterface (Type t
, Type iface
)
112 Type
[] impls
= t
.FindInterfaces (new TypeFilter (ThisApiSoVeryIsBroken
),
114 return impls
.Length
> 0;
117 // For every type in the assembly that
118 // (1) implements IQueryable
119 // (2) Has a QueryableFlavor attribute attached
120 // assemble a Queryable object and stick it into our list of queryables.
121 static void ScanAssembly (Assembly assembly
)
125 foreach (Type type
in ReflectionFu
.ScanAssemblyForInterface (assembly
, typeof (IQueryable
))) {
126 foreach (QueryableFlavor flavor
in ReflectionFu
.ScanTypeForAttribute (type
, typeof (QueryableFlavor
))) {
127 if (! UseQueryable (flavor
.Name
))
130 if (flavor
.RequireInotify
&& ! Inotify
.Enabled
) {
131 Logger
.Log
.Warn ("Can't start backend '{0}' without inotify", flavor
.Name
);
135 if (flavor
.RequireExtendedAttributes
&& ! ExtendedAttribute
.Supported
) {
136 Logger
.Log
.Warn ("Can't start backend '{0}' without extended attributes", flavor
.Name
);
140 IQueryable iq
= null;
142 iq
= Activator
.CreateInstance (type
) as IQueryable
;
143 } catch (Exception e
) {
144 Logger
.Log
.Error ("Caught exception while instantiating {0} backend", flavor
.Name
);
145 Logger
.Log
.Error (e
);
149 Queryable q
= new Queryable (flavor
, iq
);
151 iqueryable_to_queryable
[iq
] = q
;
157 Logger
.Log
.Debug ("Found {0} backends in {1}", count
, assembly
.Location
);
160 ////////////////////////////////////////////////////////
162 // Scans PathFinder.SystemIndexesDir after available
163 // system-wide indexes.
164 static void LoadSystemIndexes ()
166 if (!Directory
.Exists (PathFinder
.SystemIndexesDir
))
171 foreach (DirectoryInfo index_dir
in new DirectoryInfo (PathFinder
.SystemIndexesDir
).GetDirectories ()) {
172 if (! UseQueryable (index_dir
.Name
))
175 if (LoadStaticQueryable (index_dir
, QueryDomain
.System
))
179 Logger
.Log
.Debug ("Found {0} system-wide indexes", count
);
182 // Scans configuration for user-specified index paths
183 // to load StaticQueryables from.
184 static void LoadStaticQueryables ()
188 if (UseQueryable ("static")) {
189 foreach (string path
in Conf
.Daemon
.StaticQueryables
)
190 static_queryables
.Add (path
);
193 foreach (string path
in static_queryables
) {
194 DirectoryInfo index_dir
= new DirectoryInfo (StringFu
.SanitizePath (path
));
196 if (!index_dir
.Exists
)
199 // FIXME: QueryDomain might be other than local
200 if (LoadStaticQueryable (index_dir
, QueryDomain
.Local
))
204 Logger
.Log
.Debug ("Found {0} user-configured static queryables", count
);
207 // Instantiates and loads a StaticQueryable from an index directory
208 static private bool LoadStaticQueryable (DirectoryInfo index_dir
, QueryDomain query_domain
)
210 StaticQueryable static_queryable
= null;
212 if (!index_dir
.Exists
)
216 static_queryable
= new StaticQueryable (index_dir
.Name
, index_dir
.FullName
, true);
217 } catch (InvalidOperationException
) {
218 Logger
.Log
.Warn ("Unable to create read-only index (likely due to index version mismatch): {0}", index_dir
.FullName
);
220 } catch (Exception e
) {
221 Logger
.Log
.Error ("Caught exception while instantiating static queryable: {0}", index_dir
.Name
);
222 Logger
.Log
.Error (e
);
226 if (static_queryable
!= null) {
227 QueryableFlavor flavor
= new QueryableFlavor ();
228 flavor
.Name
= index_dir
.Name
;
229 flavor
.Domain
= query_domain
;
231 Queryable queryable
= new Queryable (flavor
, static_queryable
);
232 queryables
.Add (queryable
);
234 iqueryable_to_queryable
[static_queryable
] = queryable
;
242 ////////////////////////////////////////////////////////
244 static public void Start ()
246 ArrayList assemblies
= ReflectionFu
.ScanEnvironmentForAssemblies ("BEAGLE_BACKEND_PATH", PathFinder
.BackendDir
);
248 // Only add the executing assembly if we haven't already loaded it.
249 if (assemblies
.IndexOf (Assembly
.GetExecutingAssembly ()) == -1)
250 assemblies
.Add (Assembly
.GetExecutingAssembly ());
252 foreach (Assembly assembly
in assemblies
) {
253 ScanAssembly (assembly
);
255 // This allows backends to define their
257 Server
.ScanAssemblyForExecutors (assembly
);
260 LoadSystemIndexes ();
261 LoadStaticQueryables ();
263 if (indexing_delay
<= 0 || Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG") != null)
266 Logger
.Log
.Debug ("Waiting {0} seconds before starting queryables", indexing_delay
);
267 GLib
.Timeout
.Add ((uint) indexing_delay
* 1000, new GLib
.TimeoutHandler (StartQueryables
));
271 static private bool StartQueryables ()
273 Logger
.Log
.Debug ("Starting queryables");
275 foreach (Queryable q
in queryables
)
281 static public string ListBackends ()
283 ArrayList assemblies
= ReflectionFu
.ScanEnvironmentForAssemblies ("BEAGLE_BACKEND_PATH", PathFinder
.BackendDir
);
285 // Only add the executing assembly if we haven't already loaded it.
286 if (assemblies
.IndexOf (Assembly
.GetExecutingAssembly ()) == -1)
287 assemblies
.Add (Assembly
.GetExecutingAssembly ());
289 string ret
= "User:\n";
291 foreach (Assembly assembly
in assemblies
) {
292 foreach (Type type
in ReflectionFu
.ScanAssemblyForInterface (assembly
, typeof (IQueryable
))) {
293 foreach (QueryableFlavor flavor
in ReflectionFu
.ScanTypeForAttribute (type
, typeof (QueryableFlavor
)))
294 ret
+= String
.Format (" - {0}\n", flavor
.Name
);
298 if (!Directory
.Exists (PathFinder
.SystemIndexesDir
))
302 foreach (DirectoryInfo index_dir
in new DirectoryInfo (PathFinder
.SystemIndexesDir
).GetDirectories ()) {
303 ret
+= String
.Format (" - {0}\n", index_dir
.Name
);
309 static public Queryable
GetQueryable (string name
)
311 foreach (Queryable q
in queryables
) {
319 static public Queryable
GetQueryable (IQueryable iqueryable
)
321 return (Queryable
) iqueryable_to_queryable
[iqueryable
];
324 ////////////////////////////////////////////////////////
326 public delegate void ChangedHandler (Queryable queryable
,
327 IQueryableChangeData changeData
);
329 static public event ChangedHandler ChangedEvent
;
331 // A method to fire the ChangedEvent event.
332 static public void QueryableChanged (IQueryable iqueryable
,
333 IQueryableChangeData change_data
)
335 if (ChangedEvent
!= null) {
336 Queryable queryable
= iqueryable_to_queryable
[iqueryable
] as Queryable
;
337 ChangedEvent (queryable
, change_data
);
341 ////////////////////////////////////////////////////////
343 private class QueryClosure
: IQueryWorker
{
348 IQueryableChangeData change_data
;
350 public QueryClosure (Queryable queryable
,
353 IQueryableChangeData change_data
)
355 this.queryable
= queryable
;
357 this.result
= result
;
358 this.change_data
= change_data
;
361 public void DoWork ()
363 queryable
.DoQuery (query
, result
, change_data
);
367 static public void DoOneQuery (Queryable queryable
,
370 IQueryableChangeData change_data
)
372 if (queryable
.AcceptQuery (query
)) {
373 QueryClosure qc
= new QueryClosure (queryable
, query
, result
, change_data
);
374 result
.AttachWorker (qc
);
378 static void AddSearchTermInfo (QueryPart part
,
379 SearchTermResponse response
, StringBuilder sb
)
381 if (part
.Logic
== QueryPartLogic
.Prohibited
)
384 if (part
is QueryPart_Or
) {
385 ICollection sub_parts
;
386 sub_parts
= ((QueryPart_Or
) part
).SubParts
;
387 foreach (QueryPart qp
in sub_parts
)
388 AddSearchTermInfo (qp
, response
, sb
);
392 if (! (part
is QueryPart_Text
))
396 tp
= (QueryPart_Text
) part
;
399 split
= tp
.Text
.Split (' ');
401 // First, remove stop words
402 for (int i
= 0; i
< split
.Length
; ++i
)
403 if (LuceneCommon
.IsStopWord (split
[i
]))
406 // Assemble the phrase minus stop words
408 for (int i
= 0; i
< split
.Length
; ++i
) {
409 if (split
[i
] == null)
413 sb
.Append (split
[i
]);
415 response
.ExactText
.Add (sb
.ToString ());
417 // Now assemble a stemmed version
418 sb
.Length
= 0; // clear the previous value
419 for (int i
= 0; i
< split
.Length
; ++i
) {
420 if (split
[i
] == null)
424 sb
.Append (LuceneCommon
.Stem (split
[i
]));
426 response
.StemmedText
.Add (sb
.ToString ());
429 ////////////////////////////////////////////////////////
431 static private void DehumanizeQuery (Query query
)
433 // We need to remap any QueryPart_Human parts into
434 // lower-level part types. First, we find any
435 // QueryPart_Human parts and explode them into
436 // lower-level types.
437 ArrayList new_parts
= null;
438 foreach (QueryPart abstract_part
in query
.Parts
) {
439 if (abstract_part
is QueryPart_Human
) {
440 QueryPart_Human human
= abstract_part
as QueryPart_Human
;
441 if (new_parts
== null)
442 new_parts
= new ArrayList ();
443 foreach (QueryPart sub_part
in QueryStringParser
.Parse (human
.QueryString
))
444 new_parts
.Add (sub_part
);
448 // If we found any QueryPart_Human parts, copy the
449 // non-Human parts over and then replace the parts in
451 if (new_parts
!= null) {
452 foreach (QueryPart abstract_part
in query
.Parts
) {
453 if (! (abstract_part
is QueryPart_Human
))
454 new_parts
.Add (abstract_part
);
458 foreach (QueryPart part
in new_parts
)
459 query
.AddPart (part
);
464 static private SearchTermResponse
AssembleSearchTermResponse (Query query
)
466 StringBuilder sb
= new StringBuilder ();
467 SearchTermResponse search_term_response
;
468 search_term_response
= new SearchTermResponse ();
469 foreach (QueryPart part
in query
.Parts
)
470 AddSearchTermInfo (part
, search_term_response
, sb
);
471 return search_term_response
;
474 static private void QueryEachQueryable (Query query
,
477 // The extra pair of calls to WorkerStart/WorkerFinished ensures:
478 // (1) that the QueryResult will fire the StartedEvent
479 // and FinishedEvent, even if no queryable accepts the
481 // (2) that the FinishedEvent will only get called when all of the
482 // backends have had time to finish.
484 object dummy_worker
= new object ();
486 if (! result
.WorkerStart (dummy_worker
))
489 foreach (Queryable queryable
in queryables
)
490 DoOneQuery (queryable
, query
, result
, null);
492 result
.WorkerFinished (dummy_worker
);
495 static public void DoQueryLocal (Query query
,
498 DehumanizeQuery (query
);
500 SearchTermResponse search_term_response
;
501 search_term_response
= AssembleSearchTermResponse (query
);
502 query
.ProcessSearchTermResponse (search_term_response
);
504 QueryEachQueryable (query
, result
);
507 static public void DoQuery (Query query
,
509 RequestMessageExecutor
.AsyncResponse send_response
)
511 DehumanizeQuery (query
);
513 SearchTermResponse search_term_response
;
514 search_term_response
= AssembleSearchTermResponse (query
);
515 send_response (search_term_response
);
517 QueryEachQueryable (query
, result
);
520 ////////////////////////////////////////////////////////
522 static public string GetIndexInformation ()
524 StringBuilder builder
= new StringBuilder ('\n');
526 foreach (Queryable q
in queryables
) {
527 QueryableStatus status
= q
.GetQueryableStatus ();
529 builder
.Append ("Name: ").Append (status
.Name
).Append ('\n');
530 builder
.Append ("Count: ").Append (status
.ItemCount
).Append ('\n');
531 builder
.Append ("Indexing: ").Append (status
.IsIndexing
).Append ('\n');
532 builder
.Append ('\n');
535 return builder
.ToString ();
538 ////////////////////////////////////////////////////////
540 static public bool IsIndexing
{
542 foreach (Queryable q
in queryables
) {
543 QueryableStatus status
= q
.GetQueryableStatus ();
545 if (status
.IsIndexing
)