umlaut 4.0.3 → 4.1.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +6 -0
  3. data/app/assets/javascripts/umlaut/update_html.js +81 -23
  4. data/app/assets/stylesheets/umlaut/_misc.scss +1 -2
  5. data/app/assets/stylesheets/umlaut/_resolve.scss +44 -10
  6. data/app/controllers/export_email_controller.rb +2 -2
  7. data/app/controllers/feedback_controller.rb +1 -1
  8. data/app/controllers/umlaut/controller_behavior.rb +14 -0
  9. data/app/controllers/umlaut_configurable.rb +52 -21
  10. data/app/helpers/open_search_helper.rb +1 -1
  11. data/app/helpers/resolve_helper.rb +36 -35
  12. data/app/helpers/umlaut/section_highlights.rb +85 -0
  13. data/app/mixin_logic/metadata_helper.rb +11 -5
  14. data/app/models/collection.rb +129 -96
  15. data/app/models/dispatched_service.rb +4 -0
  16. data/app/models/referent.rb +7 -1
  17. data/app/models/request.rb +21 -0
  18. data/app/models/service_store.rb +16 -3
  19. data/app/presentation/section_renderer.rb +16 -17
  20. data/app/service_adaptors/blacklight.rb +11 -4
  21. data/app/service_adaptors/internet_archive.rb +58 -25
  22. data/app/service_adaptors/isi.rb +14 -4
  23. data/app/service_adaptors/jcr.rb +13 -4
  24. data/app/views/resolve/_fulltext.html.erb +1 -1
  25. data/app/views/resolve/_section_display.html.erb +1 -1
  26. data/app/views/resolve/_service_errors.html.erb +3 -3
  27. data/app/views/search/books.html.erb +1 -1
  28. data/app/views/search/journal_search.html.erb +2 -1
  29. data/app/views/search/journals.html.erb +2 -1
  30. data/db/orig_fixed_data/service_type_values.yml +15 -37
  31. data/lib/aws_product_sign.rb +1 -1
  32. data/lib/generators/templates/umlaut_services.yml +5 -2
  33. data/lib/generators/umlaut/install_generator.rb +1 -0
  34. data/{app/models → lib}/service_type_value.rb +36 -22
  35. data/lib/umlaut/routes.rb +34 -5
  36. data/lib/umlaut/test_help.rb +159 -0
  37. data/lib/umlaut/version.rb +2 -2
  38. data/lib/umlaut.rb +44 -9
  39. metadata +10 -8
