umlaut 3.0.0alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|