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 // Use introspection to find all classes that implement IQueryable, the construct
90 // associated Queryables objects.
92 static ArrayList queryables
= new ArrayList ();
93 static Hashtable iqueryable_to_queryable
= new Hashtable ();
95 static bool ThisApiSoVeryIsBroken (Type m
, object criteria
)
97 return m
== (Type
) criteria
;
100 static bool TypeImplementsInterface (Type t
, Type iface
)
102 Type
[] impls
= t
.FindInterfaces (new TypeFilter (ThisApiSoVeryIsBroken
),
104 return impls
.Length
> 0;
107 // For every type in the assembly that
108 // (1) implements IQueryable
109 // (2) Has a QueryableFlavor attribute attached
110 // assemble a Queryable object and stick it into our list of queryables.
111 static void ScanAssembly (Assembly assembly
)
115 foreach (Type type
in GetQueryableTypes (assembly
)) {
116 foreach (QueryableFlavor flavor
in GetQueryableFlavors (type
)) {
117 if (! UseQueryable (flavor
.Name
))
120 if (flavor
.RequireInotify
&& ! Inotify
.Enabled
) {
121 Logger
.Log
.Warn ("Can't start backend '{0}' without inotify", flavor
.Name
);
125 if (flavor
.RequireExtendedAttributes
&& ! ExtendedAttribute
.Supported
) {
126 Logger
.Log
.Warn ("Can't start backend '{0}' without extended attributes", flavor
.Name
);
130 IQueryable iq
= null;
132 iq
= Activator
.CreateInstance (type
) as IQueryable
;
133 } catch (Exception e
) {
134 Logger
.Log
.Error ("Caught exception while instantiating {0} backend", flavor
.Name
);
135 Logger
.Log
.Error (e
);
139 Queryable q
= new Queryable (flavor
, iq
);
141 iqueryable_to_queryable
[iq
] = q
;
147 Logger
.Log
.Debug ("Found {0} types in {1}", count
, assembly
.FullName
);
150 ////////////////////////////////////////////////////////
152 // Scans PathFinder.SystemIndexesDir after available
153 // system-wide indexes.
154 static void LoadSystemIndexes ()
156 if (!Directory
.Exists (PathFinder
.SystemIndexesDir
))
161 foreach (DirectoryInfo index_dir
in new DirectoryInfo (PathFinder
.SystemIndexesDir
).GetDirectories ()) {
162 if (! UseQueryable (index_dir
.Name
))
165 if (LoadStaticQueryable (index_dir
, QueryDomain
.System
))
169 Logger
.Log
.Debug ("Found {0} system-wide indexes", count
);
172 // Scans configuration for user-specified index paths
173 // to load StaticQueryables from.
174 static void LoadStaticQueryables ()
178 foreach (string path
in Conf
.Daemon
.StaticQueryables
)
179 static_queryables
.Add (path
);
181 foreach (string path
in static_queryables
) {
182 DirectoryInfo index_dir
= new DirectoryInfo (path
);
184 if (!index_dir
.Exists
)
187 // FIXME: QueryDomain might be other than local
188 if (LoadStaticQueryable (index_dir
, QueryDomain
.Local
))
192 Logger
.Log
.Debug ("Found {0} user-configured static queryables", count
);
195 // Instantiates and loads a StaticQueryable from an index directory
196 static private bool LoadStaticQueryable (DirectoryInfo index_dir
, QueryDomain query_domain
)
198 StaticQueryable static_queryable
= null;
200 if (!index_dir
.Exists
)
204 static_queryable
= new StaticQueryable (index_dir
.Name
, index_dir
.FullName
, true);
205 } catch (Exception e
) {
206 Logger
.Log
.Error ("Caught exception while instantiating static queryable: {0}", index_dir
.Name
);
207 Logger
.Log
.Error (e
);
211 if (static_queryable
!= null) {
212 QueryableFlavor flavor
= new QueryableFlavor ();
213 flavor
.Name
= index_dir
.Name
;
214 flavor
.Domain
= query_domain
;
216 Queryable queryable
= new Queryable (flavor
, static_queryable
);
217 queryables
.Add (queryable
);
219 iqueryable_to_queryable
[static_queryable
] = queryable
;
227 ////////////////////////////////////////////////////////
229 static private Type
[] GetQueryableTypes (Assembly assembly
)
231 Type
[] assembly_types
= assembly
.GetTypes ();
232 ArrayList types
= new ArrayList (assembly_types
.Length
);
234 foreach (Type type
in assembly_types
)
235 if (TypeImplementsInterface (type
, typeof (IQueryable
)))
238 return (Type
[]) types
.ToArray (typeof (Type
));
241 static private QueryableFlavor
[] GetQueryableFlavors (Type type
)
243 object[] attributes
= Attribute
.GetCustomAttributes (type
);
244 ArrayList flavors
= new ArrayList (attributes
.Length
);
246 foreach (object obj
in attributes
) {
247 QueryableFlavor flavor
= obj
as QueryableFlavor
;
249 flavors
.Add (flavor
);
252 return (QueryableFlavor
[]) flavors
.ToArray (typeof (QueryableFlavor
));
255 static private Assembly
[] GetAssemblies ()
257 Assembly
[] assemblies
;
259 DirectoryInfo backends
= new DirectoryInfo (PathFinder
.BackendDir
);
261 if (backends
.Exists
) {
262 FileInfo
[] assembly_files
= backends
.GetFiles ("*.dll");
263 assemblies
= new Assembly
[assembly_files
.Length
+ 1];
265 foreach (FileInfo assembly
in assembly_files
)
266 assemblies
[i
++] = Assembly
.LoadFile (assembly
.ToString ());
269 assemblies
= new Assembly
[1];
272 assemblies
[i
] = Assembly
.GetExecutingAssembly ();
277 static public void Start ()
279 foreach (Assembly assembly
in GetAssemblies ()) {
280 ScanAssembly (assembly
);
282 // This allows backends to define their
284 Server
.ScanAssemblyForExecutors (assembly
);
287 // FIXME: This is not nice at all
288 if (UseQueryable ("system"))
289 LoadSystemIndexes ();
290 if (UseQueryable ("static"))
291 LoadStaticQueryables ();
293 foreach (Queryable q
in queryables
)
297 static public string ListBackends ()
299 string ret
= "User:\n";
300 foreach (Assembly assembly
in GetAssemblies ())
301 foreach (Type type
in GetQueryableTypes (assembly
))
302 foreach (QueryableFlavor flavor
in GetQueryableFlavors (type
))
303 ret
+= String
.Format (" - {0}\n", flavor
.Name
);
305 if (!Directory
.Exists (PathFinder
.SystemIndexesDir
))
309 foreach (DirectoryInfo index_dir
in new DirectoryInfo (PathFinder
.SystemIndexesDir
).GetDirectories ()) {
310 ret
+= String
.Format (" - {0}\n", index_dir
.Name
);
316 static public Queryable
GetQueryable (string name
)
318 foreach (Queryable q
in queryables
) {
326 ////////////////////////////////////////////////////////
328 public delegate void ChangedHandler (Queryable queryable
,
329 IQueryableChangeData changeData
);
331 static public event ChangedHandler ChangedEvent
;
333 // A method to fire the ChangedEvent event.
334 static public void QueryableChanged (IQueryable iqueryable
,
335 IQueryableChangeData change_data
)
337 if (ChangedEvent
!= null) {
338 Queryable queryable
= iqueryable_to_queryable
[iqueryable
] as Queryable
;
339 ChangedEvent (queryable
, change_data
);
343 ////////////////////////////////////////////////////////
345 private class MarkAndForwardHits
: IQueryResult
{
350 public MarkAndForwardHits (IQueryResult result
, string name
)
352 this.result
= result
;
356 public void Add (ICollection some_hits
)
358 foreach (Hit hit
in some_hits
)
360 hit
.SourceObjectName
= name
;
361 result
.Add (some_hits
);
364 public void Subtract (ICollection some_uris
)
366 result
.Subtract (some_uris
);
370 private class QueryClosure
: IQueryWorker
{
375 IQueryableChangeData change_data
;
377 public QueryClosure (Queryable queryable
,
380 IQueryableChangeData change_data
)
382 this.queryable
= queryable
;
384 this.result
= new MarkAndForwardHits (result
, queryable
.Name
);
385 this.change_data
= change_data
;
388 public void DoWork ()
390 queryable
.DoQuery (query
, result
, change_data
);
394 static public void DoOneQuery (Queryable queryable
,
397 IQueryableChangeData change_data
)
399 if (queryable
.AcceptQuery (query
)) {
400 QueryClosure qc
= new QueryClosure (queryable
, query
, result
, change_data
);
401 result
.AttachWorker (qc
);
405 static void AddSearchTermInfo (QueryPart part
,
406 SearchTermResponse response
)
408 if (part
.Logic
== QueryPartLogic
.Prohibited
)
411 if (part
is QueryPart_Or
) {
412 ICollection sub_parts
;
413 sub_parts
= ((QueryPart_Or
) part
).SubParts
;
414 foreach (QueryPart qp
in sub_parts
)
415 AddSearchTermInfo (qp
, response
);
419 if (! (part
is QueryPart_Text
))
423 tp
= (QueryPart_Text
) part
;
426 split
= tp
.Text
.Split (' ');
428 // First, remove stop words
429 for (int i
= 0; i
< split
.Length
; ++i
)
430 if (LuceneCommon
.IsStopWord (split
[i
]))
433 // Assemble the phrase minus stop words
435 sb
= new StringBuilder ();
436 for (int i
= 0; i
< split
.Length
; ++i
) {
437 if (split
[i
] == null)
441 sb
.Append (split
[i
]);
443 response
.ExactText
.Add (sb
.ToString ());
445 // Now assemble a stemmed version
446 sb
.Length
= 0; // clear the previous value
447 for (int i
= 0; i
< split
.Length
; ++i
) {
448 if (split
[i
] == null)
452 sb
.Append (LuceneCommon
.Stem (split
[i
]));
454 response
.StemmedText
.Add (sb
.ToString ());
457 ////////////////////////////////////////////////////////
459 static private void DehumanizeQuery (Query query
)
461 // We need to remap any QueryPart_Human parts into
462 // lower-level part types. First, we find any
463 // QueryPart_Human parts and explode them into
464 // lower-level types.
465 ArrayList new_parts
= null;
466 foreach (QueryPart abstract_part
in query
.Parts
) {
467 if (abstract_part
is QueryPart_Human
) {
468 QueryPart_Human human
= abstract_part
as QueryPart_Human
;
469 if (new_parts
== null)
470 new_parts
= new ArrayList ();
471 foreach (QueryPart sub_part
in QueryStringParser
.Parse (human
.QueryString
))
472 new_parts
.Add (sub_part
);
476 // If we found any QueryPart_Human parts, copy the
477 // non-Human parts over and then replace the parts in
479 if (new_parts
!= null) {
480 foreach (QueryPart abstract_part
in query
.Parts
) {
481 if (! (abstract_part
is QueryPart_Human
))
482 new_parts
.Add (abstract_part
);
486 foreach (QueryPart part
in new_parts
)
487 query
.AddPart (part
);
492 static private SearchTermResponse
AssembleSearchTermResponse (Query query
)
494 SearchTermResponse search_term_response
;
495 search_term_response
= new SearchTermResponse ();
496 foreach (QueryPart part
in query
.Parts
)
497 AddSearchTermInfo (part
, search_term_response
);
498 return search_term_response
;
501 static private void QueryEachQueryable (Query query
,
504 // The extra pair of calls to WorkerStart/WorkerFinished ensures:
505 // (1) that the QueryResult will fire the StartedEvent
506 // and FinishedEvent, even if no queryable accepts the
508 // (2) that the FinishedEvent will only get called when all of the
509 // backends have had time to finish.
511 object dummy_worker
= new object ();
513 if (! result
.WorkerStart (dummy_worker
))
516 foreach (Queryable queryable
in queryables
)
517 DoOneQuery (queryable
, query
, result
, null);
519 result
.WorkerFinished (dummy_worker
);
522 static public void DoQueryLocal (Query query
,
525 DehumanizeQuery (query
);
527 SearchTermResponse search_term_response
;
528 search_term_response
= AssembleSearchTermResponse (query
);
529 query
.ProcessSearchTermResponse (search_term_response
);
531 QueryEachQueryable (query
, result
);
534 static public void DoQuery (Query query
,
536 RequestMessageExecutor
.AsyncResponse send_response
)
538 DehumanizeQuery (query
);
540 SearchTermResponse search_term_response
;
541 search_term_response
= AssembleSearchTermResponse (query
);
542 send_response (search_term_response
);
544 QueryEachQueryable (query
, result
);
547 ////////////////////////////////////////////////////////
549 static public string GetIndexInformation ()
551 StringBuilder builder
= new StringBuilder ("\n");
553 foreach (Queryable q
in queryables
) {
554 builder
.AppendFormat ("Name: {0}\n", q
.Name
);
555 builder
.AppendFormat ("Count: {0}\n", q
.GetItemCount ());
556 builder
.Append ("\n");
559 return builder
.ToString ();