Class: PrimoService

Inherits:
Service show all
Defined in:
lib/service_adaptors/primo_service.rb

Overview

Overview

PrimoService is a Service that makes a call to the Primo web services based on the OpenURL key value pairs. – NOT YET: It first looks for rft.primo DEPRECATED, failing that, it parses the identifier for an id. ++ It first looks for rft.primo, the Primo id. If the Primo id is present, the service gets the PNX record from the Primo web services. If no Primo id is found, the service searches Primo by (in order of precedence):

  • ISBN

  • ISSN

  • Title, Author, Genre

Available Services

Several service types are available in the Primo service. The default service types are: fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image Available service types are listed below and can be configured using the service_types parameter in service.yml:

  • fulltext - parsed from links/linktorsrc elements in the PNX record

  • holding - parsed from display/availlibrary elements in the PNX record

  • holding_search - link to an exact title search in Primo if no holdings found AND the OpenURL did not come from Primo

  • 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

  • table_of_contents - parsed from links/linktotoc elements in the PNX record

  • referent_enhance - metadata parsed from the addata section of the PNX record when the record was found by Primo id

  • cover_image - parsed from first addata/lad02 element in the PNX record

  • highlighted_link - parsed from links/addlink elements in the PNX record

Available Parameters

Several configurations parameters are available to be set in services.yml, e.g.

Primo:
  type: PrimoService
  priority: 2 # After SFX, to get SFX metadata enhancement
  status: active
  base_url: http://bobcat.library.nyu.edu
  vid: NYU
  holding_search_institution: NYU
  holding_search_text: Search for this title in BobCat.
  suppress_holdings: [ !ruby/regexp '/\$\$LWEB/', !ruby/regexp '/\$\$1Restricted Internet Resources/' ]
  ez_proxy: !ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
  service_types:
    - holding
    - holding_search
    - fulltext
    - table_of_contents
    - referent_enhance
    - cover_image
    - highlighted_link
base_url

required host and port of Primo server; used for Primo web services, deep links and holding_search

base_path

DEPRECATED previous name of base_url

vid

required view id for Primo deep links and holding_search.

institution

required institution id for Primo institution; used for Primo web services

base_view_id

DEPRECATED previous name of vid

holding_search_institution

_required if service types include holding_search_ institution to be used for the holding_search

holding_search_text

optional text to display for the holding_search

default holding search text

“Search for this title.”

link_to_search_text

DEPRECATED previous name of holding_search_text

service_types

optional array of strings that represent the service types desired. options are: fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image, primo_source defaults are: fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image if no options are specified, default service types will be added.

suppress_urls

optional array of strings or regexps to NOT use from the catalog. Used for linktorsrc elements that may duplicate resources from in other services. Regexps can be put in the services.yml like this:

[!ruby/regexp '/sagepub.com$/']
suppress_holdings

optional array of strings or regexps to NOT use from the catalog. Used for availlibrary elements that may duplicate resources from in other services. Regexps can be put in the services.yml like this:

[!ruby/regexp '/\$\$LWEB$/']
suppress_tocs

optional array of strings or regexps to NOT link to for Tables of Contents. Used for linktotoc elements that may duplicate resources from in other services. Regexps can be put in the services.yml like this:

[!ruby/regexp '/\$\$LWEB$/']
service_types

optional array of strings that represent the service types desired. options are: fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image, primo_source defaults are: fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image if no options are specified, default service types will be added.

ez_proxy

optional string or regexp of an ezproxy prefix. used in the case where an ezproxy prefix (on any other regexp) is hardcoded in the URL, and needs to be removed in order to match against SFXUrls. Example:

!ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
primo_config

optional string representing the primo yaml config file in config/umlaut_config default file name: primo.yml hash mappings from yaml config

institutions:
   "primo_institution_code": "Primo Institution String"
libraries:
   "primo_library_code": "Primo Library String"
statuses:
   "status1_code": "Status One"
sources:
  data_source1:
    base_url: "http://source1.base.url
    type: source_type
    class_name: Source1Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
    source1_config_option1: source1_config_option1
    source1_config_option2: source1_config_option2
  data_source2:
    base_url: "http://source2.base.url
    type: source_type
    class_name: Source2Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
    source2_config_option1: source2_config_option1
    source2_config_option2: source2_config_option2
holding_attributes

