umlaut 3.0.0alpha9 → 3.0.0alpha10

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 (36) hide show
  1. data/app/controllers/umlaut_controller.rb +5 -0
  2. data/app/models/collection.rb +90 -54
  3. data/app/models/referent.rb +30 -17
  4. data/app/models/service_wave.rb +59 -41
  5. data/lib/exlibris/primo/holding.rb +3 -9
  6. data/lib/exlibris/primo/related_link.rb +17 -0
  7. data/lib/exlibris/primo/searcher.rb +52 -31
  8. data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +59 -38
  9. data/lib/exlibris/primo_ws.rb +1 -1
  10. data/lib/service.rb +1 -1
  11. data/lib/service_adaptors/primo_service.rb +38 -5
  12. data/lib/service_adaptors/primo_source.rb +2 -2
  13. data/lib/service_adaptors/sfx.rb +11 -0
  14. data/lib/term_color.rb +9 -2
  15. data/lib/umlaut/version.rb +1 -1
  16. data/lib/umlaut_configurable.rb +9 -0
  17. data/test/dummy/config/umlaut_services.yml +20 -0
  18. data/test/dummy/tmp/cache/assets/CDC/680/sprockets%2F2b68ef632d12610f3c9563168bfa7c05 +0 -0
  19. data/test/dummy/tmp/cache/assets/CF5/9B0/sprockets%2F7933bfe880731b396791f1682ce3f7fa +0 -0
  20. data/test/dummy/tmp/cache/assets/CFB/2F0/sprockets%2F62d51f0aa5cac4b1cf7091823772a604 +0 -0
  21. data/test/dummy/tmp/cache/assets/D4C/0A0/sprockets%2F7810d837eec3ac57ad78756af83a6a55 +0 -0
  22. data/test/dummy/tmp/cache/assets/D4C/E30/sprockets%2F631abf89746799b7a5b2b3b4f6294bcd +0 -0
  23. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  24. data/test/dummy/tmp/cache/assets/D6C/7D0/sprockets%2F8a05d6981ec0d38c51739bef0b3a9c2b +0 -0
  25. data/test/dummy/tmp/cache/assets/D70/080/sprockets%2F24d3ce40ae5cc827a9183b1fb837e84e +0 -0
  26. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  27. data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
  28. data/test/unit/aleph_patron_test.rb +6 -6
  29. data/test/unit/aleph_record_benchmarks.rb +1 -1
  30. data/test/unit/aleph_record_test.rb +4 -4
  31. data/test/unit/primo_searcher_test.rb +90 -82
  32. data/test/unit/primo_service_test.rb +683 -680
  33. data/test/unit/primo_ws_test.rb +45 -32
  34. metadata +135 -155
  35. data/lib/tasks/#Untitled-1# +0 -1843
  36. data/test/dummy/out +0 -5
@@ -57,6 +57,11 @@ class UmlautController < ApplicationController
57
57
  # (see lib/referent_filters )
58
58
  # add_referent_filters!( :match => /.*/, :filter => DissertationCatch.new )
59
59
 
60
+ # Turn off permalink-generation? If you don't want it at all, or
61
+ # don't want it temporarily because you are pointing at a database
62
+ # that won't last.
63
+ # create_permalinks false
64
+
60
65
  # How many seconds between updates of the background updater for background
61
66
  # services?
62
67
  # poll_wait_seconds 4
@@ -53,6 +53,95 @@ class Collection
53
53
  # Sets all services in collection to have a 'queued' status if appropriate.
54
54
  # Then actually executes the services that are dispatchable (queued).
55
55
  def dispatch_services!
