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.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/multi_solr/base_searcher.rb +393 -0
- data/lib/multi_solr/filter_value_composite.rb +33 -0
- data/lib/multi_solr/rails_form_render_helper.rb +84 -0
- data/lib/multi_solr/search_request.rb +209 -0
- data/lib/multi_solr/search_result.rb +127 -0
- data/lib/multi_solr/single_core_handler.rb +341 -0
- data/lib/multi_solr/solr_filter_collection.rb +97 -0
- data/lib/multi_solr/solr_filter_date.rb +46 -0
- data/lib/multi_solr/solr_filter_date_range.rb +62 -0
- data/lib/multi_solr/solr_filter_free_query.rb +11 -0
- data/lib/multi_solr/solr_filter_simple.rb +96 -0
- data/lib/multi_solr/timeline_core_handler.rb +131 -0
- data/lib/multi_solr/version.rb +3 -0
- data/lib/multi_solr.rb +43 -0
- data/multi-solr.gemspec +28 -0
- data/spec/fixtures/solr-testdata.yml +13 -0
- data/spec/multi_solr/base_searcher_spec.rb +212 -0
- data/spec/multi_solr/search_request_spec.rb +45 -0
- data/spec/multi_solr/search_result_spec.rb +113 -0
- data/spec/multi_solr/single_core_handler_spec.rb +169 -0
- data/spec/multi_solr/timeline_core_handler_spec.rb +107 -0
- data/spec/solr_test_helper.rb +15 -0
- data/spec/solr_testdata_provider.rb +89 -0
- data/spec/spec_helper.rb +27 -0
- data/test-solr/.gitignore +4 -0
- data/test-solr/articles.xml +6 -0
- data/test-solr/etc/jetty.xml +227 -0
- data/test-solr/etc/webdefault.xml +410 -0
- data/test-solr/lib/jetty-6.1.26-patched-JETTY-1340.jar +0 -0
- data/test-solr/lib/jetty-LICENSE.txt +202 -0
- data/test-solr/lib/jetty-NOTICE.txt +36 -0
- data/test-solr/lib/jetty-util-6.1.26-patched-JETTY-1340.jar +0 -0
- data/test-solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/test-solr/lib/jsp-2.1/jsp-2.1-glassfish-2.1.v20091210.jar +0 -0
- data/test-solr/lib/jsp-2.1/jsp-2.1-jetty-6.1.26.jar +0 -0
- data/test-solr/lib/jsp-2.1/jsp-api-2.1-glassfish-2.1.v20091210.jar +0 -0
- data/test-solr/lib/lukeall-3.4.0_1.jar +0 -0
- data/test-solr/lib/servlet-api-2.5-20081211.jar +0 -0
- data/test-solr/solr/lib/apache-solr-dataimporthandler-3.4.0.jar +0 -0
- data/test-solr/solr/solr.xml +20 -0
- data/test-solr/solr/testcore/conf/dataimport-test.xml +12 -0
- data/test-solr/solr/testcore/conf/schema.xml +42 -0
- data/test-solr/solr/testcore/conf/solr_schema.css +58 -0
- data/test-solr/solr/testcore/conf/solr_schema.xsl +72 -0
- data/test-solr/solr/testcore/conf/solrconfig.xml +72 -0
- data/test-solr/start-test-solr.sh +10 -0
- data/test-solr/start.jar +0 -0
- data/test-solr/webapps/solr.war +0 -0
- 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
|