umlaut-primo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +10 -0
  3. data/Rakefile +35 -0
  4. data/lib/umlaut_primo/primo_service.rb +522 -0
  5. data/lib/umlaut_primo/primo_source.rb +59 -0
  6. data/lib/umlaut_primo/version.rb +3 -0
  7. data/lib/umlaut_primo.rb +2 -0
  8. data/test/dummy/Rakefile +7 -0
  9. data/test/dummy/app/assets/images/rails.png +0 -0
  10. data/test/dummy/app/assets/javascripts/application.js +15 -0
  11. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  12. data/test/dummy/app/controllers/application_controller.rb +3 -0
  13. data/test/dummy/app/controllers/umlaut_controller.rb +122 -0
  14. data/test/dummy/app/helpers/application_helper.rb +2 -0
  15. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  16. data/test/dummy/config/application.rb +63 -0
  17. data/test/dummy/config/boot.rb +6 -0
  18. data/test/dummy/config/database.yml +61 -0
  19. data/test/dummy/config/environment.rb +5 -0
  20. data/test/dummy/config/environments/development.rb +41 -0
  21. data/test/dummy/config/environments/production.rb +67 -0
  22. data/test/dummy/config/environments/test.rb +37 -0
  23. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  24. data/test/dummy/config/initializers/inflections.rb +15 -0
  25. data/test/dummy/config/initializers/mime_types.rb +5 -0
  26. data/test/dummy/config/initializers/secret_token.rb +7 -0
  27. data/test/dummy/config/initializers/session_store.rb +8 -0
  28. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  29. data/test/dummy/config/locales/en.yml +5 -0
  30. data/test/dummy/config/primo.yml +55 -0
  31. data/test/dummy/config/routes.rb +60 -0
  32. data/test/dummy/config/umlaut_services.yml +29 -0
  33. data/test/dummy/config.ru +4 -0
  34. data/test/dummy/db/migrate/20130306173028_umlaut_init.umlaut.rb +106 -0
  35. data/test/dummy/db/migrate/20130306173029_umlaut_add_service_response_index.umlaut.rb +10 -0
  36. data/test/dummy/db/schema.rb +118 -0
  37. data/test/dummy/db/seeds.rb +7 -0
  38. data/test/dummy/log/development.log +4 -0
  39. data/test/dummy/log/test.log +19611 -0
  40. data/test/dummy/public/404.html +26 -0
  41. data/test/dummy/public/422.html +26 -0
  42. data/test/dummy/public/500.html +25 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/dummy/public/robots.txt +5 -0
  45. data/test/dummy/script/rails +6 -0
  46. data/test/fixtures/referent_values.yml +96 -0
  47. data/test/fixtures/referents.yml +12 -0
  48. data/test/fixtures/requests.yml +20 -0
  49. data/test/fixtures/sfx_urls.yml +4 -0
  50. data/test/test_helper.rb +30 -0
  51. data/test/unit/primo_service_test.rb +228 -0
  52. data/test/unit/primo_source_test.rb +78 -0
  53. data/test/vcr_cassettes/australian_journal_of_international_affairs_by_id.yml +284 -0
  54. data/test/vcr_cassettes/musical_quarterly_by_issn.yml +263 -0
  55. data/test/vcr_cassettes/travels_with_my_by_id.yml +167 -0
  56. 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
@@ -0,0 +1,3 @@
1
+ module UmlautPrimo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ require "require_all"
2
+ require_all "#{File.dirname(__FILE__)}/umlaut_primo/"
@@ -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