56
+ queued_service_ids = prepare_for_dispatch!
57
+
58
+ dispatch_foreground!(queued_service_ids)
59
+
60
+ dispatch_background!(queued_service_ids)
61
+ end
62
+
63
+ # Call prepare_for_dispatch! first, the return value from that call
64
+ # is suitable as argument for this call: queued_service_ids, list of
65
+ # service id's already identified as suitable for running, and
66
+ # marked queued in the DispatchedService table.
67
+ #
68
+ # Will run such services in foreground priority waves. And then reload
69
+ # the UmlautRequest object in the current thread, to pick up any
70
+ # changes made in service threads.
71
+ def dispatch_foreground!(queued_service_ids)
72
+ # Foreground services
73
+ (0..9).each do | priority |
74
+ services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
75
+ next if services_to_run.empty?
76
+ ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
77
+ end
78
+
79
+ # Need to reload the request from db, so it gets changes
80
+ # made by services in threads, so future code (such as view rendering)
81
+ # will see changes.
82
+ umlaut_request.reload
83
+ end
84
+
85
+ # Call prepare_for_dispatch! first, the return value from that call
86
+ # is suitable as argument for this call: queued_service_ids, list of
87
+ # service id's already identified as suitable for running, and
88
+ # marked queued in the DispatchedService table.
89
+ #
90
+ # Will run such services in background priority waves.
91
+ def dispatch_background!(queued_service_ids)
92
+ # Now we do some crazy magic, start a Thread to run our background
93
+ # services. We are NOT going to wait for this thread to join,
94
+ # we're going to let it keep doing it's thing in the background after
95
+ # we return a response to the browser
96
+ backgroundThread = Thread.new(self, umlaut_request) do | t_collection, t_request|
97
+ # Tell our AR extension not to allow implicit checkouts
98
+ ActiveRecord::Base.forbid_implicit_checkout_for_thread! if ActiveRecord::Base.respond_to?("forbid_implicit_checkout_for_thread!")
99
+
100
+ # got to reserve an AR connection for our main 'background traffic director'
101
+ # thread, so it has a connection to use to mark services as failed, at least.
102
+ ActiveRecord::Base.connection_pool.with_connection do
103
+ begin
104
+ # Deal with ruby's brain dead thread scheduling by setting
105
+ # bg threads to a lower priority so they don't interfere with fg
106
+ # threads.
107
+ Thread.current.priority = -1
108
+
109
+ ('a'..'z').each do | priority |
110
+ services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
111
+ next if services_to_run.empty?
112
+ ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
113
+ end
114
+ rescue Exception => e
115
+ # We are divorced from any HTTP request at this point, and may not
116
+ # have access to an ActiveRecord connection. Not much
117
+ # we can do except log it.
118
+ # If we're catching an exception here, service processing was
119
+ # probably interrupted, which is bad. You should not intentionally
120
+ # raise exceptions to be caught here.
121
+ #
122
+ # Normally even unexpected exceptions were caught inside the ServiceWave,
123
+ # and logged to db as well as logfile if possible, only bugs in ServiceWave
124
+ # itself should wind up caught here.
125
+ Thread.current[:exception] = e
126
+ logger.error("Background Service execution exception: #{e}\n\n " + clean_backtrace(e).join("\n"))
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+
133
+ # Goes through all services and marks them with a DispatchedService
134
+ # record in 'queued' state.
135
+ #
136
+ # Will time out any too-old services in a running state.
137
+ #
138
+ # Will remove DispatchedService status for
139
+ # any services marked failed that are old enough to re-run, or services
140
+ # that are too old to re-use. Such services are then queuable.
141
+ #
142
+ # Returns array of Service identifiers for services that are now
143
+ # queued and execable.
144
+ def prepare_for_dispatch!
56
145
  # Go through currently dispatched services, looking for timed out
57
146
  # services -- services still in progress that have taken too long,
58
147
  # as well as service responses that are too old to be used.
@@ -117,60 +206,7 @@ class Collection
117
206
  end
118
207
  end
119
208
 
