Class: Service

Inherits:
Object
  • Object
show all
Defined in:
app/service_adaptors/service.rb

Overview

Services are defined from the config/umlaut_config/services.yml file. hey should have the following properties id : unique internal string for the service, unique in yml file display_name : user displayable string url : A base url of some kind used by specific service type : Class name of class found in lib/service_adaptors to be used for logic priority: 0-9 (foreground) or a-z (background) for order of service operation

Specific service_adaptor classes may have specific addtional configuration, commonly including 'password' or 'api_key'. specific service can put “ required_config_parms :param1, :param2” in definition, for requirement exception raising on initialize.

Service Sub-classes

Can include required config params in the class definition, eg:

required_config_params :api_key, :base_url

Should define #service_types_generated returning an array of ServiceTypeValues. This is neccesary for the Service to be run as a background service, and have the auto background updater work.

The vast majority of services are 'standard' services, however there are other 'tasks' that a service can be. Well, right now, one other, 'link_out_filter'. The services 'task' config property sets the task/function/hook of the service. Default is 'standard'.

A standard service defines handle(request)

A link_out_filter service defines link_out_filter(request, url). If service returns a new url from filter_url, that's the url the user will be directed to. If service returns original url or nil, original url will still be used.

See documentation at ServiceResponse regarding how a service generates ServiceResponses to respond to a user request.

Direct Known Subclasses

AjaxExport, Amazon, Blacklight, BookFinder, Bx, CoverThing, DummyService, ElsevierCover, Ezproxy, GoogleBookSearch, GoogleScholarLink, Gpo, HathiTrust, Hip3Service, Illiad, InternetArchive, IsbnDb, IsbnLink, Isi, Jcr, Opac, OpenLibrary, OpenLibraryCover, Pubmed, RequestToFixture, Scopus, Scopus2, Sfx, SfxBackchannelRecord, UlrichsCover, UlrichsLink, Worldcat, WorldcatIdentities

Constant Summary

StandardTask =

Some constants for 'function' values

'standard'
LinkOutFilterTask =
'link_out_filter'
@@required_params_for_subclass =

initialize class var

{}

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Service) initialize(config)

Returns a new instance of Service



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'app/service_adaptors/service.rb', line 46

def initialize(config)
  config.each do | key, val |
    self.instance_variable_set(('@'+key).to_sym, val)
  end

  # task defaults to standard
  @task ||= StandardTask

  # check required params, and throw if neccesary

  required_params = Array.new
  # Some things required for all services
  required_params << "priority"
  # Custom things for this particular sub-class
  
  required_params.concat( @@required_params_for_subclass[self.class.name] ) if @@required_params_for_subclass[self.class.name]
  required_params.each do |param|
    begin
        value = self.instance_variable_get('@' + param.to_s)
        # docs say it raises a nameerror if it doesn't exist, docs
        # lie. So we'll just raise one ourselves, and catch it, to
        # handle both cases.
        raise NameError if value.nil?          
    rescue NameError
    raise ArgumentError.new("Missing Service configuration parameter. Service type #{self.class} (id: #{self.service_id}) requires a config parameter named '#{param}'. Check your config/umlaut_services.yml file.")
    end      
  end    
end

Instance Attribute Details

- (Object) group (readonly)

Returns the value of attribute group



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def group
  @group
end

- (Object) name (readonly)

Returns the value of attribute name



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def name
  @name
end

- (Object) priority (readonly)

Returns the value of attribute priority



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def priority
  @priority
end

- (Object) request

Returns the value of attribute request



38
39
40
# File 'app/service_adaptors/service.rb', line 38

def request
  @request
end

- (Object) service_id (readonly)

Returns the value of attribute service_id



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def service_id
  @service_id
end

- (Object) status (readonly)

Returns the value of attribute status



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def status
  @status
end

- (Object) task (readonly)

Returns the value of attribute task



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def task
  @task
end

- (Object) url (readonly)

Returns the value of attribute url



37
38
39
# File 'app/service_adaptors/service.rb', line 37

def url
  @url
end

Class Method Details

+ (Object) required_config_params(*params)

Sub-class can call class method like:

required_config_params  :symbol1, :symbol2, symbol3

in class definition body. List of config parmas that are required, exception will be thrown if not present.



134
135
136
137
138
139
140
141
142
# File 'app/service_adaptors/service.rb', line 134

def self.required_config_params(*params)
  params.each do |p|
    # Key on name of specific sub-class. Since this is a class
    # method, that should be self.name
    @@required_params_for_subclass[self.name] ||= Array.new
    a = @@required_params_for_subclass[self.name]
    a.push( p ) unless a.include?( p )
  end
end

Instance Method Details

- (Object) credits

used by render_service_credits helper method, returns a hash with keys being a human-displayable name of a third party to give 'credit' to, and value being a URL (or nil) to link the name to. computed from @credits config variable, or returns empty hash.



251
252
253
# File 'app/service_adaptors/service.rb', line 251

def credits
  @credits || {}
end

- (Object) display_name

Name of this service, like “Amazon”, or “OCLC Worldcat”. First tries to look up an i18n translation using #translate, if not found, uses a @display_name set in this service, if still not found uses service_id for lack of anything else.



124
125
126
# File 'app/service_adaptors/service.rb', line 124

def display_name    
  self.translate("display_name", :default => @display_name || self.service_id)
end

- (Object) handle(request)

Implemented by sub-class. Standard response-generating services implement this method to do their work, generate responses and/or metadata.

Raises:

  • (Exception)


102
103
104
# File 'app/service_adaptors/service.rb', line 102

def handle(request)
  raise Exception.new("#{self.class}: handle() must be implemented by Service concrete sub-class, for standard services!")
