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.
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,46 @@
1
+ module Exlibris::Primo::Source
2
+ # == Overview
3
+ # Aleph is an Exlibris::Primo::Holding that provides a link to Aleph
4
+ # and a request button based on config settings in the primo_config file.
5
+ class Aleph < Exlibris::Primo::Holding
6
+ @attribute_aliases = Exlibris::Primo::Holding.attribute_aliases.merge({
7
+ :aleph_doc_library => :original_source_id, :aleph_sub_library => :library,
8
+ :aleph_collection => :collection, :aleph_call_number => :call_number,
9
+ :aleph_doc_number => :source_record_id
10
+ })
11
+ @decode_variables = Exlibris::Primo::Holding.decode_variables.merge({
12
+ :aleph_sub_library_code => { :code => :library_code }
13
+ })
14
+
15
+ # Overwrites Exlibris::Primo::Holding#new
16
+ def initialize(parameters={})
17
+ super(parameters)
18
+ @aleph_local_base = aleph_config["local_base"] unless aleph_config.nil?
19
+ # Aleph holdings page
20
+ @url = "#{@source_url}/F?func=item-global&doc_library=#{aleph_doc_library}&local_base=#{@aleph_local_base}&doc_number=#{aleph_doc_number}&sub_library=#{@aleph_sub_library_code}"
21
+ # Aleph doesn't work right so we have to push the patron to the Aleph holdings page!
22
+ @request_url = url if requestable?
23
+ @source_data.merge!({
24
+ :aleph_doc_library => aleph_doc_library,
25
+ :aleph_sub_library => aleph_sub_library,
26
+ :aleph_sub_library_code => @aleph_sub_library_code,
27
+ :aleph_collection => aleph_collection,
28
+ :aleph_call_number => aleph_call_number,
29
+ :aleph_doc_number => aleph_doc_number
30
+ })
31
+ end
32
+
33
+ protected
34
+ # Maps @source config to aleph_config for convenience.
35
+ def aleph_config
36
+ return @source_config
37
+ end
38
+
39
+ private
40
+ def requestable?
41
+ # Default to nothing is requestable
42
+ return false if aleph_config.nil? or aleph_config["requestable_statuses"].nil?
43
+ return aleph_config["requestable_statuses"].include?(@status_code)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,323 @@
1
+ require 'nokogiri'
2
+
3
+ module Exlibris::Primo::Source::Local
4
+ # == Overview
5
+ # NYUAleph is an Exlibris::Primo::Source::Aleph that expands Primo availlibrary
6
+ # elements based on of Aleph items return from the Aleph REST APIs.
7
+ # It stores metadata from these items ub the ServiceType#view[:source_data]
8
+ # element that can be used by custom controllers to extend patron services,
9
+ # including request and paging functionality.
10
+ # NYUAleph also provides coverage metadata based on bib and holding
11
+ # information from the Aleph bib and holdings REST APIs.
12
+ #
13
+ # == Benchmarks
14
+ # The following benchmarks were run on SunOS 5.10 Generic_141414-08 sun4u sparc SUNW,Sun-Fire-V240.
15
+ # Rehearsal -----------------------------------------------------------
16
+ # PrimoSource - NYUAleph: 2.120000 0.020000 2.140000 ( 3.436712)
17
+ # -------------------------------------------------- total: 2.140000sec
18
+ #
19
+ # user system total real
20
+ # PrimoSource - NYUAleph: 2.130000 0.030000 2.160000 ( 3.486879)
21
+ class NYUAleph < Exlibris::Primo::Source::Aleph
22
+ @attribute_aliases = Exlibris::Primo::Source::Aleph.attribute_aliases
23
+ @decode_variables = Exlibris::Primo::Source::Aleph.decode_variables
24
+ @source_data_elements = {
25
+ :aleph_url => :source_url,
26
+ :aleph_sub_library_code => :aleph_sub_library_code,
27
+ :aleph_item_id => :aleph_item_id,
28
+ :aleph_item_adm_library => :aleph_item_adm_library,
29
+ :aleph_item_sub_library_code => :aleph_item_sub_library_code,
30
+ :aleph_item_collection_code => :aleph_item_collection_code,
31
+ :aleph_item_doc_number => :aleph_item_doc_number,
32
+ :aleph_item_sequence_number => :aleph_item_sequence_number,
33
+ :aleph_item_barcode => :aleph_item_barcode,
34
+ :aleph_item_status_code => :aleph_item_status_code,
35
+ :aleph_item_process_status_code => :aleph_item_process_status_code,
36
+ :aleph_item_circulation_status => :aleph_item_circulation_status,
37
+ :aleph_item_location => :aleph_item_location,
38
+ :aleph_item_description => :aleph_item_description,
39
+ :aleph_item_hol_doc_number => :aleph_item_hol_doc_number
40
+ }
41
+
42
+ class << self; attr_reader :source_data_elements end
43
+
44
+ # Overwrites Exlibris::Primo::Source::Aleph#new
45
+ def initialize(parameters={})
46
+ super(parameters)
47
+ @aleph_helper ||= Exlibris::Aleph::Config::Helper.instance()
48
+ unless parameters[:holding].is_a?(NYUAleph)
49
+ # Only process this stuff the first NYUAleph is call,
50
+ # i.e. on :to_primo_source, since we don't want to
51
+ # make these expensive calls twice.
52
+ # Get Aleph record from REST API
53
+ aleph_record = Exlibris::Aleph::Record.new(
54
+ aleph_doc_library,
55
+ aleph_doc_number,
56
+ aleph_config["rest_url"]
57
+ ) unless aleph_config.nil?
58
+ begin
59
+ # Exlibris::Aleph::Record :items will raise an exception if the response isn't valid XML.
60
+ # We'll handle the exception, ignore the Aleph stuff and
61
+ # send a message to the necessary parties that something is up.
62
+ @aleph_items ||=
63
+ (aleph_record.nil? or display_type.upcase == "JOURNAL") ?
64
+ {} : aleph_record.items
65
+ # Overwrite the current coverage.
66
+ @coverage = get_coverage(aleph_record)
67
+ # Don't need to exclude JOURNALS explicitly since we handled them above.
68
+ @getting_aleph_holdings ||= !(@aleph_items.empty? or @aleph_items.size > @max_holdings)
69
+ rescue Exception => e
70
+ Rails.logger.error("Error getting data from Aleph REST APIs. #{e.message}")
71
+ @aleph_items = []
72
+ @coverage = []
73
+ # TODO: Figure out if setting the URL to Primo is the right thing to do.
74
+ # On the one hand, if Aleph REST APIs are down, Aleph may be down,
75
+ # so we don't want to send users to a dead link.
76
+ # On the other hand, if they just came from Primo, they don't want to
77
+ # go back and Aleph may not be down, just the REST APIs.
78
+ @url = primo_url
79
+ @getting_aleph_holdings = false
80
+ # Alert the authorities to the problem
81
+ alert_the_authorities e
82
+ end
83
+ else
84
+ # Only process this stuff after :expand is called,
85
+ # because we don't have correct data before
86
+ # Set library as the Aleph sub library if it exists.
87
+ aleph_sub_library = @aleph_helper.sub_library_text(
88
+ :sub_library_code => @aleph_item_sub_library_code
89
+ ) unless @aleph_helper.nil?
90
+ @library = aleph_sub_library unless aleph_sub_library.nil?
91
+ # Set id_one as the Aleph collection if it exists.
92
+ aleph_collection = @aleph_helper.collection_text(
93
+ :adm_library_code => @aleph_item_adm_library.downcase,
94
+ :sub_library_code => @aleph_item_sub_library_code,
95
+ :collection_code => @aleph_item_collection_code
96
+ ) unless @aleph_helper.nil? or @aleph_item_adm_library.nil?
97
+ @id_one = aleph_collection unless aleph_collection.nil?
98
+ # Set status and status code.
99
+ aleph_status_code, aleph_status = nil, nil
100
+ # Loop through source config for statuses
101
+ aleph_config["statuses"].each { |aleph_config_status_code, aleph_config_status|
102
+ # Set checked out as Aleph status and code
103
+ aleph_status_code = aleph_config_status_code and
104
+ aleph_status = "Due: " + @aleph_item_circulation_status and
105
+ break if (aleph_config_status_code == "checked_out" and
106
+ aleph_config_status === @aleph_item_circulation_status)
107
+ # Set circulation statuses like On Shelf, Billed as Lost, as Aleph status and code
108
+ aleph_status_code = aleph_config_status_code and
109
+ break if (aleph_config_status.instance_of?(Array) and
110
+ aleph_config_status.include?(@aleph_item_circulation_status))
111
+ } unless aleph_config.nil? or aleph_config["statuses"].nil?
112
+ if (aleph_status_code.nil?)
113
+ # Set Aleph web text as Aleph status if we haven't already gotten the Aleph status
114
+ aleph_status = @aleph_helper.item_web_text(
115
+ :adm_library_code => @aleph_item_adm_library.downcase,
116
+ :sub_library_code => @aleph_item_sub_library_code,
117
+ :item_status_code => @aleph_item_status_code,
118
+ :item_process_status_code => @aleph_item_process_status_code
119
+ ) unless @aleph_helper.nil? or @aleph_item_adm_library.nil?
120
+ # Set code as "overridden_by_nyu_aleph"
121
+ aleph_status_code = "overridden_by_nyu_aleph" unless aleph_status.nil?
122
+ end
123
+ # Set status code if we have it.
124
+ @status_code = aleph_status_code unless aleph_status_code.nil?
125
+ # Set status.
126
+ @status = (aleph_status.nil?) ?
127
+ decode(:status, {:address => "statuses"}, true) : aleph_status
128
+ # Aleph doesn't work right so we have to push the patron to the Aleph holdings page!
129
+ @request_url = url if requestable?
130
+ # We're through a second time, so we should be alright to
131
+ # to our ajax request stuff. We don't need to put this in
132
+ # requestable, since ILL doesn't need a request URL.
133
+ # TODO: Probably should specify the gap so it's clear.
134
+ # Likely "Billed as Lost" and other circ statuses.
135
+ @request_link_supports_ajax_call = true
136
+ @source_data[:illiad_url] = aleph_config["illiad_url"]
137
+ @source_data.merge!(item_source_data)
138
+ end
139
+ end
140
+
141
+ # Overwrites Exlibris::Primo::Source::Aleph#expand
142
+ def expand
143
+ aleph_holdings = get_aleph_holdings
144
+ return (aleph_holdings.empty?) ?
145
+ super : aleph_holdings if getting_aleph_holdings?
146
+ super
147
+ end
148
+
149
+ # Overwrites Exlibris::Primo::Source::Aleph#dedup?
150
+ def dedup?
151
+ @dedup ||= getting_aleph_holdings?
152
+ end
153
+
154
+ private
155
+ def requestable?
156
+ # If this is called by the super class, the item data hasn't been set yet
157
+ # so we should just call the super class.
158
+ return super if @aleph_item_adm_library.nil?
159
+ return RequestHelper.item_requestable?({ :source_data => item_source_data })
160
+ end
161
+
162
+ def item_source_data
163
+ @item_source_data ||= get_item_source_data
164
+ end
165
+
166
+ def get_item_source_data
167
+ # Get the new source data based on class array.
168
+ source_data = {} and self.class.source_data_elements.each { |element, instance_variable_name|
169
+ source_data[element] = instance_variable_get("@#{instance_variable_name}")
170
+ }
171
+ return source_data
172
+ end
173
+
174
+ def get_coverage(aleph_record)
175
+ locations_seen = []
176
+ coverage = []
177
+ return coverage unless display_type.upcase == "JOURNAL"
178
+ # Get aleph bib XML and raise exception if there is an error.
179
+ aleph_bib = aleph_record.bib
180
+ raise "Error getting bib from Aleph REST APIs. #{aleph_record.error}" unless aleph_record.error.nil?
181
+ # Parse and process bib XML
182
+ # First look at bib 866 and record sub_library and collection (through aleph config mappings)
183
+ Nokogiri::XML(aleph_bib).search("//datafield[@tag='866']") do |bib_866|
184
+ bib_866_l = bib_866.at(
185
+ "subfield[@code='l']"
186
+ ).inner_text unless bib_866.at("subfield[@code='l']").nil?
187
+ h = aleph_config["866$l_mappings"]
188
+ next if h[bib_866_l].nil?
189
+ bib_866_sub_library_code = h[bib_866_l]['sub_library']
190
+ if @aleph_sub_library_code.upcase == bib_866_sub_library_code.upcase
191
+ bib_866_collection_code = h[bib_866_l]['collection']
192
+ bib_866_adm_library = @aleph_helper.sub_library_adm(
193
+ :sub_library_code => bib_866_sub_library_code
194
+ ) unless @aleph_helper.nil?
195
+ bib_866_j = bib_866.at(
196
+ "subfield[@code='j']"
197
+ ).inner_text unless bib_866.at("subfield[@code='j']").nil?
198
+ bib_866_k = bib_866.at(
199
+ "subfield[@code='k']"
200
+ ).inner_text unless bib_866.at("subfield[@code='k']").nil?
201
+ bib_866_collection = @aleph_helper.collection_text(
202
+ :adm_library_code => bib_866_adm_library.downcase,
203
+ :sub_library_code => bib_866_sub_library_code,
204
+ :collection_code => bib_866_collection_code
205
+ ) unless @aleph_helper.nil? or bib_866_adm_library.nil?
206
+ coverage.push(
207
+ "Available in #{bib_866_collection}: #{build_coverage_string(bib_866_j, bib_866_k)}".strip
208
+ ) unless bib_866_collection.nil? or bib_866_j.nil? and bib_866_k.nil?
209
+ locations_seen.push({
210
+ :adm_library => bib_866_adm_library,
211
+ :sub_library_code => bib_866_sub_library_code })
212
+ end
213
+ end
214
+ # Get aleph holding XML.
215
+ aleph_holdings = aleph_record.holdings
216
+ # Parse and process holding XML
217
+ # Now look at holding 866 and record sub_library and collection
218
+ # to see if there is anything we missed
219
+ Nokogiri::XML(aleph_holdings).search("//holding") do |aleph_holding|
220
+ holding_sub_library_code = aleph_holding.at(
221
+ "//datafield[@tag='852']/subfield[@code='b']"
222
+ ).inner_text unless aleph_holding.at("//datafield[@tag='852']/subfield[@code='b']").nil?
223
+ if @aleph_sub_library_code.upcase == holding_sub_library_code.upcase
224
+ holding_adm_library = @aleph_helper.sub_library_adm(
225
+ :sub_library_code => holding_sub_library_code
226
+ ) unless @aleph_helper.nil?
227
+ holding_collection_code = aleph_holding.at(
228
+ "//datafield[@tag='852']/subfield[@code='c']"
229
+ ).inner_text unless aleph_holding.at("//datafield[@tag='852']/subfield[@code='c']").nil?
230
+ next if locations_seen.include?({
231
+ :adm_library => holding_adm_library,
232
+ :sub_library_code => holding_sub_library_code })
233
+ holding_collection = @aleph_helper.collection_text(
234
+ :adm_library_code => holding_adm_library.downcase,
235
+ :sub_library_code => holding_sub_library_code,
236
+ :collection_code => holding_collection_code
237
+ ) unless @aleph_helper.nil? or holding_adm_library.nil?
238
+ aleph_holding.search("//datafield[@tag='866']") do |holding_866|
239
+ holding_866_a = holding_866.at(
240
+ "subfield[@code='a']"
241
+ ).inner_text unless holding_866.at("subfield[@code='a']").nil?
242
+ coverage.push(
243
+ "Available in #{holding_collection}: #{holding_866_a.gsub(",", ", ")}".strip
244
+ ) unless holding_collection.nil? or holding_866_a.nil?
245
+ end
246
+ end
247
+ end
248
+ return coverage
249
+ end
250
+
251
+ def get_aleph_holdings
252
+ aleph_holdings = []
253
+ return aleph_holdings if @aleph_items.nil?
254
+ @aleph_items.each do |aleph_item|
255
+ aleph_item_parameters = {
256
+ :holding => self,
257
+ :aleph_item_id => aleph_item["href"].match(/items\/(.+)$/)[1],
258
+ :aleph_item_adm_library => aleph_item["z30"]["translate_change_active_library"],
259
+ :aleph_sub_library_code => aleph_item["z30_sub_library_code"].strip,
260
+ :aleph_item_sub_library_code => aleph_item["z30_sub_library_code"].strip,
261
+ :aleph_item_collection_code => aleph_item["z30_collection_code"],
262
+ :aleph_item_doc_number => aleph_item["z30"]["z30_doc_number"],
263
+ :aleph_item_sequence_number => aleph_item["z30"]["z30_item_sequence"].strip,
264
+ :aleph_item_barcode => aleph_item["z30"]["z30_barcode"],
265
+ :aleph_item_status_code => aleph_item["z30_item_status_code"],
266
+ :aleph_item_process_status_code => aleph_item["z30_item_process_status_code"],
267
+ :aleph_item_circulation_status => aleph_item["status"],
268
+ :aleph_item_location => aleph_item["z30"]["z30_call_no"],
269
+ :aleph_item_description => aleph_item["z30"]["z30_description"],
270
+ :aleph_item_hol_doc_number => aleph_item["z30"]["z30_hol_doc_number"],
271
+ :library_code => aleph_item["z30_sub_library_code"].strip,
272
+ :id_two => process_aleph_call_number(aleph_item).gsub("&nbsp;", " ")
273
+ }
274
+ aleph_holdings.push(self.class.new(aleph_item_parameters))
275
+ end
276
+ Rails.logger.warn(
277
+ "No holdings processed from Aleph items in #{self.class}: #{self.record_id}."
278
+ ) if aleph_holdings.empty? and getting_aleph_holdings?
279
+ return aleph_holdings
280
+ end
281
+
282
+ def getting_aleph_holdings?
283
+ @getting_aleph_holdings
284
+ end
285
+
286
+ def process_aleph_call_number(aleph_item)
287
+ return "" if aleph_item.nil? or
288
+ (aleph_item["z30"].fetch("z30_call_no", "").nil? and
289
+ aleph_item["z30"].fetch("z30_description", "").nil?)
290
+ return "("+
291
+ de_marc_call_number(aleph_item["z30"].fetch("z30_call_no", ""))+
292
+ ")" if aleph_item["z30"].fetch("z30_description", "").nil?
293
+ return "("+
294
+ aleph_item["z30"].fetch("z30_description", "").to_s +
295
+ ")" if aleph_item["z30"].fetch("z30_call_no", "").nil?
296
+ return "("+
297
+ de_marc_call_number(aleph_item["z30"].fetch("z30_call_no", ""))+
298
+ " "+ aleph_item["z30"].fetch("z30_description", "").to_s+ ")"
299
+ end
300
+
301
+ def de_marc_call_number(marc_call_number)
302
+ call_number = marc_call_number
303
+ call_number.gsub!(/\$\$h/, "") unless call_number.nil? or
304
+ call_number.match(/\$\$h/).nil?
305
+ call_number.gsub!(/\$\$i/, " ") unless call_number.nil? or
306
+ call_number.match(/\$\$i/).nil?
307
+ return call_number
308
+ end
309
+
310
+ def build_coverage_string(volumes, years)
311
+ rv = ""
312
+ rv += "VOLUMES: "+ volumes unless volumes.nil? or volumes.empty?
313
+ rv += " (YEARS: "+ years+ ") " unless years.nil? or years.empty?
314
+ return rv
315
+ end
316
+
317
+ # TODO: Implement to send mail.
318
+ def alert_the_authorities(error)
319
+ Rails.logger.error("Error in #{self.class}. Something is amiss with Aleph. #{error}")
320
+ puts "Something is amiss with Aleph. #{error}"
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,17 @@
1
+ module Exlibris::Primo
2
+ # Class for handling Primo TOCs from links/linktotoc
3
+ class Toc
4
+ @base_attributes = [ :record_id, :linktotoc, :url, :display, :notes ]
5
+ class << self; attr_reader :base_attributes end
6
+ def initialize(options={})
7
+ base_attributes = (self.class.base_attributes.nil?) ?
8
+ Exlibris::Primo::Toc.base_attributes : self.class.base_attributes
9
+ base_attributes.each { |attribute|
10
+ self.class.send(:attr_reader, attribute)
11
+ }
12
+ options.each { |option, value|
13
+ self.instance_variable_set(('@'+option.to_s).to_sym, value)
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,140 @@
1
+ # Module for calling Primo Web Services
2
+ # Please note the following:
3
+ # * Be sure to configure the Primo Back Office with the relevant IPs to enable interaction via the Web Services
4
+ # * This module does not parse the response but instead stores it as an Hpricot::Doc for the calling classes to parse
5
+ module Exlibris::PrimoWS
6
+ require 'nokogiri'
7
+
8
+ # PrimoWebService is the base class for all PrimoWebServices
9
+ # It can be extended but is not intended for use by itself
10
+ # To call a PrimoWebService must explicity call the method make_call.
11
+ class PrimoWebService
12
+ attr_reader :response, :error
13
+
14
+ # Call to web service is made through make_call
15
+ # Raise a method not found exception if the method name is not valid
16
+ def make_call(base_url, service, method_name, param_name, input)
17
+ require 'soap/rpc/driver'
18
+ endpoint_url = base_url + "/PrimoWebServices/services/primo/" + service
19
+ soap_client = SOAP::RPC::Driver.new(endpoint_url, "http://www.exlibris.com/primo/xsd/wsRequest", "")
20
+ soap_client.add_method(method_name, param_name) unless (respond_to? method_name)
21
+ @response = Nokogiri::XML(soap_client.method(method_name).call(input.to_s))
22
+ raise "Error making call to Primo web service. Response from web service is #{@response}." if @response.nil?
23
+ @error = []
24
+ response.search("ERROR").each do |e|
25
+ @error.push(e.attributes["MESSAGE"]) unless e.nil?
26
+ end
27
+ raise "Error making call to Primo web service. #{@error.inspect}" unless @error.empty?
28
+ end
29
+ end
30
+
31
+ # Search is the base class for Search web services
32
+ # It can be extended but is not intended for use by itself
33
+ # Two known implementations are SearchBrief and GetRecord
34
+ class Search < PrimoWebService
35
+ # Search is instantiated by calling Search.new with the following parameters
36
+ # String method_name: web service method being called
37
+ # String param_name: name of input parameter
38
+ # String input_root: input root tag name
39
+ # REXML::Element primo_search_request: REXML:Element representation of the search base on ExL Schema
40
+ # REXML:Elements[] additional_input: any additional input as an array of REXML::Elements
41
+ # String base_url: Primo URL
42
+ # Hash option: options NOT USED
43
+ def initialize(method_name, param_name, input_root, primo_search_request, additional_input, base_url, options)
44
+ input = REXML::Element.new(input_root)
45
+ input.add_namespace("http://www.exlibris.com/primo/xsd/wsRequest")
46
+ input.add_element(primo_search_request)
47
+ additional_input.each do |e|
48
+ input.add_element(e)
49
+ end
50
+ make_call(base_url, "searcher", method_name, param_name, input)
51
+ end
52
+
53
+ private
54
+ def primo_search_request(search_params={}, start_index="1", bulk_size="5", did_u_mean_enabled="false", highlighting_enabled="false", get_more=nil)
55
+ xml = REXML::Element.new("PrimoSearchRequest")
56
+ xml.add_namespace("http://www.exlibris.com/primo/xsd/search/request")
57
+ xml.add_element(query_terms(search_params))
58
+ xml.add_element(tag!("StartIndex", start_index)) unless start_index.nil?
59
+ xml.add_element(tag!("BulkSize", bulk_size)) unless bulk_size.nil?
60
+ xml.add_element(tag!("DidUMeanEnabled", did_u_mean_enabled)) unless did_u_mean_enabled.nil?
61
+ xml.add_element(tag!("HighlightingEnabled", highlighting_enabled)) unless highlighting_enabled.nil?
62
+ xml.add_element(tag!("GetMore", get_more)) unless get_more.nil?
63
+ return xml
64
+ end
65
+
66
+ def query_terms(search_params, bool_operator="AND")
67
+ xml = REXML::Element.new("QueryTerms")
68
+ xml.add_element(tag!("BoolOpeator", bool_operator)) unless bool_operator.nil?
69
+ search_params.each do |m, v|
70
+ begin
71
+ xml.add_element(self.method("#{m}_query_term").call(v))
72
+ rescue Exception => e
73
+ raise "Invalid search params.\nSupported search params are\n\t:isbn\n\t:issn\n\t:title\n\t:author\n\t:genre\n\nException: #{e.inspect}"
74
+ end
75
+ end
76
+ return xml
77
+ end
78
+
79
+ def query_term(value=nil, index_field="any", precision_operator="contains")
80
+ xml = REXML::Element.new("QueryTerm")
81
+ xml.add_element(tag!("IndexField", index_field)) unless value.nil?
82
+ xml.add_element(tag!("PrecisionOperator", precision_operator)) unless value.nil?
83
+ xml.add_element(tag!("Value", value)) unless value.nil?
84
+ return xml
85
+ end
86
+
87
+ def isbn_query_term(isbn)
88
+ return query_term(isbn, "isbn", "exact")
89
+ end
90
+
91
+ def issn_query_term(issn)
92
+ return query_term(issn, "isbn", "exact")
93
+ end
94
+
95
+ def title_query_term(title)
96
+ return query_term(title, "title")
97
+ end
98
+
99
+ def author_query_term(author)
100
+ return query_term(author, "creator")
101
+ end
102
+
103
+ def genre_query_term(genre)
104
+ return query_term(genre, "any", "exact")
105
+ end
106
+
107
+ def tag!(name, value)
108
+ REXML::Element.new(name).add_text(value)
109
+ end
110
+ end
111
+
112
+ # SearchBrief does a brief result search through the Primo APIs
113
+ # Not all options are currently supported
114
+ # Supported search params are
115
+ # :isbn
116
+ # :issn
117
+ # :title
118
+ # :author
119
+ # :genre
120
+ # e.g. {:isbn => "0143039008", :title => "Travels with My Aunt"}
121
+ # Invalid params will raise an exception
122
+ class SearchBrief < Search
123
+ def initialize(search_params, base_url, options={})
124
+ additional_input=[]
125
+ additional_input.push(tag!("institution", options.delete(:institution))) if options.has_key?(:institution)
126
+ super("searchBrief", "searchBriefRequest", "searchRequest", primo_search_request(search_params), additional_input, base_url, options)
127
+ end
128
+ end
129
+
130
+ # GetRecord get a primo record based on doc id
131
+ # Not all options are currently supported
132
+ class GetRecord < Search
133
+ def initialize(doc_id, base_url, options={})
134
+ additional_input=[]
135
+ additional_input.push(tag!("docId", doc_id))
136
+ additional_input.push(tag!("institution", options.delete(:institution))) if options.has_key?(:institution)
137
+ super("getRecord", "getRecordRequest", "fullViewRequest", primo_search_request, additional_input, base_url, options)
138
+ end
139
+ end
140
+ end