multi-solr 01.01.05

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -0
  5. data/lib/multi_solr/base_searcher.rb +393 -0
  6. data/lib/multi_solr/filter_value_composite.rb +33 -0
  7. data/lib/multi_solr/rails_form_render_helper.rb +84 -0
  8. data/lib/multi_solr/search_request.rb +209 -0
  9. data/lib/multi_solr/search_result.rb +127 -0
  10. data/lib/multi_solr/single_core_handler.rb +341 -0
  11. data/lib/multi_solr/solr_filter_collection.rb +97 -0
  12. data/lib/multi_solr/solr_filter_date.rb +46 -0
  13. data/lib/multi_solr/solr_filter_date_range.rb +62 -0
  14. data/lib/multi_solr/solr_filter_free_query.rb +11 -0
  15. data/lib/multi_solr/solr_filter_simple.rb +96 -0
  16. data/lib/multi_solr/timeline_core_handler.rb +131 -0
  17. data/lib/multi_solr/version.rb +3 -0
  18. data/lib/multi_solr.rb +43 -0
  19. data/multi-solr.gemspec +28 -0
  20. data/spec/fixtures/solr-testdata.yml +13 -0
  21. data/spec/multi_solr/base_searcher_spec.rb +212 -0
  22. data/spec/multi_solr/search_request_spec.rb +45 -0
  23. data/spec/multi_solr/search_result_spec.rb +113 -0
  24. data/spec/multi_solr/single_core_handler_spec.rb +169 -0
  25. data/spec/multi_solr/timeline_core_handler_spec.rb +107 -0
  26. data/spec/solr_test_helper.rb +15 -0
  27. data/spec/solr_testdata_provider.rb +89 -0
  28. data/spec/spec_helper.rb +27 -0
  29. data/test-solr/.gitignore +4 -0
  30. data/test-solr/articles.xml +6 -0
  31. data/test-solr/etc/jetty.xml +227 -0
  32. data/test-solr/etc/webdefault.xml +410 -0
  33. data/test-solr/lib/jetty-6.1.26-patched-JETTY-1340.jar +0 -0
  34. data/test-solr/lib/jetty-LICENSE.txt +202 -0
  35. data/test-solr/lib/jetty-NOTICE.txt +36 -0
  36. data/test-solr/lib/jetty-util-6.1.26-patched-JETTY-1340.jar +0 -0
  37. data/test-solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  38. data/test-solr/lib/jsp-2.1/jsp-2.1-glassfish-2.1.v20091210.jar +0 -0
  39. data/test-solr/lib/jsp-2.1/jsp-2.1-jetty-6.1.26.jar +0 -0
  40. data/test-solr/lib/jsp-2.1/jsp-api-2.1-glassfish-2.1.v20091210.jar +0 -0
  41. data/test-solr/lib/lukeall-3.4.0_1.jar +0 -0
  42. data/test-solr/lib/servlet-api-2.5-20081211.jar +0 -0
  43. data/test-solr/solr/lib/apache-solr-dataimporthandler-3.4.0.jar +0 -0
  44. data/test-solr/solr/solr.xml +20 -0
  45. data/test-solr/solr/testcore/conf/dataimport-test.xml +12 -0
  46. data/test-solr/solr/testcore/conf/schema.xml +42 -0
  47. data/test-solr/solr/testcore/conf/solr_schema.css +58 -0
  48. data/test-solr/solr/testcore/conf/solr_schema.xsl +72 -0
  49. data/test-solr/solr/testcore/conf/solrconfig.xml +72 -0
  50. data/test-solr/start-test-solr.sh +10 -0
  51. data/test-solr/start.jar +0 -0
  52. data/test-solr/webapps/solr.war +0 -0
  53. metadata +212 -0