120
-
121
-
122
- # Now actually dispatch.
123
-
124
- # Foreground services
125
- (0..9).each do | priority |
126
- services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
127
- next if services_to_run.empty?
128
- ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
129
- end
130
-
131
- # Need to reload the request from db, so it gets changes
132
- # made by services in threads.
133
- umlaut_request.reload
134
-
135
- # Now we run background services.
136
- # Now we do some crazy magic, start a Thread to run our background
137
- # services. We are NOT going to wait for this thread to join,
138
- # we're going to let it keep doing it's thing in the background after
139
- # we return a response to the browser
140
- backgroundThread = Thread.new(self, umlaut_request) do | t_collection, t_request|
141
- # Tell our AR extension not to allow implicit checkouts
142
- ActiveRecord::Base.forbid_implicit_checkout_for_thread! if ActiveRecord::Base.respond_to?("forbid_implicit_checkout_for_thread!")
143
-
144
- # got to reserve an AR connection for our main 'background traffic director'
145
- # thread, so it has a connection to use to mark services as failed, at least.
146
- ActiveRecord::Base.connection_pool.with_connection do
147
- begin
148
- # Deal with ruby's brain dead thread scheduling by setting
149
- # bg threads to a lower priority so they don't interfere with fg
150
- # threads.
151
- Thread.current.priority = -1
152
-
153
- ('a'..'z').each do | priority |
154
- services_to_run = self.instantiate_services!(:level => priority, :ids => queued_service_ids)
155
- next if services_to_run.empty?
156
- ServiceWave.new(services_to_run , priority).handle(umlaut_request, umlaut_request.session_id)
157
- end
158
- rescue Exception => e
159
- #debugger
160
- # We are divorced from any request at this point, not much
161
- # we can do except log it. Actually, we'll also store it in the
162
- # db, and clean up after any dispatched services that need cleaning up.
163
- # If we're catching an exception here, service processing was
164
- # probably interrupted, which is bad. You should not intentionally
165
- # raise exceptions to be caught here.
166
- Thread.current[:exception] = e
167
- logger.error("Background Service execution exception1: #{e}\n\n " + clean_backtrace(e).join("\n"))
168
- end
169
- end
170
- end
171
-
172
-
173
-
209
+ return queued_service_ids
174
210
  end
175
211
 
176
212
  def completed_dispatch_expired?(ds)
@@ -85,6 +85,9 @@ class Referent < ActiveRecord::Base
85
85
  # :permalink => false if you already have a permalink and don't
86
86
  # need to create one. Caller should attach that permalink to this referent!
87
87
  def self.create_by_context_object(co, options = {})
88
+ options = { :permalink => UmlautController.umlaut_config.create_permalinks
89
+ }.merge(options)
90
+
88
91
 
89
92
  self.clean_up_context_object(co)
90
93
  rft = Referent.new
@@ -186,8 +189,10 @@ class Referent < ActiveRecord::Base
186
189
  rv.key_name = key_name
187
190
  rv.value = value
188
191
  rv.normalized_value = normalized_value
189
-
190
- unless (key_name == "identifier" || key_name == "format")
192
+
193
+ if key_name == "private_data"
194
+ rv.private_data = true
195
+ elsif key_name != "identifier" && key_name != "format"
191
196
  rv.metadata = true
192
197
  end
193
198
 
@@ -209,11 +214,19 @@ class Referent < ActiveRecord::Base
209
214
  if rft.format
210
215
  ensure_value!('format', rft.format)
211
216
  end
212
-
217
+ if rft.private_data
218
+ # this comes in as "pid" or "rft_dat", we store it in
219
+ # our database as "private_data", sorry, easiest way to
220
+ # fit this in at the moment.
221
+ ensure_value!("private_data", rft.private_data)
222
+ end
223
+
213
224
  rft.metadata.each { | key, value |
214
225
  next unless value
215
226
  ensure_value!( key, value)
216
- }
227
+ }
228
+
229
+
217
230
  end
218
231
 
219
232
  # pass in a Referent, or a ropenurl ContextObjectEntity that has a metadata
@@ -320,9 +333,8 @@ class Referent < ActiveRecord::Base
320
333
  co.referent = OpenURL::ContextObjectEntity.new_from_format( fmt_uri )
321
334
  rft = co.referent
322
335
 
323
- # Now set all the values.
336
+ # Now set all the values.
324
337
  self.referent_values.each do | val |
325
- next if val.private_data?
326
338
  if val.metadata?
327
339
  rft.set_metadata(val.key_name, val.value)
328
340
  next
@@ -338,8 +350,7 @@ class Referent < ActiveRecord::Base
338
350
  # call self.metadata once and use the array for efficiency, don't
339
351
  # keep calling it. profiling shows it DOES make a difference.
340
352
  my_metadata = self.metadata
341
-
342
-
353
+
343
354
  if my_metadata['atitle'] && ! my_metadata['atitle'].blank?
344
355
  citation[:title] = my_metadata['atitle']
345
356
  citation[:title_label], citation[:subtitle_label] =
@@ -392,23 +403,25 @@ class Referent < ActiveRecord::Base
392
403
  end