end

- (Object) handle_wrapper(request)

Method that should actually be called to trigger the service. Will check pre-emption.



90
91
92
93
94
95
96
97
98
# File 'app/service_adaptors/service.rb', line 90

def handle_wrapper(request)
  unless ( preempted_by(request) )
    return handle(request)
  else
    # Pre-empted, log and close dispatch record as 'succesful'.
    Rails.logger.debug("Service #{service_id} was pre-empted and not run.")
    return request.dispatched(self, true)
  end
end

This method is implemented by a concrete sub-class meant to fulfill the task:link_out_filter. Will be called when the user clicks on a url that will redirect external to Umlaut. The link_out_filter service has the ability to intervene and record and/or change the url. link_out_filters are called in order of priority config param assigned, 0 through 9.

orig_url is the current url umlaut is planning on sending the user to. service_type is the ServiceType object responsible for this url. the third argument is reserved for future use an options hash.

Raises:

  • (Exception)


116
117
118
# File 'app/service_adaptors/service.rb', line 116

def link_out_filter(orig_url, service_response, other_args = {})
  raise Exception.new("#{self.class}: #link_out_filter must be implemented by Service concrete sub-class with task link_out_filter!")
end

- (Object) preempted_by(uml_request, for_type_generated = nil)

Pre-emption hashes specify a combination of existing responses or service executions that can pre-empt this service. Can specify a service, a response type (ServiceTypeValue), or a combination of both.

service's preempted_by property can either be a single pre-emption hash, or an array of pre-emption hashes.

Can also specify that pre-emption is only of a certain service type generated by self.

The Service base class will enforce pre-emption and not even run a service at all *so long as self_type is nil or '*' *. If the pre-emption only applies to certain types generated by the service and not the entire execution of the service, the concrete service subclass must implement logic to do that. Calling the preempted method with the second argument set will be helpful in writing this logic.

A preemption hash has string keys:

existing_service: id of service that will pre-empt this service.
                  If key does not exist or is "*", then not specified,
                  any service. (existing_type will be specified). 
existing_type:  ServiceTypeValue name that pre-empts this
                service. "+" means that the service specified
                in existing_service must have generated some
                response, but type does not matter. "*" means
                that the service specified in existing_service
                must have completed succesfully, but may not
                have generated any responses.
self_type:      If blank or "*", preemption applies to any running
                of this service at all. If set to a ServiceTypeValue
                name, pre-emption is only of certain types generated
                by this service.


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
# File 'app/service_adaptors/service.rb', line 208

def preempted_by(uml_request, for_type_generated=nil)
  preempted_by = @preempted_by
  return false if preempted_by.nil?
  preempted_by = [preempted_by] unless preempted_by.kind_of?(Array)
  preemption = nil

  preempted_by.each do | hash |
     service = hash["existing_service"] || "*"
     other_type = hash["existing_type"] || "*"      
     self_type = hash["self_type"] || "*"

     next unless (self_type == "*" || self_type == for_type_generated)

     if (other_type == "*")
       # Need to check dispatched services instead of service_types,
       # as we pre-empt even if no services created. 
       preemption = 
       uml_request.dispatched_services.to_a.find do |disp|
         service == "*" || 
         (disp.service_id == service &&
           (disp.status ==  DispatchedService::Successful ))
       end
     else
       # Check service responses
       preemption = Request.connection_pool.with_connection do 
           uml_request.service_responses.to_a.find do |response|
           ( other_type == "*" || other_type == "+" ||
             response.service_type_value.name == other_type)  &&
           ( service == "*" ||
             response.service_id == service)         
         end
       end
     end
     break if preemption
  end
  return (! preemption.nil? )
end

- (Object) response_url(service_response, submitted_params)

This method is called by Umlaut when user clicks on a service response. Default implementation here just returns response. You can over-ride in a sub-class to provide custom implementation of on-demand url generation. Second argument is the http request params sent by the client, used for service types that take form submissions (eg search_inside). Should return a String url.



154
155
156
157
158
# File 'app/service_adaptors/service.rb', line 154

def response_url(service_response,  )
  url = service_response[:url]
  raise "No url provided by service response" if url.nil? || url.empty?
  return url
end

- (Object) service_types_generated

Must be implemented by concrete sub-class. return an Array of ServiceTypeValues constituting the types of ServiceResponses the service might generate. Used by Umlaut infrastructure including the background service execution scheme itslef, as well asxml services returning information on services in progress.

Example for a service that only generates fulltext:

return [ ServiceTypeValue[:fulltext] ]

Raises:

  • (Exception)


83
84
85
# File 'app/service_adaptors/service.rb', line 83

def service_types_generated
  raise Exception.new("#{self.class}: service_types_generated() must be implemented by Service concrete sub-class!")
end

- (Object) translate(key, options = {})

Look up an i18n key scoped to this service, first under the unique ID of the service, then under the service class name:

  • First look for translation under `umlaut.services.#Service.service_idservice_id.underscore.key`

  • If not found, look for translation under `umlaut.services.#Service.service_class_nameservice_class_name.underscore`

  • If still not found, pass in optional default, otherwise you'll get I18n

    configured failure behavior.

second arg is options that can be passed to standard I18n.t, including defaults and template arguments.



169
170
171
172
173
174
# File 'app/service_adaptors/service.rb', line 169

def translate(key, options = {})
   # Modify/add options[:default] to look up under class name too
   options[:default] = [:umlaut.services.#{self.class.name.underscore}.#{key}"].concat(Array( options[:default] ))

   I18n.t("umlaut.services.#{self.service_id.underscore}.#{key}", options)
end