umlaut-primo 0.0.1
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/MIT-LICENSE +20 -0
- data/README.md +10 -0
- data/Rakefile +35 -0
- data/lib/umlaut_primo/primo_service.rb +522 -0
- data/lib/umlaut_primo/primo_source.rb +59 -0
- data/lib/umlaut_primo/version.rb +3 -0
- data/lib/umlaut_primo.rb +2 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/images/rails.png +0 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/umlaut_controller.rb +122 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +63 -0
- data/test/dummy/config/boot.rb +6 -0
- data/test/dummy/config/database.yml +61 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/primo.yml +55 -0
- data/test/dummy/config/routes.rb +60 -0
- data/test/dummy/config/umlaut_services.yml +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20130306173028_umlaut_init.umlaut.rb +106 -0
- data/test/dummy/db/migrate/20130306173029_umlaut_add_service_response_index.umlaut.rb +10 -0
- data/test/dummy/db/schema.rb +118 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/log/development.log +4 -0
- data/test/dummy/log/test.log +19611 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/robots.txt +5 -0
- data/test/dummy/script/rails +6 -0
- data/test/fixtures/referent_values.yml +96 -0
- data/test/fixtures/referents.yml +12 -0
- data/test/fixtures/requests.yml +20 -0
- data/test/fixtures/sfx_urls.yml +4 -0
- data/test/test_helper.rb +30 -0
- data/test/unit/primo_service_test.rb +228 -0
- data/test/unit/primo_source_test.rb +78 -0
- data/test/vcr_cassettes/australian_journal_of_international_affairs_by_id.yml +284 -0
- data/test/vcr_cassettes/musical_quarterly_by_issn.yml +263 -0
- data/test/vcr_cassettes/travels_with_my_by_id.yml +167 -0
- metadata +284 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 Scot Dalton
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Umlaut Primo
|
2
|
+
[](http://badge.fury.io/rb/umlaut-primo)
|
3
|
+
[](https://travis-ci.org/team-umlaut/umlaut-primo)
|
4
|
+
[](https://gemnasium.com/team-umlaut/umlaut-primo)
|
5
|
+
[](https://codeclimate.com/github/team-umlaut/umlaut-primo)
|
6
|
+
[](https://coveralls.io/r/team-umlaut/umlaut-primo)
|
7
|
+
|
8
|
+
Umlaut service to provide full text service responses, holdings, etc. from the Primo discovery solution.
|
9
|
+
|
10
|
+
## DOCUMENTATION COMING SOON
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Umlaut Primo Service Adaptor'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
25
|
+
require 'rake/testtask'
|
26
|
+
|
27
|
+
Rake::TestTask.new(:test) do |t|
|
28
|
+
t.libs << 'lib'
|
29
|
+
t.libs << 'test'
|
30
|
+
t.test_files = FileList['test/*_test.rb',
|
31
|
+
'test/**/*_test.rb', 'test/**/**/*_test.rb']
|
32
|
+
t.verbose = false
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
@@ -0,0 +1,522 @@
|
|
1
|
+
# == Overview
|
2
|
+
# PrimoService is a Service that makes a call to the Primo web services based on the OpenURL key value pairs.
|
3
|
+
#--
|
4
|
+
# NOT YET:
|
5
|
+
# It first looks for rft.primo *DEPRECATED*, failing that, it parses the identifier for an id.
|
6
|
+
#++
|
7
|
+
# It first looks for rft.primo, the Primo id.
|
8
|
+
# If the Primo id is present, the service gets the PNX record from the Primo web
|
9
|
+
# services.
|
10
|
+
# If no Primo id is found, the service searches Primo by (in order of precedence):
|
11
|
+
# * ISBN
|
12
|
+
# * ISSN
|
13
|
+
# * Title, Author, Genre
|
14
|
+
#
|
15
|
+
# == Available Services
|
16
|
+
# Several service types are available in the Primo service. The default service types are:
|
17
|
+
# fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image
|
18
|
+
# Available service types are listed below and can be configured using the service_types parameter
|
19
|
+
# in service.yml:
|
20
|
+
# * fulltext - parsed from links/linktorsrc elements in the PNX record
|
21
|
+
# * holding - parsed from display/availlibrary elements in the PNX record
|
22
|
+
# * holding_search - link to an exact title search in Primo if no holdings found AND the OpenURL did not come from Primo
|
23
|
+
# * primo_source - similar to holdings but used in conjuction with the PrimoSource service to map Primo records to their original sources; a PrimoSource service must be defined in service.yml for this to work
|
24
|
+
# * table_of_contents - parsed from links/linktotoc elements in the PNX record
|
25
|
+
# * referent_enhance - metadata parsed from the addata section of the PNX record when the record was found by Primo id
|
26
|
+
# * cover_image - parsed from first addata/lad02 element in the PNX record
|
27
|
+
# * highlighted_link - parsed from links/addlink elements in the PNX record
|
28
|
+
#
|
29
|
+
# ==Available Parameters
|
30
|
+
# Several configurations parameters are available to be set in services.yml, e.g.
|
31
|
+
# Primo:
|
32
|
+
# type: PrimoService
|
33
|
+
# priority: 2 # After SFX, to get SFX metadata enhancement
|
34
|
+
# status: active
|
35
|
+
# base_url: http://bobcat.library.nyu.edu
|
36
|
+
# vid: NYU
|
37
|
+
# holding_search_institution: NYU
|
38
|
+
# holding_search_text: Search for this title in BobCat.
|
39
|
+
# suppress_holdings: [ !ruby/regexp '/\$\$LWEB/', !ruby/regexp '/\$\$1Restricted Internet Resources/' ]
|
40
|
+
# ez_proxy: !ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
|
41
|
+
# service_types:
|
42
|
+
# - holding
|
43
|
+
# - holding_search
|
44
|
+
# - fulltext
|
45
|
+
# - table_of_contents
|
46
|
+
# - referent_enhance
|
47
|
+
# - cover_image
|
48
|
+
# - highlighted_link
|
49
|
+
# base_url:: _required_ host and port of Primo server; used for Primo web services, deep links and holding_search
|
50
|
+
# base_path:: *DEPRECATED* previous name of base_url
|
51
|
+
# vid:: _required_ view id for Primo deep links and holding_search.
|
52
|
+
# institution:: _required_ institution id for Primo institution; used for Primo web services
|
53
|
+
# base_view_id:: *DEPRECATED* previous name of vid
|
54
|
+
# holding_search_institution:: _required if service types include holding_search_ institution to be used for the holding_search
|
55
|
+
# holding_search_text:: _optional_ text to display for the holding_search
|
56
|
+
# default holding search text:: "Search for this title."
|
57
|
+
# link_to_search_text:: *DEPRECATED* previous name of holding_search_text
|
58
|
+
# service_types:: _optional_ array of strings that represent the service types desired.
|
59
|
+
# options are: fulltext, holding, holding_search, table_of_contents,
|
60
|
+
# referent_enhance, cover_image, primo_source
|
61
|
+
# defaults are: fulltext, holding, holding_search, table_of_contents,
|
62
|
+
# referent_enhance, cover_image
|
63
|
+
# if no options are specified, default service types will be added.
|
64
|
+
# suppress_urls:: _optional_ array of strings or regexps to NOT use from the catalog.
|
65
|
+
# Used for linktorsrc elements that may duplicate resources from in other services.
|
66
|
+
# Regexps can be put in the services.yml like this:
|
67
|
+
# [!ruby/regexp '/sagepub.com$/']
|
68
|
+
# suppress_holdings:: _optional_ array of strings or regexps to NOT use from the catalog.
|
69
|
+
# Used for availlibrary elements that may duplicate resources from in other services.
|
70
|
+
# Regexps can be put in the services.yml like this:
|
71
|
+
# [!ruby/regexp '/\$\$LWEB$/']
|
72
|
+
# suppress_tocs:: _optional_ array of strings or regexps to NOT link to for Tables of Contents.
|
73
|
+
# Used for linktotoc elements that may duplicate resources from in other services.
|
74
|
+
# Regexps can be put in the services.yml like this:
|
75
|
+
# [!ruby/regexp '/\$\$LWEB$/']
|
76
|
+
# service_types:: _optional_ array of strings that represent the service types desired.
|
77
|
+
# options are: fulltext, holding, holding_search, table_of_contents,
|
78
|
+
# referent_enhance, cover_image, primo_source
|
79
|
+
# defaults are: fulltext, holding, holding_search, table_of_contents,
|
80
|
+
# referent_enhance, cover_image
|
81
|
+
# if no options are specified, default service types will be added.
|
82
|
+
# ez_proxy:: _optional_ string or regexp of an ezproxy prefix.
|
83
|
+
# used in the case where an ezproxy prefix (on any other regexp) is hardcoded in the URL,
|
84
|
+
# and needs to be removed in order to match against SFXUrls.
|
85
|
+
# Example:
|
86
|
+
# !ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
|
87
|
+
# primo_config:: _optional_ string representing the primo yaml config file in config/
|
88
|
+
# default file name: primo.yml
|
89
|
+
# hash mappings from yaml config
|
90
|
+
# institutions:
|
91
|
+
# "primo_institution_code": "Primo Institution String"
|
92
|
+
# libraries:
|
93
|
+
# "primo_library_code": "Primo Library String"
|
94
|
+
# statuses:
|
95
|
+
# "status1_code": "Status One"
|
96
|
+
# sources:
|
97
|
+
# data_source1:
|
98
|
+
# base_url: "http://source1.base.url
|
99
|
+
# type: source_type
|
100
|
+
# class_name: Source1Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
|
101
|
+
# source1_config_option1: source1_config_option1
|
102
|
+
# source1_config_option2: source1_config_option2
|
103
|
+
# data_source2:
|
104
|
+
# base_url: "http://source2.base.url
|
105
|
+
# type: source_type
|
106
|
+
# class_name: Source2Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
|
107
|
+
# source2_config_option1: source2_config_option1
|
108
|
+
# source2_config_option2: source2_config_option2
|
109
|
+
# holding_attributes:: _optional_ array of Holding attribute readers to save to
|
110
|
+
# holding/primo_source service_data; can be used to save
|
111
|
+
# custom source implementation attributes for display by a custom holding partial
|
112
|
+
require 'exlibris-primo'
|
113
|
+
class PrimoService < Service
|
114
|
+
|
115
|
+
required_config_params :base_url, :vid, :institution
|
116
|
+
# For matching purposes.
|
117
|
+
attr_reader :title, :author
|
118
|
+
|
119
|
+
def self.default_config_file
|
120
|
+
"#{Rails.root}/config/primo.yml"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Overwrites Service#new.
|
124
|
+
def initialize(config)
|
125
|
+
# Configure Primo
|
126
|
+
configure_primo
|
127
|
+
# Attributes for holding service data.
|
128
|
+
@holding_attributes = [:record_id, :original_id, :title, :author, :display_type,
|
129
|
+
:source_id, :original_source_id, :source_record_id, :ils_api_id, :institution_code,
|
130
|
+
:institution, :library_code, :library, :collection, :call_number, :coverage, :notes,
|
131
|
+
:subfields, :status_code, :status, :source_data]
|
132
|
+
@link_attributes = [:institution, :record_id, :original_id, :url, :display, :notes, :subfields]
|
133
|
+
# TODO: Run these decisions someone to see if they make sense.
|
134
|
+
@referent_enhancements = {
|
135
|
+
# Prefer SFX journal titles to Primo journal titles
|
136
|
+
:jtitle => { :overwrite => false },
|
137
|
+
:btitle => { :overwrite => true },
|
138
|
+
:aulast => { :overwrite => true },
|
139
|
+
:aufirst => { :overwrite => true },
|
140
|
+
:aucorp => { :overwrite => true },
|
141
|
+
:au => { :overwrite => true },
|
142
|
+
:pub => { :overwrite => true },
|
143
|
+
:place => { :value => :cop, :overwrite => false },
|
144
|
+
# Prefer SFX journal titles to Primo journal titles
|
145
|
+
:title => { :value => :jtitle, :overwrite => false},
|
146
|
+
:title => { :value => :btitle, :overwrite => true},
|
147
|
+
# Primo lccn and oclcid are spotty in Primo, so don't overwrite
|
148
|
+
:lccn => { :overwrite => false },
|
149
|
+
:oclcnum => { :value => :oclcid, :overwrite => false}
|
150
|
+
}
|
151
|
+
@suppress_urls = []
|
152
|
+
@suppress_tocs = []
|
153
|
+
@suppress_related_links = []
|
154
|
+
@suppress_holdings = []
|
155
|
+
@service_types = [ "fulltext", "holding", "holding_search",
|
156
|
+
"table_of_contents", "referent_enhance", "cover_image" ] if @service_types.nil?
|
157
|
+
backward_compatibility(config)
|
158
|
+
super(config)
|
159
|
+
# For backward compatibility, handle the special case where holding_search_institution was not included.
|
160
|
+
# Set holding_search_institution to vid and print warning in the logs.
|
161
|
+
if @service_types.include?("holding_search") and @holding_search_institution.nil?
|
162
|
+
@holding_search_institution = @institution
|
163
|
+
Rails.logger.warn("Required parameter 'holding_search_institution' was not set. Please set the appropriate value in umlaut_services.yml. Defaulting institution to view id, #{@vid}.")
|
164
|
+
end # End backward compatibility maintenance
|
165
|
+
raise ArgumentError.new(
|
166
|
+
"Missing Service configuration parameter. Service type #{self.class} (id: #{self.id}) requires a config parameter named 'holding_search_institution'. Check your config/umlaut_services.yml file."
|
167
|
+
) if @service_types.include?("holding_search") and @holding_search_institution.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
# Overwrites Service#service_types_generated.
|
171
|
+
def service_types_generated
|
172
|
+
types = Array.new
|
173
|
+
@service_types.each do |type|
|
174
|
+
types.push(ServiceTypeValue[type.to_sym])
|
175
|
+
end
|
176
|
+
return types
|
177
|
+
end
|
178
|
+
|
179
|
+
# Overwrites Service#handle.
|
180
|
+
def handle(request)
|
181
|
+
# Get the possible search params
|
182
|
+
@identifier = request.referrer_id
|
183
|
+
@record_id = record_id(request)
|
184
|
+
@isbn = isbn(request)
|
185
|
+
@issn = issn(request)
|
186
|
+
@title = title(request)
|
187
|
+
@author = author(request)
|
188
|
+
@genre = genre(request)
|
189
|
+
# Setup the Primo search object
|
190
|
+
search = Exlibris::Primo::Search.new.base_url!(@base_url).institution!(@institution)
|
191
|
+
# Search if we have a:
|
192
|
+
# Primo record id OR
|
193
|
+
# ISBN OR
|
194
|
+
# ISSN OR
|
195
|
+
# Title and author and genre
|
196
|
+
if @record_id
|
197
|
+
search.record_id! @record_id
|
198
|
+
elsif @isbn
|
199
|
+
search.isbn_is @isbn
|
200
|
+
elsif @issn
|
201
|
+
search.isbn_is @issn
|
202
|
+
elsif @title and @author and @genre
|
203
|
+
search.title_is(@title).creator_is(@author).genre_is(@genre)
|
204
|
+
else # Don't do a search.
|
205
|
+
return request.dispatched(self, true)
|
206
|
+
end
|
207
|
+
|
208
|
+
begin
|
209
|
+
records = search.records
|
210
|
+
# Enhance the referent with metadata from Primo Searcher if Primo record id is present
|
211
|
+
# i.e. if we did our search with the Primo system number
|
212
|
+
if @record_id and @service_types.include?("referent_enhance")
|
213
|
+
# We'll take the first record, since there should only be one.
|
214
|
+
enhance_referent(request, records.first)
|
215
|
+
end
|
216
|
+
# Get cover image only if @record_id is defined
|
217
|
+
# TODO: make cover image service smarter and only
|
218
|
+
# include things that are actually URLs.
|
219
|
+
# if @record_id and @service_types.include?("cover_image")
|
220
|
+
# cover_image = primo_searcher.cover_image
|
221
|
+
# unless cover_image.nil?
|
222
|
+
# request.add_service_response(
|
223
|
+
# :service => self,
|
224
|
+
# :display_text => 'Cover Image',
|
225
|
+
# :key => 'medium',
|
226
|
+
# :url => cover_image,
|
227
|
+
# :size => 'medium',
|
228
|
+
# :service_type_value => :cover_image)
|
229
|
+
# end
|
230
|
+
# end
|
231
|
+
# Add holding services
|
232
|
+
if @service_types.include?("holding") or @service_types.include?("primo_source")
|
233
|
+
# Get holdings from the returned Primo records
|
234
|
+
holdings = records.collect{|record| record.holdings}.flatten
|
235
|
+
# Add the holding services
|
236
|
+
add_holding_services(request, holdings) unless holdings.empty?
|
237
|
+
# Provide title search functionality in the absence of available holdings.
|
238
|
+
# The logic below says only present the holdings search in the following case:
|
239
|
+
# We've configured to present holding search
|
240
|
+
# We didn't find any actual holdings
|
241
|
+
# We didn't come from Primo (prevent round trips since that would be weird)
|
242
|
+
# We have a title to search for.
|
243
|
+
if @service_types.include?("holding_search") and holdings.empty? and (not primo_identifier?) and (not @title.nil?)
|
244
|
+
# Add the holding search service
|
245
|
+
add_holding_search_service(request)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
# Add fulltext services
|
249
|
+
if @service_types.include?("fulltext")
|
250
|
+
# Get fulltexts from the returned Primo records
|
251
|
+
fulltexts = records.collect{|record| record.fulltexts}.flatten
|
252
|
+
# Add the fulltext services
|
253
|
+
add_fulltext_services(request, fulltexts) unless fulltexts.empty?
|
254
|
+
end
|
255
|
+
# Add table of contents services
|
256
|
+
if @service_types.include?("table_of_contents")
|
257
|
+
# Get tables of contents from the returned Primo records
|
258
|
+
tables_of_contents = records.collect{|record| record.tables_of_contents}.flatten
|
259
|
+
# Add the table of contents services
|
260
|
+
add_table_of_contents_services(request, tables_of_contents) unless tables_of_contents.empty?
|
261
|
+
end
|
262
|
+
if @service_types.include?("highlighted_link")
|
263
|
+
# Get related links from the returned Primo records
|
264
|
+
highlighted_links = records.collect{|record| record.related_links}.flatten
|
265
|
+
add_highlighted_link_services(request, highlighted_links) unless highlighted_links.empty?
|
266
|
+
end
|
267
|
+
rescue Exception => e
|
268
|
+
# Log error and return finished
|
269
|
+
Rails.logger.error(
|
270
|
+
"Error in Exlibris::Primo::Search. "+
|
271
|
+
"Returning 0 Primo services for search #{search.inspect}. "+
|
272
|
+
"Exlibris::Primo::Search raised the following exception:\n#{e}\n#{e.backtrace.inspect}")
|
273
|
+
end
|
274
|
+
return request.dispatched(self, true)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Called by ServiceType#view_data to provide custom functionality for Primo sources.
|
278
|
+
# For more information on Primo sources see PrimoSource.
|
279
|
+
def to_primo_source(service_response)
|
280
|
+
source_parameters = {}
|
281
|
+
@holding_attributes.each { |attr|
|
282
|
+
source_parameters[attr] = service_response.data_values[attr] }
|
283
|
+
return Exlibris::Primo::Holding.new(source_parameters).to_source
|
284
|
+
end
|
285
|
+
|
286
|
+
def default_config_file
|
287
|
+
self.class.default_config_file
|
288
|
+
end
|
289
|
+
|
290
|
+
# Return the Primo dlDisplay URL.
|
291
|
+
def deep_link_display_url(holding)
|
292
|
+
"#{@base_url}/primo_library/libweb/action/dlDisplay.do?docId=#{holding.record_id}&institution=#{@institution}&vid=#{@vid}"
|
293
|
+
end
|
294
|
+
protected :deep_link_display_url
|
295
|
+
|
296
|
+
# Return the Primo dlSearch URL.
|
297
|
+
def deep_link_search_url
|
298
|
+
@base_url+"/primo_library/libweb/action/dlSearch.do?institution=#{@holding_search_institution}&vid=#{@vid}&onCampus=false&query=#{CGI::escape("title,exact,"+@title)}&indx=1&bulkSize=10&group=GUEST"
|
299
|
+
end
|
300
|
+
protected :deep_link_search_url
|
301
|
+
|
302
|
+
# Configure Primo if this is the first time through
|
303
|
+
def configure_primo
|
304
|
+
Exlibris::Primo.configure { |primo_config|
|
305
|
+
primo_config.load_yaml config_file unless primo_config.load_time
|
306
|
+
} if File.exists?(config_file)
|
307
|
+
end
|
308
|
+
private :configure_primo
|
309
|
+
|
310
|
+
# Reset Primo configuration
|
311
|
+
# Only used in testing
|
312
|
+
def reset_primo_config
|
313
|
+
Exlibris::Primo.configure do |primo_config|
|
314
|
+
primo_config.load_time = nil
|
315
|
+
primo_config.libraries = {}
|
316
|
+
primo_config.availability_statuses = {}
|
317
|
+
primo_config.sources = {}
|
318
|
+
end
|
319
|
+
end
|
320
|
+
private :reset_primo_config
|
321
|
+
|
322
|
+
# Enhance the referent based on metadata in the given record
|
323
|
+
def enhance_referent(request, record)
|
324
|
+
@referent_enhancements.each do |key, options|
|
325
|
+
metadata_element = (options[:value].nil?) ? key : options[:value]
|
326
|
+
# Enhance the referent from the 'addata' section
|
327
|
+
metadata_method = "addata_#{metadata_element}".to_sym
|
328
|
+
# Get the metadata value if it's there
|
329
|
+
metadata_value = record.send(metadata_method) if record.respond_to? metadata_method
|
330
|
+
# Enhance the referent
|
331
|
+
request.referent.enhance_referent(key.to_s, metadata_value,
|
332
|
+
true, false, options) unless metadata_value.nil?
|
333
|
+
end
|
334
|
+
end
|
335
|
+
private :enhance_referent
|
336
|
+
|
337
|
+
# Add a holding service for each holding returned from Primo
|
338
|
+
def add_holding_services(request, holdings)
|
339
|
+
holdings.each do |holding|
|
340
|
+
next if @suppress_holdings.find {|suppress_holding| suppress_holding === holding.availlibrary}
|
341
|
+
service_data = {}
|
342
|
+
# Availability status from Primo is probably out of date, so set to "check_holdings"
|
343
|
+
holding.status_code = "check_holdings"
|
344
|
+
@holding_attributes.each do |attr|
|
345
|
+
service_data[attr] = holding.send(attr) if holding.respond_to?(attr)
|
346
|
+
end
|
347
|
+
# Only add one service type, either "primo_source" OR "holding", not both.
|
348
|
+
service_type = (@service_types.include?("primo_source")) ? "primo_source" : "holding"
|
349
|
+
# Umlaut specific attributes.
|
350
|
+
service_data[:match_reliability] =
|
351
|
+
(reliable_match?(:title => holding.title, :author => holding.author)) ?
|
352
|
+
ServiceResponse::MatchExact : ServiceResponse::MatchUnsure
|
353
|
+
service_data[:url] = deep_link_display_url(holding)
|
354
|
+
# Add some other holding information
|
355
|
+
service_data.merge!({
|
356
|
+
:collection_str => "#{holding.library} #{holding.collection}",
|
357
|
+
:coverage_str => holding.coverage.join("<br />"),
|
358
|
+
:coverage_str_array => holding.coverage }) if service_type.eql? "holding"
|
359
|
+
request.add_service_response(
|
360
|
+
service_data.merge(
|
361
|
+
:service => self,
|
362
|
+
:service_type_value => service_type))
|
363
|
+
end
|
364
|
+
end
|
365
|
+
private :add_holding_services
|
366
|
+
|
367
|
+
# Add a holding search service
|
368
|
+
def add_holding_search_service(request)
|
369
|
+
service_data = {}
|
370
|
+
service_data[:type] = "link_to_search"
|
371
|
+
service_data[:display_text] = (@holding_search_text.nil?) ? "Search for this title." : @holding_search_text
|
372
|
+
service_data[:note] = ""
|
373
|
+
service_data[:url] = deep_link_search_url
|
374
|
+
request.add_service_response(
|
375
|
+
service_data.merge(
|
376
|
+
:service => self,
|
377
|
+
:service_type_value => 'holding_search'))
|
378
|
+
end
|
379
|
+
private :add_holding_search_service
|
380
|
+
|
381
|
+
# Add a full text service for each fulltext returned from Primo
|
382
|
+
def add_fulltext_services(request, fulltexts)
|
383
|
+
add_link_services(request, fulltexts, 'fulltext', @suppress_urls) { |fulltext|
|
384
|
+
# Don't add the URL if it matches our SFXUrl finder (unless fulltext is empty,
|
385
|
+
# [assuming something is better than nothing]), because
|
386
|
+
# that means we think this is an SFX controlled URL.
|
387
|
+
next if SfxUrl.sfx_controls_url?(handle_ezproxy(fulltext.url)) and
|
388
|
+
request.referent.metadata['genre'] != "book" and
|
389
|
+
!request.get_service_type("fulltext", { :refresh => true }).empty?
|
390
|
+
}
|
391
|
+
end
|
392
|
+
private :add_fulltext_services
|
393
|
+
|
394
|
+
# Add a table of contents service for each table of contents returned from Primo
|
395
|
+
def add_table_of_contents_services(request, tables_of_contents)
|
396
|
+
add_link_services(request, tables_of_contents, 'table_of_contents', @suppress_tocs)
|
397
|
+
end
|
398
|
+
private :add_table_of_contents_services
|
399
|
+
|
400
|
+
# Add a highlighted link service for each related link returned from Primo
|
401
|
+
def add_highlighted_link_services(request, highlight_links)
|
402
|
+
add_link_services(request, highlight_links, 'highlighted_link', @suppress_related_links)
|
403
|
+
end
|
404
|
+
private :add_highlighted_link_services
|
405
|
+
|
406
|
+
# Add a link service (specified by the given type) for each link returned from Primo
|
407
|
+
def add_link_services(request, links, service_type, suppress_links, &block)
|
408
|
+
links_seen = [] # for de-duplicating urls
|
409
|
+
links.each do |link|
|
410
|
+
next if links_seen.include?(link.url)
|
411
|
+
# Check the list of URLs to suppress, array of strings or regexps.
|
412
|
+
# If we have a match, suppress.
|
413
|
+
next if suppress_links.find {|suppress_link| suppress_link === link.url}
|
414
|
+
# No url? Forget it.
|
415
|
+
next if link.url.nil?
|
416
|
+
yield link unless block.nil?
|
417
|
+
links_seen.push(link.url)
|
418
|
+
service_data = {}
|
419
|
+
@link_attributes.each do |attr|
|
420
|
+
service_data[attr] = link.send(attr)
|
421
|
+
end
|
422
|
+
# Default display text to URL.
|
423
|
+
service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
|
424
|
+
# Add the response
|
425
|
+
request.add_service_response(
|
426
|
+
service_data.merge(
|
427
|
+
:service => self,
|
428
|
+
:service_type_value => service_type))
|
429
|
+
end
|
430
|
+
end
|
431
|
+
private :add_link_services
|
432
|
+
|
433
|
+
# Map old config names to new config names for backwards compatibility
|
434
|
+
def backward_compatibility(config)
|
435
|
+
# For backward compatibility, re-map "old" config values to new more
|
436
|
+
# Umlaut-y names and print deprecation warning in the logs.
|
437
|
+
old_to_new_mappings = {
|
438
|
+
:base_path => :base_url,
|
439
|
+
:base_view_id => :vid,
|
440
|
+
:link_to_search_text => :holding_search_text
|
441
|
+
}
|
442
|
+
old_to_new_mappings.each do |old_param, new_param|
|
443
|
+
unless config["#{old_param}"].nil?
|
444
|
+
config["#{new_param}"] = config["#{old_param}"] if config["#{new_param}"].nil?
|
445
|
+
Rails.logger.warn("Parameter '#{old_param}' is deprecated. Please use '#{new_param}' instead.")
|
446
|
+
end
|
447
|
+
end # End backward compatibility maintenance
|
448
|
+
end
|
449
|
+
private :backward_compatibility
|
450
|
+
|
451
|
+
# Determine how sure we are that this is a match.
|
452
|
+
# Dynamically compares record metadata to input values
|
453
|
+
# based on the values passed in.
|
454
|
+
# Minimum requirement is to check title.
|
455
|
+
def reliable_match?(record_metadata)
|
456
|
+
return true unless (@record_id.nil? or @record_id.empty?)
|
457
|
+
return true unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
|
458
|
+
return false if (record_metadata.nil? or record_metadata.empty? or record_metadata[:title].nil? or record_metadata[:title].empty?)
|
459
|
+
# Titles must be equal
|
460
|
+
return false unless record_metadata[:title].to_s.downcase.eql?(@title.downcase)
|
461
|
+
# Author must be equal
|
462
|
+
return false unless record_metadata[:author].to_s.downcase.eql?(@author.downcase)
|
463
|
+
return true
|
464
|
+
end
|
465
|
+
private :reliable_match?
|
466
|
+
|
467
|
+
def config_file
|
468
|
+
config_file = @primo_config.nil? ? default_config_file : "#{Rails.root}/config/"+ @primo_config
|
469
|
+
Rails.logger.warn("Primo config file not found: #{config_file}.") and return "" unless File.exists?(config_file)
|
470
|
+
config_file
|
471
|
+
end
|
472
|
+
private :config_file
|
473
|
+
|
474
|
+
# If an ezproxy prefix (on any other regexp) is hardcoded in the URL,
|
475
|
+
# strip it out for matching against SFXUrls
|
476
|
+
def handle_ezproxy(str)
|
477
|
+
return str if @ez_proxy.nil?
|
478
|
+
return (str.gsub(@ez_proxy, '').nil? ? str : str.gsub(@ez_proxy, ''))
|
479
|
+
end
|
480
|
+
private :handle_ezproxy
|
481
|
+
|
482
|
+
def record_id(request)
|
483
|
+
# Let SFX handle primoArticles (is that even a thing anymore?)
|
484
|
+
return if @identifier.match(/primoArticle/) if primo_identifier?
|
485
|
+
@identifier.match(/primo-(.+)/)[1] if primo_identifier?
|
486
|
+
end
|
487
|
+
private :record_id
|
488
|
+
|
489
|
+
def isbn(request)
|
490
|
+
request.referent.metadata['isbn']
|
491
|
+
end
|
492
|
+
private :isbn
|
493
|
+
|
494
|
+
def issn(request)
|
495
|
+
# don't send mal-formed issn
|
496
|
+
request.referent.metadata['issn'] if request.referent.metadata['issn'] =~ /\d{4}(-)?\d{3}(\d|X)/
|
497
|
+
end
|
498
|
+
private :issn
|
499
|
+
|
500
|
+
def title(request)
|
501
|
+
(request.referent.metadata['jtitle'] || request.referent.metadata['btitle'] ||
|
502
|
+
request.referent.metadata['title'] || request.referent.metadata['atitle'])
|
503
|
+
end
|
504
|
+
private :title
|
505
|
+
|
506
|
+
def author(request)
|
507
|
+
(request.referent.metadata['au'] || request.referent.metadata['aulast'] ||
|
508
|
+
request.referent.metadata['aucorp'])
|
509
|
+
end
|
510
|
+
private :author
|
511
|
+
|
512
|
+
def genre(request)
|
513
|
+
request.referent.metadata['genre']
|
514
|
+
end
|
515
|
+
private :genre
|
516
|
+
|
517
|
+
def primo_identifier?
|
518
|
+
return false if @identifier.nil?
|
519
|
+
return @identifier.start_with?('info:sid/primo.exlibrisgroup.com')
|
520
|
+
end
|
521
|
+
private :primo_identifier?
|
522
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# == Overview
|
2
|
+
# PrimoSource is a PrimoService that converts primo_source service types into Primo source holdings.
|
3
|
+
# This mechanism allows linking to original data sources and their holdings information
|
4
|
+
# based on the given Primo sources and can be implemented per source.
|
5
|
+
#
|
6
|
+
# PrimoSources are not necessary to use the Primo service andthey require programming.
|
7
|
+
# However, they do allow further customization and functionality.
|
8
|
+
#
|
9
|
+
class PrimoSource < PrimoService
|
10
|
+
|
11
|
+
# Overwrites PrimoService#new.
|
12
|
+
def initialize(config)
|
13
|
+
@service_types = ["holding"]
|
14
|
+
@source_attributes = []
|
15
|
+
super(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Overwrites PrimoService#handle.
|
19
|
+
def handle(request)
|
20
|
+
primo_sources = request.get_service_type('primo_source', {:refresh => true})
|
21
|
+
sources = [] # for de-duplicating holdings from catalog.
|
22
|
+
primo_sources.each do |primo_source|
|
23
|
+
# Calls PrimoService#to_primo_source.
|
24
|
+
source = primo_source.view_data
|
25
|
+
# There are some cases where source records may need to be de-duplicated against existing records
|
26
|
+
# Check if we've already seen this record.
|
27
|
+
next if sources.include?(source)
|
28
|
+
# Include the source so that it's available for deduping
|
29
|
+
sources << source
|
30
|
+
# There may be multiple holdings mapped to one availlibrary here,
|
31
|
+
# so we get the additional holdings and add them.
|
32
|
+
source.expand.each do |holding|
|
33
|
+
service_data = {}
|
34
|
+
@holding_attributes.each do |attr|
|
35
|
+
service_data[attr] = holding.send(attr.to_sym) if holding.respond_to?(attr.to_sym)
|
36
|
+
end
|
37
|
+
@source_attributes.each do |attr|
|
38
|
+
service_data[attr.to_sym] = holding.send(attr.to_sym) if holding.respond_to?(attr.to_sym)
|
39
|
+
end
|
40
|
+
service_data.merge!({
|
41
|
+
:url => (holding.respond_to? :url) ? holding.url : deep_link_display_url(holding),
|
42
|
+
:collection_str => "#{holding.library} #{holding.collection}",
|
43
|
+
:coverage_str => holding.coverage.join("<br />"),
|
44
|
+
:coverage_str_array => holding.coverage,
|
45
|
+
# :expired determines whether we show the holding in this service
|
46
|
+
# Since this is fresh, the data has not yet expired.
|
47
|
+
:expired => false,
|
48
|
+
# :latest determines whether we show the holding in other services, e.g. txt and email.
|
49
|
+
# It persists for one more cycle than :expired so services that run after
|
50
|
+
# this one, but in the same resolution request have access to the latest holding data.
|
51
|
+
:latest => true })
|
52
|
+
request.add_service_response(
|
53
|
+
service_data.merge(:service=>self,
|
54
|
+
:service_type_value => "holding" ))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return request.dispatched(self, true)
|
58
|
+
end
|
59
|
+
end
|
data/lib/umlaut_primo.rb
ADDED
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
3
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
4
|
+
|
5
|
+
require File.expand_path('../config/application', __FILE__)
|
6
|
+
|
7
|
+
Dummy::Application.load_tasks
|
Binary file
|