393
404
  if ! my_metadata["au"].blank?
394
405
  citation[:author] = my_metadata["au"]
395
- elsif my_metadata["aulast"]
406
+ elsif my_metadata["aulast"]
396
407
  citation[:author] = my_metadata["aulast"]
397
408
  if ! my_metadata["aufirst"].blank?
398
- citation[:author] += ', '+my_metadata["aufirst"]
409
+ citation[:author] += ', '+my_metadata["aufirst"]
399
410
  else
400
411
  if ! my_metadata["auinit"].blank?
401
412
  citation[:author] += ', '+my_metadata["auinit"]
402
413
  else
403
- if ! my_metadata["auinit1"].blank?
414
+ if ! my_metadata["auinit1"].blank?
404
415
  citation[:author] += ', '+my_metadata["auinit1"]
405
- end
406
- if ! my_metadata["auinitm"].blank?
416
+ end
417
+ if ! my_metadata["auinitm"].blank?
407
418
  citation[:author] += my_metadata["auinitm"]
408
- end
409
- end
410
- end
411
- end
419
+ end
420
+ end
421
+ end
422
+ elsif my_metadata["aucorp"]
423
+ citation[:author] = my_metadata["aucorp"]
424
+ end
412
425
  if my_metadata['spage']
413
426
  citation[:page] = my_metadata['spage']
414
427
  citation[:page] += ' - ' + my_metadata['epage'] if ! my_metadata['epage'].blank?
@@ -5,12 +5,13 @@
5
5
  class ServiceWave
6
6
  attr_accessor :services
7
7
  attr_accessor :priority_level
8
+ attr_reader :config
8
9
 
9
10
  # Priority level is purely information, used for debug output.
10
11
  def initialize(service_objects, priority_level = nil, config = UmlautController.umlaut_config)
11
12
  @services = service_objects
12
13
  @priority_level = priority_level
13
-
14
+ @config = config
14
15
  @log_timing = config.lookup!("log_service_timing", true)
15
16
 
16
17
  # Don't forward exceptions, that'll interrupt other service processing.
@@ -44,58 +45,75 @@ class ServiceWave
44
45
  Rails.logger.info(TermColor.color("Umlaut: Launching service wave #{@priority_level}", :yellow) + ", request #{request.id}") if @log_timing
45
46
 
46
47
 
48
+
47
49
  threads = []
48
50
  some_service_executed = false
49
51
  @services.each do | service |
50
52
  some_service_executed = true
51
53
  local_request = nil
52
-
53
- threads << Thread.new(request.id, service.clone) do | request_id, local_service |
54
- # Deal with ruby's brain dead thread scheduling by setting
55
- # bg threads to a lower priority so they don't interfere with fg
56
- # threads.
57
- Thread.current.priority = -1
54
+
55
+ service_start = Time.now
58
56
 
59
- # Save some things in thread local hash useful for debugging
60
- Thread.current[:debug_name] = local_service.class.name
61
- Thread.current[:service] = service
57
+ if config.lookup!("threaded_service_wave", true)
62
58
 
