umlaut-primo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/umlaut-primo.png)](http://badge.fury.io/rb/umlaut-primo)
|
3
|
+
[![Build Status](https://api.travis-ci.org/team-umlaut/umlaut-primo.png?branch=master)](https://travis-ci.org/team-umlaut/umlaut-primo)
|
4
|
+
[![Dependency Status](https://gemnasium.com/team-umlaut/umlaut-primo.png)](https://gemnasium.com/team-umlaut/umlaut-primo)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/team-umlaut/umlaut-primo.png)](https://codeclimate.com/github/team-umlaut/umlaut-primo)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/team-umlaut/umlaut-primo/badge.png?branch=master)](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
|