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,73 @@
1
+ # Looks for cover images from OpenLibrary Cover API.
2
+ # Lookig up covers in OL can require multiple HTTP requests, one for each
3
+ # identifier, which can sometimes lead to slowness.
4
+ # OL also doesn't have great cover image coverage. So if you have access to
5
+ # Amazon or Google covers, you probably don't need this service.
6
+ class OpenLibraryCover < Service
7
+ require 'net/http'
8
+
9
+ def service_types_generated
10
+ return [ServiceTypeValue[:cover_image]]
11
+ end
12
+
13
+ def initialize(config)
14
+ @base_url = "http://covers.openlibrary.org/b/"
15
+ @size = "medium" # "small", "medium" or "large"
16
+
17
+ @credits = {
18
+ "OpenLibrary" => "http://openlibrary.org/"
19
+ }
20
+
21
+ super(config)
22
+ end
23
+
24
+ def handle(request)
25
+ ids = {
26
+ :isbn => request.referent.isbn,
27
+ :oclc => request.referent.oclcnum,
28
+ :lccn => request.referent.lccn
29
+ }
30
+ ids.delete_if {|k,v| v.blank?}
31
+
32
+ # Return if we don't have any identifiers
33
+ return request.dispatched(self, true) unless ids.size > 0
34
+
35
+ # What order is best for trying first?
36
+ [:isbn, :oclc, :lccn].each do |type|
37
+ next unless ids[type]
38
+
39
+ uri = cover_uri(type, ids[type] )
40
+ s_time = Time.now
41
+ response = Net::HTTP.get_response(URI.parse(uri))
42
+ Rails.logger.debug("#{@id}: #{Time.now - s_time}s to lookup #{uri}")
43
+
44
+ if response.kind_of?( Net::HTTPNotFound )
45
+ # OL has no cover
46
+ next
47
+ end
48
+
49
+ unless response.kind_of?( Net::HTTPSuccess )
50
+ # unexpected response
51
+ Rails.logger.error("#{@id}: Error in HTTP response when requesting #{uri}, #{response.inspect}")
52
+ end
53
+
54
+ # Got this far, we've got a response.
55
+ request.add_service_response(
56
+ :service => self,
57
+ :display_text => "Cover Image",
58
+ :size => "medium",
59
+ :url => uri,
60
+ :service_type_value => :cover_image
61
+ )
62
+ break
63
+ end
64
+
65
+ return request.dispatched(self, true)
66
+ end
67
+
68
+ def cover_uri(type, id)
69
+ @base_url + type.to_s + "/" + id.to_s + "-" + @size[0,1].upcase + ".jpg?default=false"
70
+ end
71
+
72
+
73
+ end
@@ -0,0 +1,392 @@
1
+ # == Overview
2
+ # PrimoService is a Service that makes a call to the Primo web services based on the OpenURL key value pairs.
3
+ #--
4
+ # NOT YET:
5
+ # It first looks for rft.primo *DEPRECATED*, failing that, it parses the identifier for an id.
6
+ #++
7
+ # It first looks for rft.primo, the Primo id.
8
+ # If the Primo id is present, the service gets the PNX record from the Primo web
9
+ # services.
10
+ # If no Primo id is found, the service searches Primo by (in order of precedence):
11
+ # * ISBN
12
+ # * ISSN
13
+ # * Title, Author, Genre
14
+ #
15
+ # == Available Services
16
+ # Several service types are available in the Primo service. The default service types are:
17
+ # fulltext, holding, holding_search, table_of_contents, referent_enhance, cover_image
18
+ # Available service types are listed below and can be configured using the service_types parameter
19
+ # in service.yml:
20
+ # * fulltext - parsed from links/linktorsrc elements in the PNX record
21
+ # * holding - parsed from display/availlibrary elements in the PNX record
22
+ # * holding_search - link to an exact title search in Primo if no holdings found AND the OpenURL did not come from Primo
23
+ # * primo_source - similar to holdings but used in conjuction with the PrimoSource service to map Primo records to their original sources; a PrimoSource service must be defined in service.yml for this to work
24
+ # * table_of_contents - parsed from links/linktotoc elements in the PNX record
25
+ # * referent_enhance - metadata parsed from the addata section of the PNX record when the record was found by Primo id
26
+ # * cover_image - parsed from first addata/lad02 element in the PNX record
27
+ #
28
+ # ==Available Parameters
29
+ # Several configurations parameters are available to be set in services.yml, e.g.
30
+ # Primo:
31
+ # type: PrimoService
32
+ # priority: 2 # After SFX, to get SFX metadata enhancement
33
+ # status: active
34
+ # base_url: http://bobcat.library.nyu.edu
35
+ # vid: NYU
36
+ # holding_search_institution: NYU
37
+ # holding_search_text: Search for this title in BobCat.
38
+ # suppress_holdings: [ !ruby/regexp '/\$\$LWEB/', !ruby/regexp '/\$\$1Restricted Internet Resources/' ]
39
+ # ez_proxy: !ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
40
+ # service_types:
41
+ # - holding
42
+ # - holding_search
43
+ # - fulltext
44
+ # - table_of_contents
45
+ # - referent_enhance
46
+ # - cover_image
47
+ # base_url:: _required_ host and port of Primo server; used for Primo web services, deep links and holding_search
48
+ # base_path:: *DEPRECATED* previous name of base_url
49
+ # vid:: _required_ view id for Primo deep links and holding_search.
50
+ # institution:: _required_ institution id for Primo institution; used for Primo web services
51
+ # base_view_id:: *DEPRECATED* previous name of vid
52
+ # holding_search_institution:: _required if service types include holding_search_ institution to be used for the holding_search
53
+ # holding_search_text:: _optional_ text to display for the holding_search
54
+ # default holding search text:: "Search for this title."
55
+ # link_to_search_text:: *DEPRECATED* previous name of holding_search_text
56
+ # service_types:: _optional_ array of strings that represent the service types desired.
57
+ # options are: fulltext, holding, holding_search, table_of_contents,
58
+ # referent_enhance, cover_image, primo_source
59
+ # defaults are: fulltext, holding, holding_search, table_of_contents,
60
+ # referent_enhance, cover_image
61
+ # if no options are specified, default service types will be added.
62
+ # suppress_urls:: _optional_ array of strings or regexps to NOT use from the catalog.
63
+ # Used for linktorsrc elements that may duplicate resources from in other services.
64
+ # Regexps can be put in the services.yml like this:
65
+ # [!ruby/regexp '/sagepub.com$/']
66
+ # suppress_holdings:: _optional_ array of strings or regexps to NOT use from the catalog.
67
+ # Used for availlibrary elements that may duplicate resources from in other services.
68
+ # Regexps can be put in the services.yml like this:
69
+ # [!ruby/regexp '/\$\$LWEB$/']
70
+ # suppress_tocs:: _optional_ array of strings or regexps to NOT link to for Tables of Contents.
71
+ # Used for linktotoc elements that may duplicate resources from in other services.
72
+ # Regexps can be put in the services.yml like this:
73
+ # [!ruby/regexp '/\$\$LWEB$/']
74
+ # service_types:: _optional_ array of strings that represent the service types desired.
75
+ # options are: fulltext, holding, holding_search, table_of_contents,
76
+ # referent_enhance, cover_image, primo_source
77
+ # defaults are: fulltext, holding, holding_search, table_of_contents,
78
+ # referent_enhance, cover_image
79
+ # if no options are specified, default service types will be added.
80
+ # ez_proxy:: _optional_ string or regexp of an ezproxy prefix.
81
+ # used in the case where an ezproxy prefix (on any other regexp) is hardcoded in the URL,
82
+ # and needs to be removed in order to match against SFXUrls.
83
+ # Example:
84
+ # !ruby/regexp '/https\:\/\/ezproxy\.library\.nyu\.edu\/login\?url=/'
85
+ # primo_config:: _optional_ string representing the primo yaml config file in config/umlaut_config
86
+ # default file name: primo.yml
87
+ # hash mappings from yaml config
88
+ # institutions:
89
+ # "primo_institution_code": "Primo Institution String"
90
+ # libraries:
91
+ # "primo_library_code": "Primo Library String"
92
+ # statuses:
93
+ # "status1_code": "Status One"
94
+ # sources:
95
+ # data_source1:
96
+ # base_url: "http://source1.base.url
97
+ # type: source_type
98
+ # class_name: Source1Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
99
+ # source1_config_option1: source1_config_option1
100
+ # source1_config_option2: source1_config_option2
101
+ # data_source2:
102
+ # base_url: "http://source2.base.url
103
+ # type: source_type
104
+ # class_name: Source2Implementation (in exlibris/primo/sources or exlibris/primo/sources/local)
105
+ # source2_config_option1: source2_config_option1
106
+ # source2_config_option2: source2_config_option2
107
+ # holding_attributes:: _optional_ array of Holding attribute readers to save to
108
+ # holding/primo_source service_data; can be used to save
109
+ # custom source implementation attributes for display by a custom holding partial
110
+ # ==Benchmarks
111
+ # The following benchmarks were run on SunOS 5.10 Generic_141414-08 sun4u sparc SUNW,Sun-Fire-V240.
112
+ # Rehearsal ----------------------------------------------------------------
113
+ # PrimoService Minimum Config: 3.850000 0.060000 3.910000 ( 4.163065)
114
+ # PrimoService Default Config: 3.410000 0.060000 3.470000 ( 3.958777)
115
+ # ------------------------------------------------------- total: 7.380000sec
116
+ #
117
+ # user system total real
118
+ # PrimoService Minimum Config: 3.470000 0.050000 3.520000 ( 4.567797)
119
+ # PrimoService Default Config: 3.420000 0.050000 3.470000 ( 3.990271)
120
+
121
+ class PrimoService < Service
122
+ required_config_params :base_url, :vid, :institution
123
+
124
+ # Overwrites Service#new.
125
+ def initialize(config)
126
+ # defaults
127
+ @holding_attributes = Exlibris::Primo::Holding.base_attributes
128
+ @rsrc_attributes = Exlibris::Primo::Rsrc.base_attributes
129
+ @toc_attributes = Exlibris::Primo::Toc.base_attributes
130
+ # TODO: Run these decisions by Bill M. to see if they make sense.
131
+ @referent_enhancements = {
132
+ # Prefer SFX journal titles to Primo journal titles
133
+ :jtitle => { :overwrite => false },
134
+ :btitle => { :overwrite => true }, :aulast => { :overwrite => true },
135
+ :aufirst => { :overwrite => true }, :aucorp => { :overwrite => true },
136
+ :au => { :overwrite => true }, :pub => { :overwrite => true },
137
+ :place => { :value => :cop, :overwrite => false },
138
+ # Prefer SFX journal titles to Primo journal titles
139
+ :title => { :value => :jtitle, :overwrite => false},
140
+ :title => { :value => :btitle, :overwrite => true},
141
+ # Primo lccn and oclcid are spotty in Primo, so don't overwrite
142
+ :lccn => { :overwrite => false }, :oclcnum => { :value => :oclcid, :overwrite => false}
143
+ }
144
+ @suppress_urls = []
145
+ @suppress_tocs = []
146
+ @suppress_holdings = []
147
+ @service_types = [ "fulltext", "holding", "holding_search",
148
+ "table_of_contents", "referent_enhance", "cover_image" ] if @service_types.nil?
149
+ # For backward compatibility, re-map "old" config values to new more
150
+ # Umlaut-y names and print deprecation warning in the logs.
151
+ old_to_new_mappings = {
152
+ :base_path => :base_url,
153
+ :base_view_id => :vid,
154
+ :link_to_search_text => :holding_search_text
155
+ }
156
+ old_to_new_mappings.each do |old_param, new_param|
157
+ unless config["#{old_param}"].nil?
158
+ config["#{new_param}"] = config["#{old_param}"] if config["#{new_param}"].nil?
159
+ Rails.logger.warn("Parameter '#{old_param}' is deprecated. Please use '#{new_param}' instead.")
160
+ end
161
+ end # End backward compatibility maintenance
162
+ super(config)
163
+ # For backward compatibility, handle the special case where holding_search_institution was not included.
164
+ # Set holding_search_institution to vid and print warning in the logs.
165
+ if @service_types.include?("holding_search") and @holding_search_institution.nil?
166
+ @holding_search_institution = @institution
167
+ RAILS_DEFAULT_LOGGER.warn("Required parameter 'holding_search_institution' was not set. Please set the appropriate value in services.yml. Defaulting institution to view id, #{@vid}.")
168
+ end # End backward compatibility maintenance
169
+ raise ArgumentError.new(
170
+ "Missing Service configuration parameter. Service type #{self.class} (id: #{self.id}) requires a config parameter named 'holding_search_institution'. Check your config/umlaut_config/services.yml file."
171
+ ) if @service_types.include?("holding_search") and @holding_search_institution.nil?
172
+ end
173
+
174
+ # Overwrites Service#service_types_generated.
175
+ def service_types_generated
176
+ types = Array.new
177
+ @service_types.each do |type|
178
+ types.push(ServiceTypeValue[type.to_sym])
179
+ end
180
+ return types
181
+ end
182
+
183
+ # Overwrites Service#handle.
184
+ def handle(request)
185
+ @identifier = request.referrer_id
186
+ primo_id = @identifier.match(/primo-(.+)/)[1] if primo_identifier? unless @identifier.nil? or @identifier.match(/primo-(.+)/).nil?
187
+ # DEPRECATED
188
+ # Extend OpenURL standard to take Primo Doc Id
189
+ primo_id = request.referent.metadata['primo'] unless request.referent.metadata['primo'].nil?
190
+ Rails.logger.warn("Use of 'rft.primo' is deprecated. Please use the identifier instead.") unless request.referent.metadata['primo'].nil?
191
+ # End DEPRECATED
192
+ searcher_setup = {
193
+ :base_url => @base_url, :vid => @vid, :institution => @institution,
194
+ :config => primo_config
195
+ }
196
+ # don't send mal-formed issn
197
+ issn = request.referent.metadata['issn'] if request.referent.metadata['issn'] =~ /\d{4}(-)?\d{3}(\d|X)/
198
+ title = title(request)
199
+ search_params = {
200
+ :primo_id => primo_id,
201
+ :isbn => request.referent.metadata['isbn'],
202
+ :issn => issn,
203
+ :title => title,
204
+ :author => author(request),
205
+ :genre => request.referent.metadata['genre']
206
+ }
207
+ begin
208
+ primo_searcher = Exlibris::Primo::Searcher.new(searcher_setup, search_params)
209
+ rescue Exception => e
210
+ # Log error and return finished
211
+ Rails.logger.error(
212
+ "Error in Exlibris::Primo::Searcher. "+
213
+ "Returning 0 Primo services for search #{search_params.inspect}. "+
214
+ "Exlibris::Primo::Searcher raised the following exception:\n#{e}")
215
+ return request.dispatched(self, true)
216
+ end
217
+ # Enhance the referent with metadata from Primo Searcher if primo id is present
218
+ # i.e. if we did our search with the Primo system number
219
+ if primo_id and @service_types.include?("referent_enhance")
220
+ @referent_enhancements.each do |key, options|
221
+ value = (options[:value].nil?) ? key.to_sym : options[:value].to_sym
222
+ request.referent.enhance_referent(
223
+ key.to_s, primo_searcher.method(value).call,
224
+ true, false, options
225
+ ) if primo_searcher.respond_to? value and not primo_searcher.method(value).call.nil?
226
+ end
227
+ end
228
+ # Get cover image only if primo_id is defined
229
+ if primo_id and @service_types.include?("referent_enhance")
230
+ cover_image = primo_searcher.cover_image
231
+ unless cover_image.nil?
232
+ request.add_service_response(
233
+ :service => self,
234
+ :display_text => 'Cover Image',
235
+ :key => 'medium',
236
+ :url => cover_image,
237
+ :size => 'medium',
238
+ :service_type_value => :cover_image)
239
+ end
240
+ end
241
+ # Get holdings from Primo Searcher
242
+ if @service_types.include?("holding") or @service_types.include?("primo_source")
243
+ holdings = primo_searcher.holdings # Array of Exlibris::Primo::Holding
244
+ holdings.each do |holding|
245
+ next if @suppress_holdings.find {|suppress| suppress === holding.availlibrary}
246
+ service_data = {}
247
+ @holding_attributes.each do |attr|
248
+ service_data[attr] = holding.method(attr).call
249
+ end
250
+ # Only add one service type, either "primo_source" OR "holding", not both.
251
+ service_type = (@service_types.include?("primo_source")) ? "primo_source" : "holding"
252
+ # Add some other holding information for compatibility with default holding partial
253
+ service_data.merge!({
254
+ :call_number => holding.call_number, :collection => holding.collection,
255
+ :collection_str => "#{holding.library} #{holding.collection}",
256
+ :coverage_str => holding.coverage.join("<br />"),
257
+ :coverage_str_array => holding.coverage }) if service_type.eql? "holding"
258
+ request.add_service_response(
259
+ service_data.merge(
260
+ :service => self,
261
+ :service_type_value => service_type
262
+ )
263
+ )
264
+ end
265
+ # Provide title search functionality in the absence of available holdings.
266
+ if @service_types.include?("holding_search")
267
+ if holdings.empty? and
268
+ not primo_identifier? and
269
+ not title.nil?
270
+ service_data = {}
271
+ service_data[:type] = "link_to_search"
272
+ service_data[:display_text] = (@holding_search_text.nil?) ? "Search for this title." : @holding_search_text
273
+ service_data[:note] = ""
274
+ service_data[:url] = @base_url+"/primo_library/libweb/action/dlSearch.do?institution=#{@holding_search_institution}&vid=#{@vid}&onCampus=false&query=#{CGI::escape("title,exact,"+title)}&indx=1&bulkSize=10&group=GUEST"
275
+ request.add_service_response(
276
+ service_data.merge(
277
+ :service => self,
278
+ :service_type_value => 'holding_search'
279
+ )
280
+ )
281
+ end
282
+ end
283
+ end
284
+ # Get fulltext
285
+ if @service_types.include?("fulltext")
286
+ # Get RSRCs from Primo Searcher (executes search)
287
+ # Let's find any URLs, and add full text responses for those.
288
+ urls_seen = [] # for de-duplicating urls from catalog.
289
+ primo_searcher.rsrcs.each do |rsrc|
290
+ # No url? Forget it.
291
+ next if rsrc.url.nil?
292
+ # Next if duplicate.
293
+ next if urls_seen.include?(rsrc.url)
294
+ # Don't add the URL if it matches our SFXUrl finder (unless fulltext is empty,
295
+ # [assuming something is better than nothing]), because
296
+ # that means we think this is an SFX controlled URL.
297
+ next if SfxUrl.sfx_controls_url?(handle_ezproxy(rsrc.url)) and
298
+ request.referent.metadata['genre'] != "book" and
299
+ !request.get_service_type("fulltext", { :refresh => true }).empty?
300
+ # We have our own list of URLs to suppress, array of strings
301
+ # or regexps.
302
+ next if @suppress_urls.find {|suppress| suppress === rsrc.url}
303
+ urls_seen.push(rsrc.url)
304
+ service_data = {}
305
+ @rsrc_attributes.each do |attr|
306
+ service_data[attr] = rsrc.method(attr).call
307
+ end
308
+ # Default display text to URL.
309
+ service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
310
+ # Add the response
311
+ request.add_service_response(
312
+ service_data.merge(
313
+ :service => self,
314
+ :service_type_value => 'fulltext'
315
+ )
316
+ )
317
+ end
318
+ end
319
+ # Get TOCs
320
+ if @service_types.include?("table_of_contents")
321
+ # Let's find any TOCs, and add table of contents responses for those.
322
+ tocs_seen = [] # for de-duplicating urls from catalog.
323
+ primo_searcher.tocs.each do |toc|
324
+ url = toc.url # actual url
325
+ next if tocs_seen.include?(toc.url)
326
+ # We have our own list of URLs to suppress, array of strings
327
+ # or regexps.
328
+ next if @suppress_tocs.find {|suppress| suppress === toc.url}
329
+ # No url? Forget it.
330
+ next if toc.url.nil?
331
+ tocs_seen.push(toc.url)
332
+ service_data = {}
333
+ @toc_attributes.each do |attr|
334
+ service_data[attr] = toc.method(attr).call
335
+ end
336
+ # Default display text to URL.
337
+ service_data[:display_text] = (service_data[:display].nil?) ? service_data[:url] : service_data[:display]
338
+ # Add the response
339
+ request.add_service_response(
340
+ service_data.merge(
341
+ :service => self,
342
+ :service_type_value => 'table_of_contents'
343
+ )
344
+ )
345
+ end
346
+ end
347
+ return request.dispatched(self, true)
348
+ end
349
+
350
+ # Called by ServiceType#view_data to provide custom functionality for Primo sources.
351
+ # For more information on Primo sources see PrimoSource.
352
+ def to_primo_source(service_response)
353
+ source_parameters = { :base_url => @base_url, :vid => @vid, :config => primo_config }
354
+ @holding_attributes.each { |attr|
355
+ source_parameters[attr] = service_response.data_values[attr] }
356
+ return Exlibris::Primo::Holding.new(source_parameters).to_source
357
+ end
358
+
359
+ private
360
+ def primo_config
361
+ default_file = "#{Rails.root}/config/umlaut_config/primo.yml"
362
+ config_file = @primo_config.nil? ? default_file : "#{Rails.root}/config/umlaut_config/"+ @primo_config
363
+ Rails.logger.warn("Primo config file not found: #{config_file}.") and return {} unless File.exists?(config_file)
364
+ config_hash = YAML.load_file(config_file)
365
+ return (config_hash.nil?) ? {} : config_hash
366
+ end
367
+
368
+ # If an ezproxy prefix (on any other regexp) is hardcoded in the URL,
369
+ # strip it out for matching against SFXUrls
370
+ def handle_ezproxy(str)
371
+ return str if @ez_proxy.nil?
372
+ return (str.gsub(@ez_proxy, '').nil? ? str : str.gsub(@ez_proxy, ''))
373
+ end
374
+
375
+ def title(request)
376
+ return request.referent.metadata['jtitle'] unless request.referent.metadata['jtitle'].nil? or request.referent.metadata['jtitle'].empty?
377
+ return request.referent.metadata['btitle'] unless request.referent.metadata['btitle'].nil? or request.referent.metadata['btitle'].empty?
378
+ return request.referent.metadata['title'] unless request.referent.metadata['title'].nil? or request.referent.metadata['title'].empty?
379
+ return request.referent.metadata['atitle'] unless request.referent.metadata['atitle'].nil? or request.referent.metadata['atitle'].empty?
380
+ end
381
+
382
+ def author(request)
383
+ return request.referent.metadata['au'] unless request.referent.metadata['au'].nil? or request.referent.metadata['au'].empty?
384
+ return request.referent.metadata['aulast'] unless request.referent.metadata['aulast'].nil? or request.referent.metadata['aulast'].empty?
385
+ return request.referent.metadata['aucorp'] unless request.referent.metadata['aucorp'].nil? or request.referent.metadata['aucorp'].empty?
386
+ end
387
+
388
+ def primo_identifier?
389
+ return false if @identifier.nil?
390
+ return @identifier.start_with?('info:sid/primo.exlibrisgroup.com')
391
+ end
392
+ end