optional array of Holding attribute readers to save to holding/primo_source service_data; can be used to save custom source implementation attributes for display by a custom holding partial

Benchmarks

The following benchmarks were run on SunOS 5.10 Generic_141414-08 sun4u sparc SUNW,Sun-Fire-V240.

Rehearsal ----------------------------------------------------------------
PrimoService Minimum Config:   3.850000   0.060000   3.910000 (  4.163065)
PrimoService Default Config:   3.410000   0.060000   3.470000 (  3.958777)
------------------------------------------------------- total: 7.380000sec

                                 user     system      total        real
PrimoService Minimum Config:   3.470000   0.050000   3.520000 (  4.567797)
PrimoService Default Config:   3.420000   0.050000   3.470000 (  3.990271)

Direct Known Subclasses

PrimoSource

Constant Summary

Constants inherited from Service

Service::LinkOutFilterTask, Service::StandardTask

Instance Attribute Summary

Attributes inherited from Service

#name, #priority, #request, #service_id, #session_id, #status, #task, #url

Instance Method Summary collapse

Methods inherited from Service

#credits, #display_name, #handle_wrapper, #link_out_filter, #preempted_by, required_config_params, #response_to_view_data, #response_url, #view_data_from_service_type

Constructor Details

#initialize(config) ⇒ PrimoService

Overwrites Service#new.

Raises:

  • (ArgumentError)


130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/service_adaptors/primo_service.rb', line 130

def initialize(config)
  # defaults
  @holding_attributes = Exlibris::Primo::Holding.base_attributes
  @rsrc_attributes = Exlibris::Primo::Rsrc.base_attributes
  @toc_attributes = Exlibris::Primo::Toc.base_attributes
  @related_link_attributes = Exlibris::Primo::RelatedLink.base_attributes
  # TODO: Run these decisions by Bill M. to see if they make sense.
  @referent_enhancements = {
    # Prefer SFX journal titles to Primo journal titles
    :jtitle => { :overwrite => false },
    :btitle => { :overwrite => true }, :aulast => { :overwrite => true },
    :aufirst => { :overwrite => true }, :aucorp => { :overwrite => true }, 
    :au => { :overwrite => true }, :pub => { :overwrite => true },
    :place => { :value => :cop, :overwrite => false },
    # Prefer SFX journal titles to Primo journal titles
    :title => { :value => :jtitle, :overwrite => false},
    :title => { :value => :btitle, :overwrite => true},
    # Primo lccn and oclcid are spotty in Primo, so don't overwrite
    :lccn => { :overwrite => false }, :oclcnum => { :value => :oclcid, :overwrite => false}
  }
  @suppress_urls = []
  @suppress_tocs = []
  @suppress_related_links = []
  @suppress_holdings = []
  @service_types = [ "fulltext", "holding", "holding_search",
    "table_of_contents", "referent_enhance", "cover_image" ] if @service_types.nil?
  # For backward compatibility, re-map "old" config values to new more 
  # Umlaut-y names and print deprecation warning in the logs.
  old_to_new_mappings = {
    :base_path => :base_url,
    :base_view_id => :vid,
    :link_to_search_text => :holding_search_text
  }
  old_to_new_mappings.each do |old_param, new_param|
    unless config["#{old_param}"].nil?
      config["#{new_param}"] = config["#{old_param}"] if config["#{new_param}"].nil?
      Rails.logger.warn("Parameter '#{old_param}' is deprecated.  Please use '#{new_param}' instead.")
    end
  end # End backward compatibility maintenance
  super(config)
  # For backward compatibility, handle the special case where holding_search_institution was not included.
  # Set holding_search_institution to vid and print warning in the logs.
  if @service_types.include?("holding_search") and @holding_search_institution.nil?
    @holding_search_institution = @institution
    Rails.logger.warn("Required parameter 'holding_search_institution' was not set.  Please set the appropriate value in services.yml.  Defaulting institution to view id, #{@vid}.")
  end # End backward compatibility maintenance
  raise ArgumentError.new(
    "Missing Service configuration parameter. Service type #{self.class} (id: #{self.id}) requires a config parameter named 'holding_search_institution'. Check your config/umlaut_config/services.yml file."
  ) if @service_types.include?("holding_search") and @holding_search_institution.nil?
end

Instance Method Details

#handle(request) ⇒ Object

