Class: Request
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Request
- Defined in:
- app/models/request.rb
Overview
An ActiveRecord which represents a parsed OpenURL resolve service request, and other persistent state related to Umlaut’s handling of that OpenURL request) should not be confused with the Rails ActionController::Request class (which represents the complete details of the current ‘raw’ HTTP request, and is not stored persistently in the db).
Constituent openurl data is stored in Referent and Referrer.
Class Method Summary collapse
-
.context_object_params(a_rails_request) ⇒ Object
input is a Rails request (representing http request) We pull out a hash of request params (get and post) that define a context object.
-
.find_or_create(params, session, a_rails_request, options = {}) ⇒ Object
Either creates a new Request, or recovers an already created Request from the db–in either case return a Request matching the OpenURL.
Instance Method Summary collapse
-
#add_service_response(response_data) ⇒ Object
Create a ServiceResponse and it’s associated ServiceType(s) object, attached to this request.
- #any_services_in_progress? ⇒ Boolean
-
#can_dispatch?(service) ⇒ Boolean
Someone asks us if it’s okay to dispatch this guy.
-
#dispatch_objects_with(options = {}) ⇒ Object
Returns an array of 0 or more ServiceDispatch objects matching specified conditions.
-
#dispatched(service, status, exception = nil) ⇒ Object
Method that registers the dispatch status of a given service participating in this request.
-
#failed_service_dispatches ⇒ Object
Methods to look at status of dispatched services.
-
#get_service_type(svc_type, options = {}) ⇒ Object
pass in a ServiceTypeValue (or string name of such), get back list of ServiceResponse objects with that value belonging to this request.
-
#new_dispatch_object!(service, status) ⇒ Object
Warning, doesn’t check for existing object first.
-
#register_in_progress(service) ⇒ Object
Sets a DispatchedService object attached to this Request, for given service, marked InProgress – but only if existing DispatchedService object did not already exist, or existed and was marked Queued or FailedTemporary.
-
#service_type_in_progress?(svc_type) ⇒ Boolean
convenience method to call service_types_in_progress with one element.
-
#service_types_in_progress?(type_array) ⇒ Boolean
pass in array of ServiceTypeValue or string name of same.
-
#services_in_progress ⇒ Object
Returns array of Services in progress or queued.
-
#title_level_citation? ⇒ Boolean
Is the citation represetned by this request a title-level only citation, with no more specific article info? Or no, does it include article or vol/iss info?.
- #to_context_object ⇒ Object
Class Method Details
.context_object_params(a_rails_request) ⇒ Object
input is a Rails request (representing http request) We pull out a hash of request params (get and post) that define a context object. We use CGI::parse instead of relying on Rails parsing because rails parsing ignores multiple params with same key value, which is legal in CGI and is sometimes used in OpenURLs.
So in general values of this hash will be an array. ContextObject.new_from_form_vars is good with that. Exception is url_ctx_fmt and url_ctx_val, which we’ll convert to single values, because ContextObject wants it so.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'app/models/request.rb', line 109 def self.context_object_params(a_rails_request) # GET params co_params = CGI::parse( a_rails_request.query_string ) # add in the POST params please co_params.merge!( CGI::parse(a_rails_request.raw_post)) if a_rails_request.raw_post # default value nil please, that's what ropenurl wants co_params.default = nil # CGI::parse annoyingly sometimes puts a nil key in there, for an empty # query param (like a url that has two consecutive && in it). Let's get rid # of it please, only confuses our code. co_params.delete(nil) # Exclude params that are for Rails or Umlaut, and don't belong to the # context object. Except leave in umlaut.* keys that DO matter for # cacheability, like umlaut.institution (legacy) and umlaut.service_group excluded_keys = ["action", "controller", "page", /\Aumlaut\.(?!(institution|service_group\[\])\Z)/, 'rft.action', 'rft.controller'] co_params.keys.each do |key| excluded_keys.each do |exclude| co_params.delete(key) if exclude === key; end end # 'id' is a special one, cause it can be a OpenURL 0.1 key, or # it can be just an application-level primary key. If it's only a # number, we assume the latter--an openurl identifier will never be # just a number. if co_params['id'] co_params['id'].each do |id| co_params['id'].delete(id) if id =~ /^\d+$/ end end return co_params end |
.find_or_create(params, session, a_rails_request, options = {}) ⇒ Object
Either creates a new Request, or recovers an already created Request from the db–in either case return a Request matching the OpenURL. options => false, will not create a new request, return nil if no existing request can be found.
35 36 37 38 39 40 41 42 43 44 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'app/models/request.rb', line 35 def self.find_or_create(params, session, a_rails_request, = {} ) # Pull out the http params that are for the context object, # returning a CGI::parse style hash, customized for what # ContextObject.new_from_form_vars wants. co_params = self.context_object_params( a_rails_request ) # Create a context object from our http params context_object = OpenURL::ContextObject.new_from_form_vars( co_params ) # Sometimes umlaut puts in a 'umlaut.request_id' parameter. # first look by that, if we have it, for an existing request. request_id = params['umlaut.request_id'] # We're trying to identify an already existing response that matches # this request, in this session. We don't actually match the # session_id in the cache lookup though, so background processing # will hook up with the right request even if user has no cookies. # We don't check IP change anymore either, that was too open to # mistaken false negative when req.ip was being used. req = Request.find_by_id(request_id) unless request_id.nil? # No match? Just pretend we never had a request_id in url at all. request_id = nil if req == nil # Serialized fingerprint of openurl http params, suitable for looking # up in the db to see if we've seen it before. We got our co_params # direct from parsing path ourselves, but in case a before_filter # added in certain other params after that, we want to merge them in # too. fingerprintable_params = co_params.merge( {"umlaut.service_group" => params["umlaut.service_group"]}.delete_if {|k, v| v.blank?} ) param_fingerprint = self.co_params_fingerprint( fingerprintable_params ) client_ip = params['req.ip'] || a_rails_request.remote_ip() unless (req || params["umlaut.force_new_request"] == "true" || param_fingerprint.blank? ) # If not found yet, then look for an existing request that had the same # openurl params as this one, in the same session. In which case, reuse. # Here we do require same session, since we don't have an explicit # request_id given. req = Request.where( :session_id => a_rails_request.[:id], :contextobj_fingerprint => param_fingerprint, :client_ip_addr => client_ip ). order("created_at DESC, id DESC").first end # Okay, if we found a req, it might NOT have a referent, it might # have been purged. If so, create a new one. if ( req && ! req.referent ) req.referent = Referent.create_by_context_object(context_object) end unless (req || [:allow_create] == false) # didn't find an existing one at all, just create one req = self.create_new_request!( :params => params, :session => session, :rails_request => a_rails_request, :contextobj_fingerprint => param_fingerprint, :context_object => context_object ) end return req end |
Instance Method Details
#add_service_response(response_data) ⇒ Object
Create a ServiceResponse and it’s associated ServiceType(s) object, attached to this request. Arg is a hash of key/values. Keys MUST include:
-
:service, with the value being the actual Service object, not just the ID.
-
:service_type_value => the ServiceTypeValue object (or string name) for
the the ‘type’ of response this is.
Other keys are as conventional for the service. See documentation of conventional keys in ServiceResponse
Some keys end up stored in columns in the db directly, others end up serialized in a hash in a ‘text’ column, caller doesn’t have to worry about that, just pass em all in.
Eg, called from a service adapter plugin:
request.add_service_response(:service=>self,
:service_type_value => 'cover_image',
:display_text => 'Cover Image',
:url => img.inner_html,
:asin => asin,
:size => size)
Safe to call in thread, uses connection pool checkout.
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 |
# File 'app/models/request.rb', line 239 def add_service_response(response_data) raise ArgumentError.new("missing required `:service` key") unless response_data[:service].kind_of?(Service) raise ArgumentError.new("missing required `:service_type_value` key") unless response_data[:service_type_value] svc_resp = nil ActiveRecord::Base.connection_pool.with_connection do svc_resp = self.service_responses.build svc_resp.service_id = response_data[:service].service_id response_data.delete(:service) type_value = response_data.delete(:service_type_value) type_value = ServiceTypeValue[type_value.to_s] unless type_value.kind_of?(ServiceTypeValue) svc_resp.service_type_value = type_value # response_data now includes actual key/values for the ServiceResponse # send em, take_key_values takes care of deciding which go directly # in columns, and which in serialized hash. svc_resp.take_key_values( response_data ) svc_resp.save! end return svc_resp end |
#any_services_in_progress? ⇒ Boolean
307 308 309 |
# File 'app/models/request.rb', line 307 def any_services_in_progress? return services_in_progress.length > 0 end |
#can_dispatch?(service) ⇒ Boolean
Someone asks us if it’s okay to dispatch this guy. Only if it’s marked as Queued, or Failed—otherwise it should be already working, or done.
178 179 180 181 182 |
# File 'app/models/request.rb', line 178 def can_dispatch?(service) ds= self.dispatched_services.where(:service_id => service.service_id).first return ds.nil? || (ds.status == DispatchedService::Queued) || (ds.status == DispatchedService::FailedTemporary) end |
#dispatch_objects_with(options = {}) ⇒ Object
Returns an array of 0 or more ServiceDispatch objects matching specified conditions. Right now only one condition is supported:
dispatch_objects_with(:service_type_values => values)
values can be one or more string names of service types, returns
DispatchedServices for services whose generated values include
one or more of what you specified.
398 399 400 401 402 403 404 405 406 407 408 |
# File 'app/models/request.rb', line 398 def dispatch_objects_with( = {}) value_names = Array([:service_type_values]) raise ArgumentError.new("Need to supply a :service_type_values argument") unless value_names.present? list = self.dispatched_services.to_a.find_all do |ds| (value_names & ds.service.service_types_generated.collect(&:name)).present? end return list end |
#dispatched(service, status, exception = nil) ⇒ Object
Method that registers the dispatch status of a given service participating in this request.
Status can be true (shorthand for DispatchedService::Success), false (shorthand for DispatchedService::FailedTemporary), or one of the other DispatchedService status codes. If a DispatchedService row already exists in the db, that row will be re-used, over-written with new status value.
Exception can optionally be provided, generally with failed statuses, to be stored for debugging purposes.
Safe to call in thread, uses explicit connectionpool checkout.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'app/models/request.rb', line 158 def dispatched(service, status, exception=nil) ActiveRecord::Base.connection_pool.with_connection do ds = self.find_dispatch_object( service ) unless ds ds= self.new_dispatch_object!(service, status) end # In case it was already in the db, make sure to over-write status. # and add the exception either way. ds.status = status ds.store_exception( exception ) ds.save! end end |
#failed_service_dispatches ⇒ Object
Methods to look at status of dispatched services
268 269 270 271 272 |
# File 'app/models/request.rb', line 268 def failed_service_dispatches return self.dispatched_services.where( :status => [DispatchedService::FailedTemporary, DispatchedService::FailedFatal] ).to_a end |
#get_service_type(svc_type, options = {}) ⇒ Object
pass in a ServiceTypeValue (or string name of such), get back list of ServiceResponse objects with that value belonging to this request. :refresh=>true will force a trip to the db to get latest values. otherwise, association is used.
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 |
# File 'app/models/request.rb', line 344 def get_service_type(svc_type, = {}) svc_type_obj = (svc_type.kind_of?(ServiceTypeValue)) ? svc_type : ServiceTypeValue[svc_type] responses = if ( [:refresh]) ActiveRecord::Base.connection_pool.with_connection do self.service_responses.where(["service_type_value_name = ?", svc_type_obj.name ]).to_a end else # find on an assoc will go to db, unless we convert it to a plain # old array first. self.service_responses.to_a.find_all { |response| response.service_type_value == svc_type_obj } end # Filter out any services with ID's not currently registered in # ServiceStore (responses, excluded_responses) = responses.partition do |r| ServiceStore.service_definition_for(r.service_id).present? end if excluded_responses.present? Rails.logger.warn("ServiceResponses skipped for unknown service_ids: " + excluded_responses.collect {|s| s.service_id}.uniq.join(",")) end return responses end |
#new_dispatch_object!(service, status) ⇒ Object
Warning, doesn’t check for existing object first. Use carefully, usually paired with find_dispatch_object. Doesn’t actually call save though, caller must do that (in case caller wants to further initialize first).
377 378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'app/models/request.rb', line 377 def new_dispatch_object!(service, status) service_id = if service.kind_of?(Service) service.service_id else service.to_s end ds = DispatchedService.new ds.service_id = service_id ds.status = status self.dispatched_services << ds return ds end |
#register_in_progress(service) ⇒ Object
Sets a DispatchedService object attached to this Request, for given service, marked InProgress – but only if existing DispatchedService object did not already exist, or existed and was marked Queued or FailedTemporary.
Returns true if was able to register as InProgress for given service, otherwise false.
Wrapped in a connection_pool.with_connection, safe for calling from threaded context.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'app/models/request.rb', line 192 def register_in_progress(service) ActiveRecord::Base.connection_pool.with_connection do ds = self.find_dispatch_object( service ) if ds # Already existed, need to update atomically, only if it's got # a compatible existing status. updated_count = self.dispatched_services.where(:id => ds.id, :status => [DispatchedService::Queued || DispatchedService::FailedTemporary] ). update_all(:status => DispatchedService::InProgress) return (updated_count > 0) else # create new one, if race condition happened in between `find` above and now, # we might wind up with a constraint violation raised, sorry. ds= self.new_dispatch_object!(service, DispatchedService::InProgress) ds.save! return true end end end |
#service_type_in_progress?(svc_type) ⇒ Boolean
convenience method to call service_types_in_progress with one element.
290 291 292 |
# File 'app/models/request.rb', line 290 def service_type_in_progress?(svc_type) return service_types_in_progress?( [svc_type] ) end |
#service_types_in_progress?(type_array) ⇒ Boolean
pass in array of ServiceTypeValue or string name of same. Returns true if ANY of them are in progress.
296 297 298 299 300 301 302 303 304 305 |
# File 'app/models/request.rb', line 296 def service_types_in_progress?(type_array) # convert strings to ServiceTypeValues type_array = type_array.collect {|s| s.kind_of?(ServiceTypeValue)? s : ServiceTypeValue[s] } self.services_in_progress.each do |s| # array intersection return true unless (s.service_types_generated & type_array).empty? end return false; end |
#services_in_progress ⇒ Object
Returns array of Services in progress or queued. Intentionally uses cached in memory association, so it wont’ be a trip to the db every time you call this.
277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'app/models/request.rb', line 277 def services_in_progress # Intentionally using the in-memory array instead of going to db. # that's what the "to_a" is. Minimize race-condition on progress # check, to some extent, although it doesn't really get rid of it. dispatches = self.dispatched_services.to_a.find_all do | ds | (ds.status == DispatchedService::Queued) || (ds.status == DispatchedService::InProgress) end svcs = dispatches.collect { |ds| ds.service } return svcs end |
#title_level_citation? ⇒ Boolean
Is the citation represetned by this request a title-level only citation, with no more specific article info? Or no, does it include article or vol/iss info?
326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'app/models/request.rb', line 326 def title_level_citation? data = referent. # atitle can't generlaly get us article-level, but it can with # lexis nexis, so we'll consider it article-level. Since it is! return ( data['atitle'].blank? && data['volume'].blank? && data['issue'].blank? && # pmid or doi is considered article-level, because SFX can # respond to those. Other identifiers may be useless. (! referent.identifiers.find {|i| i =~ /^info\:(doi|pmid)/}) ) end |
#to_context_object ⇒ Object
311 312 313 314 315 316 317 318 319 320 321 |
# File 'app/models/request.rb', line 311 def to_context_object #Mostly just the referent context_object = self.referent.to_context_object #But a few more things context_object.referrer.add_identifier(self.referrer_id) if self.referrer_id context_object.requestor.('ip', self.client_ip_addr) if self.client_ip_addr return context_object end |