Class: Service

Inherits:
Object
  • Object
show all
Defined in:
lib/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.

Constant Summary collapse

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

#initialize(config) ⇒ Service

Returns a new instance of Service.



45
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
# File 'lib/service.rb', line 45

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_config/services.yml file.")
    end      
  end    
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



35
36
37
# File 'lib/service.rb', line 35

def name
  @name
end

#priorityObject (readonly)

Returns the value of attribute priority.



35
36
37
# File 'lib/service.rb', line 35

def priority
  @priority
end

#requestObject

Returns the value of attribute request.



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

def request
  @request
end

#service_idObject (readonly)

Returns the value of attribute service_id.



35
36
37
# File 'lib/service.rb', line 35

def service_id
  @service_id
end

#session_idObject



172
173
174
175
176
177
178
179
# File 'lib/service.rb', line 172

def session_id
  unless (@session_id)
    raise Exception.new("This service has not been initialized with a request!") unless request
    
    @session_id = request.session_id
  end
  return @session_id
end

#statusObject (readonly)

Returns the value of attribute status.



35
36
37
# File 'lib/service.rb', line 35

def status
  @status
end

#taskObject (readonly)

Returns the value of attribute task.



35
36
37
# File 'lib/service.rb', line 35

def task
  @task
end

#urlObject (readonly)

Returns the value of attribute url.



35
36
37
# File 'lib/service.rb', line 35

def url
  @url
end

Class Method Details

.required_config_params(*params) ⇒ Object

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.



161
162
163
164
165
166
167
168
169
# File 'lib/service.rb', line 161

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

#creditsObject

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.



269
270
271
# File 'lib/service.rb', line 269

def credits
  @credits || {}
end

#display_nameObject



120
121
122
123
124
# File 'lib/service.rb', line 120

def display_name
  # If no display_name is set, default to the id string. Not a good idea,
  # but hey. 
  return @display_name ||= self.service_id    
end

#handle(request) ⇒ Object

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

Raises:

  • (Exception)


101
102
103
# File 'lib/service.rb', line 101

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

#handle_wrapper(request) ⇒ Object

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



89
90
91
92
93
94
95
96
97
# File 'lib/service.rb', line 89

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)


115
116
117
# File 'lib/service.rb', line 115

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

#preempted_by(uml_request, for_type_generated = nil) ⇒ Object

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.


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
# File 'lib/service.rb', line 227

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.Succesful ))
       end
     else
       # Check service responses
       preemption = 
         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
     break if preemption
  end
  return (! preemption.nil? )
end

#response_to_view_data(service_response) ⇒ Object

Default implementation to take a ServiceResponse and parse into a hash of values useful to the view. Basic implementation just asks ServiceResposne for it’s data_values object, which contains all ServiceResponse data (including arbitrary keys serialized in the hash) in an object with the hash accessor method [] .



153
154
155
# File 'lib/service.rb', line 153

def response_to_view_data(service_response)
    return service_response.data_values
end

#response_url(service_response, submitted_params) ⇒ Object

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.



188
189
190
191
192
# File 'lib/service.rb', line 188

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

#service_types_generatedObject

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)


82
83
84
# File 'lib/service.rb', line 82

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

#view_data_from_service_type(service_response) ⇒ Object

Pass this method a ServiceResponse object, it will return a hash-like object of display values, for the view. Implementation is usually in sub-class, by means of a set of methods “to_[service type name]” implemented in sub-class . parseResponse will find those. Subclasses will not generally override view_data_from_service_type, although they can for complete custom handling. Make sure to return a Hash or hash-like (duck-typed) object.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/service.rb', line 133

def view_data_from_service_type(service_response)

  service_type_code = service_response.service_type_value.name
  
  begin
    # try to call a method named "to_#{service_type_code}", implemented by sub-class
    self.send("to_#{service_type_code}", service_response)
  rescue NoMethodError 
  # No to_#{response_type} method? How about the catch-all method?
  # If not implemented in sub-class, we have a VERY basic
  # default implementation in this class. 
      self.send("response_to_view_data", service_response)
  end
end