umlaut 3.0.0alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. data/LICENSE +7 -0
  2. data/README.md +49 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/images/error.gif +0 -0
  5. data/app/assets/images/export_bg_bot.gif +0 -0
  6. data/app/assets/images/export_bg_mid.gif +0 -0
  7. data/app/assets/images/export_bg_top.gif +0 -0
  8. data/app/assets/images/famfamfam/book_open.png +0 -0
  9. data/app/assets/images/famfamfam/cross.png +0 -0
  10. data/app/assets/images/famfamfam/page_sound.gif +0 -0
  11. data/app/assets/images/famfamfam/page_text.gif +0 -0
  12. data/app/assets/images/famfamfam/page_up.gif +0 -0
  13. data/app/assets/images/famfamfam/page_white.png +0 -0
  14. data/app/assets/images/famfamfam/readme.html +1495 -0
  15. data/app/assets/images/famfamfam/tiny_cross.png +0 -0
  16. data/app/assets/images/frame_remove.gif +0 -0
  17. data/app/assets/images/ico_go.gif +0 -0
  18. data/app/assets/images/jhu_findit.gif +0 -0
  19. data/app/assets/images/list_closed.png +0 -0
  20. data/app/assets/images/list_open.png +0 -0
  21. data/app/assets/images/more_info.gif +0 -0
  22. data/app/assets/images/rails.png +0 -0
  23. data/app/assets/images/request.gif +0 -0
  24. data/app/assets/images/spinner.gif +0 -0
  25. data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
  26. data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
  27. data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
  28. data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
  29. data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
  30. data/app/assets/javascripts/umlaut/update_html.js +152 -0
  31. data/app/assets/javascripts/umlaut.js +17 -0
  32. data/app/assets/stylesheets/umlaut.css +857 -0
  33. data/app/controllers/application_controller.rb +14 -0
  34. data/app/controllers/export_email_controller.rb +123 -0
  35. data/app/controllers/js_helper_controller.rb +10 -0
  36. data/app/controllers/link_router_controller.rb +87 -0
  37. data/app/controllers/open_search_controller.rb +9 -0
  38. data/app/controllers/resolve_controller.rb +288 -0
  39. data/app/controllers/resource_controller.rb +83 -0
  40. data/app/controllers/search_controller.rb +328 -0
  41. data/app/controllers/search_methods/sfx3.rb +148 -0
  42. data/app/controllers/search_methods/sfx4.rb +257 -0
  43. data/app/controllers/search_methods/sfx_api.rb +47 -0
  44. data/app/controllers/store_controller.rb +64 -0
  45. data/app/controllers/umlaut/controller_behavior.rb +20 -0
  46. data/app/controllers/umlaut/controller_logic.rb +96 -0
  47. data/app/controllers/umlaut/error_handling.rb +48 -0
  48. data/app/controllers/umlaut_controller.rb +112 -0
  49. data/app/helpers/application_helper.rb +4 -0
  50. data/app/helpers/emailer_helper.rb +43 -0
  51. data/app/helpers/export_email_helper.rb +34 -0
  52. data/app/helpers/open_search_helper.rb +7 -0
  53. data/app/helpers/resolve_helper.rb +225 -0
  54. data/app/helpers/search_helper.rb +50 -0
  55. data/app/helpers/umlaut/footer_helper.rb +64 -0
  56. data/app/helpers/umlaut/helper.rb +62 -0
  57. data/app/helpers/umlaut/html_head_helper.rb +37 -0
  58. data/app/helpers/umlaut/url_generation.rb +77 -0
  59. data/app/mailers/emailer.rb +48 -0
  60. data/app/models/clickthrough.rb +2 -0
  61. data/app/models/collection.rb +259 -0
  62. data/app/models/crossref_lookup.rb +2 -0
  63. data/app/models/dispatched_service.rb +58 -0
  64. data/app/models/permalink.rb +29 -0
  65. data/app/models/referent.rb +473 -0
  66. data/app/models/referent_value.rb +14 -0
  67. data/app/models/request.rb +449 -0
  68. data/app/models/service_response.rb +179 -0
  69. data/app/models/service_store.rb +59 -0
  70. data/app/models/service_type_value.rb +58 -0
  71. data/app/models/service_wave.rb +150 -0
  72. data/app/models/sfx_db/az_additional_title.rb +11 -0
  73. data/app/models/sfx_db/az_letter_group.rb +11 -0
  74. data/app/models/sfx_db/az_title.rb +38 -0
  75. data/app/models/sfx_db/az_title_v2.rb +34 -0
  76. data/app/models/sfx_db/isbn.rb +12 -0
  77. data/app/models/sfx_db/issn.rb +12 -0
  78. data/app/models/sfx_db/object.rb +35 -0
  79. data/app/models/sfx_db/object_portfolio.rb +6 -0
  80. data/app/models/sfx_db/publisher.rb +10 -0
  81. data/app/models/sfx_db/sfx_db_base.rb +54 -0
  82. data/app/models/sfx_db/target.rb +9 -0
  83. data/app/models/sfx_db/target_service.rb +10 -0
  84. data/app/models/sfx_db/title.rb +10 -0
  85. data/app/models/sfx_db.rb +10 -0
  86. data/app/models/sfx_url.rb +35 -0
  87. data/app/views/emailer/citation.text.erb +28 -0
  88. data/app/views/emailer/short_citation.text.erb +8 -0
  89. data/app/views/export_email/_email.html.erb +25 -0
  90. data/app/views/export_email/_send_email.html.erb +3 -0
  91. data/app/views/export_email/_send_txt.html.erb +3 -0
  92. data/app/views/export_email/_txt.html.erb +62 -0
  93. data/app/views/export_email/email.html.erb +3 -0
  94. data/app/views/export_email/send_email.html.erb +1 -0
  95. data/app/views/export_email/send_txt.html.erb +1 -0
  96. data/app/views/export_email/txt.html.erb +3 -0
  97. data/app/views/js_helper/loader.erb.js +13 -0
  98. data/app/views/layouts/umlaut.html.erb +52 -0
  99. data/app/views/open_search/index.html.erb +9 -0
  100. data/app/views/resolve/_api_in_progress.xml.erb +21 -0
  101. data/app/views/resolve/_background_progress.html.erb +51 -0
  102. data/app/views/resolve/_background_updater.html.erb +38 -0
  103. data/app/views/resolve/_citation.html.erb +87 -0
  104. data/app/views/resolve/_coins.html.erb +1 -0
  105. data/app/views/resolve/_compact_citation.html.erb +33 -0
  106. data/app/views/resolve/_cover_image.html.erb +35 -0
  107. data/app/views/resolve/_fulltext.html.erb +55 -0
  108. data/app/views/resolve/_help.html.erb +17 -0
  109. data/app/views/resolve/_holding.html.erb +91 -0
  110. data/app/views/resolve/_related_items.html.erb +35 -0
  111. data/app/views/resolve/_search_inside.html.erb +62 -0
  112. data/app/views/resolve/_section_display.html.erb +49 -0
  113. data/app/views/resolve/_service_errors.html.erb +29 -0
  114. data/app/views/resolve/_standard_response_item.html.erb +89 -0
  115. data/app/views/resolve/api.xml.builder +72 -0
  116. data/app/views/resolve/background_status.html.erb +26 -0
  117. data/app/views/resolve/index.html.erb +73 -0
  118. data/app/views/resolve/partial_html_sections.xml.erb +30 -0
  119. data/app/views/search/_a_to_z.html.erb +6 -0
  120. data/app/views/search/_citation.html.erb +94 -0
  121. data/app/views/search/_pager.html.erb +60 -0
  122. data/app/views/search/books.html.erb +103 -0
  123. data/app/views/search/journal_search.html.erb +90 -0
  124. data/app/views/search/journals.html.erb +167 -0
  125. data/app/views/search/opensearch_description.rxml +10 -0
  126. data/app/views/testing/index.html.erb +1 -0
  127. data/app/views/umlaut/README +5 -0
  128. data/app/views/umlaut/error.html.erb +45 -0
  129. data/db/migrate/01_umlaut_init.rb +113 -0
  130. data/db/orig_fixed_data/service_type_values.yml +120 -0
  131. data/db/seeds.rb +7 -0
  132. data/lib/CronTab.rb +192 -0
  133. data/lib/aws_product_sign.rb +146 -0
  134. data/lib/exlibris/aleph/patron.rb +64 -0
  135. data/lib/exlibris/aleph/record.rb +54 -0
  136. data/lib/exlibris/aleph/rest_api.rb +29 -0
  137. data/lib/exlibris/primo/holding.rb +192 -0
  138. data/lib/exlibris/primo/rsrc.rb +17 -0
  139. data/lib/exlibris/primo/searcher.rb +276 -0
  140. data/lib/exlibris/primo/source/aleph.rb +46 -0
  141. data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
  142. data/lib/exlibris/primo/toc.rb +17 -0
  143. data/lib/exlibris/primo_ws.rb +140 -0
  144. data/lib/generators/templates/umlaut_services.yml +237 -0
  145. data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
  146. data/lib/generators/umlaut/install_generator.rb +110 -0
  147. data/lib/hip3/bib.rb +291 -0
  148. data/lib/hip3/bib_searcher.rb +302 -0
  149. data/lib/hip3/custom_field_lookup.rb +44 -0
  150. data/lib/hip3/holding.rb +50 -0
  151. data/lib/hip3/item.rb +65 -0
  152. data/lib/hip3/receipt.rb +7 -0
  153. data/lib/hip3/serial_copy.rb +82 -0
  154. data/lib/holding.rb +32 -0
  155. data/lib/marc_helper.rb +254 -0
  156. data/lib/metadata_helper.rb +312 -0
  157. data/lib/opensearch_feed.rb +398 -0
  158. data/lib/opensearch_query.rb +98 -0
  159. data/lib/referent_filter.rb +16 -0
  160. data/lib/referent_filters/dissertation_catch.rb +45 -0
  161. data/lib/section_renderer.rb +503 -0
  162. data/lib/service.rb +336 -0
  163. data/lib/service_adaptors/ajax_export.rb +37 -0
  164. data/lib/service_adaptors/amazon.rb +412 -0
  165. data/lib/service_adaptors/blacklight.rb +327 -0
  166. data/lib/service_adaptors/book_finder.rb +40 -0
  167. data/lib/service_adaptors/bx.rb +51 -0
  168. data/lib/service_adaptors/cover_thing.rb +73 -0
  169. data/lib/service_adaptors/elsevier_cover.rb +57 -0
  170. data/lib/service_adaptors/email_export.rb +10 -0
  171. data/lib/service_adaptors/ezproxy.rb +171 -0
  172. data/lib/service_adaptors/google_book_search.rb +442 -0
  173. data/lib/service_adaptors/gpo.rb +124 -0
  174. data/lib/service_adaptors/hathi_trust.rb +308 -0
  175. data/lib/service_adaptors/hip3_service.rb +150 -0
  176. data/lib/service_adaptors/hip_holding_search.rb +237 -0
  177. data/lib/service_adaptors/internet_archive.rb +488 -0
  178. data/lib/service_adaptors/isbn_db.rb +86 -0
  179. data/lib/service_adaptors/isi.rb +258 -0
  180. data/lib/service_adaptors/jcr.rb +146 -0
  181. data/lib/service_adaptors/opac.rb +351 -0
  182. data/lib/service_adaptors/open_library.rb +316 -0
  183. data/lib/service_adaptors/open_library_cover.rb +73 -0
  184. data/lib/service_adaptors/primo_service.rb +392 -0
  185. data/lib/service_adaptors/primo_source.rb +78 -0
  186. data/lib/service_adaptors/pubmed.rb +133 -0
  187. data/lib/service_adaptors/request_to_fixture.rb +68 -0
  188. data/lib/service_adaptors/scopus.rb +295 -0
  189. data/lib/service_adaptors/sfx-new.rb +557 -0
  190. data/lib/service_adaptors/sfx.rb +566 -0
  191. data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
  192. data/lib/service_adaptors/txt_holding_export.rb +32 -0
  193. data/lib/service_adaptors/ulrichs_cover.rb +57 -0
  194. data/lib/service_adaptors/ulrichs_link.rb +47 -0
  195. data/lib/service_adaptors/worldcat.rb +116 -0
  196. data/lib/service_adaptors/worldcat_identities.rb +591 -0
  197. data/lib/tasks/umlaut.rake +134 -0
  198. data/lib/umlaut/default_configuration.rb +5 -0
  199. data/lib/umlaut/routes.rb +136 -0
  200. data/lib/umlaut/version.rb +3 -0
  201. data/lib/umlaut.rb +37 -0
  202. data/lib/umlaut_configurable.rb +343 -0
  203. data/lib/umlaut_http.rb +100 -0
  204. data/lib/xml_schema_helper.rb +109 -0
  205. data/test/dummy/Rakefile +7 -0
  206. data/test/dummy/app/assets/javascripts/application.js +13 -0
  207. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  208. data/test/dummy/app/controllers/application_controller.rb +3 -0
  209. data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
  210. data/test/dummy/app/helpers/application_helper.rb +2 -0
  211. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  212. data/test/dummy/config/application.rb +45 -0
  213. data/test/dummy/config/boot.rb +10 -0
  214. data/test/dummy/config/database-jhu.yml +44 -0
  215. data/test/dummy/config/database.yml +25 -0
  216. data/test/dummy/config/environment.rb +5 -0
  217. data/test/dummy/config/environments/development.rb +34 -0
  218. data/test/dummy/config/environments/production.rb +60 -0
  219. data/test/dummy/config/environments/test.rb +39 -0
  220. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  221. data/test/dummy/config/initializers/inflections.rb +10 -0
  222. data/test/dummy/config/initializers/mime_types.rb +5 -0
  223. data/test/dummy/config/initializers/secret_token.rb +7 -0
  224. data/test/dummy/config/initializers/session_store.rb +8 -0
  225. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  226. data/test/dummy/config/locales/en.yml +5 -0
  227. data/test/dummy/config/routes.rb +61 -0
  228. data/test/dummy/config/umlaut_services.yml +237 -0
  229. data/test/dummy/config.ru +4 -0
  230. data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
  231. data/test/dummy/db/schema.rb +124 -0
  232. data/test/dummy/log/development.log +12981 -0
  233. data/test/dummy/log/production.log +0 -0
  234. data/test/dummy/public/404.html +26 -0
  235. data/test/dummy/public/422.html +26 -0
  236. data/test/dummy/public/500.html +26 -0
  237. data/test/dummy/public/favicon.ico +0 -0
  238. data/test/dummy/script/rails +6 -0
  239. data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
  240. data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
  241. data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
  242. data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
  243. data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
  244. data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
  245. data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
  246. data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
  247. data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
  248. data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
  249. data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
  250. data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
  251. data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
  252. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  253. data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
  254. data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
  255. data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
  256. data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
  257. data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
  258. data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
  259. data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
  260. data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
  261. data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
  262. data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
  263. data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
  264. data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
  265. data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
  266. data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
  267. data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
  268. data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
  269. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  270. data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
  271. data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
  272. data/test/fixtures/dispatched_services.yml +5 -0
  273. data/test/fixtures/permalinks.yml +5 -0
  274. data/test/fixtures/referent_values.yml +1734 -0
  275. data/test/fixtures/referents.yml +156 -0
  276. data/test/fixtures/requests.yml +284 -0
  277. data/test/fixtures/service_responses.yml +5 -0
  278. data/test/fixtures/sfx_urls.yml +4 -0
  279. data/test/performance/browsing_test.rb +9 -0
  280. data/test/test_helper.rb +10 -0
  281. data/test/umlaut_test.rb +7 -0
  282. data/test/unit/aleph_patron_test.rb +39 -0
  283. data/test/unit/aleph_record_benchmarks.rb +28 -0
  284. data/test/unit/aleph_record_test.rb +30 -0
  285. data/test/unit/aws_product_sign_test.rb +93 -0
  286. data/test/unit/collection_test.rb +76 -0
  287. data/test/unit/google_book_search_test.rb +101 -0
  288. data/test/unit/primo_searcher_test.rb +403 -0
  289. data/test/unit/primo_service_test.rb +939 -0
  290. data/test/unit/primo_ws_test.rb +131 -0
  291. data/test/unit/service_response_test.rb +9 -0
  292. data/test/unit/service_test.rb +33 -0
  293. metadata +580 -0
