umlaut 3.0.0alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -0
- data/README.md +49 -0
- data/Rakefile +37 -0
- data/app/assets/images/error.gif +0 -0
- data/app/assets/images/export_bg_bot.gif +0 -0
- data/app/assets/images/export_bg_mid.gif +0 -0
- data/app/assets/images/export_bg_top.gif +0 -0
- data/app/assets/images/famfamfam/book_open.png +0 -0
- data/app/assets/images/famfamfam/cross.png +0 -0
- data/app/assets/images/famfamfam/page_sound.gif +0 -0
- data/app/assets/images/famfamfam/page_text.gif +0 -0
- data/app/assets/images/famfamfam/page_up.gif +0 -0
- data/app/assets/images/famfamfam/page_white.png +0 -0
- data/app/assets/images/famfamfam/readme.html +1495 -0
- data/app/assets/images/famfamfam/tiny_cross.png +0 -0
- data/app/assets/images/frame_remove.gif +0 -0
- data/app/assets/images/ico_go.gif +0 -0
- data/app/assets/images/jhu_findit.gif +0 -0
- data/app/assets/images/list_closed.png +0 -0
- data/app/assets/images/list_open.png +0 -0
- data/app/assets/images/more_info.gif +0 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/images/request.gif +0 -0
- data/app/assets/images/spinner.gif +0 -0
- data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
- data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
- data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
- data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
- data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
- data/app/assets/javascripts/umlaut/update_html.js +152 -0
- data/app/assets/javascripts/umlaut.js +17 -0
- data/app/assets/stylesheets/umlaut.css +857 -0
- data/app/controllers/application_controller.rb +14 -0
- data/app/controllers/export_email_controller.rb +123 -0
- data/app/controllers/js_helper_controller.rb +10 -0
- data/app/controllers/link_router_controller.rb +87 -0
- data/app/controllers/open_search_controller.rb +9 -0
- data/app/controllers/resolve_controller.rb +288 -0
- data/app/controllers/resource_controller.rb +83 -0
- data/app/controllers/search_controller.rb +328 -0
- data/app/controllers/search_methods/sfx3.rb +148 -0
- data/app/controllers/search_methods/sfx4.rb +257 -0
- data/app/controllers/search_methods/sfx_api.rb +47 -0
- data/app/controllers/store_controller.rb +64 -0
- data/app/controllers/umlaut/controller_behavior.rb +20 -0
- data/app/controllers/umlaut/controller_logic.rb +96 -0
- data/app/controllers/umlaut/error_handling.rb +48 -0
- data/app/controllers/umlaut_controller.rb +112 -0
- data/app/helpers/application_helper.rb +4 -0
- data/app/helpers/emailer_helper.rb +43 -0
- data/app/helpers/export_email_helper.rb +34 -0
- data/app/helpers/open_search_helper.rb +7 -0
- data/app/helpers/resolve_helper.rb +225 -0
- data/app/helpers/search_helper.rb +50 -0
- data/app/helpers/umlaut/footer_helper.rb +64 -0
- data/app/helpers/umlaut/helper.rb +62 -0
- data/app/helpers/umlaut/html_head_helper.rb +37 -0
- data/app/helpers/umlaut/url_generation.rb +77 -0
- data/app/mailers/emailer.rb +48 -0
- data/app/models/clickthrough.rb +2 -0
- data/app/models/collection.rb +259 -0
- data/app/models/crossref_lookup.rb +2 -0
- data/app/models/dispatched_service.rb +58 -0
- data/app/models/permalink.rb +29 -0
- data/app/models/referent.rb +473 -0
- data/app/models/referent_value.rb +14 -0
- data/app/models/request.rb +449 -0
- data/app/models/service_response.rb +179 -0
- data/app/models/service_store.rb +59 -0
- data/app/models/service_type_value.rb +58 -0
- data/app/models/service_wave.rb +150 -0
- data/app/models/sfx_db/az_additional_title.rb +11 -0
- data/app/models/sfx_db/az_letter_group.rb +11 -0
- data/app/models/sfx_db/az_title.rb +38 -0
- data/app/models/sfx_db/az_title_v2.rb +34 -0
- data/app/models/sfx_db/isbn.rb +12 -0
- data/app/models/sfx_db/issn.rb +12 -0
- data/app/models/sfx_db/object.rb +35 -0
- data/app/models/sfx_db/object_portfolio.rb +6 -0
- data/app/models/sfx_db/publisher.rb +10 -0
- data/app/models/sfx_db/sfx_db_base.rb +54 -0
- data/app/models/sfx_db/target.rb +9 -0
- data/app/models/sfx_db/target_service.rb +10 -0
- data/app/models/sfx_db/title.rb +10 -0
- data/app/models/sfx_db.rb +10 -0
- data/app/models/sfx_url.rb +35 -0
- data/app/views/emailer/citation.text.erb +28 -0
- data/app/views/emailer/short_citation.text.erb +8 -0
- data/app/views/export_email/_email.html.erb +25 -0
- data/app/views/export_email/_send_email.html.erb +3 -0
- data/app/views/export_email/_send_txt.html.erb +3 -0
- data/app/views/export_email/_txt.html.erb +62 -0
- data/app/views/export_email/email.html.erb +3 -0
- data/app/views/export_email/send_email.html.erb +1 -0
- data/app/views/export_email/send_txt.html.erb +1 -0
- data/app/views/export_email/txt.html.erb +3 -0
- data/app/views/js_helper/loader.erb.js +13 -0
- data/app/views/layouts/umlaut.html.erb +52 -0
- data/app/views/open_search/index.html.erb +9 -0
- data/app/views/resolve/_api_in_progress.xml.erb +21 -0
- data/app/views/resolve/_background_progress.html.erb +51 -0
- data/app/views/resolve/_background_updater.html.erb +38 -0
- data/app/views/resolve/_citation.html.erb +87 -0
- data/app/views/resolve/_coins.html.erb +1 -0
- data/app/views/resolve/_compact_citation.html.erb +33 -0
- data/app/views/resolve/_cover_image.html.erb +35 -0
- data/app/views/resolve/_fulltext.html.erb +55 -0
- data/app/views/resolve/_help.html.erb +17 -0
- data/app/views/resolve/_holding.html.erb +91 -0
- data/app/views/resolve/_related_items.html.erb +35 -0
- data/app/views/resolve/_search_inside.html.erb +62 -0
- data/app/views/resolve/_section_display.html.erb +49 -0
- data/app/views/resolve/_service_errors.html.erb +29 -0
- data/app/views/resolve/_standard_response_item.html.erb +89 -0
- data/app/views/resolve/api.xml.builder +72 -0
- data/app/views/resolve/background_status.html.erb +26 -0
- data/app/views/resolve/index.html.erb +73 -0
- data/app/views/resolve/partial_html_sections.xml.erb +30 -0
- data/app/views/search/_a_to_z.html.erb +6 -0
- data/app/views/search/_citation.html.erb +94 -0
- data/app/views/search/_pager.html.erb +60 -0
- data/app/views/search/books.html.erb +103 -0
- data/app/views/search/journal_search.html.erb +90 -0
- data/app/views/search/journals.html.erb +167 -0
- data/app/views/search/opensearch_description.rxml +10 -0
- data/app/views/testing/index.html.erb +1 -0
- data/app/views/umlaut/README +5 -0
- data/app/views/umlaut/error.html.erb +45 -0
- data/db/migrate/01_umlaut_init.rb +113 -0
- data/db/orig_fixed_data/service_type_values.yml +120 -0
- data/db/seeds.rb +7 -0
- data/lib/CronTab.rb +192 -0
- data/lib/aws_product_sign.rb +146 -0
- data/lib/exlibris/aleph/patron.rb +64 -0
- data/lib/exlibris/aleph/record.rb +54 -0
- data/lib/exlibris/aleph/rest_api.rb +29 -0
- data/lib/exlibris/primo/holding.rb +192 -0
- data/lib/exlibris/primo/rsrc.rb +17 -0
- data/lib/exlibris/primo/searcher.rb +276 -0
- data/lib/exlibris/primo/source/aleph.rb +46 -0
- data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
- data/lib/exlibris/primo/toc.rb +17 -0
- data/lib/exlibris/primo_ws.rb +140 -0
- data/lib/generators/templates/umlaut_services.yml +237 -0
- data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
- data/lib/generators/umlaut/install_generator.rb +110 -0
- data/lib/hip3/bib.rb +291 -0
- data/lib/hip3/bib_searcher.rb +302 -0
- data/lib/hip3/custom_field_lookup.rb +44 -0
- data/lib/hip3/holding.rb +50 -0
- data/lib/hip3/item.rb +65 -0
- data/lib/hip3/receipt.rb +7 -0
- data/lib/hip3/serial_copy.rb +82 -0
- data/lib/holding.rb +32 -0
- data/lib/marc_helper.rb +254 -0
- data/lib/metadata_helper.rb +312 -0
- data/lib/opensearch_feed.rb +398 -0
- data/lib/opensearch_query.rb +98 -0
- data/lib/referent_filter.rb +16 -0
- data/lib/referent_filters/dissertation_catch.rb +45 -0
- data/lib/section_renderer.rb +503 -0
- data/lib/service.rb +336 -0
- data/lib/service_adaptors/ajax_export.rb +37 -0
- data/lib/service_adaptors/amazon.rb +412 -0
- data/lib/service_adaptors/blacklight.rb +327 -0
- data/lib/service_adaptors/book_finder.rb +40 -0
- data/lib/service_adaptors/bx.rb +51 -0
- data/lib/service_adaptors/cover_thing.rb +73 -0
- data/lib/service_adaptors/elsevier_cover.rb +57 -0
- data/lib/service_adaptors/email_export.rb +10 -0
- data/lib/service_adaptors/ezproxy.rb +171 -0
- data/lib/service_adaptors/google_book_search.rb +442 -0
- data/lib/service_adaptors/gpo.rb +124 -0
- data/lib/service_adaptors/hathi_trust.rb +308 -0
- data/lib/service_adaptors/hip3_service.rb +150 -0
- data/lib/service_adaptors/hip_holding_search.rb +237 -0
- data/lib/service_adaptors/internet_archive.rb +488 -0
- data/lib/service_adaptors/isbn_db.rb +86 -0
- data/lib/service_adaptors/isi.rb +258 -0
- data/lib/service_adaptors/jcr.rb +146 -0
- data/lib/service_adaptors/opac.rb +351 -0
- data/lib/service_adaptors/open_library.rb +316 -0
- data/lib/service_adaptors/open_library_cover.rb +73 -0
- data/lib/service_adaptors/primo_service.rb +392 -0
- data/lib/service_adaptors/primo_source.rb +78 -0
- data/lib/service_adaptors/pubmed.rb +133 -0
- data/lib/service_adaptors/request_to_fixture.rb +68 -0
- data/lib/service_adaptors/scopus.rb +295 -0
- data/lib/service_adaptors/sfx-new.rb +557 -0
- data/lib/service_adaptors/sfx.rb +566 -0
- data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
- data/lib/service_adaptors/txt_holding_export.rb +32 -0
- data/lib/service_adaptors/ulrichs_cover.rb +57 -0
- data/lib/service_adaptors/ulrichs_link.rb +47 -0
- data/lib/service_adaptors/worldcat.rb +116 -0
- data/lib/service_adaptors/worldcat_identities.rb +591 -0
- data/lib/tasks/umlaut.rake +134 -0
- data/lib/umlaut/default_configuration.rb +5 -0
- data/lib/umlaut/routes.rb +136 -0
- data/lib/umlaut/version.rb +3 -0
- data/lib/umlaut.rb +37 -0
- data/lib/umlaut_configurable.rb +343 -0
- data/lib/umlaut_http.rb +100 -0
- data/lib/xml_schema_helper.rb +109 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database-jhu.yml +44 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +34 -0
- data/test/dummy/config/environments/production.rb +60 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +61 -0
- data/test/dummy/config/umlaut_services.yml +237 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
- data/test/dummy/db/schema.rb +124 -0
- data/test/dummy/log/development.log +12981 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
- data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
- data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
- data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
- data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
- data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
- data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
- data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
- data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
- data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
- data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
- data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
- data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
- data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
- data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
- data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
- data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
- data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
- data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
- data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
- data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
- data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
- data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
- data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
- data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
- data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
- data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
- data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
- data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
- data/test/fixtures/dispatched_services.yml +5 -0
- data/test/fixtures/permalinks.yml +5 -0
- data/test/fixtures/referent_values.yml +1734 -0
- data/test/fixtures/referents.yml +156 -0
- data/test/fixtures/requests.yml +284 -0
- data/test/fixtures/service_responses.yml +5 -0
- data/test/fixtures/sfx_urls.yml +4 -0
- data/test/performance/browsing_test.rb +9 -0
- data/test/test_helper.rb +10 -0
- data/test/umlaut_test.rb +7 -0
- data/test/unit/aleph_patron_test.rb +39 -0
- data/test/unit/aleph_record_benchmarks.rb +28 -0
- data/test/unit/aleph_record_test.rb +30 -0
- data/test/unit/aws_product_sign_test.rb +93 -0
- data/test/unit/collection_test.rb +76 -0
- data/test/unit/google_book_search_test.rb +101 -0
- data/test/unit/primo_searcher_test.rb +403 -0
- data/test/unit/primo_service_test.rb +939 -0
- data/test/unit/primo_ws_test.rb +131 -0
- data/test/unit/service_response_test.rb +9 -0
- data/test/unit/service_test.rb +33 -0
- metadata +580 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
class ReferentValue < ActiveRecord::Base
|
2
|
+
belongs_to :referent, :include => :referent_values
|
3
|
+
|
4
|
+
# Class method to normalize a string for normalized_value attribute.
|
5
|
+
# Right now normalization is just downcasing. Only
|
6
|
+
# metadata values should be normalized (ie, not 'identifier' or 'format').
|
7
|
+
# identifier and format shoudl be stored in normalized_value unchanged.
|
8
|
+
def self.normalize(input)
|
9
|
+
# 'mb_chars' is neccesary for unicode.
|
10
|
+
# normalized_value column only holds 254 bytes..
|
11
|
+
return input.mb_chars.downcase.to_s[0..254]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,449 @@
|
|
1
|
+
# An ActiveRecord which represents a parsed OpenURL resolve service request,
|
2
|
+
# and other persistent state related to Umlaut's handling of that OpenURL
|
3
|
+
# request) should not be confused with the Rails ActionController::Request
|
4
|
+
# class (which represents the complete details of the current 'raw' HTTP
|
5
|
+
# request, and is not stored persistently in the db).
|
6
|
+
#
|
7
|
+
# Constituent openurl data is stored in Referent and Referrer.
|
8
|
+
class Request < ActiveRecord::Base
|
9
|
+
has_many :dispatched_services
|
10
|
+
# Order service_responses by id, so the first
|
11
|
+
# added to the db comes first. Less confusing to have a consistent order.
|
12
|
+
# Also lets installation be sure services run first will have their
|
13
|
+
# responses show up first
|
14
|
+
has_many :service_responses, :order => 'id ASC'
|
15
|
+
|
16
|
+
belongs_to :referent, :include => :referent_values
|
17
|
+
# holds a hash representing submitted http params
|
18
|
+
serialize :http_env
|
19
|
+
|
20
|
+
# Either creates a new Request, or recovers an already created Request from
|
21
|
+
# the db--in either case return a Request matching the OpenURL.
|
22
|
+
# options[:allow_create] => false, will not create a new request, return
|
23
|
+
# nil if no existing request can be found.
|
24
|
+
def self.find_or_create(params, session, a_rails_request, options = {} )
|
25
|
+
# Pull out the http params that are for the context object,
|
26
|
+
# returning a CGI::parse style hash, customized for what
|
27
|
+
# ContextObject.new_from_form_vars wants.
|
28
|
+
co_params = self.context_object_params( a_rails_request )
|
29
|
+
|
30
|
+
# Create a context object from our http params
|
31
|
+
context_object = OpenURL::ContextObject.new_from_form_vars( co_params )
|
32
|
+
# Sometimes umlaut puts in a 'umlaut.request_id' parameter.
|
33
|
+
# first look by that, if we have it, for an existing request.
|
34
|
+
request_id = params['umlaut.request_id']
|
35
|
+
|
36
|
+
# We're trying to identify an already existing response that matches
|
37
|
+
# this request, in this session. We don't actually match the
|
38
|
+
# session_id in the cache lookup though, so background processing
|
39
|
+
# will hook up with the right request even if user has no cookies.
|
40
|
+
# We don't check IP change anymore either, that was too open to
|
41
|
+
# mistaken false negative when req.ip was being used.
|
42
|
+
req = Request.find_by_id(request_id) unless request_id.nil?
|
43
|
+
|
44
|
+
# No match? Just pretend we never had a request_id in url at all.
|
45
|
+
request_id = nil if req == nil
|
46
|
+
|
47
|
+
# Serialized fingerprint of openurl http params, suitable for looking
|
48
|
+
# up in the db to see if we've seen it before.
|
49
|
+
param_fingerprint = self.co_params_fingerprint( co_params )
|
50
|
+
client_ip = params['req.ip'] || a_rails_request.remote_ip()
|
51
|
+
unless (req)
|
52
|
+
# If not found yet, then look for an existing request that had the same
|
53
|
+
# openurl params as this one, in the same session. In which case, reuse.
|
54
|
+
# Here we do require same session, since we don't have an explicit
|
55
|
+
# request_id given.
|
56
|
+
|
57
|
+
req = Request.find(:first, :conditions => ["session_id = ? and contextobj_fingerprint = ? and client_ip_addr = ?", a_rails_request.session_options[:id], param_fingerprint, client_ip ] ) unless param_fingerprint.blank?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Okay, if we found a req, it might NOT have a referent, it might
|
61
|
+
# have been purged. If so, create a new one.
|
62
|
+
if ( req && ! req.referent )
|
63
|
+
req.referent = Referent.find_or_create_by_context_object(context_object, req.referrer)
|
64
|
+
end
|
65
|
+
|
66
|
+
unless (req || options[:allow_create] == false)
|
67
|
+
# didn't find an existing one at all, just create one
|
68
|
+
req = self.create_new_request!( :params => params, :session => session, :rails_request => a_rails_request, :contextobj_fingerprint => param_fingerprint, :context_object => context_object )
|
69
|
+
end
|
70
|
+
|
71
|
+
return req
|
72
|
+
end
|
73
|
+
|
74
|
+
# input is a Rails request (representing http request)
|
75
|
+
# We pull out a hash of request params (get and post) that
|
76
|
+
# define a context object. We use CGI::parse instead of relying
|
77
|
+
# on Rails parsing because rails parsing ignores multiple params
|
78
|
+
# with same key value, which is legal in CGI.
|
79
|
+
#
|
80
|
+
# So in general values of this hash will be an array.
|
81
|
+
# ContextObject.new_from_form_vars is good with that.
|
82
|
+
# Exception is url_ctx_fmt and url_ctx_val, which we'll
|
83
|
+
# convert to single values, because ContextObject wants it so.
|
84
|
+
def self.context_object_params(a_rails_request)
|
85
|
+
require 'cgi'
|
86
|
+
|
87
|
+
# GET params
|
88
|
+
co_params = CGI::parse( a_rails_request.query_string )
|
89
|
+
# add in the POST params please
|
90
|
+
co_params.merge!( CGI::parse(a_rails_request.raw_post)) if a_rails_request.raw_post
|
91
|
+
# default value nil please, that's what ropenurl wants
|
92
|
+
co_params.default = nil
|
93
|
+
|
94
|
+
# CGI::parse annoyingly sometimes puts a nil key in there, for an empty
|
95
|
+
# query param (like a url that has two consecutive && in it). Let's get rid
|
96
|
+
# of it please, only confuses our code.
|
97
|
+
co_params.delete(nil)
|
98
|
+
|
99
|
+
# Exclude params that are for Rails or Umlaut, and don't belong to the
|
100
|
+
# context object. Except leave in umlaut.institution, that matters for
|
101
|
+
# cachability.
|
102
|
+
excluded_keys = ["action", "controller", "page", /^umlaut\.(?!institution)/, 'rft.action', 'rft.controller']
|
103
|
+
co_params.keys.each do |key|
|
104
|
+
excluded_keys.each do |exclude|
|
105
|
+
co_params.delete(key) if exclude === key;
|
106
|
+
end
|
107
|
+
end
|
108
|
+
# 'id' is a special one, cause it can be a OpenURL 0.1 key, or
|
109
|
+
# it can be just an application-level primary key. If it's only a
|
110
|
+
# number, we assume the latter--an openurl identifier will never be
|
111
|
+
# just a number.
|
112
|
+
if co_params['id']
|
113
|
+
co_params['id'].each do |id|
|
114
|
+
co_params['id'].delete(id) if id =~ /^\d+$/
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return co_params
|
119
|
+
end
|
120
|
+
|
121
|
+
# Method that registers the dispatch status of a given service participating
|
122
|
+
# in this request.
|
123
|
+
#
|
124
|
+
# Status can be true (shorthand for DispatchedService::Success), false
|
125
|
+
# (shorthand for DispatchedService::FailedTemporary), or one of the other
|
126
|
+
# DispatchedService status codes.
|
127
|
+
# If a DispatchedService row already exists in the db, that row will be
|
128
|
+
# re-used, over-written with new status value.
|
129
|
+
#
|
130
|
+
# Exception can optionally be provided, generally with failed statuses,
|
131
|
+
# to be stored for debugging purposes.
|
132
|
+
#
|
133
|
+
# Safe to call in thread, uses explicit connectionpool checkout.
|
134
|
+
def dispatched(service, status, exception=nil)
|
135
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
136
|
+
ds = self.find_dispatch_object( service )
|
137
|
+
unless ds
|
138
|
+
ds= self.new_dispatch_object!(service, status)
|
139
|
+
end
|
140
|
+
# In case it was already in the db, make sure to over-write status.
|
141
|
+
# and add the exception either way.
|
142
|
+
ds.status = status
|
143
|
+
ds.store_exception( exception )
|
144
|
+
|
145
|
+
ds.save!
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# See can_dispatch below, you probably want that instead.
|
151
|
+
# This method checks to see if a particular service has been dispatched, and
|
152
|
+
# is succesful or in progress---that is, if this method returns false,
|
153
|
+
# you might want to dispatch the service (again). If it returns true though,
|
154
|
+
# don't, it's been done.
|
155
|
+
def dispatched?(service)
|
156
|
+
ds= self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
|
157
|
+
# Return true if it exists and is any value but FailedTemporary.
|
158
|
+
# FailedTemporary, it's worth running again, the others we shouldn't.
|
159
|
+
return (! ds.nil?) && (ds.status != DispatchedService::FailedTemporary)
|
160
|
+
end
|
161
|
+
# Someone asks us if it's okay to dispatch this guy. Only if it's
|
162
|
+
# marked as Queued, or Failed---otherwise it should be already working,
|
163
|
+
# or done.
|
164
|
+
def can_dispatch?(service)
|
165
|
+
ds= self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
|
166
|
+
|
167
|
+
return ds.nil? || (ds.status == DispatchedService::Queued) || (ds.status == DispatchedService::FailedTemporary)
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
# Create a ServiceResponse and it's associated ServiceType(s) object,
|
173
|
+
# attached to this request.
|
174
|
+
# Arg is a hash of key/values. Keys MUST include:
|
175
|
+
# * :service, with the value being the actual Service object, not just the ID.
|
176
|
+
# * :service_type_value => the ServiceTypeValue object (or string name) for
|
177
|
+
# the the 'type' of response this is.
|
178
|
+
#
|
179
|
+
# Other keys are as conventional for the service. See documentation of
|
180
|
+
# conventional keys in ServiceResponse
|
181
|
+
#
|
182
|
+
# Some keys end up stored in columns in the db directly, others
|
183
|
+
# end up serialized in a hash in a 'text' column, caller doesn't have
|
184
|
+
# to worry about that, just pass em all in.
|
185
|
+
#
|
186
|
+
# Eg, called from a service adapter plugin:
|
187
|
+
# request.add_service_response(:service=>self,
|
188
|
+
# :service_type_value => 'cover_image',
|
189
|
+
# :display_text => 'Cover Image',
|
190
|
+
# :url => img.inner_html,
|
191
|
+
# :asin => asin,
|
192
|
+
# :size => size)
|
193
|
+
#
|
194
|
+
# Safe to call in thread, uses connection pool checkout.
|
195
|
+
def add_service_response(response_data)
|
196
|
+
raise ArgumentError.new("missing required `:service` key") unless response_data[:service].kind_of?(Service)
|
197
|
+
raise ArgumentError.new("missing required `:service_type_value` key") unless response_data[:service_type_value]
|
198
|
+
|
199
|
+
svc_resp = nil
|
200
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
201
|
+
svc_resp = ServiceResponse.new
|
202
|
+
|
203
|
+
|
204
|
+
svc_resp.service_id = response_data[:service].service_id
|
205
|
+
response_data.delete(:service)
|
206
|
+
|
207
|
+
type_value = response_data.delete(:service_type_value)
|
208
|
+
type_value = ServiceTypeValue[type_value.to_s] unless type_value.kind_of?(ServiceTypeValue)
|
209
|
+
svc_resp.service_type_value = type_value
|
210
|
+
|
211
|
+
svc_resp.request = self
|
212
|
+
|
213
|
+
# response_data now includes actual key/values for the ServiceResponse
|
214
|
+
# send em, take_key_values takes care of deciding which go directly
|
215
|
+
# in columns, and which in serialized hash.
|
216
|
+
svc_resp.take_key_values( response_data )
|
217
|
+
|
218
|
+
svc_resp.save!
|
219
|
+
end
|
220
|
+
|
221
|
+
return svc_resp
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
# Methods to look at status of dispatched services
|
226
|
+
def failed_service_dispatches
|
227
|
+
return self.dispatched_services.find(:all,
|
228
|
+
:conditions => ['status IN (?, ?)',
|
229
|
+
DispatchedService::FailedTemporary, DispatchedService::FailedFatal])
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns array of Services in progress or queued. Intentionally
|
233
|
+
# uses cached in memory association, so it wont' be a trip to the
|
234
|
+
# db every time you call this.
|
235
|
+
def services_in_progress
|
236
|
+
# Intentionally using the in-memory array instead of going to db.
|
237
|
+
# that's what the "to_a" is. Minimize race-condition on progress
|
238
|
+
# check, to some extent, although it doesn't really get rid of it.
|
239
|
+
dispatches = self.dispatched_services.to_a.find_all do | ds |
|
240
|
+
(ds.status == DispatchedService::Queued) ||
|
241
|
+
(ds.status == DispatchedService::InProgress)
|
242
|
+
end
|
243
|
+
|
244
|
+
svcs = dispatches.collect { |ds| ds.service }
|
245
|
+
return svcs
|
246
|
+
end
|
247
|
+
# convenience method to call service_types_in_progress with one element.
|
248
|
+
def service_type_in_progress?(svc_type)
|
249
|
+
return service_types_in_progress?( [svc_type] )
|
250
|
+
end
|
251
|
+
|
252
|
+
#pass in array of ServiceTypeValue or string name of same. Returns
|
253
|
+
# true if ANY of them are in progress.
|
254
|
+
def service_types_in_progress?(type_array)
|
255
|
+
# convert strings to ServiceTypeValues
|
256
|
+
type_array = type_array.collect {|s| s.kind_of?(ServiceTypeValue)? s : ServiceTypeValue[s] }
|
257
|
+
|
258
|
+
self.services_in_progress.each do |s|
|
259
|
+
# array intersection
|
260
|
+
return true unless (s.service_types_generated & type_array).empty?
|
261
|
+
end
|
262
|
+
return false;
|
263
|
+
end
|
264
|
+
|
265
|
+
def any_services_in_progress?
|
266
|
+
return services_in_progress.length > 0
|
267
|
+
end
|
268
|
+
|
269
|
+
def to_context_object
|
270
|
+
#Mostly just the referent
|
271
|
+
context_object = self.referent.to_context_object
|
272
|
+
|
273
|
+
#But a few more things
|
274
|
+
context_object.referrer.add_identifier(self.referrer_id) if self.referrer_id
|
275
|
+
|
276
|
+
context_object.requestor.set_metadata('ip', self.client_ip_addr) if self.client_ip_addr
|
277
|
+
|
278
|
+
return context_object
|
279
|
+
end
|
280
|
+
|
281
|
+
# Is the citation represetned by this request a title-level only
|
282
|
+
# citation, with no more specific article info? Or no, does it
|
283
|
+
# include article or vol/iss info?
|
284
|
+
def title_level_citation?
|
285
|
+
data = referent.metadata
|
286
|
+
|
287
|
+
# atitle can't generlaly get us article-level, but it can with
|
288
|
+
# lexis nexis, so we'll consider it article-level. Since it is!
|
289
|
+
return ( data['atitle'].blank? &&
|
290
|
+
data['volume'].blank? &&
|
291
|
+
data['issue'].blank? &&
|
292
|
+
# A date means we're not title-level only if
|
293
|
+
# we're not a book.
|
294
|
+
(data['date'].blank? ||
|
295
|
+
referent.format == "book") &&
|
296
|
+
# pmid or doi is considered article-level, because SFX can
|
297
|
+
# respond to those. Other identifiers may be useless.
|
298
|
+
(! referent.identifiers.find {|i| i =~ /^info\:(doi|pmid)/})
|
299
|
+
)
|
300
|
+
end
|
301
|
+
|
302
|
+
# pass in a ServiceTypeValue (or string name of such), get back list of
|
303
|
+
# ServiceResponse objects with that value belonging to this request.
|
304
|
+
# :refresh=>true will force a trip to the db to get latest values.
|
305
|
+
# otherwise, association is used.
|
306
|
+
def get_service_type(svc_type, options = {})
|
307
|
+
svc_type_obj = (svc_type.kind_of?(ServiceTypeValue)) ? svc_type : ServiceTypeValue[svc_type]
|
308
|
+
|
309
|
+
if ( options[:refresh])
|
310
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
311
|
+
return self.service_responses.find(:all,
|
312
|
+
:conditions =>
|
313
|
+
["service_type_value_name = ?",
|
314
|
+
svc_type_obj.name ]
|
315
|
+
)
|
316
|
+
end
|
317
|
+
else
|
318
|
+
# find on an assoc will go to db, unless we convert it to a plain
|
319
|
+
# old array first.
|
320
|
+
|
321
|
+
return self.service_responses.to_a.find_all { |response|
|
322
|
+
response.service_type_value == svc_type_obj }
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
# Warning, doesn't check for existing object first. Use carefully, usually
|
328
|
+
# paired with find_dispatch_object. Doesn't actually call save though,
|
329
|
+
# caller must do that (in case caller wants to further initialize first).
|
330
|
+
def new_dispatch_object!(service, status)
|
331
|
+
service_id = if service.kind_of?(Service)
|
332
|
+
service.service_id
|
333
|
+
else
|
334
|
+
service.to_s
|
335
|
+
end
|
336
|
+
|
337
|
+
ds = DispatchedService.new
|
338
|
+
ds.service_id = service_id
|
339
|
+
ds.status = status
|
340
|
+
self.dispatched_services << ds
|
341
|
+
return ds
|
342
|
+
end
|
343
|
+
|
344
|
+
protected
|
345
|
+
|
346
|
+
# Called by self.find_or_create, if a new request _really_ needs to be created.
|
347
|
+
def self.create_new_request!( args )
|
348
|
+
|
349
|
+
# all of these are required
|
350
|
+
params = args[:params]
|
351
|
+
session = args[:session]
|
352
|
+
a_rails_request = args[:rails_request]
|
353
|
+
contextobj_fingerprint = args[:contextobj_fingerprint]
|
354
|
+
context_object = args[:context_object]
|
355
|
+
|
356
|
+
# We don't have a complete Request, but let's try finding
|
357
|
+
# an already existing referent and/or referrer to use, if possible, or
|
358
|
+
# else create new ones.
|
359
|
+
|
360
|
+
rft = nil
|
361
|
+
if ( params['umlaut.referent_id'])
|
362
|
+
rft = Referent.find(:first, :conditions => {:id => params['umlaut.referent_id']})
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
# No id given, or no object found? Create it.
|
367
|
+
unless (rft )
|
368
|
+
rft = Referent.find_or_create_by_context_object(context_object)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Create the Request
|
372
|
+
req = Request.new
|
373
|
+
req.session_id = a_rails_request.session_options[:id]
|
374
|
+
req.contextobj_fingerprint = contextobj_fingerprint
|
375
|
+
# Don't do this! It is a performance problem.
|
376
|
+
# rft.requests << req
|
377
|
+
# (rfr.requests << req) if rfr
|
378
|
+
# Instead, say it like this:
|
379
|
+
req.referent = rft
|
380
|
+
req.referrer_id = context_object.referrer.identifier unless context_object.referrer.empty? || context_object.referrer.identifier.empty?
|
381
|
+
|
382
|
+
# Save client ip
|
383
|
+
req.client_ip_addr = params['req.ip'] || a_rails_request.remote_ip()
|
384
|
+
req.client_ip_is_simulated = true if req.client_ip_addr != a_rails_request.remote_ip()
|
385
|
+
|
386
|
+
# Save selected http headers, keep some out to avoid being too long to
|
387
|
+
# serialize.
|
388
|
+
req.http_env = {}
|
389
|
+
a_rails_request.env.each {|k, v| req.http_env[k] = v if ((k.slice(0,5) == 'HTTP_' && k != 'HTTP_COOKIE') || k == 'REQUEST_URI' || k == 'SERVER_NAME') }
|
390
|
+
|
391
|
+
req.save!
|
392
|
+
return req
|
393
|
+
end
|
394
|
+
|
395
|
+
def find_dispatch_object(service)
|
396
|
+
return self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
|
397
|
+
end
|
398
|
+
|
399
|
+
# Input is a CGI::parse style of HTTP params (array values)
|
400
|
+
# output is a string "fingerprint" canonically representing the input
|
401
|
+
# params, which can be stored in the db, so that when another request
|
402
|
+
# comes in, we can easily see if this exact request was seen before.
|
403
|
+
#
|
404
|
+
# This method will exclude certain params that are not part of the context
|
405
|
+
# object, or which we do not want to consider for equality, and will
|
406
|
+
# then serialize in a canonical way such that two co's considered
|
407
|
+
# equivelent will have equivelent serialization.
|
408
|
+
#
|
409
|
+
# Returns nil if there aren't any params to include in the fingerprint.
|
410
|
+
def self.co_params_fingerprint(params)
|
411
|
+
# Don't use ctx_time, consider two co's equal if they are equal but for ctx_tim.
|
412
|
+
excluded_keys = ["action", "controller", "page", "rft.action", "rft.controller", "ctx_tim"]
|
413
|
+
# "url_ctx_val", "request_xml"
|
414
|
+
|
415
|
+
# Hash.sort will do a first run through of canonicalization for us
|
416
|
+
# production an array of two-element arrays, sorted by first element (key)
|
417
|
+
params = params.sort
|
418
|
+
|
419
|
+
# Now exclude excluded keys, and sort value array for further
|
420
|
+
# canonicalization
|
421
|
+
params.each do |pair|
|
422
|
+
# CGI::parse().sort sometimes leaves us a value string with nils in it,
|
423
|
+
# annoyingly. Especially for malformed requests, which can happen.
|
424
|
+
# Remove them please.
|
425
|
+
pair[1].compact! if pair[1]
|
426
|
+
|
427
|
+
# === works for regexp and string
|
428
|
+
if ( excluded_keys.find {|exc_key| exc_key === pair[0]})
|
429
|
+
params.delete( pair )
|
430
|
+
else
|
431
|
+
pair[1].sort! if (pair[1] && pair[1].respond_to?("sort!"))
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
return nil if params.blank?
|
436
|
+
|
437
|
+
# And YAML-ize for a serliazation
|
438
|
+
serialized = params.to_yaml
|
439
|
+
|
440
|
+
|
441
|
+
# And make an MD5 hash/digest. Why store the whole thing if all we need to
|
442
|
+
# do is look it up? hash/digest works well for this.
|
443
|
+
require 'digest/md5'
|
444
|
+
return Digest::MD5.hexdigest( serialized )
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
|
449
|
+
end
|