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,566 @@
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
+ # Include a CrossRef credit, becuase SFX uses CrossRef api internally,
73
+ # and CrossRef ToS may require us to give credit.
74
+ @credits = {
75
+ "SFX" => "http://www.exlibrisgroup.com/sfx.htm",
76
+ "CrossRef" => "http://www.crossref.org/"
77
+ }
78
+
79
+ super(config)
80
+ end
81
+
82
+ # Standard method, used by auto background updater. See Service docs.
83
+ def service_types_generated
84
+ service_strings = []
85
+ service_strings.concat( @services_of_interest.values() )
86
+ service_strings.concat( @extra_targets_of_interest.values() )
87
+ service_strings.uniq!
88
+
89
+ return service_strings.collect { |s| ServiceTypeValue[s] }
90
+ end
91
+
92
+ def base_url
93
+ return @base_url
94
+ end
95
+
96
+ def handle(request)
97
+ client = self.initialize_client(request)
98
+ begin
99
+ response = self.do_request(client)
100
+ self.parse_response(response, request)
101
+ return request.dispatched(self, true)
102
+ rescue Errno::ETIMEDOUT, Timeout::Error => e
103
+ # Request to SFX timed out. Record this as unsuccessful in the dispatch table. Temporary.
104
+ return request.dispatched(self, DispatchedService::FailedTemporary, e)
105
+ end
106
+ end
107
+
108
+ def initialize_client(request)
109
+ transport = OpenURL::Transport.new(@base_url, nil, :open_timeout => @sfx_timeout, :read_timeout => @sfx_timeout)
110
+
111
+ context_object = request.to_context_object
112
+ transport.add_context_object(context_object)
113
+ transport.extra_args["sfx.response_type"]="multi_obj_xml"
114
+
115
+ @get_coverage = false
116
+
117
+ metadata = request.referent.metadata
118
+ if ( metadata['date'].blank? &&
119
+ metadata['year'].blank? &&
120
+ (! request.referent.identifiers.find {|i| i =~ /^info\:(doi|pmid)/})
121
+ )
122
+ # No article-level metadata, do some special stuff.
123
+ transport.extra_args["sfx.ignore_date_threshold"]="1"
124
+ transport.extra_args["sfx.show_availability"]="1"
125
+ @get_coverage = true
126
+ end
127
+ # Workaround to SFX bug, not sure if this is really still neccesary
128
+ # I think it's not, but leave it in anyway just in case.
129
+ if (context_object.referent.identifiers.find {|i| i =~ /^info:doi\// })
130
+ transport.extra_args['sfx.doi_url']='http://dx.doi.org'
131
+ end
132
+
133
+ return transport
134
+ end
135
+
136
+ def do_request(client)
137
+ client.transport_inline
138
+ return client.response
139
+ end
140
+
141
+ def parse_response(resolver_response, request)
142
+ doc = Nokogiri::XML(resolver_response)
143
+
144
+ # Catch an SFX error message (in HTML) that's not an XML
145
+ # document at all.
146
+ unless doc.at('/ctx_obj_set')
147
+ Rails.logger.error("sfx.rb: SFX did not return expected response. SFX response: #{resolver_response}")
148
+ raise "SFX did not return expected response."
149
+ end
150
+
151
+ # There can be several context objects in the response.
152
+ # We need to keep track of which data comes from which, for
153
+ # SFX click-through generating et alia
154
+ sfx_objs = doc.search('/ctx_obj_set/ctx_obj')
155
+
156
+ # As we go through the possibly multiple SFX context objects,
157
+ # we need to keep track of which one, if any, we want to use
158
+ # to enhance the Umlaut referent metadata.
159
+ #
160
+ # We only enhance for journal type metadata. For book type
161
+ # metadata SFX will return something, but it may not be the manifestation
162
+ # we want. With journal titles, less of an issue.
163
+ #
164
+ # In case of multiple SFX hits, enhance metadata only from the
165
+ # one that actually had fulltext. If more than one had fulltext, forget it,
166
+ # too error prone. If none had full text, just pick the first.
167
+ #
168
+ # We'll use these variables to keep track of our 'best fit' as
169
+ # we loop through em.
170
+ best_fulltext_ctx = nil
171
+ best_nofulltext_ctx = nil
172
+
173
+ # We're going to keep our @really_distant_relationship stuff here.
174
+ related_titles = {}
175
+
176
+ 0.upto(sfx_objs.length - 1 ) do |sfx_obj_index|
177
+
178
+ sfx_obj = sfx_objs[sfx_obj_index]
179
+
180
+ # Get out the "perl_data" section, with our actual OpenURL style
181
+ # context object information. This was XML escaped as a String (actually
182
+ # double-escaped, weirdly), so
183
+ # we need to extract the string, unescape it, and then feed it to Nokogiri
184
+ # again.
185
+ ctx_obj_atts =
186
+ CGI.unescapeHTML( sfx_obj.at('./ctx_obj_attributes').inner_html)
187
+
188
+ perl_data = Nokogiri::XML( ctx_obj_atts )
189
+ # parse it into an OpenURL, we might need it like that.
190
+ sfx_co = Sfx.parse_perl_data(perl_data)
191
+ sfx_metadata = sfx_co.to_hash
192
+
193
+
194
+ # get SFX objectID
195
+ object_id_node =
196
+ perl_data.at("./perldata/hash/item[@key='rft.object_id']")
197
+ object_id = object_id_node ? object_id_node.inner_html : nil
198
+
199
+ # Get SFX requestID
200
+ request_id_node =
201
+ perl_data.at("./perldata/hash/item[@key='sfx.request_id']")
202
+ request_id = request_id_node ? request_id_node.inner_html : nil
203
+
204
+ # Get targets service ids
205
+ sfx_target_service_ids =
206
+ sfx_obj.search('ctx_obj_targets/target/target_service_id').collect {|e| e.inner_html}
207
+
208
+
209
+
210
+ metadata = request.referent.metadata
211
+
212
+ # For each target delivered by SFX
213
+ sfx_obj.search("./ctx_obj_targets/target").each_with_index do|target, target_index|
214
+ response_data = {}
215
+
216
+ # First check @extra_targets_of_interest
217
+ sfx_target_name = target.at('./target_name').inner_html
218
+ umlaut_service = @extra_targets_of_interest[sfx_target_name]
219
+
220
+ # If not found, look for it in services_of_interest
221
+ unless ( umlaut_service )
222
+ sfx_service_type = target.at("./service_type").inner_html
223
+ umlaut_service = @services_of_interest[sfx_service_type]
224
+ end
225
+
226
+ # If we have multiple context objs, skip the ill and ask-a-librarian
227
+ # links for all but the first, to avoid dups. This is a bit messy,
228
+ # but this whole multiple hits thing is messy.
229
+ if ( sfx_obj_index > 0 &&
230
+ ( umlaut_service == 'document_delivery' ||
231
+ umlaut_service == 'export_citation' ||
232
+ umlaut_service == 'help'))
233
+ next
234
+ end
235
+
236
+
237
+ # Okay, keep track of best fit ctx for metadata enhancement
238
+ if request.referent.format == "journal"
239
+ if ( umlaut_service == 'fulltext')
240
+ best_fulltext_ctx = perl_data
241
+ best_nofulltext_ctx = nil
242
+ elsif best_nofulltext_ctx == nil
243
+ best_nofulltext_ctx = perl_data
244
+ end
245
+ end
246
+
247
+ if ( umlaut_service ) # Okay, it's in services or targets of interest
248
+ if (target/"./displayer")
249
+ source = "SFX/"+(target/"./displayer").inner_html
250
+ else
251
+ source = "SFX"+URI.parse(self.url).path
252
+ end
253
+
254
+ target_service_id = (target/"./target_service_id").inner_html
255
+
256
+ coverage = nil
257
+ if ( @get_coverage )
258
+ # Make sure you turn on "Include availability info in text format"
259
+ # in the SFX Admin API configuration.
260
+ thresholds_str = ""
261
+ target.search('coverage/coverage_text/threshold_text/coverage_statement').each do | threshold |
262
+ thresholds_str += threshold.inner_html.to_s + ".\n";
263
+ end
264
+
265
+ embargoes_str = "";
266
+ target.search('coverage/coverage_text/embargo_text/embargo_statement').each do |embargo |
267
+ embargoes_str += embargo.inner_html.to_s + ".\n";
268
+ end
269
+
270
+ unless ( thresholds_str.blank? && embargoes_str.blank? )
271
+ coverage = thresholds_str + embargoes_str
272
+ end
273
+ end
274
+
275
+
276
+ related_note = ""
277
+ # If this is from a related object, add that on as a note too...
278
+ # And maybe skip this entirely!
279
+ if (related_node = target.at('./related_service_info'))
280
+ relationship = related_node.at('./relation_type').inner_text
281
+ issn = related_node.at('./related_object_issn').inner_text
282
+ sfx_object_id = related_node.at('./related_object_id').inner_text
283
+ title = related_node.at('./related_object_title').inner_text
284
+
285
+ if @really_distant_relationships.include?(
286
+ related_node.at('./relation_type').inner_text)
287
+ # Show title-level link in see-also instead of full text.
288
+ related_titles[issn] = {
289
+ :sfx_object_id => sfx_object_id,
290
+ :title => title,
291
+ :relationship => relationship,
292
+ :issn => issn
293
+ }
294
+
295
+ next
296
+ end
297
+
298
+ related_note = "This version provided from related title: <i>" + CGI.unescapeHTML( title ) + "</i>.\n"
299
+ end
300
+
301
+ if ( sfx_service_type == 'getDocumentDelivery' )
302
+ value_string = request_id
303
+ else
304
+ value_string = (target/"./target_service_id").inner_html
305
+ end
306
+
307
+ response_data[:url] = CGI.unescapeHTML((target/"./target_url").inner_html)
308
+ response_data[:notes] = related_note.to_s + CGI.unescapeHTML((target/"./note").inner_html)
309
+ response_data[:authentication] = CGI.unescapeHTML((target/"./authentication").inner_html)
310
+ response_data[:source] = source
311
+ response_data[:coverage] = coverage if coverage
312
+
313
+ # Sfx metadata we want
314
+ response_data[:sfx_base_url] = @base_url
315
+ response_data[:sfx_obj_index] = sfx_obj_index + 1 # sfx is 1 indexed
316
+ response_data[:sfx_target_index] = target_index + 1
317
+ response_data[:sfx_request_id] = (perl_data/"//hash/item[@key='sfx.request_id']").first.inner_html
318
+ response_data[:sfx_target_service_id] = target_service_id
319
+ response_data[:sfx_target_name] = sfx_target_name
320
+ # At url-generation time, the request isn't available to us anymore,
321
+ # so we better store this citation info here now, since we need it
322
+ # for sfx click passthrough
323
+
324
+ # Oops, need to take this from SFX delivered metadata.
325
+
326
+ response_data[:citation_year] = sfx_metadata['rft.date'].to_s[0,4] if sfx_metadata['rft.date']
327
+ response_data[:citation_volume] = sfx_metadata['rft.volume'];
328
+ response_data[:citation_issue] = sfx_metadata['rft.issue']
329
+ response_data[:citation_spage] = sfx_metadata['rft.spage']
330
+
331
+ # Some debug info
332
+ response_data[:debug_info] =" Target: #{sfx_target_name} ; SFX object ID: #{object_id}"
333
+
334
+ response_data[:display_text] = (target/"./target_public_name").inner_html
335
+
336
+ request.add_service_response(
337
+ response_data.merge(
338
+ :service => self,
339
+ :service_type_value => umlaut_service
340
+ ))
341
+
342
+
343
+
344
+ end
345
+ end
346
+ end
347
+
348
+ # Add in links to our related titles
349
+ related_titles.each_pair do |issn, hash|
350
+ request.add_service_response(
351
+ :service => self,
352
+ :display_text => "#{sfx_relationship_display(hash[:relationship])}: #{hash[:title]}",
353
+ :notes => "#{ServiceTypeValue['fulltext'].display_name} available",
354
+ :related_object_hash => hash,
355
+ :service_type_value => "highlighted_link")
356
+ end
357
+
358
+ # Did we find a ctx best fit for enhancement?
359
+ if best_fulltext_ctx
360
+ enhance_referent(request, best_fulltext_ctx)
361
+ elsif best_nofulltext_ctx
362
+ enhance_referent(request, best_nofulltext_ctx)
363
+ end
364
+
365
+ end
366
+
367
+
368
+ def sfx_click_passthrough
369
+ # From config, or default to false.
370
+ return @click_passthrough || false;
371
+ end
372
+
373
+ # Using the value of sfx_request_expire_crontab, determine if the
374
+ # umlaut service response is so old that we can't use it for
375
+ # sfx click passthrough anymore.
376
+ def expired_sfx_request(response)
377
+ require 'CronTab'
378
+
379
+ crontab_str = @sfx_requests_expire_crontab
380
+
381
+ return false unless crontab_str # no param, no determination possible
382
+
383
+ crontab = CronTab.new( crontab_str )
384
+
385
+ time_of_response = response.created_at
386
+
387
+ return false unless time_of_response # no recorded time, not possible either
388
+
389
+ expire_time = crontab.nexttime( time_of_response )
390
+
391
+ # Give an extra five minutes of time, in case the expire
392
+ # process takes up to five minutes to finish.
393
+ return( Time.now > (expire_time + 5.minutes) )
394
+ end
395
+
396
+ # Try to provide a weird reverse-engineered url to take the user THROUGH
397
+ # sfx to their destination, so sfx will capture for statistics.
398
+ # This relies on certain information from the orignal sfx response
399
+ # being stored in the Response object at that point. Used by
400
+ # sfx_backchannel_record service.
401
+ def self.pass_through_url(response)
402
+ base_url = response[:sfx_base_url]
403
+
404
+ sfx_resolver_cgi_url = base_url + "/cgi/core/sfxresolver.cgi"
405
+
406
+
407
+ dataString = "?tmp_ctx_svc_id=#{response[:sfx_target_index]}"
408
+ dataString += "&tmp_ctx_obj_id=#{response[:sfx_obj_index]}"
409
+
410
+ # Don't understand what this is, but it sometimes needs to be 1?
411
+ # Hopefully it won't mess anything up when it's not neccesary.
412
+ # Really have no idea when it would need to be something other
413
+ # than 1.
414
+ # Nope, sad to say it does mess up cases where it is not neccesary.
415
+ # Grr.
416
+ #dataString += "&tmp_parent_ctx_obj_id=1"
417
+
418
+ dataString += "&service_id=#{response[:sfx_target_service_id]}"
419
+ dataString += "&request_id=#{response[:sfx_request_id]}"
420
+ dataString += "&rft.year="
421
+ dataString += URI.escape(response[:citation_year].to_s) if response[:citation_year]
422
+ dataString += "&rft.volume="
423
+ dataString += URI.escape(response[:citation_volume].to_s) if response[:citation_volume]
424
+ dataString += "&rft.issue="
425
+ dataString += URI.escape(response[:citation_issue].to_s) if response[:citation_issue]
426
+ dataString += "&rft.spage="
427
+ dataString += URI.escape(response[:citation_spage]).to_s if response[:citation_spage]
428
+
429
+ return sfx_resolver_cgi_url + dataString
430
+ end
431
+
432
+ # Class method to parse a perl_data block as XML in String
433
+ # into a ContextObject. Argument is Nokogiri doc containing
434
+ # the SFX <perldata> element and children.
435
+ def self.parse_perl_data(doc)
436
+
437
+ co = OpenURL::ContextObject.new
438
+ co.referent.set_format('journal') # default
439
+
440
+ html_ent_coder = HTMLEntities.new
441
+
442
+ doc.search('perldata/hash/item').each do |item|
443
+ key = item['key'].to_s
444
+
445
+ value = item.inner_html
446
+
447
+ # Some normalization. SFX uses rft.year, which is not actually
448
+ # legal. Stick it in rft.date instead.
449
+ key = "rft.date" if key == "rft.year"
450
+
451
+ prefix, stripped = key.split('.')
452
+
453
+ # The auinit1 value is COMPLETELY messed up for reasons I do not know.
454
+ # Double encoded in bizarre ways.
455
+ next if key == '@rft.auinit1' || key == '@rft.auinit'
456
+
457
+
458
+
459
+
460
+ # Darn multi-value SFX hackery, indicated with keys beginning
461
+ # with '@'. Just take the first one,
462
+ # our context object can't store more than one. Then regularize the
463
+ # key name.
464
+ if (prefix == '@rft')
465
+ array_items = item.search("array/item")
466
+ array_i = array_items[0] unless array_items.blank?
467
+
468
+ prefix = prefix.slice(1, prefix.length)
469
+ value = array_i ? array_i.inner_html : nil
470
+ end
471
+
472
+ # But this still has HTML entities in it sometimes. Now we've
473
+ # got to decode THAT.
474
+ value = html_ent_coder.decode(value)
475
+
476
+ # object_type? Fix that to be the right way.
477
+ if (prefix=='rft') && (key=='object_type')
478
+ co.referent.set_format( value.downcase )
479
+ next
480
+ end
481
+
482
+ if (prefix == 'rft' && value)
483
+ co.referent.set_metadata(stripped, value)
484
+ end
485
+
486
+ if (prefix=='@rft_id')
487
+ identifiers = item.search('array/item')
488
+ identifiers.each do |id|
489
+ co.referent.add_identifier(id.inner_html)
490
+ end
491
+ end
492
+ if (prefix=='@rfr_id')
493
+ identifiers = item.search('array/item')
494
+ identifiers.each do |id|
495
+ co.referrer.add_identifier(id.inner_html)
496
+ end
497
+ end
498
+ end
499
+ return co
500
+ end
501
+
502
+ # Custom url generation for the weird case
503
+ def response_url(service_response, submitted_params)
504
+ if (related_object = service_response.data_values[:related_object_hash])
505
+ {:controller => 'resolve', "rft.issn" => related_object[:issn], "rft.title" => related_object[:title], "rft.object_id" => related_object[:sfx_object_id] }
506
+ else
507
+ service_response['url']
508
+ end
509
+ end
510
+
511
+ protected
512
+ # Second argument is a Nokogiri element representing the <perldata>
513
+ # tag and children.
514
+ def enhance_referent(request, perl_data)
515
+ ActiveRecord::Base.connection_pool.with_connection do
516
+ metadata = request.referent.metadata
517
+
518
+ sfx_co = Sfx.parse_perl_data(perl_data)
519
+
520
+ sfx_metadata = sfx_co.referent.metadata
521
+ # Do NOT enhance for metadata type 'BOOK', unreliable matching from
522
+ # SFX!
523
+ return if sfx_metadata["object_type"] == "BOOK" || sfx_metadata["genre"] == "book"
524
+
525
+ # If we already had metadata for journal title and the SFX one
526
+ # differs, we want to over-write it. This is good for ambiguous
527
+ # incoming OpenURLs, among other things.
528
+
529
+ if request.referent.format == 'journal'
530
+ request.referent.enhance_referent("jtitle", sfx_metadata['jtitle'])
531
+ end
532
+ # And ISSN
533
+ if request.referent.format == 'journal' && ! sfx_metadata['issn'].blank?
534
+ request.referent.enhance_referent('issn', sfx_metadata['issn'])
535
+ end
536
+
537
+
538
+ # The rest, we write only if blank, we don't over-write
539
+ sfx_metadata.each do |key, value|
540
+ if (metadata[key].blank?)
541
+
542
+ # watch out for SFX's weird array values.
543
+ request.referent.enhance_referent(key, value)
544
+ end
545
+ end
546
+ end
547
+ end
548
+
549
+
550
+
551
+ # From Table 18 in SFX General User's Guide 3.0.
552
+ def sfx_relationship_display(sfx_code)
553
+ sfx_code = sfx_code.to_s
554
+ # Most can simply be #humanized, a couple of over-rides
555
+ @sfx_relationship_display ||= {
556
+ "TRANSLATION_ENTRY" => "Translation",
557
+ }
558
+
559
+ display = @sfx_relationship_display[sfx_code]
560
+ display = sfx_code.humanize if display.nil?
561
+
562
+ return display
563
+ end
564
+
565
+ end
566
+