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
data/lib/service.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
# Services are defined from the config/umlaut_config/services.yml file.
|
2
|
+
# hey should have the following properties
|
3
|
+
# id : unique internal string for the service, unique in yml file
|
4
|
+
# display_name : user displayable string
|
5
|
+
# url : A base url of some kind used by specific service
|
6
|
+
# type : Class name of class found in lib/service_adaptors to be used for logic
|
7
|
+
# priority: 0-9 (foreground) or a-z (background) for order of service operation
|
8
|
+
#
|
9
|
+
# Specific service_adaptor classes may have specific addtional configuration,
|
10
|
+
# commonly including 'password' or 'api_key'.
|
11
|
+
# specific service can put " required_config_parms :param1, :param2"
|
12
|
+
# in definition, for requirement exception raising on initialize.
|
13
|
+
#
|
14
|
+
# = Service Sub-classes
|
15
|
+
# Can include required config params in the class definition, eg:
|
16
|
+
# required_config_params :api_key, :base_url
|
17
|
+
#
|
18
|
+
# Should define #service_types_generated returning an array of
|
19
|
+
# ServiceTypeValues. This is neccesary for the Service to be
|
20
|
+
# run as a background service, and have the auto background updater
|
21
|
+
# work.
|
22
|
+
#
|
23
|
+
# The vast majority of services are 'standard' services, however
|
24
|
+
# there are other 'tasks' that a service can be. Well, right now, one
|
25
|
+
# other, 'link_out_filter'. The services 'task' config property
|
26
|
+
# sets the task/function/hook of the service. Default is 'standard'.
|
27
|
+
#
|
28
|
+
# A standard service defines handle(request)
|
29
|
+
#
|
30
|
+
# A link_out_filter service defines link_out_filter(request, url). If service
|
31
|
+
# returns a new url from filter_url, that's the url the user will be directed
|
32
|
+
# to. If service returns original url or nil, original url will still be used.
|
33
|
+
|
34
|
+
class Service
|
35
|
+
attr_reader :priority, :service_id, :url, :task, :status, :name
|
36
|
+
attr_writer :session_id
|
37
|
+
attr_accessor :request
|
38
|
+
@@required_params_for_subclass = {} # initialize class var
|
39
|
+
|
40
|
+
# Some constants for 'function' values
|
41
|
+
StandardTask = 'standard'
|
42
|
+
LinkOutFilterTask = 'link_out_filter'
|
43
|
+
|
44
|
+
|
45
|
+
def initialize(config)
|
46
|
+
|
47
|
+
config.each do | key, val |
|
48
|
+
self.instance_variable_set(('@'+key).to_sym, val)
|
49
|
+
end
|
50
|
+
|
51
|
+
# task defaults to standard
|
52
|
+
@task ||= StandardTask
|
53
|
+
|
54
|
+
# check required params, and throw if neccesary
|
55
|
+
|
56
|
+
required_params = Array.new
|
57
|
+
# Some things required for all services
|
58
|
+
required_params << "priority"
|
59
|
+
# Custom things for this particular sub-class
|
60
|
+
|
61
|
+
required_params.concat( @@required_params_for_subclass[self.class.name] ) if @@required_params_for_subclass[self.class.name]
|
62
|
+
required_params.each do |param|
|
63
|
+
begin
|
64
|
+
value = self.instance_variable_get('@' + param.to_s)
|
65
|
+
# docs say it raises a nameerror if it doesn't exist, docs
|
66
|
+
# lie. So we'll just raise one ourselves, and catch it, to
|
67
|
+
# handle both cases.
|
68
|
+
raise NameError if value.nil?
|
69
|
+
rescue NameError
|
70
|
+
raise ArgumentError.new("Missing Service configuration parameter. Service type #{self.class} (id: #{self.id}) requires a config parameter named '#{param}'. Check your config/umlaut_config/services.yml file.")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Must be implemented by concrete sub-class. return an Array of
|
76
|
+
# ServiceTypeValues constituting the types of ServiceResponses the service
|
77
|
+
# might generate. Used by Umlaut infrastructure including the background
|
78
|
+
# service execution scheme itslef, as well asxml services returning
|
79
|
+
# information on services in progress.
|
80
|
+
#
|
81
|
+
# Example for a service that only generates fulltext:
|
82
|
+
# return [ ServiceTypeValue[:fulltext] ]
|
83
|
+
def service_types_generated
|
84
|
+
raise Exception.new("#{self.class}: service_types_generated() must be implemented by Service concrete sub-class!")
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Method that should actually be called to trigger the service.
|
89
|
+
# Will check pre-emption.
|
90
|
+
def handle_wrapper(request)
|
91
|
+
unless ( preempted_by(request) )
|
92
|
+
return handle(request)
|
93
|
+
else
|
94
|
+
# Pre-empted, log and close dispatch record as 'succesful'.
|
95
|
+
Rails.logger.debug("Service #{service_id} was pre-empted and not run.")
|
96
|
+
return request.dispatched(self, true)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Implemented by sub-class. Standard response-generating services implement
|
101
|
+
# this method to do their work, generate responses and/or metadata.
|
102
|
+
def handle(request)
|
103
|
+
raise Exception.new("#{self.class}: handle() must be implemented by Service concrete sub-class, for standard services!")
|
104
|
+
end
|
105
|
+
|
106
|
+
# This method is implemented by a concrete sub-class meant to
|
107
|
+
# fulfill the task:link_out_filter. Will be called when the user clicks
|
108
|
+
# on a url that will redirect external to Umlaut. The link_out_filter
|
109
|
+
# service has the ability to intervene and record and/or change
|
110
|
+
# the url. link_out_filters are called in order of priority config param
|
111
|
+
# assigned, 0 through 9.
|
112
|
+
#
|
113
|
+
# orig_url is the current url umlaut is planning on sending the user to.
|
114
|
+
# service_type is the ServiceType object responsible for this url.
|
115
|
+
# the third argument is reserved for future use an options hash.
|
116
|
+
def link_out_filter(orig_url, service_response, other_args = {})
|
117
|
+
raise Exception.new("#{self.class}: #link_out_filter must be implemented by Service concrete sub-class with task link_out_filter!")
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def display_name
|
122
|
+
# If no display_name is set, default to the id string. Not a good idea,
|
123
|
+
# but hey.
|
124
|
+
return @display_name ||= self.service_id
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Pass this method a ServiceResponse object, it will return a hash-like object of
|
129
|
+
# display values, for the view. Implementation is usually in sub-class, by
|
130
|
+
# means of a set of methods "to_[service type name]" implemented in sub-class
|
131
|
+
#. parseResponse will find those. Subclasses will not generally override
|
132
|
+
# view_data_from_service_type, although they can for complete custom
|
133
|
+
# handling. Make sure to return a Hash or hash-like (duck-typed) object.
|
134
|
+
def view_data_from_service_type(service_response)
|
135
|
+
|
136
|
+
service_type_code = service_response.service_type_value.name
|
137
|
+
|
138
|
+
begin
|
139
|
+
# try to call a method named "to_#{service_type_code}", implemented by sub-class
|
140
|
+
self.send("to_#{service_type_code}", service_response)
|
141
|
+
rescue NoMethodError
|
142
|
+
# No to_#{response_type} method? How about the catch-all method?
|
143
|
+
# If not implemented in sub-class, we have a VERY basic
|
144
|
+
# default implementation in this class.
|
145
|
+
self.send("response_to_view_data", service_response)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Default implementation to take a ServiceResponse and parse
|
150
|
+
# into a hash of values useful to the view. Basic implementation
|
151
|
+
# just asks ServiceResposne for it's data_values object, which
|
152
|
+
# contains all ServiceResponse data (including arbitrary keys serialized
|
153
|
+
# in the hash) in an object with the hash accessor method [] .
|
154
|
+
def response_to_view_data(service_response)
|
155
|
+
return service_response.data_values
|
156
|
+
end
|
157
|
+
|
158
|
+
# Sub-class can call class method like:
|
159
|
+
# required_config_params :symbol1, :symbol2, symbol3
|
160
|
+
# in class definition body. List of config parmas that
|
161
|
+
# are required, exception will be thrown if not present.
|
162
|
+
def self.required_config_params(*params)
|
163
|
+
params.each do |p|
|
164
|
+
# Key on name of specific sub-class. Since this is a class
|
165
|
+
# method, that should be self.name
|
166
|
+
@@required_params_for_subclass[self.name] ||= Array.new
|
167
|
+
a = @@required_params_for_subclass[self.name]
|
168
|
+
a.push( p ) unless a.include?( p )
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def session_id
|
174
|
+
unless (@session_id)
|
175
|
+
raise Exception.new("This service has not been initialized with a request!") unless request
|
176
|
+
|
177
|
+
@session_id = request.session_id
|
178
|
+
end
|
179
|
+
return @session_id
|
180
|
+
end
|
181
|
+
|
182
|
+
# FakeSession little placeholder class we'll use in self.session to trick
|
183
|
+
# out Rails.
|
184
|
+
class FakeSession
|
185
|
+
attr_accessor :session_id
|
186
|
+
def initialize(a_session_id)
|
187
|
+
self.session_id = a_session_id
|
188
|
+
end
|
189
|
+
def new_session
|
190
|
+
return self.session_id
|
191
|
+
end
|
192
|
+
end
|
193
|
+
# Returns a read-only version of the session hash. Lazy loaded.
|
194
|
+
# See #update_session for making changes. Will have to be fixed
|
195
|
+
# for Rails 2.3.
|
196
|
+
def session
|
197
|
+
#lazy load
|
198
|
+
unless (@session_data || session_id.blank? )
|
199
|
+
# Craziness to restore a session in Rails pre 2.2. Will definitely
|
200
|
+
# need to be changed for Rails 2.3
|
201
|
+
|
202
|
+
fake_cgi_session = FakeSession.new(session_id)
|
203
|
+
|
204
|
+
@session_store_obj =
|
205
|
+
ActionController::Base.session_store.new(fake_cgi_session)
|
206
|
+
@_session_data = @session_store_obj.restore
|
207
|
+
# modifications here are not going to be automatically stored,
|
208
|
+
# so don't do them. See #update_session instead.
|
209
|
+
@session_data = @_session_data.clone
|
210
|
+
@session_data.freeze
|
211
|
+
end
|
212
|
+
if ( session_id.blank? && @session_data.nil?)
|
213
|
+
raise Exception.new("No session_id is available, therefore no session is available.")
|
214
|
+
end
|
215
|
+
return @session_data
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
# If we just allowed changes to our session hash, and rewrote it to
|
220
|
+
# the store, we'd get a race condition where we could over-write another
|
221
|
+
# service's changes. Rails itself is actually subject to that too.
|
222
|
+
# So, you tell us exactly which values you want to update, we'll
|
223
|
+
# refetch a fresh session, and save it with your changes.
|
224
|
+
#
|
225
|
+
# We're not actually guarding against the race condition, just
|
226
|
+
# fetching and storing quickly and hoping to miss it. FIXME.
|
227
|
+
#
|
228
|
+
# example: update_session( :new_value => "foo", :other => "bar")
|
229
|
+
#
|
230
|
+
# Will have to be fixed for Rails 2.3.
|
231
|
+
def update_session(new_values)
|
232
|
+
# force a new fetch
|
233
|
+
@session = nil
|
234
|
+
session
|
235
|
+
#and update that guy, with our mutable version
|
236
|
+
@_session_data.merge!(new_values)
|
237
|
+
@session_store_obj.close
|
238
|
+
# and update our cached copy to have the changes.
|
239
|
+
@session_data = @_session_data.clone
|
240
|
+
@session_data.freeze
|
241
|
+
end
|
242
|
+
|
243
|
+
# This method is called by Umlaut when user clicks on a service response.
|
244
|
+
# Default implementation here just returns response['url']. You can
|
245
|
+
# over-ride in a sub-class to provide custom implementation of on-demand
|
246
|
+
# url generation. Second argument is the http request params sent
|
247
|
+
# by the client, used for service types that take form submissions (eg
|
248
|
+
# search_inside).
|
249
|
+
# Should return a String url.
|
250
|
+
def response_url(service_response, submitted_params )
|
251
|
+
url = service_response[:url]
|
252
|
+
raise "No url provided by service response" if url.nil? || url.empty?
|
253
|
+
return url
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
# Pre-emption hashes specify a combination of existing responses or
|
258
|
+
# service executions that can pre-empt this service. Can specify
|
259
|
+
# a service, a response type (ServiceTypeValue), or a combination of both.
|
260
|
+
#
|
261
|
+
# service's preempted_by property can either be a single pre-emption hash,
|
262
|
+
# or an array of pre-emption hashes.
|
263
|
+
#
|
264
|
+
# Can also specify that pre-emption is only of a certain service type
|
265
|
+
# generated by self.
|
266
|
+
#
|
267
|
+
# The Service base class will enforce pre-emption and not even run
|
268
|
+
# a service at all *so long as self_type is nil or '*' *. If the pre-emption
|
269
|
+
# only applies to certain types generated by the service and not the entire
|
270
|
+
# execution of the service, the concrete service subclass must implement
|
271
|
+
# logic to do that. Calling the preempted method with the second argument
|
272
|
+
# set will be helpful in writing this logic.
|
273
|
+
#
|
274
|
+
# A preemption hash has string keys:
|
275
|
+
# existing_service: id of service that will pre-empt this service.
|
276
|
+
# If key does not exist or is "*", then not specified,
|
277
|
+
# any service. (existing_type will be specified).
|
278
|
+
# existing_type: ServiceTypeValue name that pre-empts this
|
279
|
+
# service. "+" means that the service specified
|
280
|
+
# in existing_service must have generated some
|
281
|
+
# response, but type does not matter. "*" means
|
282
|
+
# that the service specified in existing_service
|
283
|
+
# must have completed succesfully, but may not
|
284
|
+
# have generated any responses.
|
285
|
+
# self_type: If blank or "*", preemption applies to any running
|
286
|
+
# of this service at all. If set to a ServiceTypeValue
|
287
|
+
# name, pre-emption is only of certain types generated
|
288
|
+
# by this service.
|
289
|
+
def preempted_by(uml_request, for_type_generated=nil)
|
290
|
+
preempted_by = @preempted_by
|
291
|
+
return false if preempted_by.nil?
|
292
|
+
preempted_by = [preempted_by] unless preempted_by.kind_of?(Array)
|
293
|
+
preemption = nil
|
294
|
+
|
295
|
+
preempted_by.each do | hash |
|
296
|
+
service = hash["existing_service"] || "*"
|
297
|
+
other_type = hash["existing_type"] || "*"
|
298
|
+
self_type = hash["self_type"] || "*"
|
299
|
+
|
300
|
+
next unless (self_type == "*" || self_type == for_type_generated)
|
301
|
+
|
302
|
+
if (other_type == "*")
|
303
|
+
# Need to check dispatched services instead of service_types,
|
304
|
+
# as we pre-empt even if no services created.
|
305
|
+
preemption =
|
306
|
+
uml_request.dispatched_services.to_a.find do |disp|
|
307
|
+
service == "*" ||
|
308
|
+
(disp.service_id == service &&
|
309
|
+
(disp.status == DispatchedService.Succesful ))
|
310
|
+
end
|
311
|
+
else
|
312
|
+
# Check service responses
|
313
|
+
preemption =
|
314
|
+
uml_request.service_responses.to_a.find do |response|
|
315
|
+
( other_type == "*" || other_type == "+" ||
|
316
|
+
response.service_type_value.name == other_type) &&
|
317
|
+
( service == "*" ||
|
318
|
+
response.service_id == service)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
break if preemption
|
322
|
+
end
|
323
|
+
return (! preemption.nil? )
|
324
|
+
end
|
325
|
+
|
326
|
+
# used by render_service_credits helper method, returns
|
327
|
+
# a hash with keys being a human-displayable name of a third party
|
328
|
+
# to give 'credit' to, and value being a URL (or nil) to link the
|
329
|
+
# name to.
|
330
|
+
# computed from @credits config variable, or returns empty hash.
|
331
|
+
def credits
|
332
|
+
@credits || {}
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# This is an abstract superclass other services over-ride to get
|
2
|
+
# extra ajaxy windows upon click on link.
|
3
|
+
class AjaxExport < Service
|
4
|
+
required_config_params :ajax_id, :controller
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
super(config)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Standard method, used by background service updater. See Service docs.
|
11
|
+
def service_types_generated
|
12
|
+
types = [ ServiceTypeValue[:export_citation] ]
|
13
|
+
|
14
|
+
return types
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle(request)
|
18
|
+
|
19
|
+
request.add_service_response(:service=>self,
|
20
|
+
:display_text => @display_text,
|
21
|
+
:link_supports_ajax_call => true,
|
22
|
+
:notes=> @note,
|
23
|
+
:service_type_value => 'export_citation' )
|
24
|
+
|
25
|
+
return request.dispatched(self, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def response_url(service_response, params)
|
29
|
+
# Hash that caller will pass to url_for to create an internally
|
30
|
+
# facing link.
|
31
|
+
return {:controller=>@form_controller,
|
32
|
+
:action=>@form_action,
|
33
|
+
:id => service_response.id,
|
34
|
+
:format => params[:format]}
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
#
|
2
|
+
# As of Aug 15 2009, Amazon API calls will require a secret key from Amazon.
|
3
|
+
# If you are unable to get such an account/key, this service can still be used
|
4
|
+
# for certain functions by setting 'make_aws_call' to false, and configuring
|
5
|
+
# 'service_types' to only include one or more of: ["search_inside",
|
6
|
+
# "highlighted_link", "excerpts"]
|
7
|
+
# Other services, such as enhance_referent and cover_image require api access.
|
8
|
+
#
|
9
|
+
# More about registering for and finding your AWS access key and secret key
|
10
|
+
# can be found here:
|
11
|
+
# http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/AWSCredentials.html
|
12
|
+
# http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/ViewingCredentials.html
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# services.yml params:
|
16
|
+
# api_key: required. AWS "access key".
|
17
|
+
# secret_key: required unless make_aws_call==false. AWS "secret access key".
|
18
|
+
# associate_tag: required unless make_aws_call==false. Now required by Amazon API.
|
19
|
+
# sign up for an associates_id at: https://affiliate-program.amazon.com/
|
20
|
+
# it's the same thing as your 'associate id'.
|
21
|
+
# service_types: Optional. Array of strings of service type values to be
|
22
|
+
# loaded, to over-ride defaults.
|
23
|
+
# make_aws_call: default true. If false, then either uses an ASIN stored
|
24
|
+
# in referent from previous service, or tries assuming
|
25
|
+
# that an ISBN, if present, is the ASIN.
|
26
|
+
# of services ran an amazon service adaptor
|
27
|
+
# Can be used to split amazon into
|
28
|
+
# two waves, since highlighted_link and cover_image
|
29
|
+
# calls require _another_ HTTP request to amazon
|
30
|
+
# and screen scrape. Or can be used if you don't
|
31
|
+
# have access to Amazon API.
|
32
|
+
#
|
33
|
+
# See example of two-wave amazon in config/umlaut_distribution/services.yml-dist.
|
34
|
+
#
|
35
|
+
class Amazon < Service
|
36
|
+
require 'open-uri'
|
37
|
+
require 'nokogiri'
|
38
|
+
require 'isbn'
|
39
|
+
|
40
|
+
|
41
|
+
include MetadataHelper
|
42
|
+
|
43
|
+
required_config_params :url, :api_key, :associate_tag
|
44
|
+
attr_reader :url
|
45
|
+
|
46
|
+
def initialize(config)
|
47
|
+
# defaults
|
48
|
+
@url = 'http://webservices.amazon.com/onca/xml'
|
49
|
+
@reader_base_url = 'http://www.amazon.com/gp/reader/'
|
50
|
+
# Old version non-lightboxed, whcih doesn't work very well anymore.
|
51
|
+
# @reader_base_url = 'http://www.amazon.com/gp/sitbv3/reader/'
|
52
|
+
@display_name = "Amazon.com"
|
53
|
+
@display_text = "Amazon's page"
|
54
|
+
@service_types = ["abstract", "highlighted_link", "cover_image", "search_inside", "referent_enhance", "excerpts"]
|
55
|
+
@make_aws_call = true
|
56
|
+
@http_timeout = 5
|
57
|
+
|
58
|
+
@credits = {
|
59
|
+
"Amazon" => "http://www.amazon.com/"
|
60
|
+
}
|
61
|
+
|
62
|
+
super(config)
|
63
|
+
|
64
|
+
# Need the secret_key after 15 aug 09.
|
65
|
+
unless (@secret_key || ! @make_aws_call)
|
66
|
+
if ( Time.now < Time.gm(2009, 8, 15))
|
67
|
+
Rails.logger.warn("Amazon service will require a secret_key after 15 August 2009 to make Amazon API calls.")
|
68
|
+
else
|
69
|
+
raise Exception.new("Amazon API now requires a secret_key. The Amazon service can only be used with make_aws_call=false unless you have an Amazon secret key configured.")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Only a few service types can get by without an aws call
|
74
|
+
if (! @make_aws_call &&
|
75
|
+
@service_types.find {|type| ! ["search_inside", "highlighted_link", "excerpts"].include?(type) } )
|
76
|
+
raise Exception.new("You can only set make_aws_call == false on the definition of an Amazon service adaptor when the adaptor is also set to generate no service responses other than highlighted_link, search_inside, and excerpts")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def service_types_generated
|
82
|
+
types = Array.new
|
83
|
+
|
84
|
+
@service_types.each do |type|
|
85
|
+
types.push( ServiceTypeValue[type])
|
86
|
+
end
|
87
|
+
|
88
|
+
return types
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle(request)
|
92
|
+
|
93
|
+
isbn = request.referent.metadata['isbn']
|
94
|
+
isbn = isbn.gsub(/[^0-9X]/,'') if isbn
|
95
|
+
|
96
|
+
return request.dispatched(self, true) if isbn.blank?
|
97
|
+
|
98
|
+
begin
|
99
|
+
|
100
|
+
selected_aws_vals = {}
|
101
|
+
if ( @make_aws_call )
|
102
|
+
aws_response = make_aws_request( isbn )
|
103
|
+
|
104
|
+
return request.dispatched(self, true) if aws_response.blank?
|
105
|
+
|
106
|
+
# Add service responses based on AWS response
|
107
|
+
selected_aws_vals =
|
108
|
+
self.add_aws_service_responses(request, aws_response)
|
109
|
+
end
|
110
|
+
|
111
|
+
if ( selected_aws_vals == nil)
|
112
|
+
# no aws found.
|
113
|
+
return request.dispatched(self, true)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Add service responses based on ASIN--may be run in a
|
117
|
+
# later service wave. Look up asin in db if we don't have
|
118
|
+
# it from current service exec.
|
119
|
+
asin = selected_aws_vals[:asin] ||
|
120
|
+
get_identifier(:urn, "asin", request.referent)
|
121
|
+
|
122
|
+
self.add_asin_service_responses(request, asin, selected_aws_vals[:item_url])
|
123
|
+
|
124
|
+
rescue TimeoutError
|
125
|
+
# Try again later if we timeout; temporary error condition.
|
126
|
+
return request.dispatched(self, DispatchedService::FailedTemporary)
|
127
|
+
rescue Exception => e
|
128
|
+
# Unexpected error, fatal error condition.
|
129
|
+
return request.dispatched(self, DispatchedService::FailedFatal, e)
|
130
|
+
end
|
131
|
+
|
132
|
+
return request.dispatched(self, true)
|
133
|
+
end
|
134
|
+
|
135
|
+
def make_aws_request(isbn)
|
136
|
+
# We're assuming the ISBN is the ASIN Amazon ID. Not neccesarily valid
|
137
|
+
# assumption, but works enough of the time and there's no easy
|
138
|
+
# alternative.
|
139
|
+
# Convert 13 to 10 if neccesary.
|
140
|
+
|
141
|
+
# got to try converting to 10. An ISBN-13 is never an ASIN.
|
142
|
+
isbn = ISBN.ten( isbn )
|
143
|
+
|
144
|
+
|
145
|
+
query_params = {
|
146
|
+
"Service"=>"AWSECommerceService",
|
147
|
+
"AWSAccessKeyId"=>@api_key,
|
148
|
+
"AssociateTag"=>@associate_tag,
|
149
|
+
"Operation"=>"ItemLookup",
|
150
|
+
"ResponseGroup"=>"Large",
|
151
|
+
"ItemId"=>isbn }
|
152
|
+
|
153
|
+
# has to be signed
|
154
|
+
query = nil
|
155
|
+
|
156
|
+
if ( @secret_key )
|
157
|
+
aws = AwsProductSign.new(:access_key => @api_key,
|
158
|
+
:secret_key => @secret_key )
|
159
|
+
query = aws.query_with_signature( query_params )
|
160
|
+
else
|
161
|
+
query = query_params.collect {|key, value| CGI.escape(key) + '=' + CGI.escape(value)}.join("&")
|
162
|
+
end
|
163
|
+
|
164
|
+
uri = URI.parse(self.url+'?'+query)
|
165
|
+
# send the request
|
166
|
+
http = Net::HTTP.new(uri.host, 80)
|
167
|
+
http.open_timeout = @http_timeout
|
168
|
+
http.read_timeout = @http_timeout
|
169
|
+
http_response = http.send_request('GET', uri.path + '?' + uri.query)
|
170
|
+
|
171
|
+
return http_response
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_aws_service_responses(request, aws_response)
|
175
|
+
return_hash = Hash.new
|
176
|
+
|
177
|
+
aws = Nokogiri::XML(aws_response.body)
|
178
|
+
# extract and collect info from the xml
|
179
|
+
|
180
|
+
# if we get an error from Amazon, return now.
|
181
|
+
err = (aws.at("ItemLookupResponse/Items/Request/Errors/Error"))
|
182
|
+
err = (aws.at("ItemLookupErrorResponse")) if err.blank?
|
183
|
+
|
184
|
+
unless (err.blank?)
|
185
|
+
if (err.at('code').inner_text == 'AWS.InvalidParameterValue')
|
186
|
+
# Indicates an ISBN that Amazon doesn't know about, or that
|
187
|
+
# was mal-formed. We can't tell the difference, so either
|
188
|
+
# way let's silently ignore.
|
189
|
+
return
|
190
|
+
else
|
191
|
+
raise Exception.new("Error from Amazon web service: " + err.to_s)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
asin = (aws.at("ItemLookupResponse/Items/Item/ASIN")).inner_text
|
196
|
+
|
197
|
+
# Store the asin in the referent as non-metadata private data, so
|
198
|
+
# a future background service can use it. Store as a urn identifier.
|
199
|
+
request.referent.add_identifier("urn:asin:#{asin}") unless asin.blank?
|
200
|
+
|
201
|
+
return_hash[:asin] = asin
|
202
|
+
|
203
|
+
if ( @service_types.include?("cover_image") )
|
204
|
+
# collect cover art urls
|
205
|
+
["small","medium","large"].each do | size |
|
206
|
+
if (img = aws.at("ItemLookupResponse/Items/Item/"+size.capitalize+"Image/URL"))
|
207
|
+
request.add_service_response(
|
208
|
+
:service=>self,
|
209
|
+
:display_text => 'Cover Image',
|
210
|
+
:key=>size,
|
211
|
+
:url => img.inner_text,
|
212
|
+
:asin => asin,
|
213
|
+
:size => size,
|
214
|
+
:service_type_value => :cover_image)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
item_url = (aws.at("ItemLookupResponse/Items/Item/DetailPageURL")).inner_text
|
221
|
+
# Store to return to caller
|
222
|
+
return_hash[:item_url] = item_url
|
223
|
+
|
224
|
+
|
225
|
+
# get description
|
226
|
+
if ( @service_types.include?("abstract") &&
|
227
|
+
desc =
|
228
|
+
(aws.at("ItemLookupResponse/Items/Item/EditorialReviews/EditorialReview/Content")))
|
229
|
+
|
230
|
+
# For some reason we need to un-escape the desc. Don't entirely get it.
|
231
|
+
desc_text = CGI.unescapeHTML( desc.inner_text )
|
232
|
+
|
233
|
+
unless ( desc_text.blank? )
|
234
|
+
request.add_service_response(
|
235
|
+
:service=>self,
|
236
|
+
:display_text => "Description from Amazon.com",
|
237
|
+
:url => item_url,
|
238
|
+
:key=>'abstract',
|
239
|
+
:value_string=>asin,
|
240
|
+
:content=>desc_text ,
|
241
|
+
:service_type_value => 'abstract')
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
if ( @service_types.include?("similar_item"))
|
247
|
+
# Get Amazon's 'similar products' to help recommend other useful items
|
248
|
+
(aws.search("ItemLookupResponse/Items/Item/SimilarProducts/SimilarProduct")).each do |similar|
|
249
|
+
request.add_service_response(
|
250
|
+
:service=>self,
|
251
|
+
:key=>'book',
|
252
|
+
:value_string=>(similar.at("ASIN")).inner_text,
|
253
|
+
:value_alt_string=>(similar.at("Title")).inner_text,
|
254
|
+
:service_type_value => 'similar_item')
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
if ( @service_types.include?("referent_enhance"))
|
260
|
+
item_attributes = aws.at("ItemLookupResponse/Items/Item/ItemAttributes")
|
261
|
+
|
262
|
+
request.referent.enhance_referent('format', 'book', false) unless request.referent.format == 'book'
|
263
|
+
metadata = request.referent.metadata
|
264
|
+
unless (metadata['btitle'] || metadata['title'])
|
265
|
+
if title = (item_attributes.at("Title"))
|
266
|
+
request.referent.enhance_referent('btitle', normalize_aws_title(title.inner_text))
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
# Enhance with full author name string even if aulast is already present, because full string may be useful for worldcat identities.
|
271
|
+
unless (metadata['au'] )
|
272
|
+
if author = (item_attributes.at("Author"))
|
273
|
+
request.referent.enhance_referent('au', author.inner_text)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
unless metadata['pub']
|
278
|
+
if pub = (item_attributes.at("Publisher"))
|
279
|
+
request.referent.enhance_referent('pub', pub.inner_text)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
unless metadata['tpages']
|
283
|
+
if tpages = (item_attributes.at("NumberOfPages"))
|
284
|
+
request.referent.enhance_referent('tpages', tpages.inner_text)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
return return_hash
|
290
|
+
end
|
291
|
+
|
292
|
+
def add_asin_service_responses(request, asin, item_url)
|
293
|
+
# we want to highlight Amazon to link to 'search in this book', etc.
|
294
|
+
if asin
|
295
|
+
# Search or Look inside the book offered? We only know by trying and
|
296
|
+
# then screen-scraping.
|
297
|
+
search_inside = false
|
298
|
+
look_inside = false
|
299
|
+
|
300
|
+
# Check for search_inside or look_inside if we're configured
|
301
|
+
# to supply highlighted_link or search_inside, that's what we
|
302
|
+
# need it for.
|
303
|
+
if ( @service_types.include?("highlighted_link") ||
|
304
|
+
@service_types.include?("search_inside"))
|
305
|
+
inside_base = @reader_base_url + asin
|
306
|
+
# lame screen-scrape for search inside availability. We need to
|
307
|
+
# distinguish between no results, "look inside", and "search inside".
|
308
|
+
response = open(inside_base).read
|
309
|
+
|
310
|
+
# This regexp only suitable for screen-scraping the old-style "sitbv3"
|
311
|
+
# reader page screen
|
312
|
+
if (response.include?("<div class='sitb-pop-search'>"))
|
313
|
+
# then we have search_inside. I think this always includes 'look', but
|
314
|
+
# we'll test seperate for that.
|
315
|
+
search_inside= true
|
316
|
+
end
|
317
|
+
|
318
|
+
if (response.include?('<a href="/gp/reader/'))
|
319
|
+
# then we have look inside, not neccesarily search.
|
320
|
+
look_inside = true
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
if ( @service_types.include?("search_inside") && search_inside )
|
325
|
+
request.add_service_response(
|
326
|
+
:service => self,
|
327
|
+
:display_text=>@display_name,
|
328
|
+
:url=> inside_base,
|
329
|
+
:service_type_value => :search_inside
|
330
|
+
)
|
331
|
+
end
|
332
|
+
|
333
|
+
# Link to look inside if we have it, otherwise ordinary amazon detail
|
334
|
+
# page.
|
335
|
+
|
336
|
+
if (@service_types.include?("excerpts") &&
|
337
|
+
( search_inside || look_inside ))
|
338
|
+
|
339
|
+
|
340
|
+
request.add_service_response(
|
341
|
+
:service=>self,
|
342
|
+
:url => inside_base,
|
343
|
+
:asin=>asin,
|
344
|
+
:display_text => @display_name,
|
345
|
+
:service_type_value => 'excerpts')
|
346
|
+
|
347
|
+
elsif ( @service_types.include?("highlighted_link"))
|
348
|
+
# Just link to Amazon page if we can. If we did the AWS request
|
349
|
+
# before, afraid we didn't store the item url, just the
|
350
|
+
# asin, reconstruct a valid one, even if not the one given to us
|
351
|
+
# by AWS.
|
352
|
+
amazon_page = item_url || ("http://www.amazon.com/o/ASIN/" + asin)
|
353
|
+
|
354
|
+
|
355
|
+
request.add_service_response(
|
356
|
+
:service=>self,
|
357
|
+
:url => amazon_page,
|
358
|
+
:asin=>asin,
|
359
|
+
:display_text => @display_text,
|
360
|
+
:service_type_value => 'highlighted_link')
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
# Catch url_for call for search_inside, because we're going to redirect
|
368
|
+
def response_url(service_response, submitted_params)
|
369
|
+
if ( ! (service_response.service_type_value.name == "search_inside" ))
|
370
|
+
return super(service_response, submitted_params)
|
371
|
+
else
|
372
|
+
# search inside!
|
373
|
+
base = service_response[:url]
|
374
|
+
query = CGI.escape(submitted_params["query"] || "")
|
375
|
+
url = base + "/ref=sib_dp_srch_pop?v=search-inside&keywords=#{query}&go=Go%21"
|
376
|
+
return url
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
#amazon is in the habit of including things in parens at the end
|
381
|
+
#of the title that aren't really part of the title. The parens
|
382
|
+
# are really an edition and/or series statement. We have nowhere
|
383
|
+
# good to store that.
|
384
|
+
def normalize_aws_title(title)
|
385
|
+
title.sub(/\([^)]*\)\s*$/, '')
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
end
|
390
|
+
|
391
|
+
# Example of no look or search:
|
392
|
+
# www.amazon.com/gp/reader/0794521789
|
393
|
+
|
394
|
+
# Example of look and search:
|
395
|
+
# http://www.amazon.com/gp/reader/1851960511
|
396
|
+
|
397
|
+
# Example of 'look inside' with no search:
|
398
|
+
# http://www.amazon.com/gp/reader/0140441115/
|
399
|
+
|
400
|
+
# Scraping /gp/reader page.
|
401
|
+
|
402
|
+
# Only look inside (preview) if link like:
|
403
|
+
# <a href="/gp/reader/1851960511/ref=sib_dp_pop_sup?ie=UTF8&p=random#reader-link
|
404
|
+
# OR:
|
405
|
+
# <a href="/gp/reader/0140441115/ref=sib_dp_kd#reader-link" onclick="if (typeof(SitbReader) != 'undefined')
|
406
|
+
|
407
|
+
|
408
|
+
# Only search inside if:
|
409
|
+
|
410
|
+
# '<td class="tinypopup">Search Inside This Book:</td>'
|
411
|
+
# OR: <div class='sitb-pop-search'>
|
412
|
+
|