@@ -0,0 +1,85 @@
1
+ module Umlaut
2
+ # NOT Rails helper methods, but a helper class with logic to determine
3
+ # whether a given umlaut display section should be given the
4
+ # umlaut-section-highlighted class, used to mark recommended access
5
+ # methods. (For instance, fulltext if it's available, or maybe
6
+ # document_delivery if it's not, but it gets more complicated.)
7
+ class SectionHighlights
8
+ attr_reader :umlaut_request, :umlaut_config
9
+
10
+ # * First arg is the umlaut Request
11
+ # * Second optional is an UmlautConfiguration object, used for
12
+ # `section_highlights_filter` lambda -- will default to
13
+ # UmlautController.umlaut_config
14
+ def initialize(umlaut_request, umlaut_config = UmlautController.umlaut_config)
15
+ @umlaut_config = umlaut_config
16
+ @umlaut_request = umlaut_request
17
+ end
18
+
19
+ def should_highlight_section?(section_id)
20
+ highlighted_sections.include? section_id.to_s
21
+ end
22
+
23
+ # array of section div_id's that should be highlighted for
24
+ # the current request in it's current state. Calculated
25
+ # with calc_highlighted_sections!, then cached.
26
+ def highlighted_sections
27
+ @highlighted_sections ||= calc_highlighted_sections!
28
+ end
29
+
30
+ # Returns an array of zero or more sections to display with
31
+ # .umlaut-section-highlighted -- usually the recommended section,
32
+ # fulltext if we have it, etc.
33
+ #
34
+ # A bit hard to get exactly right for both technical and contextual
35
+ # policy issues, this is a basic starting point.
36
+ def calc_highlighted_sections!
37
+ sections = []
38
+
39
+ if umlaut_request.get_service_type("fulltext").present?
40
+ sections << "fulltext"
41
+ end
42
+
43
+ # Highlight holdings if it's present AND:
44
+ # no fulltext is present OR it's a book (non-serial) type
45
+ # We think people want print for books more often.
46
+ if umlaut_request.get_service_type("holding").present? &&
47
+ ( umlaut_request.get_service_type("fulltext").blank? || (! MetadataHelper.title_is_serial?(umlaut_request.referent)) )
48
+ sections << "holding"
49
+ end
50
+
51
+
52
+ # Return document_delivery as highlighted only if
53
+ # fulltext and holdings are done being fetched. AND.
54
+ # If there's no fulltext or holdings, OR there's holdings, but
55
+ # it's a journal type thing, where we probably don't know if the
56
+ # particular volume/issue wanted is present. Ugh.
57
+ if ( umlaut_request.get_service_type("document_delivery").present? &&
58
+ umlaut_request.get_service_type("fulltext").empty? &&
59
+ (! umlaut_request.service_types_in_progress?(["fulltext", "holding"])) && (
60
+ umlaut_request.get_service_type("holding").empty? ||
61
+ umlaut_request.referent.format == "journal"
62
+ )
63
+ )
64
+ sections << "document_delivery"
65
+ end
66
+
67
+ sections = apply_filters!(sections)
68
+
69
+ return sections
70
+ end
71
+
72
+
73
+ def apply_filters!(sections)
74
+ sections = sections.dup
75
+
76
+ (umlaut_config.section_highlights_filter || []).each do |filter|
77
+ # filters are expected to mutate 'sections' if they want
78
+ filter.call(umlaut_request, sections, self)
79
+ end
80
+
81
+ return sections
82
+ end
83
+
84
+ end
85
+ end
@@ -74,7 +74,7 @@ module MetadataHelper
74
74
  title.gsub!(/\&/, ' and ') if options[:normalize_ampersand]
75
75
 
76
76
  # remove non-alphanumeric, excluding apostrophe
