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.
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