multi-solr 01.01.05

Sign up to get free protection for your applications and to get access to all the features.
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