@@ -0,0 +1,557 @@
1
+ # NOTE: In your SFX Admin, under Menu Configuration / API, you should enable ALL
2
+ # 'extra' API information for full umlaut functionality.
3
+ # With the exception of "Include openURL parameter", can't figure out how
4
+ # that's useful.
5
+ #
6
+ # config parameters in services.yml
7
+ # display name: User displayable name for this service
8
+ # base_url: SFX base url.
9
+ # click_passthrough: DEPRECATED. Caused problems. Use the SFXBackchannelRecord
10
+ # link filter service instead.
11
+ # When set to true, Umlaut will send all SFX clicks
12
+ # through SFX, for SFX to capture statistics. This is currently done
13
+ # using a backdoor into the SFX sfxresolve.cgi script. Defaults to false.
14
+ # Note that
15
+ # after SFX requests have been removed in the nightly job, the
16
+ # click passthrough will cause an error! Set sfx_requests_expire_crontab
17
+ # with the crontab pattern you use for requests to expire, and we won't
18
+ # try to click passthrough with expired requests.
19
+ # sfx_requests_expire_crontab: Crontab pattern that the SFX admin is using
20
+ # to expire SFX requests. Used to refrain from click passthrough with
21
+ # expired requests, since that is destined to fail.
22
+ # services_of_interest: Optional. over-ride the built in list of what types of
23
+ # SFX services we want to grab, and what the corresponding umlaut types are.
24
+ # hash, with SFX service type name as key, and Umlaut ServiceTypeValue
25
+ # name as value.
26
+ # extra_targets_of_interest: sfx target_names of targets you want to make
27
+ # sure to include in umlaut. A hash with target_name as key, and umlaut
28
+ # ResponseTypeValue name as value.
29
+ # really_distant_relationships: An array of relationship type codes from SFX
30
+ # "related objects". See Table 18 in SFX 3.0 User's Manual. Related
31
+ # objects that have only a "really distant relationship" will NOT
32
+ # be shown as fulltext, but will instead be banished to the see also
33
+ # "highlighted_link" section. You must have display of related objects
34
+ # turned ON in SFX display admin, to get related objects at all in
35
+ # Umlaut. NOTE: This parameter has a default value to a certain set of
36
+ # relationships, set to empty array [] to eliminate defaults.
37
+ # sfx_timeout: in seconds, for both open/read timeout value for SFX connection.
38
+ # Defaults to 8.
39
+ class Sfx < Service
40
+ require 'uri'
41
+ require 'htmlentities'
42
+ require 'cgi'
43
+ require 'nokogiri'
44
+
45
+ #require 'open_url'
46
+
47
+ required_config_params :base_url
48
+
49
+ def initialize(config)
50
+
51
+ # Key is sfx service_type, value is umlaut servicetype string.
52
+ # These are the SFX service types we will translate to umlaut
53
+ @services_of_interest = {'getFullTxt' => 'fulltext',
54
+ 'getSelectedFullTxt' => 'fulltext',
55
+ 'getDocumentDelivery' => 'document_delivery',
56
+ 'getDOI' => 'highlighted_link',
57
+ 'getAbstract' => 'abstract',
58
+ 'getTOC' => 'table_of_contents'}
59
+
60
+ # Special targets. Key is SFX target_name.
61
+ # Value is umlaut service type.
62
+ # These targets will be included even if their sfx service_type doesn't
63
+ # match our services_of_interest, and the umlaut service ID string given
64
+ # here will take precedence and be used even if these targets DO match
65
+ # services_of_interest. Generally loaded from yml config in super.
66
+ @extra_targets_of_interest = {}
67
+
68
+ @sfx_timeout = 8
69
+
70
+ @really_distant_relationships = ["CONTINUES_IN_PART", "CONTINUED_IN_PART_BY", "ABSORBED_IN_PART", "ABSORBED_BY"]
71
+
72
+ super(config)
73
+ end
74
+
75
+ # Standard method, used by auto background updater. See Service docs.
76
+ def service_types_generated
77
+ service_strings = []
78
+ service_strings.concat( @services_of_interest.values() )
79
+ service_strings.concat( @extra_targets_of_interest.values() )
80
+ service_strings.uniq!
81
+
82
+ return service_strings.collect { |s| ServiceTypeValue[s] }
83
+ end
84
+
85
+ def base_url
86
+ return @base_url
87
+ end
88
+
89
+ def handle(request)
90
+ client = self.initialize_client(request)
91
+ begin
92
+ response = self.do_request(client)
93
+ self.parse_response(response, request)
94
+ return request.dispatched(self, true)
95
+ rescue Errno::ETIMEDOUT, Timeout::Error => e
96
+ # Request to SFX timed out. Record this as unsuccessful in the dispatch table. Temporary.
97
+ return request.dispatched(self, DispatchedService::FailedTemporary, e)
98
+ end
99
+ end
100
+
101
+ def initialize_client(request)
102
+ transport = OpenURL::Transport.new(@base_url, nil, :open_timeout => @sfx_timeout, :read_timeout => @sfx_timeout)
103
+
104
+ context_object = request.to_context_object
105
+ transport.add_context_object(context_object)
106
+ transport.extra_args["sfx.response_type"]="multi_obj_xml"
107
+
108
+ @get_coverage = false
109
+
110
+ metadata = request.referent.metadata
111
+ if ( metadata['date'].blank? &&
112
+ metadata['year'].blank? &&
113
+ (! request.referent.identifiers.find {|i| i =~ /^info\:(doi|pmid)/})
114
+ )
115
+ # No article-level metadata, do some special stuff.
116
+ transport.extra_args["sfx.ignore_date_threshold"]="1"
117
+ transport.extra_args["sfx.show_availability"]="1"
118
+ @get_coverage = true
119
+ end
120
+ # Workaround to SFX bug, not sure if this is really still neccesary
121
+ # I think it's not, but leave it in anyway just in case.
122
+ if (context_object.referent.identifiers.find {|i| i =~ /^info:doi\// })
123
+ transport.extra_args['sfx.doi_url']='http://dx.doi.org'
124
+ end
125
+
126
+ return transport
127
+ end
128
+
129
+ def do_request(client)
130
+ client.transport_inline
131
+ return client.response
132
+ end
133
+
134
+ def parse_response(resolver_response, request)
135
+ doc = Nokogiri::XML(resolver_response)
136
+
137
+ # Catch an SFX error message (in HTML) that's not an XML
138
+ # document at all.
139
+ unless doc.at('/ctx_obj_set')
140
+ Rails.logger.error("sfx.rb: SFX did not return expected response. SFX response: #{resolver_response}")
141
+ raise "SFX did not return expected response."
142
+ end
143
+
144
+ # There can be several context objects in the response.
145
+ # We need to keep track of which data comes from which, for
146
+ # SFX click-through generating et alia
147
+ sfx_objs = doc.search('/ctx_obj_set/ctx_obj')
148
+
149
+ # As we go through the possibly multiple SFX context objects,
150
+ # we need to keep track of which one, if any, we want to use
151
+ # to enhance the Umlaut referent metadata.
152
+ #
153
+ # We only enhance for journal type metadata. For book type
154
+ # metadata SFX will return something, but it may not be the manifestation
155
+ # we want. With journal titles, less of an issue.
156
+ #
157
+ # In case of multiple SFX hits, enhance metadata only from the
158
+ # one that actually had fulltext. If more than one had fulltext, forget it,
159
+ # too error prone. If none had full text, just pick the first.
160
+ #
161
+ # We'll use these variables to keep track of our 'best fit' as
162
+ # we loop through em.
163
+ best_fulltext_ctx = nil
164
+ best_nofulltext_ctx = nil
165
+
166
+ # We're going to keep our @really_distant_relationship stuff here.
167
+ related_titles = {}
168
+
169
+ 0.upto(sfx_objs.length - 1 ) do |sfx_obj_index|
170
+
171
+ sfx_obj = sfx_objs[sfx_obj_index]
172
+
173
+ # Get out the "perl_data" section, with our actual OpenURL style
174
+ # context object information. This was XML escaped as a String (actually
175
+ # double-escaped, weirdly), so
176
+ # we need to extract the string, unescape it, and then feed it to Nokogiri
177
+ # again.
178
+ ctx_obj_atts =
179
+ CGI.unescapeHTML( sfx_obj.at('./ctx_obj_attributes').inner_html)
180
+
181
+ perl_data = Nokogiri::XML( ctx_obj_atts )
182
+ # parse it into an OpenURL, we might need it like that.
183
+ sfx_co = Sfx.parse_perl_data(perl_data)
184
+ sfx_metadata = sfx_co.to_hash
185
+
186
+
187
+ # get SFX objectID
188
+ object_id_node =
189
+ perl_data.at("./perldata/hash/item[@key='rft.object_id']")
190
+ object_id = object_id_node ? object_id_node.inner_html : nil
191
+
192
+ # Get SFX requestID
193
+ request_id_node =
194
+ perl_data.at("./perldata/hash/item[@key='sfx.request_id']")
195
+ request_id = request_id_node ? request_id_node.inner_html : nil
196
+
197
+ # Get targets service ids
198
+ sfx_target_service_ids =
199
+ sfx_obj.search('ctx_obj_targets/target/target_service_id').collect {|e| e.inner_html}
200
+
201
+
202
+
203
+ metadata = request.referent.metadata
204
+
205
+ # For each target delivered by SFX
206
+ sfx_obj.search("./ctx_obj_targets/target").each_with_index do|target, target_index|
207
+ response_data = {}
208
+
209
+ # First check @extra_targets_of_interest
210
+ sfx_target_name = target.at('./target_name').inner_html
211
+ umlaut_service = @extra_targets_of_interest[sfx_target_name]
212
+
213
+ # If not found, look for it in services_of_interest
214
+ unless ( umlaut_service )
215
+ sfx_service_type = target.at("./service_type").inner_html
216
+ umlaut_service = @services_of_interest[sfx_service_type]
217
+ end
218
+
219
+ # If we have multiple context objs, skip the ill and ask-a-librarian
220
+ # links for all but the first, to avoid dups. This is a bit messy,
221
+ # but this whole multiple hits thing is messy.
222
+ if ( sfx_obj_index > 0 &&
223
+ ( umlaut_service == 'document_delivery' ||
224
+ umlaut_service == 'export_citation' ||
225
+ umlaut_service == 'help'))
226
+ next
227
+ end
228
+
229
+
230
+ # Okay, keep track of best fit ctx for metadata enhancement
231
+ if request.referent.format == "journal"
232
+ if ( umlaut_service == 'fulltext')
233
+ best_fulltext_ctx = perl_data
234
+ best_nofulltext_ctx = nil
235
+ elsif best_nofulltext_ctx == nil
236
+ best_nofulltext_ctx = perl_data
237
+ end
238
+ end
239
+
240
+ if ( umlaut_service ) # Okay, it's in services or targets of interest
241
+ if (target/"./displayer")
242
+ source = "SFX/"+(target/"./displayer").inner_html
243
+ else
244
+ source = "SFX"+URI.parse(self.url).path
245
+ end
246
+
247
+ target_service_id = (target/"./target_service_id").inner_html
248
+
249
+ coverage = nil
250
+ if ( @get_coverage )
251
+ # Make sure you turn on "Include availability info in text format"
252
+ # in the SFX Admin API configuration.
253
+ thresholds_str = ""
254
+ target.search('coverage/coverage_text/threshold_text/coverage_statement').each do | threshold |
255
+ thresholds_str += threshold.inner_html.to_s + ".\n";
256
+ end
257
+
258
+ embargoes_str = "";
259
+ target.search('coverage/coverage_text/embargo_text/embargo_statement').each do |embargo |
260
+ embargoes_str += embargo.inner_html.to_s + ".\n";
261
+ end
262
+
263
+ unless ( thresholds_str.blank? && embargoes_str.blank? )
264
+ coverage = thresholds_str + embargoes_str
265
+ end
266
+ end
267
+
268
+
269
+ related_note = ""
270
+ # If this is from a related object, add that on as a note too...
271
+ # And maybe skip this entirely!
272
+ if (related_node = target.at('./related_service_info'))
273
+ relationship = related_node.at('./relation_type').inner_text
274
+ issn = related_node.at('./related_object_issn').inner_text
275
+ sfx_object_id = related_node.at('./related_object_id').inner_text
276
+ title = related_node.at('./related_object_title').inner_text
277
+
278
+ if @really_distant_relationships.include?(
279
+ related_node.at('./relation_type').inner_text)
280
+ # Show title-level link in see-also instead of full text.
281
+ related_titles[issn] = {
282
+ :sfx_object_id => sfx_object_id,
283
+ :title => title,
284
+ :relationship => relationship,
285
+ :issn => issn
286
+ }
287
+
288
+ next
289
+ end
290
+
291
+ related_note = "This version provided from related title: <i>" + CGI.unescapeHTML( title ) + "</i>.\n"
292
+ end
293
+
294
+ if ( sfx_service_type == 'getDocumentDelivery' )
295
+ value_string = request_id
296
+ else
297
+ value_string = (target/"./target_service_id").inner_html
298
+ end
299
+
300
+ response_data[:url] = CGI.unescapeHTML((target/"./target_url").inner_html)
301
+ response_data[:notes] = related_note.to_s + CGI.unescapeHTML((target/"./note").inner_html)
302
+ response_data[:authentication] = CGI.unescapeHTML((target/"./authentication").inner_html)
303
+ response_data[:source] = source
304
+ response_data[:coverage] = coverage if coverage
305
+
306
+ # Sfx metadata we want
307
+ response_data[:sfx_base_url] = @base_url
308
+ response_data[:sfx_obj_index] = sfx_obj_index + 1 # sfx is 1 indexed
309
+ response_data[:sfx_target_index] = target_index + 1
310
+ response_data[:sfx_request_id] = (perl_data/"//hash/item[@key='sfx.request_id']").first.inner_html
311
+ response_data[:sfx_target_service_id] = target_service_id
312
+ response_data[:sfx_target_name] = sfx_target_name
313
+ # At url-generation time, the request isn't available to us anymore,
314
+ # so we better store this citation info here now, since we need it
315
+ # for sfx click passthrough
316
+
317
+ # Oops, need to take this from SFX delivered metadata.
318
+
319
+ response_data[:citation_year] = sfx_metadata['rft.date'].to_s[0,4] if sfx_metadata['rft.date']
320
+ response_data[:citation_volume] = sfx_metadata['rft.volume'];
321
+ response_data[:citation_issue] = sfx_metadata['rft.issue']
322
+ response_data[:citation_spage] = sfx_metadata['rft.spage']
323
+
324
+ # Some debug info
325
+ response_data[:debug_info] =" Target: #{sfx_target_name} ; SFX object ID: #{object_id}"
326
+
327
+ response_data[:display_text] = (target/"./target_public_name").inner_html
328
+
329
+ request.add_service_response(
330
+ response_data.merge(
331
+ :service => self,
332
+ :service_type_value => umlaut_service
333
+ ))
334
+
335
+
336
+
337
+ end
338
+ end
339
+ end
340
+
341
+ # Add in links to our related titles
342
+ related_titles.each_pair do |issn, hash|
343
+ request.add_service_response(
344
+ :service => self,
345
+ :display_text => "#{sfx_relationship_display(hash[:relationship])}: #{hash[:title]}",
346
+ :notes => "#{ServiceTypeValue['fulltext'].display_name} available",
347
+ :related_object_hash => hash,
348
+ :service_type_value => "highlighted_link")
349
+ end
350
+
351
+ # Did we find a ctx best fit for enhancement?
352
+ if best_fulltext_ctx
353
+ enhance_referent(request, best_fulltext_ctx)
354
+ elsif best_nofulltext_ctx
355
+ enhance_referent(request, best_nofulltext_ctx)
356
+ end
357
+
358
+ end
359
+
360
+
361
+ def sfx_click_passthrough
362
+ # From config, or default to false.
363
+ return @click_passthrough || false;
364
+ end
365
+
366
+ # Using the value of sfx_request_expire_crontab, determine if the
367
+ # umlaut service response is so old that we can't use it for
368
+ # sfx click passthrough anymore.
369
+ def expired_sfx_request(response)
370
+ require 'CronTab'
371
+
372
+ crontab_str = @sfx_requests_expire_crontab
373
+
374
+ return false unless crontab_str # no param, no determination possible
375
+
376
+ crontab = CronTab.new( crontab_str )
377
+
378
+ time_of_response = response.created_at
379
+
380
+ return false unless time_of_response # no recorded time, not possible either
381
+
382
+ expire_time = crontab.nexttime( time_of_response )
383
+
384
+ # Give an extra five minutes of time, in case the expire
385
+ # process takes up to five minutes to finish.
386
+ return( Time.now > (expire_time + 5.minutes) )
387
+ end
388
+
389
+ # Try to provide a weird reverse-engineered url to take the user THROUGH
390
+ # sfx to their destination, so sfx will capture for statistics.
391
+ # This relies on certain information from the orignal sfx response
392
+ # being stored in the Response object at that point. Used by
393
+ # sfx_backchannel_record service.
394
+ def self.pass_through_url(response)
395
+ base_url = response[:sfx_base_url]
396
+
397
+ sfx_resolver_cgi_url = base_url + "/cgi/core/sfxresolver.cgi"
398
+
399
+
400
+ dataString = "?tmp_ctx_svc_id=#{response[:sfx_target_index]}"
401
+ dataString += "&tmp_ctx_obj_id=#{response[:sfx_obj_index]}"
402
+
403
+ # Don't understand what this is, but it sometimes needs to be 1?
404
+ # Hopefully it won't mess anything up when it's not neccesary.
405
+ # Really have no idea when it would need to be something other
406
+ # than 1.
407
+ # Nope, sad to say it does mess up cases where it is not neccesary.
408
+ # Grr.
409
+ #dataString += "&tmp_parent_ctx_obj_id=1"
410
+
411
+ dataString += "&service_id=#{response[:sfx_target_service_id]}"
412
+ dataString += "&request_id=#{response[:sfx_request_id]}"
413
+ dataString += "&rft.year="
414
+ dataString += URI.escape(response[:citation_year].to_s) if response[:citation_year]
415
+ dataString += "&rft.volume="
416
+ dataString += URI.escape(response[:citation_volume].to_s) if response[:citation_volume]
417
+ dataString += "&rft.issue="
418
+ dataString += URI.escape(response[:citation_issue].to_s) if response[:citation_issue]
419
+ dataString += "&rft.spage="
420
+ dataString += URI.escape(response[:citation_spage]).to_s if response[:citation_spage]
421
+
422
+ return sfx_resolver_cgi_url + dataString
423
+ end
424
+
425
+ # Class method to parse a perl_data block as XML in String
426
+ # into a ContextObject. Argument is Nokogiri doc containing
427
+ # the SFX <perldata> element and children.
428
+ def self.parse_perl_data(doc)
429
+
430
+ co = OpenURL::ContextObject.new
431
+ co.referent.set_format('journal') # default
432
+
433
+ html_ent_coder = HTMLEntities.new
434
+
435
+ doc.search('perldata/hash/item').each do |item|
436
+ key = item['key'].to_s
437
+ value = item.inner_html
438
+ # But this still has HTML entities in it sometimes. Now we've
439
+ # got to decode THAT.
440
+ value = html_ent_coder.decode(value)
441
+
442
+ # Some normalization. SFX uses rft.year, which is not actually
443
+ # legal. Stick it in rft.date instead.
444
+ key = "rft.date" if key == "rft.year"
445
+
446
+ prefix, stripped = key.split('.')
447
+
448
+ # The auinit1 value is COMPLETELY messed up for reasons I do not know.
449
+ # Double encoded in bizarre ways.
450
+ next if key == '@rft.auinit1' || key == '@rft.auinit'
451
+
452
+
453
+
454
+
455
+ # Darn multi-value SFX hackery, indicated with keys beginning
456
+ # with '@'. Just take the first one,
457
+ # our context object can't store more than one. Then regularize the
458
+ # key name.
459
+ if (prefix == '@rft')
460
+ array_items = item.search("array/item")
461
+ array_i = array_items[0] unless array_items.blank?
462
+
463
+ prefix = prefix.slice(1, prefix.length)
464
+ value = array_i ? array_i.inner_html : nil
465
+ end
466
+
467
+ # object_type? Fix that to be the right way.
468
+ if (prefix=='rft') && (key=='object_type')
469
+ co.referent.set_format( value.downcase )
470
+ next
471
+ end
472
+
473
+ if (prefix == 'rft' && value)
474
+ co.referent.set_metadata(stripped, value)
475
+ end
476
+
477
+ if (prefix=='@rft_id')
478
+ identifiers = item.search('array/item')
479
+ identifiers.each do |id|
480
+ co.referent.add_identifier(id.inner_html)
481
+ end
482
+ end
483
+ if (prefix=='@rfr_id')
484
+ identifiers = item.search('array/item')
485
+ identifiers.each do |id|
486
+ co.referrer.add_identifier(id.inner_html)
487
+ end
488
+ end
489
+ end
490
+ return co
491
+ end
492
+
493
+ # Custom url generation for the weird case
494
+ def response_url(service_response, submitted_params)
495
+ if (related_object = service_response.data_values[:related_object_hash])
496
+ {:controller => 'resolve', "rft.issn" => related_object[:issn], "rft.title" => related_object[:title], "rft.object_id" => related_object[:sfx_object_id] }
497
+ else
498
+ service_response['url']
499
+ end
500
+ end
501
+
502
+ protected
503
+ # Second argument is a Nokogiri element representing the <perldata>
504
+ # tag and children.
505
+ def enhance_referent(request, perl_data)
506
+ ActiveRecord::Base.connection_pool.with_connection do
507
+ metadata = request.referent.metadata
508
+
509
+ sfx_co = Sfx.parse_perl_data(perl_data)
510
+
511
+ sfx_metadata = sfx_co.referent.metadata
512
+ # Do NOT enhance for metadata type 'BOOK', unreliable matching from
513
+ # SFX!
514
+ return if sfx_metadata["object_type"] == "BOOK" || sfx_metadata["genre"] == "book"
515
+
516
+ # If we already had metadata for journal title and the SFX one
517
+ # differs, we want to over-write it. This is good for ambiguous
518
+ # incoming OpenURLs, among other things.
519
+
520
+ if request.referent.format == 'journal'
521
+ request.referent.enhance_referent("jtitle", sfx_metadata['jtitle'])
522
+ end
523
+ # And ISSN
524
+ if request.referent.format == 'journal' && ! sfx_metadata['issn'].blank?
525
+ request.referent.enhance_referent('issn', sfx_metadata['issn'])
526
+ end
527
+
528
+
529
+ # The rest, we write only if blank, we don't over-write
530
+ sfx_metadata.each do |key, value|
531
+ if (metadata[key].blank?)
532
+
533
+ # watch out for SFX's weird array values.
534
+ request.referent.enhance_referent(key, value)
535
+ end
536
+ end
537
+ end
538
+ end
539
+
540
+
541
+
542
+ # From Table 18 in SFX General User's Guide 3.0.
543
+ def sfx_relationship_display(sfx_code)
544
+ sfx_code = sfx_code.to_s
545
+ # Most can simply be #humanized, a couple of over-rides
546
+ @sfx_relationship_display ||= {
547
+ "TRANSLATION_ENTRY" => "Translation",
548
+ }
549
+
550
+ display = @sfx_relationship_display[sfx_code]
551
+ display = sfx_code.humanize if display.nil?
552
+
553
+ return display
554
+ end
555
+
556
+ end
557
+