63
- # Tell our AR extension not to allow implicit checkouts
64
- ActiveRecord::Base.forbid_implicit_checkout_for_thread! if ActiveRecord::Base.respond_to?("forbid_implicit_checkout_for_thread!")
65
- begin
66
- service_start = Time.now
67
- local_request = ActiveRecord::Base.connection_pool.with_connection do
68
- # pre-load all relationships so no ActiveRecord activity will be
69
- # needed later to see em.
70
- Request.includes(:referent, :service_responses, :dispatched_services).find(request_id)
59
+
60
+ threads << Thread.new(request.id, service.clone) do | request_id, local_service |
61
+ # Deal with ruby's brain dead thread scheduling by setting
62
+ # bg threads to a lower priority so they don't interfere with fg
63
+ # threads.
64
+ Thread.current.priority = -1
65
+
66
+ # Save some things in thread local hash useful for debugging
67
+ Thread.current[:debug_name] = local_service.class.name
68
+ Thread.current[:service] = service
69
+
70
+ # Tell our AR extension not to allow implicit checkouts
71
+ ActiveRecord::Base.forbid_implicit_checkout_for_thread! if ActiveRecord::Base.respond_to?("forbid_implicit_checkout_for_thread!")
72
+ begin
73
+ local_request = ActiveRecord::Base.connection_pool.with_connection do
74
+ # pre-load all relationships so no ActiveRecord activity will be
75
+ # needed later to see em.
76
+ Request.includes(:referent, :service_responses, :dispatched_services).find(request_id)
77
+ end
78
+
79
+
80
+ if prepare_dispatch!(local_request, local_service, session_id)
81
+ local_service.handle_wrapper(local_request)
82
+ else
83
+ Rails.logger.info("NOT launching service #{local_service.service_id}, level #{@priority_level}, request #{local_request.id}: not in runnable state") if @log_timing
84
+ end
85
+
86
+
87
+ rescue Exception => e
88
+ # We may not be able to access ActiveRecord because it may
89
+ # have been an AR connection error, perhaps out of connections
90
+ # in the pool. So log and record in non-AR ways.
91
+ # the code waiting on our thread will see exception
92
+ # reported in Thread local var, and log it AR if possible.
93
+
94
+
95
+ # Log it too, although experience shows it may never make it to the
96
+ # log for mysterious reasons.
97
+ Rails.logger.error(TermColor.color("Umlaut: Threaded service raised exception.", :red, true) + "Service: #{service.service_id}, #{e}\n #{clean_backtrace(e).join("\n ")}")
98
+
99
+ # And stick it in a thread variable too
100
+ Thread.current[:exception] = e
101
+ ensure
102
+ Rails.logger.info(TermColor.color("Umlaut: Completed service #{local_service.service_id}", :yellow)+ ", level #{@priority_level}, request #{local_request && local_request.id}: in #{Time.now - service_start}.") if @log_timing
71
103
  end
72
-
73
-
74
- if prepare_dispatch!(local_request, local_service, session_id)
75
- local_service.handle_wrapper(local_request)
104
+ end
105
+ else # not threaded
106
+ begin
107
+ if prepare_dispatch!(request, service, session_id)
108
+ service.handle_wrapper(request)
76
109
  else
77
- Rails.logger.info("NOT launching service #{local_service.service_id}, level #{@priority_level}, request #{local_request.id}: not in runnable state") if @log_timing
110
+ Rails.logger.info("NOT launching service #{service.service_id}, level #{@priority_level}, request #{request.id}: not in runnable state") if @log_timing
78
111
  end
79
-
80
-
81
- rescue Exception => e
82
- # We may not be able to access ActiveRecord because it may
83
- # have been an AR connection error, perhaps out of connections
84
- # in the pool. So log and record in non-AR ways.
85
- # the code waiting on our thread will see exception
86
- # reported in Thread local var, and log it AR if possible.
87
-
88
-
89
- # Log it too, although experience shows it may never make it to the
90
- # log for mysterious reasons.
91
- Rails.logger.error(TermColor.color("Umlaut: Threaded service raised exception.", :red, true) + "Service: #{service.service_id}, #{e}\n #{clean_backtrace(e).join("\n ")}")
92
-
93
- # And stick it in a thread variable too
94
- Thread.current[:exception] = e
95
112
  ensure
96
- Rails.logger.info(TermColor.color("Umlaut: Completed service #{local_service.service_id}", :yellow)+ ", level #{@priority_level}, request #{local_request && local_request.id}: in #{Time.now - service_start}.") if @log_timing
113
+ Rails.logger.info(TermColor.color("Umlaut: Completed service #{service.service_id}", :yellow)+ ", level #{@priority_level}, request #{request && request.id}: in #{Time.now - service_start}.") if @log_timing
97
114
  end
98
- end
115
+ end
116
+
99
117
  end
100
118
 
101
119
  # Wait for all the threads to complete, if any.
@@ -36,7 +36,6 @@ module Exlibris::Primo
36
36
  # == Examples
37
37
  # Example of Primo source implementations are:
38
38
  # * Exlibris::Primo::Source::Aleph
39
- # * Exlibris::Primo::Source::Local::NYUAleph
40
39
  class Holding