Overwrites Service#handle.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/service_adaptors/primo_service.rb', line 191

def handle(request)
  @identifier = request.referrer_id 
  @primo_id = @identifier.match(/primo-(.+)/)[1] if primo_identifier? unless @identifier.nil? or @identifier.match(/primo-(.+)/).nil?
  # DEPRECATED
  # Extend OpenURL standard to take Primo Doc Id
  @primo_id = request.referent.['primo'] unless request.referent.['primo'].nil?
  Rails.logger.warn("Use of 'rft.primo' is deprecated.  Please use the identifier instead.") unless request.referent.['primo'].nil?
  # End DEPRECATED
  searcher_setup = {
    :base_url => @base_url, :vid => @vid, :institution => @institution,
    :config => primo_config
  }
  # don't send mal-formed issn
  @issn = request.referent.['issn'] if request.referent.['issn'] =~ /\d{4}(-)?\d{3}(\d|X)/
  @isbn = request.referent.['isbn']
  @title = title(request)
  search_params = {
    :primo_id => @primo_id,
    :isbn => @isbn, 
    :issn => @issn,
    :title => @title,
    :author => author(request),
    :genre => request.referent.['genre']
  }
  begin
     primo_searcher = Exlibris::Primo::Searcher.new(searcher_setup, search_params)
  rescue Exception => e
    # Log error and return finished
    Rails.logger.error(
      "Error in Exlibris::Primo::Searcher. "+ 
      "Returning 0 Primo services for search #{search_params.inspect}. "+ 
      "Exlibris::Primo::Searcher raised the following exception:\n#{e}\n#{e.backtrace.inspect}")
    return request.dispatched(self, true)
  end
  # Enhance the referent with metadata from Primo Searcher if primo id is present
  # i.e. if we did our search with the Primo system number
  if @primo_id and @service_types.include?("referent_enhance")
    @referent_enhancements.each do |key, options|
      value = (options[:value].nil?) ? key.to_sym : options[:value].to_sym
      request.referent.enhance_referent(
        key.to_s, primo_searcher.method(value).call, 
        true, false, options
      ) if primo_searcher.respond_to? value and not primo_searcher.method(value).call.nil?
    end
  end
  # Get cover image only if @primo_id is defined
  # TODO: make cover image service smarter and only 
  # include things that are actually URLs.
  if @primo_id and @service_types.include?("cover_image")
    cover_image = primo_searcher.cover_image
    unless cover_image.nil?
      request.add_service_response(
        :service => self, 
        :display_text => 'Cover Image',
        :key => 'medium', 
        :url => cover_image, 
        :size => 'medium',
        :service_type_value => :cover_image)
    end
  end
  # Get holdings from Primo Searcher
  if @service_types.include?("holding") or @service_types.include?("primo_source")
    holdings = primo_searcher.holdings # Array of Exlibris::Primo::Holding
    holdings.each do |holding|
      next if @suppress_holdings.find {|suppress| suppress === holding.availlibrary}
      service_data = {}
      @holding_attributes.each do |attr|
        service_data[attr] = holding.method(attr).call
      end
      # Umlaut specific attributes.
      service_data[:match_reliability] = 
        (reliable_match?(:title => holding.title, :author => holding.author)) ? 
          ServiceResponse::MatchExact : ServiceResponse::MatchUnsure
      service_data[:request_link_supports_ajax_call] = 
        (holding.respond_to?(:request_link_supports_ajax_call)) ? 
          holding.request_link_supports_ajax_call : false
      # Only add one service type, either "primo_source" OR "holding", not both.
      service_type = (@service_types.include?("primo_source")) ? "primo_source" : "holding"
      # Add some other holding information for compatibility with default holding partial
      service_data.merge!({ 
        :call_number => holding.call_number, :collection => holding.collection,
        :collection_str => "#{holding.library} #{holding.collection}",
        :coverage_str => holding.coverage.join("<br />"), 
        :coverage_str_array => holding.coverage }) if service_type.eql? "holding"
      request.add_service_response(
        service_data.merge(
          :service => self,
          :service_type_value => service_type
        )
      )
    end
    # Provide title search functionality in the absence of available holdings.
    if @service_types.include?("holding_search")
      if holdings.empty? and
         not primo_identifier? and 
         not @title.nil?
        service_data = {}
        service_data[:type] = "link_to_search"
        service_data[:display_text] = (@holding_search_text.nil?) ? "Search for this title." : @holding_search_text
        service_data[:note] = ""
        service_data[:url] = @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"
        request.add_service_response(
          service_data.merge(
            :service => self,
            :service_type_value => 'holding_search'
          )
        )
      end
    end
  end
  # Get fulltext
  if @service_types.include?("fulltext")
    # Get RSRCs from Primo Searcher (executes search)
    # Let's find any URLs, and add full text responses for those.
    urls_seen = [] # for de-duplicating urls from catalog.
    primo_searcher.rsrcs.each do |rsrc|
      # No url? Forget it.
      next if rsrc.url.nil?
      # Next if duplicate.
      next if urls_seen.include?(rsrc.url)
      # Don't add the URL if it matches our SFXUrl finder (unless fulltext is empty, 
      # [assuming something is better than nothing]), because
      # that means we think this is an SFX controlled URL.
      next if SfxUrl.sfx_controls_url?(handle_ezproxy(rsrc.url)) and 
        request.referent.['genre'] != "book" and 
          !request.get_service_type("fulltext", { :refresh => true }).empty?
      # We have our own list of URLs to suppress, array of strings
      # or regexps.
      next if @suppress_urls.find {|suppress| suppress === rsrc.url}
      urls_seen.push(rsrc.url)
      service_data = {}
      @rsrc_attributes.each do |attr|
        service_data[attr] = rsrc.method(attr).call
      end
      # Default display text to URL.
      service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
      # Add the response
      request.add_service_response(
        service_data.merge(
          :service => self,
          :service_type_value => 'fulltext'
        )
      )
    end
  end
  # Get TOCs
  if @service_types.include?("table_of_contents")
    # Let's find any TOCs, and add table of contents responses for those.
    tocs_seen = [] # for de-duplicating urls from catalog.
    primo_searcher.tocs.each do |toc|
      url = toc.url # actual url
      next if tocs_seen.include?(toc.url)
      # We have our own list of URLs to suppress, array of strings
      # or regexps.
      next if @suppress_tocs.find {|suppress| suppress === toc.url}
      # No url? Forget it.
      next if toc.url.nil?
      tocs_seen.push(toc.url)
      service_data = {}
      @toc_attributes.each do |attr|
        service_data[attr] = toc.method(attr).call
      end
      # Default display text to URL.
      service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
      # Add the response
      request.add_service_response(
        service_data.merge(
          :service => self,
          :service_type_value => 'table_of_contents'
        )
      )
    end
  end
  if @service_types.include?("highlighted_link")
    # Let's find any related links, and add highlighted link responses for those.
    related_links_seen = [] # for de-duplicating urls from catalog.
    primo_searcher.related_links.each do |related_link|
      url = related_link.url # actual url
      next if related_links_seen.include?(related_link.url)
      # We have our own list of URLs to suppress, array of strings
      # or regexps.
      next if @suppress_related_links.find {|suppress| suppress === related_link.url}
      # No url? Forget it.
      next if related_link.url.nil?
      related_links_seen.push(related_link.url)
      service_data = {}
      @related_link_attributes.each do |attr|
        service_data[attr] = related_link.method(attr).call
      end
      # Default display text to URL.
      service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
      # Add the response
      request.add_service_response(
        service_data.merge(
          :service => self,
          :service_type_value => 'highlighted_link'
        )
      )
    end
  end
  return request.dispatched(self, true)
end

#service_types_generatedObject

Overwrites Service#service_types_generated.



182
183
184
185
186
187
188
# File 'lib/service_adaptors/primo_service.rb', line 182

def service_types_generated
  types = Array.new
  @service_types.each do |type|
    types.push(ServiceTypeValue[type.to_sym])
  end
  return types
end

#to_primo_source(service_response) ⇒ Object

Called by ServiceType#view_data to provide custom functionality for Primo sources. For more information on Primo sources see PrimoSource.



396
397
398
399
400
401
# File 'lib/service_adaptors/primo_service.rb', line 396

def to_primo_source(service_response)
  source_parameters = { :base_url => @base_url, :vid => @vid, :config => primo_config }
  @holding_attributes.each { |attr| 
      source_parameters[attr] = service_response.data_values[attr] }
  return Exlibris::Primo::Holding.new(source_parameters).to_source
end