@@ -0,0 +1,209 @@
1
+ # Solr-Suchanfrage
2
+ # Diese enthält einmal die möglichen Suchfilter und die konkreten Such-Werte
3
+ # sowie andere Suchparameter (Paginierung etc.)
4
+
5
+ class MultiSolr::SearchRequest
6
+
7
+ # Array(String) mit den Feldnamen zu denen eine Facet gebildet werden soll
8
+ attr_accessor :facets
9
+
10
+ # Hash mit optionalen Parametern zur Facet-Bildung
11
+ # z.B. {:lagerort_h => {:limit => 1000, :sort => :index},
12
+ # :mdate => {:range => true, :start => 'NOW/DAY-5Day', :end => 'NOW/DAY', :gap => '+1DAY'},
13
+ # :saison => {:stats_field => 'volumen'}}
14
+ attr_accessor :facet_params
15
+
16
+ attr_accessor :sorts # Array(String) mit den Feldnamen für die Sortierung (optional mit Sortierrichtung)
17
+ attr_accessor :stats_fields # Array(String) mit den Feldnamen für die Gesamt-Statistiken berechnet werden sollen (Summe, min, max,...)
18
+ attr_accessor :group_field # Name des Feldes nach dem gruppiert werden soll. Wenn nil, dann erfolgt keine Gruppierung(default)
19
+ attr_accessor :group_size # Anzahl der Ergebnisse je Gruppe, wenn nil so ist laut SOLR default=1
20
+ attr_accessor :group_truncate # wenn true, dann werden Facets auf Basis der Gruppierung gebildet
21
+ attr_reader :filter_values # Hash(Symbol => String,Hash) mit den konkreten Filter-Werten
22
+ attr_writer :page_size # angeforderte Anzahl Einträge je Seite
23
+ attr_accessor :active_filters # Array mit den Namen der aktiven Filter (als Symbole)
24
+
25
+ @default_page_size = 25
26
+
27
+ BASE_FILTER_MAP = { :simple => MultiSolr::SolrFilterSimple,
28
+ :collection => MultiSolr::SolrFilterCollection,
29
+ :date_range => MultiSolr::SolrFilterDateRange,
30
+ :date => MultiSolr::SolrFilterDate,
31
+ :free_query => MultiSolr::SolrFilterFreeQuery
32
+ }
33
+
34
+ class << self
35
+
36
+ # Definieren eines Filters
37
+ # Erzeugt für den Filter entsprechende Attribut-Methoden
38
+ # Dies vereinfacht die Nutzung in Formularen
39
+ # Z.B. für einen Filter mit den Namen :firma_nr werden die Methoden
40
+ # filter_firma_nr und filter_firma_nr=(value) generiert
41
+ #
42
+ # Params:
43
+ # filter_name: Name des Filters als Symbol
44
+ # options: optionale Hash mit:
45
+ # :type Typ des Filters, entweder Typ-Bezeichner ein Symbol oder die Klasse des Filters
46
+ # Der Typ-Bezeichner muss in MultiSolr::SearchRequest::BASE_FILTER_MAP definiert sein.
47
+ # Ist kein Type angegeben so wird
48
+ # andere: Alle anderen Angaben in den Options werden an den Filter weitergereicht.
49
+ # Siehe also dazu die Beschreibungen am Constructor der einzelnen Filtertypen
50
+ #
51
+ def define_filter filter_name, options={}
52
+ @possible_filters ||= {}
53
+ filter_type = options.delete(:type) || :simple
54
+ if filter_type.is_a?(Symbol)
55
+ # Zum Symbol die konkrete Filterklasse holen
56
+ filter_type = BASE_FILTER_MAP[filter_type]
57
+ raise "Unknown type: #{filter_type}" if filter_type.nil?
58
+ end
59
+ filter = filter_type.new filter_name, options
60
+ @possible_filters[filter_name] = filter
61
+
62
+ # Definieren der Methode zum Lesen des Values dieses Filters
63
+ define_method "filter_#{filter_name}" do
64
+ @filter_values[filter_name]
65
+ end
66
+ #
67
+ # Definieren der Methode zum zuweisen des Wertes dieses Filters
68
+ define_method "filter_#{filter_name}=" do |value|
69
+ if value.blank? || (value.is_a?(Array) && (value.empty? || value.all?(&:blank?)) ) || (value.is_a?(Hash) && value.values.all?(&:blank?))
70
+ @active_filters.delete(filter_name)
71
+ return
72
+ end
73
+ @filter_values[filter_name] = value
74
+ @active_filters << filter_name unless @active_filters.include?(filter_name)
75
+ end
76
+ end
77
+
78
+ # mögliche Filter (Hash Filtername(Smybol) => Filter(SolrFilterSimple-Instance))
79
+ def possible_filters
80
+ @possible_filters || {}
81
+ end
82
+
83
+
84
+ def set_default_page_size size
85
+ @default_page_size = size
86
+ end
87
+
88
+ def default_page_size
89
+ @default_page_size
90
+ end
91
+
92
+ end # of Class-Methods
93
+
94
+
95
+
96
+ # Create new SolrSearch-Instance
97
+ # @param attributes mit den Attributewerten als Hash (optional)
98
+ def initialize attributes=nil
99
+ @filter_values = {}
100
+ @active_filters = []
101
+ @sorts = []
102
+ @facets = []
103
+ if attributes.is_a? Hash
104
+ attributes.each do |aname, value|
105
+ m_name = "#{aname}="
106
+ send(m_name, value) if self.respond_to?(m_name)
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ # Erzeugen ein FilterValue-Composite-Object welches
113
+ # aus dem Filter und den in @filter_values hinterlegten Wert erzeugt wird
114
+ # params:
115
+ # filter_name: Name des Filters als Symbol
116
+ # returns: MultiSolr::FilterValueComposite-Instance mit dem Filter und den zugehörigen Wert
117
+ def build_filter_value_composite filter_name, value=nil
118
+ filter = self.class.possible_filters[filter_name]
119
+ raise "Unknown filter: #{filter_name}" if filter.nil?
120
+ value ||= @filter_values[filter_name]
121
+ MultiSolr::FilterValueComposite.new filter, value
122
+ end
123
+
124
+
125
+ # Erzeugt aus den filter_values die Solr-Query
126
+ # liefert die Query als String
127
+ def build_query
128
+ solr_queries = []
129
+ @filter_values.each do |fname, value|
130
+ fa_composite = build_filter_value_composite fname, value
131
+ solr_query = fa_composite.build_solr_query
132
+ solr_queries << solr_query unless solr_query.blank?
133
+ end
134
+ solr_queries.join(' ')
135
+ end
136
+
137
+
138
+ def render_filter_description
139
+ descriptions = []
140
+ @filter_values.each do |fname, value|
141
+ filter = self.class.possible_filters[fname]
142
+ raise "Unknown filter: #{fname}" if filter.nil?
143
+ value = filter.render_value value
144
+ descriptions << "#{filter.label} = #{value}"
145
+ end
146
+ descriptions
147
+ end
148
+
149
+
150
+ # Filter mit konkreten Wert belegen
151
+ def set_filter filter_name, value
152
+ filter_name = filter_name.to_sym
153
+ raise "Unbekannter Filter'#{filter_name}'" unless self.class.possible_filters.has_key?(filter_name)
154
+ @filter_values[filter_name] = value
155
+ @active_filters << filter_name unless @active_filters.include?(filter_name)
156
+ end
157
+
158
+
159
+ def remove_facet facet_name
160
+ @facets.delete(facet_name.to_s) if @facets
161
+ end
162
+
163
+
164
+ def page= page_nr
165
+ @page = [1, page_nr.to_i].max
166
+ end
167
+
168
+ def page
169
+ @page || 1
170
+ end
171
+
172
+ def page_size
173
+ @page_size || self.class.default_page_size
174
+ end
175
+
176
+
177
+ def possible_filter_keys
178
+ self.class.possible_filters.keys
179
+ end
180
+
181
+ # liefert Liste der möglichen Filternamen incl. der Labels
182
+ # sortiert nach Labels
183
+ # als Array aus Tuples
184
+ # z.B. [ [:firma_nr, 'Bestandsfirma'], [:lkz, 'LKZ'] ]
185
+ def possible_filter_keys_sorted_with_labels
186
+ keys = self.class.possible_filters.keys
187
+ result = keys.map{|k| [k, I18n.t("solr_search.#{k}")]}
188
+ result.sort!{|e1,e2| e1.last <=> e2.last}
189
+ result
190
+ end
191
+
192
+ # Liefert die Liste der inactiven Filter
193
+ # (possible-filters - active-filters)
194
+ # returns Array of Symbols
195
+ def inactive_filters
196
+ self.class.possible_filters.keys - @active_filters
197
+ end
198
+
199
+ # Abbilden von 3 Sortiermöglichkeiten als sort1 ... sort3
200
+ (1..3).each do |i|
201
+ define_method "sort#{i}" do
202
+ @sorts[i-1]
203
+ end
204
+
205
+ define_method "sort#{i}=" do |value|
206
+ @sorts[i-1] = value
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,127 @@
1
+ # Ergebnis der SolR-Suche
2
+
3
+
4
+ class MultiSolr::SearchResult
5
+
6
+ attr_reader :hits, # Integer mit Anzahl der Treffer
7
+ :hits_grouped, # Anzahl der Treffer über alle Gruppen (nur bei Suche mit Gruppierung)
8
+ :metadata, # Hash mit Metadaten so wie sie vom SolR kommen (QTime u.a.)
9
+ :facet_fields, # Hash(String,Array) mit Facet-Fields-Daten wenn angefordert
10
+ :facet_ranges, # Hash(String,Array) mit Facet-Range-Daten wenn angefordert
11
+ :stats, # Hash mit Stats-Daten wenn angefordert
12
+ :stats_facets, # Hash mit Stats-Facet-Werten (wenn Facets über Stats-Componente gebildet wurden)
13
+ :solr_result, # Original Solr-Result
14
+ :search_request # Original SearchRequest
15
+
16
+ attr_accessor :data_rows # Array(Hash) mit den gefundenen Daten-Zeilen
17
+
18
+
19
+ # Params:
20
+ # solr_result: Hash, so wie sie direkt vom SolR kommt
21
+ # solr_search_request: der zugehörige Such-Request (eine Instance von MultiSolr::SearchRequest)
22
+ # context: Context-Inforamtionen, so wie sie bei der Suchausführung angegeben wurden (siehe MultiSolr::BaseSearcher#execute)
23
+ def initialize solr_result, solr_search_request, context
24
+ @solr_result = solr_result
25
+ @search_request = solr_search_request
26
+ @context = context
27
+ if solr_result['grouped']
28
+ parse_grouping_result
29
+ else
30
+ parse_non_grouping_result
31
+ end
32
+
33
+ if @hits && @hits > 0
34
+ parse_stats if solr_result.key?('stats')
35
+ parse_facet_counts
36
+ end
37
+ rescue => ex
38
+ MultiSolr.logger.warn "#{self.class.name}.initialize: #{ex}\n\t"+ex.backtrace[0..10].join("\n\t")
39
+ MultiSolr.logger.warn solr_result.inspect
40
+ end
41
+
42
+
43
+ # Mapper facet_fields => facet_counts wegen Abwärtskompatibilität
44
+ def facet_counts
45
+ @facet_fields
46
+ end
47
+
48
+ def page
49
+ @search_request.page
50
+ end
51
+
52
+ def total_pages
53
+ return @_total_pages if @_total_pages
54
+ @_total_pages, rest = @hits.divmod(@search_request.page_size)
55
+ @_total_pages += 1 if rest > 0
56
+ @_total_pages
57
+ end
58
+
59
+
60
+ # Paginierung der Result-Docs als WillPaginate::Collection
61
+ # Achtung! Dies bedingt das Gem "willpaginate". Dieses ist nicht als Abhängikeit definiert
62
+ def paginate_result
63
+ return if @data_rows.nil? || @data_rows.empty?
64
+ @data_rows = WillPaginate::Collection.create(@search_request.page, @search_request.page_size) do |pager|
65
+ pager.replace(@data_rows)
66
+ pager.total_entries = @hits
67
+ end
68
+ end
69
+
70
+
71
+ private
72
+
73
+
74
+ def parse_non_grouping_result
75
+ @metadata = @solr_result['response']
76
+ if @metadata
77
+ @data_rows = @metadata.delete('docs')
78
+ @metadata.merge!(@solr_result['responseHeader'])
79
+ @hits = @metadata['numFound'].to_i
80
+ end
81
+ end
82
+
83
+
84
+ def parse_grouping_result
85
+ @metadata = @solr_result['responseHeader']
86
+ grouped_data = @solr_result['grouped'][@search_request.group_field]
87
+ @data_rows = grouped_data['groups']
88
+ @hits = grouped_data['ngroups'].to_i
89
+ @hits_grouped = grouped_data['matches'].to_i
90
+ end
91
+
92
+
93
+ def parse_facet_counts
94
+ solr_facet_counts = @solr_result['facet_counts']
95
+ if MultiSolr.logger.debug?
96
+ MultiSolr.logger.debug "parse_facet_counts: #{solr_facet_counts.inspect}"
97
+ end
98
+ return if solr_facet_counts.nil?
99
+ @facet_fields = solr_facet_counts['facet_fields']
100
+ @facet_ranges = solr_facet_counts['facet_ranges']
101
+ end
102
+
103
+
104
+ def parse_stats
105
+ @stats = solr_result['stats']['stats_fields']
106
+ return if @stats.nil?
107
+ # Parsen der evtl. Stats-Facets
108
+ @stats.each do |stats_field, values|
109
+ # z.B.: stats_field : 'volumen', values : {min : xxx, ..., facets : {...} }
110
+ facets = values['facets']
111
+ next if facets.nil?
112
+ @stats_facets ||= {}
113
+ facets.each do |facet, facet_values|
114
+ # z.B.: facet : 'saison', facet_values: { '125': {min:xxx, sum:xx, ....}, '121': {min:xxx, sum:xx, ....}}
115
+ @stats_facets[facet] ||= {}
116
+ stats_facet = @stats_facets[facet]
117
+ facet_values.each do |facet_entry, entry_values|
118
+ # z.B: facet_entry : '125', entry_values : {min:xxx, sum:xx, ....}
119
+ # Dies wird nun umgemapt auf facet : facet_entry : stats_field : entry_values
120
+ stats_facet[facet_entry] ||= {}
121
+ stats_facet[facet_entry][stats_field] = entry_values
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ end
@@ -0,0 +1,341 @@
1
+ # Basis-Klasse für das Handling von SOLR-Indizies (sogenannte SOLR-Cores)
2
+
3
+ require 'rsolr'
4
+
5
+ class MultiSolr::SingleCoreHandler
6
+
7
+ attr_reader :solr_url # String mit der Base-Solr-Url (ohne Core-Anteil!)
8
+ attr_accessor :core_name # der zu verwendende SOLR-Core
9
+ attr_accessor :result_class # die zu nutzende Klasse für das Suchergebnis
10
+ attr_accessor :facet_enum_fields # Array mit den Namen der Felder, die die Facet-Enum-Methode für Facets nutzen (Felder mit kleinen Wertebereichen)
11
+ attr_accessor :default_search_options # Hash mit weiteren Optionen für Query Anfragen (fieldlist,..) siehe params bei der Methode search
12
+
13
+
14
+ # Erzeugen neuen Core-Handler
15
+ # Parameter:
16
+ # solr_url : String mit der Base-Solr-Url (ohne Core-Anteil!)
17
+ # core_name : der zu verwendende SOLR-Core
18
+ # options: optionaler Hash mit folgenden Werten:
19
+ # :result_class : die zu nutzende Klasse für das Suchergebnis, default ist MultiSolr:SearchResult
20
+ # :facet_enum_fields : Array mit den Namen der Felder,
21
+ # die die Facet-Enum-Methode für Facets nutzen
22
+ # (Felder mit kleinen Wertebereichen)
23
+ # :default_search_options : Hash mit weiteren Optionen für Query Anfragen:
24
+ # :fieldlist Field-List (siehe Solr-Doku fl-Parameter), default ist alle Felder
25
+ # :facets_only wenn true, dann nur Facets ermittlen
26
+ # :without_facets wenn true, dann auf Facets verzichten
27
+ # :result_format Rückgabe-Format (json, xml),
28
+ # wenn nicht gesetzt wird "ruby" geliefert;
29
+ # Ist es gesetzt wird das Ergebnis nicht geparst (also raw als String zurückgegeben)
30
+ def initialize solr_url, core_name, options=nil
31
+ @solr_url = solr_url || raise("No 'solr_url' given!")
32
+ @core_name = core_name
33
+ if options
34
+ options.each do |k,v|
35
+ self.send("#{k}=", v)
36
+ end
37
+ end
38
+ @result_class ||= MultiSolr::SearchResult
39
+ @default_search_options ||= {}
40
+ end
41
+
42
+
43
+ # Liefert RSolr-Connection (RSolr.connect) zum konfigurierten SOLR-Server(see solr_url)
44
+ # und konfiguriertem Core (see core_name)
45
+ def solr_connection
46
+ url = "#{@solr_url}/#{@core_name}"
47
+ MultiSolr.logger.debug("solr_connection: url==#{url}") if MultiSolr.logger.debug?
48
+ RSolr.connect :url => url
49
+ end
50
+
51
+
52
+ # sendet Ping an SOLR-Instance
53
+ # returns true if ok
54
+ def ping
55
+ self.solr_connection.head("admin/ping").response[:status] == 200
56
+ end
57
+
58
+
59
+
60
+ # Liefert zu dem spezifizierten Kontext und Feld die möglichen Werte
61
+ # Parameter:
62
+ # fieldname: Name des Feldes (als Symbol)
63
+ # context: optionales Object mit weiteren Context-Informationen, dieser wird an den Force-Query-Builder übergeben
64
+ # search_request: optional bestehendes SearchRequest (für DrillDown-Funktionalität)
65
+ # Beispiel:
66
+ # searcher.list_possible_values :lkz, :whs_id => 5
67
+ #
68
+ def list_possible_values fieldname, context=nil, search_request=nil
69
+ search_request ||= MultiSolr::SearchRequest.new
70
+ search_request.facets = [fieldname]
71
+ result = search(search_request, :context => context, :facets_only => true)
72
+ facet_counts = result.facet_counts
73
+ values = []
74
+ if MultiSolr.logger.debug?
75
+ MultiSolr.logger.debug "#{self.class.name}.list_possible_values(#{fieldname}, #{context.inspect}, #{search_request.inspect}): facet_counts=#{facet_counts.inspect}"
76
+ end
77
+ if facet_counts
78
+ value_pairs = facet_counts[fieldname.to_s]
79
+ if value_pairs && !value_pairs.empty?
80
+ # das value_pairs besteht Paar-weise aus Value und Anzahl
81
+ # Es werden hier nur die Values gebraucht, wo die Anzahl >0 ist
82
+ value_pairs.each do |value_count_pair|
83
+ val, count = value_count_pair
84
+ if val && count > 0
85
+ # wenn val nur aus Zahlen besteht, dann nach Integer konvertieren (wegen der Sortierung)
86
+ val = val.to_i if val =~ /\d+/
87
+ values << val
88
+ end
89
+ end
90
+ values.sort!
91
+ end
92
+ end
93
+ if MultiSolr.logger.debug?
94
+ MultiSolr.logger.debug "#{self.class.name}.list_possible_values(#{fieldname}, #{context.inspect}) => #{values.inspect}"
95
+ end
96
+ values
97
+ end
98
+
99
+
100
+ # Ermittelt und cached die möglichen Werte der als pre_cache_value_fields definierten Felder
101
+ # Diese werden im Rails-Cache zwischengespeichert
102
+ # Ansonsten see BaseSearcher.list_possible_values
103
+ # zusätzlicher Parameter:
104
+ # expires_in: optional, Gültigkeit des Cache-Wertes in Sekunden, default ist 3 Stunden
105
+ #
106
+ def cached_list_possible_values fieldname, context=nil, expires_in=3.hours
107
+ cache_key = "solr-#{self.core_name}-#{context}-#{fieldname}-values"
108
+ values = MultiSolr.cache.read(cache_key)
109
+ if values.nil?
110
+ # dann sind noch gar keine Werte gecached => diese holen
111
+ values = list_possible_values fieldname, context
112
+ # und nun im Cache ablegen
113
+ MultiSolr.logger.debug "#{self.class.name}.cached_list_possible_values: write in cache '#{cache_key}' => #{values.inspect}" if MultiSolr.logger.debug?
114
+ MultiSolr.cache.write(cache_key, values, :expires_in => expires_in)
115
+ end
116
+ values
117
+ end
118
+
119
+
120
+
121
+
122
+ # ermitteln Zeitstempel des letzten Datenimports
123
+ def import_status solr_core_import_handler_propfile_path
124
+ MultiSolr.cache.fetch("Solr.import_status.#{self.core_name}.#{solr_core_import_handler_propfile_path}", :expires_in => 1.hours) do
125
+ result = 'unbekannt'
126
+ begin
127
+ raise("Solr-Import-Status-Propertiesfile not exist") unless File.exist?(solr_core_import_handler_propfile_path)
128
+ matcher = /^last_index_time=(.*)$/
129
+ data = File.read(solr_core_import_handler_propfile_path)
130
+ match = matcher.match(data)
131
+ if match
132
+ result = match[1]
133
+ result.gsub!('\\', '') # Zeit enthält \ vor :
134
+ result = result[0...-3] # Sekunden entfernen
135
+ end
136
+ rescue => ex
137
+ MultiSolr.logger.warn "SolrSearch.import_status: source=#{solr_core_import_handler_propfile_path}, error=#{ex.message}\n\t"+ex.backtrace.join("\n\t")
138
+ end
139
+ MultiSolr.logger.info "SolrSearch.import_status: #{solr_core_import_handler_propfile_path}, result=#{result}"
140
+ result
141
+ end
142
+ end
143
+
144
+
145
+ # liefert einzelnes Solr-Dokument an Hand der Id
146
+ # Parameter:
147
+ # id: die gewünschte Id (Integer oder String)
148
+ # id_field_name : optional, der Name des ID-Fields, default ist 'id'
149
+ def get_doc_by_id(id, id_field_name=:id)
150
+ solr_result = solr_connection.get 'select', :params => {:q => "#{id_field_name}:#{id}", :rows => 1}
151
+ docs = solr_result['response']['docs']
152
+ return nil if docs.nil? || docs.empty?
153
+ docs.first
154
+ end
155
+
156
+
157
+
158
+
159
+ # Ausführen der Suche / Recherche
160
+ # params:
161
+ # solr_search_request : die Suchanfrage als SolrSearchRequest-Instance
162
+ # options : Hash mit weiteren Optionen für Query Anfragen:
163
+ # :context : Hash mit weiteren Context-Informationen, dieser wird u.a. an den Force-Query-Builder übergeben
164
+ # :fieldlist Field-List (siehe Solr-Doku fl-Parameter), default ist *
165
+ # :facets_only wenn true, dann nur Facets ermittlen
166
+ # :without_facets wenn true, dann auf Facets verzichten
167
+ # :result_format Rückgabe-Format (json, xml),
168
+ # wenn nicht gesetzt wird "ruby" geliefert;
169
+ # Ist es gesetzt wird das Ergebnis nicht geparst (also raw als String zurückgegeben)
170
+ def search solr_search_request, options=nil
171
+ used_options = @default_search_options.clone
172
+ used_options.merge! options if options
173
+
174
+ solr_params = build_solr_params solr_search_request, used_options
175
+ solr_result = solr_connection.get 'select', :params => solr_params
176
+
177
+ # RAW-Result liefern wenn Result ein String ist
178
+ return solr_result if solr_result.is_a? String
179
+
180
+ # Parsen des Ergebnisses
181
+ result = self.result_class.new solr_result, solr_search_request, used_options[:context]
182
+ result
183
+ end
184
+
185
+
186
+ # Bilden der SOLR-Parameter für eine SOLR-Anfrage
187
+ # params:
188
+ # solr_search_request: die Suchanfrage als SolrSearchRequest-Instance
189
+ # options: siehe search
190
+ # returns: Hash mit den SOLR-Parametern
191
+ def build_solr_params solr_search_request, options
192
+
193
+ solr_params = {}
194
+ solr_params['stats.field'] = []
195
+
196
+ fq_params = self.force_query_params options[:context]
197
+ if fq_params && fq_params.is_a?(Hash)
198
+ fq = []
199
+ fq_params.each{|k, v| fq << "#{k}:#{v}"}
200
+ solr_params[:fq] = fq.join(' ')
201
+ end
202
+
203
+ q = solr_search_request.build_query
204
+ q = "*:*" if q.blank? # wenn keine Query angegeben ist, dann nach allem Suchen
205
+ solr_params[:q] = q
206
+
207
+ # Fieldlist
208
+ if q['_val_:']
209
+ # dann enthält die Query eine query-function
210
+ # Der Wert dieser wird immer im field "score" abgelegt
211
+ # daher dieses Feld zur Feldliste hinzufügen
212
+ if options[:fieldlist]
213
+ options[:fieldlist] << ',score' unless self.fieldlist[',score']
214
+ else
215
+ options[:fieldlist] = '*,score'
216
+ end
217
+ end
218
+ solr_params[:fl] = options[:fieldlist] if options.key? :fieldlist
219
+
220
+ # Sortierung
221
+ if solr_search_request.sorts && !solr_search_request.sorts.empty?
222
+ solr_search_request.sorts.delete_if{|s| s.blank?}
223
+ solr_params[:sort] = solr_search_request.sorts.map{|s| s =~ /\s(asc|desc)$/ ? s : "#{s} asc"}.join(',')
224
+ end
225
+
226
+ # Facets
227
+ if !options[:without_facets] && solr_search_request.facets
228
+ parse_facets solr_search_request, solr_params
229
+ end
230
+
231
+ # Stats
232
+ if solr_search_request.stats_fields && !solr_search_request.stats_fields.empty?
233
+ s_fields = solr_search_request.stats_fields.select{|f| !f.blank?}
234
+ unless s_fields.empty?
235
+ solr_params[:stats] = true
236
+ solr_params['stats.field'] += s_fields
237
+ end
238
+ end
239
+ solr_params['stats.field'].map!{|f| f.to_s}.uniq!
240
+
241
+ # Gruppierung
242
+ if !solr_search_request.group_field.blank? && !self.facets_only
243
+ solr_params[:group] = true
244
+ solr_params['group.field'] = solr_search_request.group_field
245
+ solr_params['group.ngroups'] = true
246
+ solr_params['group.limit'] = solr_search_request.group_size || 1
247
+ solr_params['group.truncate'] = true if solr_params[:facet] && solr_search_request.group_truncate
248
+ end
249
+
250
+
251
+ # Paginierung
252
+ if options[:facets_only]
253
+ solr_params[:rows] = 0
254
+ else
255
+ solr_params[:rows] = solr_search_request.page_size
256
+ solr_params[:start] = (solr_search_request.page-1) * solr_search_request.page_size
257
+ end
258
+
259
+ # Ausgabe-Format
260
+ solr_params[:wt] = options[:result_format] if options.key? :result_format
261
+
262
+ if MultiSolr.logger.debug?
263
+ MultiSolr.logger.debug "SolrSearch#build_solr_params: #{self.inspect}\n\tSEARCH-REQUEST=#{solr_search_request.inspect}\n\t=> SOLR_PARAMS=#{solr_params.inspect}\n"
264
+ end
265
+ solr_params
266
+ end
267
+
268
+
269
+ protected
270
+
271
+ # liefert wenn notwendig die Parameter als Hash für eine SOLR-Force-Query
272
+ # sollte bei Bedarf von der nutzenden Klasse überschrieben werden.
273
+ # params:
274
+ # context: Hash mit weiteren Context-Informationen
275
+ def force_query_params context
276
+ context
277
+ end
278
+
279
+
280
+ private
281
+
282
+
283
+ # Bilden der Facet-Solr-Params aus den Facet-Definitionen des Requests
284
+ def parse_facets solr_search_request, solr_params
285
+ solr_search_request.facets.delete_if(&:blank?) # leer Elemente entfernen
286
+ return if solr_search_request.facets.empty?
287
+
288
+ solr_params['facet.field'] = []
289
+ solr_params['facet.range'] = []
290
+ facet_params = solr_search_request.facet_params || {}
291
+ solr_search_request.facets.map(&:to_sym).each do |facet_field|
292
+ field_facet_params = facet_params[facet_field] || {}
293
+ stats_facet_field = field_facet_params[:stats_field]
294
+ if stats_facet_field
295
+ # dann Facet per Stats-Componente
296
+ solr_params[:stats] = true
297
+ if stats_facet_field.is_a?(Array)
298
+ solr_params['stats.field'] += stats_facet_field
299
+ else
300
+ solr_params['stats.field'] << stats_facet_field
301
+ stats_facet_field = [stats_facet_field]
302
+ end
303
+ stats_facet_field.each do |sfield_name|
304
+ solr_params["f.#{sfield_name}.stats.facet"] ||= []
305
+ solr_params["f.#{sfield_name}.stats.facet"] << facet_field
306
+ end
307
+ else
308
+ solr_params[:facet] = true
309
+ if field_facet_params[:range]
310
+ # Range-Facet
311
+ solr_params[:facet] = true
312
+ solr_params['facet.range'] << facet_field
313
+ field_facet_params[:end] ||= 'NOW/DAY'
314
+ field_facet_params[:gap] ||= '+1DAY'
315
+ field_facet_params.each do |option_name, value|
316
+ solr_params["f.#{facet_field}.facet.range.#{option_name}"] = value
317
+ end
318
+ else
319
+ # normale Facet-Field
320
+ solr_params['facet.field'] << facet_field
321
+ field_facet_params[:mincount] ||= 1 # Facets ohne Treffer ignorieren
322
+ # Nutzen der Facet-Enum-Methode fuer spezielle Felder (Felder mit kleinen Wertebereichen)
323
+ if self.facet_enum_fields && self.facet_enum_fields.include?(facet_field)
324
+ field_facet_params['method'] = 'enum'
325
+ end
326
+
327
+ field_facet_params.each do |option_name, value|
328
+ solr_params["f.#{facet_field}.facet.#{option_name}"] = value
329
+ end
330
+ end
331
+ end
332
+ end
333
+ if solr_params[:facet]
334
+ # Umschalten der Facet-Ergebnisse in Array-Darstellung
335
+ # (der json.nl-Schalter wirkt auch bei Ruby siehe http://wiki.apache.org/solr/SolJSON)
336
+ solr_params['json.nl'] = 'arrarr'
337
+ end
338
+
339
+ end
340
+
341
+ end