77
- title.gsub!(/[^\w\s\']/, ' ') if options[:remove_punctuation]
77
+ title.gsub!(/[^[[:alnum:]][[:space:]]\']/, ' ') if options[:remove_punctuation]
78
78
 
79
79
  # apostrophe not to space, just eat it.
80
80
  title.gsub!(/[\']/, '') if options[:remove_punctuation] && ! options[:keep_apostrophes]
@@ -341,10 +341,16 @@ module MetadataHelper
341
341
  # Look at weird bad OpenURLs, use heuristics to see if the 'title' probably
342
342
  # represents a journal rather than a book. A guess at best, based on the bad
343
343
  # data we've seen, sigh.
344
- def title_is_serial?(rft)
345
- rft.format != "book" &&
346
- ( rft.metadata['jtitle'].present? || %w{journal article}.include?(rft.metadata["genre"]) ) &&
347
- rft.metadata['btitle'].blank?
344
+ def title_is_serial?(rft)
345
+ ( rft.format != "book" && rft.format != "dissertation") &&
346
+ ( rft.metadata["btitle"].blank? ) &&
347
+ ( %w{journal article}.include?(rft.metadata["genre"]) ||
348
+ rft.metadata['jtitle'].present? ||
349
+ (rft.metadata["genre"].blank? && rft.metadata["issn"].present?)
350
+ )
348
351
  end
352
+ # Mark it a module function so it can be called as a utility as
353
+ # MetadataHelper.title_is_serial?(referent)
354
+ module_function :title_is_serial?
349
355
 
350
356
  end
@@ -12,6 +12,8 @@ require 'confstruct'
12
12
  # The Collection holds and executes the logic for running those services,
13
13
  # foreground and background, making sure no service is run twice if it's
14
14
  # already in progress, timing out expired services, etc.
15
+ #
16
+ # This code is a mess, sorry.
15
17
  class Collection
16
18
  attr_accessor :umlaut_request
17
19
  attr_accessor :logger
@@ -66,26 +68,22 @@ class Collection
66
68
  #
67
69
  # Returns the Thread object used for dispatching background services
68
70
  def dispatch_services!
69
- queued_service_ids = prepare_for_dispatch!
70
-
71
- dispatch_foreground!(queued_service_ids)
71
+ freshen_dispatches!
72
+ mark_queued_if_empty!
73
+
74
+ dispatch_foreground!
72
75
 
73
76
  # return main thread for background services.
74
- return dispatch_background!(queued_service_ids)
77
+ return dispatch_background!
75
78
  end
76
79
 
77
- # Call prepare_for_dispatch! first, the return value from that call
78
- # is suitable as argument for this call: queued_service_ids, list of
79
- # service id's already identified as suitable for running, and
80
- # marked queued in the DispatchedService table.
81
- #
82
80
  # Will run such services in foreground priority waves. And then reload
83
81
  # the UmlautRequest object in the current thread, to pick up any
84
82
  # changes made in service threads.
85
- def dispatch_foreground!(queued_service_ids)
83
+ def dispatch_foreground!
86
84
  # Foreground services
87
85
  (0..9).each do | priority |
88
- services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
86
+ services_to_run = self.instantiate_services!(:level => priority, :ids => runnable_services_for_priority(priority))
89
87
  next if services_to_run.empty?
90
88
  ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
91
89
  end
@@ -96,15 +94,13 @@ class Collection
96
94
  umlaut_request.reload
97
95
  end
98
96
 
99
- # Call prepare_for_dispatch! first, the return value from that call
100
- # is suitable as argument for this call: queued_service_ids, list of
101
- # service id's already identified as suitable for running, and
102
- # marked queued in the DispatchedService table.
103
- #
104
- # Will run such services in background priority waves.
97
+ # Will run such services in background priority waves. If some
98
+ # services are already running, will not run services in subsequent
99
+ # waves until they are done -- guard against multiple HTTP
100
+ # requests while services in progress.
105
101
  #
106
102
  # Returns the Thread object used for dispatching background services.
107
- def dispatch_background!(queued_service_ids)
103
+ def dispatch_background!
108
104
  # Now we do some crazy magic, start a Thread to run our background
109
105
  # services. We are NOT going to wait for this thread to join,
110
106
  # we're going to let it keep doing it's thing in the background after
@@ -124,11 +120,22 @@ class Collection
124
120
  # other stuff done before this thread.
125
121
  Thread.pass
126
122
 
123
+ force_refresh = false
127
124
 
128
125
  ('a'..'z').each do | priority |
129
- services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
130
- next if services_to_run.empty?
126
+ # force refresh only if we just ran some services, otherwise not enough
127
+ # time has gone by to be worthwhile.
128
+ runnable_ids = runnable_services_for_priority(priority, :refresh => force_refresh)
129
+
130
+ services_to_run = self.instantiate_services!(:level => priority, :ids => runnable_ids)
131
+
132
+ if services_to_run.empty?
133
+ force_refresh = false
134
+ next
135
+ end
136
+
131
137
  ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
138
+ force_refresh = true
132
139
  end
133
140
  rescue Exception => e
134
141
  # We are divorced from any HTTP request at this point, and may not
@@ -154,90 +161,116 @@ class Collection
154
161
  end
155
162
  end
156
163
 
157
-
158
- # Goes through all services and marks them with a DispatchedService
159
- # record in 'queued' state.
160
- #
161
- # Will time out any too-old services in a running state.
162
- #
163
- # Will remove DispatchedService status for
164
- # any services marked failed that are old enough to re-run, or services
165
- # that are too old to re-use. Such services are then queuable.
166
- #
167
- # Returns array of Service identifiers for services that are now
168
- # queued and execable.
169
- def prepare_for_dispatch!
170
- # Go through currently dispatched services, looking for timed out
171
- # services -- services still in progress that have taken too long,
172
- # as well as service responses that are too old to be used.
173
-
174
- queued_service_ids = []
175
- DispatchedService.transaction do
176
- umlaut_request.dispatched_services.each do | ds |
177
- # go through dispatched_services and set stil in progress but too long to failed temporary
178
- if ( (ds.status == DispatchedService::InProgress ||
179
- ds.status == DispatchedService::Queued ) &&
180
- (Time.now - ds.updated_at) > self.background_service_timeout)
181
-
182
- ds.store_exception( Exception.new("background service timed out (took longer than #{self.background_service_timeout} to run); thread assumed dead.")) unless ds.exception_info
183
- # Fail it temporary, it'll be run again.
184
- ds.status = DispatchedService::FailedTemporary
185
- ds.save!
186
- logger.warn("Background service timed out, thread assumed dead. #{umlaut_request.id} / #{ds.service_id}")
187
- end
188
-
189
-
190
-
191
- # go through dispatched_services and delete:
192
- # 1) old completed dispatches, too old to use.
193
- # 2) failedtemporary dispatches that are older than our resurrection time
194
- # -> And all responses associated with those dispatches.
195
- # After being deleted, they'll end up re-queued.
196
- if ( (ds.completed? && completed_dispatch_expired?(ds) ) ||
197
- ( ds.status == DispatchedService::FailedTemporary &&
198
- (Time.now - ds.updated_at) > self.requeue_failedtemporary_services_in
199
- )
200
- )
201
-
202
- # Need to expire. Delete all the service responses, and
203
- # the DispatchedService record, and service will be automatically
204
- # run again.
205
- serv_id = ds.service_id
206
-
207
- umlaut_request.service_responses.each do |response|
208
- if response.service_id == serv_id
209
- umlaut_request.service_responses.delete(response)
210
- response.destroy
211
- end
212
- end
213
-
214
- umlaut_request.dispatched_services.destroy(ds)
164
+ # Goes through existing DispatchedService objects, and freshens them up:
165
+ # * If a service is marked in progress longer than timeout, mark
166
+ # it failed temporary.
167
+ # * If an existing failed temporary is older than our resurrection time,
168
+ # delete the dispatch (and all it's responses), so it can be re-queued.
169
+ def freshen_dispatches!
170
+ umlaut_request.dispatched_services.each do | ds |
171
+ # go through dispatched_services and set still in progress but too long to failed temporary
172
+ if ( (ds.status == DispatchedService::InProgress ||
173
+ ds.status == DispatchedService::Queued ) &&
174
+ (Time.now - ds.updated_at) > self.background_service_timeout)
175
+
176
+ ds.store_exception( Exception.new("background service timed out (took longer than #{self.background_service_timeout} to run); thread assumed dead.")) unless ds.exception_info
177
+ # Fail it temporary, it'll be run again.
178
+ ds.status = DispatchedService::FailedTemporary
179
+ ds.save!
180
+ logger.warn("Background service timed out, thread assumed dead. #{umlaut_request.id} / #{ds.service_id}")
181
+ end
182
+
183
+
184
+
185
+ # go through dispatched_services and delete:
186
+ # 1) old completed dispatches, too old to use.
187
+ # 2) failedtemporary dispatches that are older than our resurrection time
188
+ # -> And all responses associated with those dispatches.
189
+ # After being deleted, they'll end up re-queued.
190
+ if ( (ds.completed? && completed_dispatch_expired?(ds) ) ||
191
+ ( ds.status == DispatchedService::FailedTemporary &&
192
+ (Time.now - ds.updated_at) > self.requeue_failedtemporary_services_in
193
+ )
194
+ )
195
+
196
+ # Need to expire. Delete all the service responses, and
197
+ # the DispatchedService record, and service will be automatically
198
+ # run again.
199
+ serv_id = ds.service_id
200
+
201
+ umlaut_request.service_responses.each do |response|
202
+ if response.service_id == serv_id
203
+ umlaut_request.service_responses.delete(response)
204
+ response.destroy
215
205
  end
206
+ end
207
+
208
+ umlaut_request.dispatched_services.destroy(ds)
216
209
  end
210
+ end
211
+ end
212
+
213
+ # For all configured services, if they have NO DispatchedService
214
+ # object, then create one with status Queued
215
+ def mark_queued_if_empty!
216
+ our_service_ids = self.get_service_definitions.collect {|d| d["service_id"]}
217
+
218
+ existing_dispatches = umlaut_request.dispatched_services.collect {|d| d.service_id}
219
+
220
+ not_yet_existing = our_service_ids - existing_dispatches
217
221
 
218
- # Queue any services without a dispatch marker at all, keeping
219
- # track of queued services, already existing or newly created.
220
-
221
- # Just in case, we're going to refetch dispatched_services from the db,
222
- # in case some other http request or background service updated things
223
- # recently.
224
- umlaut_request.dispatched_services.reset
225
-
226
- self.get_service_definitions.each do |service|
227
- service_id = service['service_id']
228
- # use in-memory #to_a search, don't go to db each time!
229
- if found = umlaut_request.dispatched_services.to_a.find {|s| s.service_id == service_id}
230
- queued_service_ids.push(service_id) if found.status == DispatchedService::Queued
231
- else
232
- umlaut_request.new_dispatch_object!(service_id, DispatchedService::Queued).save!
233
- queued_service_ids.push(service_id)
222
+ not_yet_existing.each do |service_id|
223
+ umlaut_request.new_dispatch_object!(service_id, DispatchedService::Queued).save!
224
+ end
225
+ end
226
+
227
+ # All services for priority that are marked Queued, so long as
228
+ # no previous waves are still marked running.
229
+ #
230
+ # Pass `:refresh => true` as second argument to force trip
231
+ # to the database to get fresh DispatchedService objects.
232
+ #
233
+ # Returns array of service_id's, or empty array.
234
+ def runnable_services_for_priority(priority, options = {})
235
+ DispatchedService.connection_pool.with_connection do
236
+ service_definitions = self.get_service_definitions
237
+
238
+ # Make a hash where key is service id, and value is priority.to_s
239
+ service_to_priority = Hash[
240
+ service_definitions.collect do |d|
241
+ [ d["service_id"], d["priority"].to_s ]
234
242
  end
243
+ ]
244
+
245
+ if options[:refresh]
246
+ # force a refresh
247
+ umlaut_request.dispatched_services(true)
235
248
  end
236
- end
237
249
 
238
- return queued_service_ids
250
+ # If there is any service earlier than this wave still marked InProgress,
251
+ # we're not ready to run this wave, return empty array.
252
+ # Important to avoid race condition on HTTP requests, don't
253
+ # dispatch later background waves unless earlier are actually complete,
254
+ # even on an HTTP status check.
255
+ previous_waves_running = umlaut_request.dispatched_services.find do |ds|
256
+ ds.status == DispatchedService::InProgress &&
257
+ service_to_priority[ ds.service_id ] < priority.to_s
258
+ end.present?
259
+ return [] if previous_waves_running
260
+
261
+ # otherwise, the services for this priority are runnable if
262
+ # they are already marked Queued
263
+ # We use .to_a, we want to use the already in memory array, not
264
+ # go to the db here.
265
+ return umlaut_request.dispatched_services.to_a.find_all do |ds|
266
+ ds.status == DispatchedService::Queued &&
267
+ service_to_priority[ ds.service_id ] == priority.to_s
268
+ end.collect {|ds| ds.service_id}
269
+ end
239
270
  end
240
271
 
272
+
273
+
241
274
  def completed_dispatch_expired?(ds)
242
275
  interval = self.response_expire_interval
243
276
  crontab = self.response_expire_crontab_format
@@ -68,4 +68,8 @@ class DispatchedService < ActiveRecord::Base
68
68
  def completed?
69
69
  return (self.status != InProgress) && (self.status != Queued)
70
70
  end
71
+
72
+ def failed?
73
+ return (self.status == FailedTemporary) || (self.status == FailedFatal)
74
+ end
71
75
  end
@@ -390,11 +390,17 @@ class Referent < ActiveRecord::Base
390
390
 
391
391
  # options => { :overwrite => false } to only enhance if not already there
392
392
  def enhance_referent(key, value, metadata=true, private_data=false, options = {})
393
+
394
+
393
395
  ActiveRecord::Base.connection_pool.with_connection do
394
396
  return if value.nil?
395
397
 
396
398
  matches = self.referent_values.to_a.find_all do |rv|
397
- (rv.key_name == key) && (rv.metadata == metadata) && (rv.private_data == private_data)
399
+ # We ignore #metadata and #private_data matches in overwriting
400
+ # existing value. We used to take them into account, but it triggered
401
+ # a bug in Jruby, and pretty much isn't neccesary, those fields
402
+ # are pretty useless and mostly not used and should prob be removed.
403
+ (rv.key_name == key) # && (rv.metadata == metadata) && (rv.private_data == private_data)
398
404
  end
399
405
 
400
406
  matches.each do |rv|
@@ -387,6 +387,25 @@ class Request < ActiveRecord::Base
387
387
  self.dispatched_services << ds
388
388
  return ds
389
389
  end
390
+
391
+ # Returns an array of 0 or more ServiceDispatch objects matching
392
+ # specified conditions. Right now only one condition is supported:
393
+ #
394
+ # dispatch_objects_with(:service_type_values => values)
395
+ # values can be one or more string names of service types, returns
396
+ # DispatchedServices for services whose generated values include
397
+ # one or more of what you specified.
398
+ def dispatch_objects_with(options = {})
399
+ value_names = Array(options[:service_type_values])
400
+
401
+ raise ArgumentError.new("Need to supply a :service_type_values argument") unless value_names.present?
402
+
403
+ list = self.dispatched_services.to_a.find_all do |ds|
404
+ (value_names & ds.service.service_types_generated.collect(&:name)).present?
405
+ end
406
+
407
+ return list
408
+ end
390
409
 
391
410
  protected
392
411
 
@@ -465,6 +484,8 @@ class Request < ActiveRecord::Base
465
484
  return self.dispatched_services.where(:service_id => service.service_id).first
466
485
  end
467
486
 
487
+
488
+
468
489
  # Input is a CGI::parse style of HTTP params (array values)
469
490
  # output is a string "fingerprint" canonically representing the input
470
491
  # params, which can be stored in the db, so that when another request
@@ -33,12 +33,19 @@ class ServiceStore
33
33
  end
34
34
 
35
35
 
36
- # Returns complete hash loaded from services.yml
36
+ # Returns complete hash loaded from config/umlaut_services.yml
37
+ # Passes through ERB first, allowing ERB in umlaut_services.yml
37
38
  def config
38
39
  # cache hash loaded from YAML, ensure it has the keys we expect.
39
40
  unless defined? @services_config_list
40
41
  yaml_path = File.expand_path("config/umlaut_services.yml", Rails.root)
41
- @services_config_list = (File.exists? yaml_path) ? YAML::load(File.open(yaml_path)) : {}
42
+
43
+ @services_config_list = if File.exists? yaml_path
44
+ YAML::load(ERB.new(File.open(yaml_path).read).result)
45
+ else
46
+ {}
47
+ end
48
+
42
49
  @services_config_list["default"] ||= {}
43
50
  @services_config_list["default"]["services"] ||= {}
44
51
  end
@@ -120,13 +127,19 @@ class ServiceStore
120
127
  # pass in string unique key OR a service definition hash,
121
128
  # and a current UmlautRequest.
122
129
  # get back instantiated Service object.
130
+ #
131
+ # If string service_id is passed in, but is not defined in application services,
132
+ # a ServiceStore::NoSuchService exception will be raised.
123
133
  def instantiate_service!(service, request)
124
134
  definition = service.kind_of?(Hash) ? service : service_definition_for(service.to_s)
125
- raise "Service '#{service}'' does not exist in umlaut-services.yml" if definition.nil?
135
+ raise NoSuchService.new("Service '#{service}'' does not exist in umlaut-services.yml") if definition.nil?
126
136
  className = definition["type"] || definition["service_id"]
127
137
  classConst = Kernel.const_get(className)
128
138
  service = classConst.new(definition)
129
139
  service.request = request
130
140
  return service
131
141
  end
142
+
143
+ class NoSuchService < RuntimeError ; end
144
+
132
145
  end
@@ -236,7 +236,6 @@
236
236
  # {:div_id => "search_inside", :partial => "search_inside", :show_partial_only => true}
237
237
  class SectionRenderer
238
238
  include ActionView::Helpers::TagHelper
239
- @@bg_update_sections = @@partial_update_sections = nil
240
239
 
241
240
  # First argument is the current umlaut Request object.
242
241
  # Second argument is a session description hash. See class overview
@@ -266,12 +265,12 @@ class SectionRenderer
266
265
  def initialize(a_umlaut_request, section_def = {})
267
266
  @umlaut_request = a_umlaut_request
268
267
 
269
- @section_id = section_def[:id] || section_def[:div_id]
270
- raise Exception.new("SectionRenderer needs an :id passed in arguments hash") unless @section_id
268
+ @div_id = section_def[:div_id] || section_def[:id]
269
+ raise Exception.new("SectionRenderer needs a :div_id passed in arguments hash") unless @div_id
270
+
271
271
 
272
272
  # Merge in default arguments for this section from config.
273
273
  construct_options(section_def)
274
-
275
274
  end
276
275
 
277
276
  # Returns all ServiceTypeValue objects contained in this section, as
@@ -319,7 +318,7 @@ class SectionRenderer
319
318
  end
320
319
 
321
320
  def div_id
322
- return @section_id
321
+ return @div_id
323
322
  end
324
323
 
325
324
  def show_heading?
@@ -341,7 +340,7 @@ class SectionRenderer
341
340
 
342
341
  { :partial => "background_progress",
343
342
  :locals =>{ :svc_types => service_type_values,
344
- :div_id => "progress_#{@section_id}",
343
+ :div_id => "progress_#{self.div_id}",
345
344
  :current_set_empty => responses_empty?,
346
345
  :item_name => custom_item_name
347
346
  }
@@ -404,7 +403,7 @@ class SectionRenderer
404
403
  when :complete_with_responses
405
404
  (! responses.empty?) && ! (services_in_progress?)
406
405
  when Proc
407
- # It's a lambda, which takes @umlaut_request as an arg
406
+ # It's a lambda, which takes this SectionRenderer as an arg
408
407
  @options[:visibility].call(self)
409
408
  else true
410
409
  end
@@ -504,20 +503,20 @@ class SectionRenderer
504
503
 
505
504
 
506
505
  # service type value default to same name as section_id
507
- @options[:service_type_values] ||= [@section_id]
506
+ @options[:service_type_values] ||= [self.div_id]
508
507
 
509
508
  # Partials to display. Default to _standard_response_item item partial.
510
509
  if ( @options[:partial] == true)
511
- @options[:partial] = @section_id
512
- end
513
- if (@options[:partial].blank?)
514
- @options[:item_partial] =
515
- case @options[:item_partial]
516
- when true then @section_id + "_item"
517
- when String then options[:item_partial]
518
- else "standard_response_item"
519
- end
510
+ @options[:partial] = self.div_id
520
511
  end
512
+
513
+ @options[:item_partial] =
514
+ case @options[:item_partial]
515
+ when true then self.div_id + "_item"
516
+ when String then options[:item_partial]
517
+ else "standard_response_item"
518
+ end
519
+
521
520
 
522
521
  # sanity check
523
522
  if ( @options[:show_partial_only] && ! @options[:partial])
@@ -90,6 +90,7 @@ class Blacklight < Service
90
90
  holdings_url = blacklight_precise_search_url( request, "dlf_expanded" )
91
91
  holdings_added += add_holdings( holdings_url ) if holdings_url
92
92
  end
93
+
93
94
  #keyword search.
94
95
  if (@keyword_search &&
95
96
  url = blacklight_keyword_search_url(request))
@@ -210,8 +211,8 @@ class Blacklight < Service
210
211
  # phrase search for title, just raw dismax for author
211
212
  # Embed quotes inside the quoted value, need to backslash-quote for CQL,
212
213
  # and backslash the backslashes for ruby literal.
213
- clauses.push("#{@bl_fields["title"]} = \"\\\"#{remove_quotes_for_phrase_value title}\\\"\"")
214
- clauses.push("#{@bl_fields["author"]} = \"#{remove_quotes_for_phrase_value author}\"") if author
214
+ clauses.push("#{@bl_fields["title"]} = \"\\\"#{escape_for_cql_double_quotes title}\\\"\"")
215
+ clauses.push("#{@bl_fields["author"]} = \"#{escape_for_cql_double_quotes author}\"") if author
215
216
 
216
217
  url = base_url + "?search_field=#{@cql_search_field}&content_format=#{options[:content_format]}&q=#{CGI.escape(clauses.join(" AND "))}"
217
218
 
@@ -228,8 +229,14 @@ class Blacklight < Service
228
229
  # error if we do nothing. Can we escape it somehow? CQL is really
229
230
  # unclear, we're ALREADY backslash escaping the phrase quotes themselves!
230
231
  # We just replace them with space, should work for our actual indexing.
231
- def remove_quotes_for_phrase_value(str)
232
- str.gsub('"', " ")
232
+ #
233
+ # Single quotes (apostrophes) need to be escaped with an apostrophe itself,
234
+ # `''`, apparently. http://mail-archives.apache.org/mod_mbox/cassandra-user/201108.mbox/%3C20110803152250.294300@gmx.net%3E
235
+ def escape_for_cql_double_quotes(str)
236
+ str = str.gsub('"', " ")
237
+ str = str.gsub("'", "''")
238
+
239
+ return str
233
240
  end
234
241
 
235
242