umlaut 3.0.0alpha9 → 3.0.0alpha10
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/umlaut_controller.rb +5 -0
- data/app/models/collection.rb +90 -54
- data/app/models/referent.rb +30 -17
- data/app/models/service_wave.rb +59 -41
- data/lib/exlibris/primo/holding.rb +3 -9
- data/lib/exlibris/primo/related_link.rb +17 -0
- data/lib/exlibris/primo/searcher.rb +52 -31
- data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +59 -38
- data/lib/exlibris/primo_ws.rb +1 -1
- data/lib/service.rb +1 -1
- data/lib/service_adaptors/primo_service.rb +38 -5
- data/lib/service_adaptors/primo_source.rb +2 -2
- data/lib/service_adaptors/sfx.rb +11 -0
- data/lib/term_color.rb +9 -2
- data/lib/umlaut/version.rb +1 -1
- data/lib/umlaut_configurable.rb +9 -0
- data/test/dummy/config/umlaut_services.yml +20 -0
- data/test/dummy/tmp/cache/assets/CDC/680/sprockets%2F2b68ef632d12610f3c9563168bfa7c05 +0 -0
- data/test/dummy/tmp/cache/assets/CF5/9B0/sprockets%2F7933bfe880731b396791f1682ce3f7fa +0 -0
- data/test/dummy/tmp/cache/assets/CFB/2F0/sprockets%2F62d51f0aa5cac4b1cf7091823772a604 +0 -0
- data/test/dummy/tmp/cache/assets/D4C/0A0/sprockets%2F7810d837eec3ac57ad78756af83a6a55 +0 -0
- data/test/dummy/tmp/cache/assets/D4C/E30/sprockets%2F631abf89746799b7a5b2b3b4f6294bcd +0 -0
- data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/D6C/7D0/sprockets%2F8a05d6981ec0d38c51739bef0b3a9c2b +0 -0
- data/test/dummy/tmp/cache/assets/D70/080/sprockets%2F24d3ce40ae5cc827a9183b1fb837e84e +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
- data/test/unit/aleph_patron_test.rb +6 -6
- data/test/unit/aleph_record_benchmarks.rb +1 -1
- data/test/unit/aleph_record_test.rb +4 -4
- data/test/unit/primo_searcher_test.rb +90 -82
- data/test/unit/primo_service_test.rb +683 -680
- data/test/unit/primo_ws_test.rb +45 -32
- metadata +135 -155
- data/lib/tasks/#Untitled-1# +0 -1843
- 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
|
data/app/models/collection.rb
CHANGED
@@ -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)
|
data/app/models/referent.rb
CHANGED
@@ -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
|
-
|
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
|
406
|
+
elsif my_metadata["aulast"]
|
396
407
|
citation[:author] = my_metadata["aulast"]
|
397
408
|
if ! my_metadata["aufirst"].blank?
|
398
|
-
|
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
|
-
|
414
|
+
if ! my_metadata["auinit1"].blank?
|
404
415
|
citation[:author] += ', '+my_metadata["auinit1"]
|
405
|
-
|
406
|
-
|
416
|
+
end
|
417
|
+
if ! my_metadata["auinitm"].blank?
|
407
418
|
citation[:author] += my_metadata["auinitm"]
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
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?
|
data/app/models/service_wave.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
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 #{
|
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
|
-
#
|
145
|
-
return
|
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
|
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").
|
100
|
-
name = addata_child.
|
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
|
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 |
|
113
|
-
@authors.push(
|
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 =
|
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.
|
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.
|
137
|
-
display_type = record.
|
138
|
-
original_source_id = record.
|
139
|
-
original_source_ids = process_control_hash(record, "control/originalsourceid")
|
140
|
-
source_id = record.
|
141
|
-
source_ids = process_control_hash(record, "control/sourceid")
|
142
|
-
source_record_id = record.
|
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.
|
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
|
-
(
|
162
|
-
|
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.
|
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.
|
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.
|
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
|