41
40
  @base_attributes = [ :record_id, :source_id, :original_source_id, :source_record_id,
42
41
  :availlibrary, :institution_code, :institution, :library_code, :library,
@@ -141,16 +140,11 @@ module Exlibris::Primo
141
140
  def to_source
142
141
  return self if @source_class.nil?
143
142
  begin
144
- # Check source class in source module, if not found, see if there is a local class
145
- return (Exlibris::Primo::Source.const_defined?(@source_class)) ?
146
- Exlibris::Primo::Source.const_get(@source_class).new(:holding => self) :
147
- Exlibris::Primo::Source::Local.const_get(@source_class).new(:holding => self)
143
+ # Get source class in Primo::Source module
144
+ return Exlibris::Primo::Source.const_get(@source_class).new(:holding => self)
148
145
  rescue Exception => e
149
- # !!!!!!!!!!REMOVE NEXT LINE (raise e) WHEN GOING TO PRODUCTION!!!!!!!!!
150
- raise e
151
146
  Rails.logger.error("#{e.message}")
152
- Rails.logger.error("Class #{@source_class} can't be found in either
153
- Exlibris::Primo::Source or Exlibris::Primo::Source::Local.
147
+ Rails.logger.error("Class #{@source_class} can't be found in Exlibris::Primo::Source.
154
148
  Please check primo.yml to ensure the class_name is defined correctly.
155
149
  Not converting to source.")
156
150
  return self
@@ -0,0 +1,17 @@
1
+ module Exlibris::Primo
2
+ # Class for handling Primo related links from links/addlink
3
+ class RelatedLink
4
+ @base_attributes = [ :record_id, :addlink, :url, :display, :notes ]
5
+ class << self; attr_reader :base_attributes end
6
+ def initialize(options={})
7
+ base_attributes = (self.class.base_attributes.nil?) ?
8
+ Exlibris::Primo::RelatedLink.base_attributes : self.class.base_attributes
9
+ base_attributes.each { |attribute|
10
+ self.class.send(:attr_reader, attribute)
11
+ }
12
+ options.each { |option, value|
13
+ self.instance_variable_set(('@'+option.to_s).to_sym, value)
14
+ }
15
+ end
16
+ end
17
+ end
@@ -16,7 +16,9 @@ module Exlibris::Primo
16
16
 
17
17
  attr_reader :response, :count
18
18
  attr_reader :cover_image, :titles, :author
19
- attr_reader :holdings, :rsrcs, :tocs
19
+ attr_reader :holdings, :rsrcs, :tocs, :related_links
20
+ PNX_NS = {'pnx' => 'http://www.exlibrisgroup.com/xsd/primo/primo_nm_bib'}
21
+ SEARCH_NS = {'search' => 'http://www.exlibrisgroup.com/xsd/jaguar/search'}
20
22
 
21
23
  # Instantiates the object and performs the search for based on the input search criteria.
22
24
  # setup parameter requires { :base_url => http://primo.server.institution.edu }
@@ -28,6 +30,7 @@ module Exlibris::Primo
28
30
  @holdings = []
29
31
  @rsrcs = []
30
32
  @tocs = []
33
+ @related_links = []
31
34
  @holding_attributes = Exlibris::Primo::Holding.base_attributes
32
35
  @base_url = setup[:base_url]
33
36
  raise_required_setup_parameter_error :base_url if @base_url.nil?
@@ -95,22 +98,21 @@ module Exlibris::Primo
95
98
 
96
99
  # Process a single record
97
100
  def process_record
98
- @count = response.at("//DOCSET")["TOTALHITS"] unless response.nil? or @count
99
- response.at("//addata").each_child do |addata_child|
100
- name = addata_child.pathname and value = addata_child.inner_text.chars.to_s if addata_child.elem?
101
+ @count = response.at("//search:DOCSET", SEARCH_NS)["TOTALHITS"] unless response.nil? or @count
102
+ response.at("//pnx:addata", PNX_NS).children.each do |addata_child|
103
+ name = addata_child.name and value = addata_child.inner_text if addata_child.elem?
101
104
  next if value.nil?
102
105
  self.class.add_attr_reader name.to_sym unless name.nil?
103
- # instance_variable_set("@#{name}".to_sym, "#{convert_diacritics(value)}") unless name.nil?
104
106
  instance_variable_set("@#{name}".to_sym, "#{value}") unless name.nil?
105
107
  end
106
- @cover_image = response.at("//addata/lad02").inner_text unless response.at("//addata/lad02").nil?
108
+ @cover_image = response.at("//pnx:addata/pnx:lad02", PNX_NS).inner_text unless response.at("//pnx:addata/pnx:lad02", PNX_NS).nil?
107
109
  @titles = []
108
- response.search("//display/title") do |title|
109
- @titles.push(title.inner_text.chars.to_s)
110
+ response.search("//pnx:display/pnx:title", PNX_NS).each do |title|
111
+ @titles.push(title.inner_text)
110
112
  end
111
113
  @authors = []
112
- response.search("//display/creator") do |e|
113
- @authors.push(e.inner_text.chars.to_s)
114
+ response.search("//pnx:display/pnx:creator", PNX_NS).each do |creator|
115
+ @authors.push(creator.inner_text)
114
116
  end
115
117
  end
116
118
 
@@ -119,30 +121,27 @@ module Exlibris::Primo
119
121
  # Process URLs based on links/linktorsrc
120
122
  # Process TOCs based on links/linktotoc
121
123
  def process_search_results
122
- @count = (response.at("//DOCSET").nil?) ?
123
- (response.at("//sear:DOCSET")["TOTALHITS"].nil?) ? 0 :
124
- response.at("//sear:DOCSET")["TOTALHITS"] :
125
- response.at("//DOCSET")["TOTALHITS"] unless response.nil? or @count
124
+ @count = response.at("//search:DOCSET", SEARCH_NS)["TOTALHITS"] unless response.nil? or @count
126
125
  # Loop through records to set metadata for holdings, urls and tocs
127
- response.search("//record") do |record|
126
+ response.search("//pnx:record", PNX_NS).each do |record|
128
127
  # Default genre to article if necessary
129
- record_genre = (record.at("addata/genre").nil?) ? "article" : record.at("addata/genre").inner_text
128
+ record_genre = (record.xpath("pnx:addata/pnx:genre", PNX_NS).nil?) ? "article" : record.xpath("pnx:addata/pnx:genre", PNX_NS).inner_text
130
129
  # Don't process if passed in genre doesn't match the record genre unless the discrepancy is only b/w journals and articles
131
130
  # If we're working off id numbers, we should be good to proceed
132
131
  next unless @primo_id or @isbn or @issn or
133
132
  @genre == record_genre or (@genre == "journal" and record_genre == "article")
134
133
  # Just take the first element for record level elements
135
134
  # (should only be one, except sourceid which will be handled later)
136
- record_id = record.at("control/recordid").inner_text
137
- display_type = record.at("display/type").inner_text
138
- original_source_id = record.at("control/originalsourceid").inner_text unless record.at("control/originalsourceid").nil?
139
- original_source_ids = process_control_hash(record, "control/originalsourceid")
140
- source_id = record.at("control/sourceid").inner_text
141
- source_ids = process_control_hash(record, "control/sourceid")
142
- source_record_id = record.at("control/sourcerecordid").inner_text
135
+ record_id = record.xpath("pnx:control/pnx:recordid", PNX_NS).inner_text
136
+ display_type = record.xpath("pnx:display/pnx:type", PNX_NS).inner_text
137
+ original_source_id = record.xpath("pnx:control/pnx:originalsourceid", PNX_NS).inner_text unless record.xpath("pnx:control/pnx:originalsourceid", PNX_NS).nil?
138
+ original_source_ids = process_control_hash(record, "pnx:control/pnx:originalsourceid", PNX_NS)
139
+ source_id = record.xpath("pnx:control/pnx:sourceid", PNX_NS).inner_text
140
+ source_ids = process_control_hash(record, "pnx:control/pnx:sourceid", PNX_NS)
141
+ source_record_id = record.xpath("pnx:control/pnx:sourcerecordid", PNX_NS).inner_text
143
142
  # Process holdings
144
- source_record_ids = process_control_hash(record, "control/sourcerecordid")
145
- record.search("display/availlibrary") do |availlibrary|
143
+ source_record_ids = process_control_hash(record, "pnx:control/pnx:sourcerecordid", PNX_NS)
144
+ record.xpath("pnx:display/pnx:availlibrary", PNX_NS).each do |availlibrary|
146
145
  availlibrary, institution_code, library_code, id_one, id_two, status_code, origin = process_availlibrary availlibrary
147
146
  holding_original_source_id = (origin.nil?) ? original_source_ids[record_id] : original_source_ids[origin] unless original_source_ids.empty?
148
147
  holding_original_source_id = original_source_id if holding_original_source_id.nil?
@@ -158,14 +157,15 @@ module Exlibris::Primo
158
157
  :library_code => library_code, :id_one => id_one, :id_two => id_two,
159
158
  :status_code => status_code, :origin => origin, :display_type => display_type, :notes => "",
160
159
  :match_reliability =>
161
- (reliable_match?(:title => record.at("display/title").inner_text, :author => record.at("display/creator").inner_text)) ?
162
- ServiceResponse::MatchExact : ServiceResponse::MatchUnsure
160
+ (record.xpath("pnx:display/pnx:title", PNX_NS) and record.xpath("pnx:display/pnx:creator", PNX_NS)) ?
161
+ (reliable_match?(:title => record.xpath("pnx:display/pnx:title", PNX_NS).inner_text, :author => record.xpath("pnx:display/pnx:creator", PNX_NS).inner_text)) ?
162
+ ServiceResponse::MatchExact : ServiceResponse::MatchUnsure : ServiceResponse::MatchExact
163
163
  }
164
164
  holding = Exlibris::Primo::Holding.new(holding_parameters)
165
165
  @holdings.push(holding) unless holding.nil?
166
166
  end
167
167
  # Process urls
168
- record.search("links/linktorsrc") do |linktorsrc|
168
+ record.xpath("pnx:links/pnx:linktorsrc", PNX_NS).each do |linktorsrc|
169
169
  linktorsrc, v, url, display, institution_code, origin = process_linktorsrc linktorsrc
170
170
  rsrc = Exlibris::Primo::Rsrc.new({
171
171
  :record_id => record_id, :linktorsrc => linktorsrc,
@@ -176,7 +176,7 @@ module Exlibris::Primo
176
176
  @rsrcs.push(rsrc) unless (rsrc.nil? or rsrc.url.nil?)
177
177
  end
178
178
  # Process tocs
179
- record.search("links/linktotoc") do |linktotoc|
179
+ record.xpath("pnx:links/pnx:linktotoc", PNX_NS).each do |linktotoc|
180
180
  linktotoc, url, display = process_linktotoc linktotoc
181
181
  toc = Exlibris::Primo::Toc.new({
182
182
  :record_id => record_id, :linktotoc => linktotoc,
@@ -185,12 +185,22 @@ module Exlibris::Primo
185
185
  }) unless linktotoc.nil?
186
186
  @tocs.push(toc) unless (toc.nil? or toc.url.nil?)
187
187
  end
188
+ # Process addlinks
189
+ record.xpath("pnx:links/pnx:addlink", PNX_NS).each do |addlink|
190
+ addlink, url, display = process_addlink addlink
191
+ related_link = Exlibris::Primo::RelatedLink.new({
192
+ :record_id => record_id, :addlink => addlink,
193
+ :url => url, :display => display,
194
+ :notes => ""
195
+ }) unless addlink.nil?
196
+ @related_links.push(related_link) unless (related_link.nil? or related_link.url.nil?)
197
+ end
188
198
  end
189
199
  end
190
200
 
191
- def process_control_hash(record, xpath)
201
+ def process_control_hash(record, xpath, ns)
192
202
  h = {}
193
- record.search(xpath) do |e|
203
+ record.xpath(xpath, ns).each do |e|
194
204
  str = e.inner_text unless e.nil?
195
205
  a = str.split(/\$(?=\$)/) unless str.nil?
196
206
  v = nil
@@ -263,6 +273,17 @@ module Exlibris::Primo
263
273
  return linktotoc, url, display
264
274
  end
265
275
 
276
+ def process_addlink(input)
277
+ addlink, url, display, = nil, nil, nil
278
+ return addlink, url, display if input.nil? or input.inner_text.nil?
279
+ addlink = input.inner_text
280
+ addlink.split(/\$(?=\$)/).each do |s|
281
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
282
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
283
+ end
284
+ return addlink, url, display
285
+ end
286
+
266
287
  def raise_required_setup_parameter_error(parameter)
267
288
  raise "Initialization error in #{self.class}. Missing required setup parameter: #{parameter}."
268
289
  end