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,2 @@
1
+ class CrossrefLookup < ActiveRecord::Base
2
+ end
@@ -0,0 +1,58 @@
1
+ class DispatchedService < ActiveRecord::Base
2
+ belongs_to :request
3
+
4
+ # Serialized hash of exception info.
5
+ serialize :exception_info
6
+
7
+ # Statuses for status column
8
+ # Still executing, has started
9
+ InProgress = 'in_progress'
10
+ # Queued up, not yet started. Generally used for background services.
11
+ Queued = 'queued'
12
+ # Complete, succesful. May or may not have produced responses, but
13
+ # completed succesfully either way.
14
+ Successful = 'successful'
15
+ # Failed, and do not advise trying again.
16
+ FailedFatal = 'failed_fatal' # Complete, failed,
17
+ # Failed, but it might be worth trying again.
18
+ FailedTemporary = 'failed_temporary'
19
+
20
+
21
+ def service=(service)
22
+ self.service_id = service.service_id
23
+ end
24
+ # instantiates a new service object that represents the service
25
+ # that dispatched.
26
+ def service
27
+ @service ||= ServiceStore.instantiate_service!( self.service_id, request )
28
+ end
29
+
30
+ # For old-time's sake, true can be used for Succesful
31
+ # and false can be used for FailedTemporary (that keeps
32
+ # previous semantics for false intact).
33
+ def status=(a_status)
34
+ a_status = FailedTemporary if a_status.kind_of?(FalseClass)
35
+ a_status = Successful if a_status.kind_of?(TrueClass)
36
+
37
+ # NO: @status = a_status
38
+ # Instead, this is how you 'override' an AR attribute:
39
+ write_attribute(:status, a_status)
40
+ end
41
+
42
+ # Will silently refuse to over-write an existing stored exception.
43
+ def store_exception(a_exc)
44
+ return if a_exc.nil? || ! self.exception_info.nil?
45
+ # Just yaml'izing the exception doesn't keep the backtrace, which is
46
+ # what we wanted. Doh!
47
+ e_hash = Hash.new
48
+ e_hash[:class_name] = a_exc.class.name
49
+ e_hash[:message] = a_exc.message
50
+ e_hash[:backtrace] = a_exc.backtrace
51
+
52
+ write_attribute(:exception_info, e_hash)
53
+ end
54
+
55
+ def completed?
56
+ return (self.status != InProgress) && (self.status != Queued)
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ # attribute context_obj_serialized has an XML OpenURL ContextObject sufficient to restore
2
+ # the original request and resolve the permalink. A link to a referent is
3
+ # also stored. But the referent may be purged, so self.referent may be null.
4
+ # The serialized contextobject will still be there.
5
+ class Permalink < ActiveRecord::Base
6
+ belongs_to :referent
7
+
8
+ # You should create Permalinks with this. Pass in a referent and referrer
9
+ #. Will save permalink to db
10
+ def self.new_with_values!(rft, rfr_id)
11
+ permalink = Permalink.new
12
+
13
+ permalink.referent = rft
14
+ permalink.orig_rfr_id = rfr_id
15
+
16
+ permalink.context_obj_serialized = permalink.referent.to_context_object.xml
17
+
18
+ permalink.save!
19
+
20
+ return permalink
21
+ end
22
+
23
+
24
+ # Takes the XML stored in self.context_obj_serialized, and turns it back
25
+ # into an OpenURL ContextObject
26
+ def restore_context_object
27
+ return OpenURL::ContextObject.new_from_xml(self.context_obj_serialized)
28
+ end
29
+ end
@@ -0,0 +1,473 @@
1
+ class Referent < ActiveRecord::Base
2
+ # for shortcut metadata manipulations
3
+ include MetadataHelper
4
+
5
+ # Shortcuts are really used as retrieval keys to 'shortcut' matching
6
+ # referent. They hold normalized value (use ReferentValue.normalize) or
7
+ # empty string. Never nil.
8
+ @@shortcut_attributes = [:atitle, :title, :issn, :isbn, :volume, :year]
9
+ has_many :requests
10
+ has_many :referent_values
11
+ has_many :permalinks
12
+
13
+ def before_validation_on_create
14
+ # shortcuts initialize to empty string, they should never be null.
15
+ @@shortcut_attributes.each do |key|
16
+ self[key] = "" if self[key].nil?
17
+ end
18
+ end
19
+
20
+ # When provided an OpenURL::ContextObject, it will return a Referent object
21
+ # (if one exists). At least that's the intent.
22
+ #This turns out to be a really
23
+ # tricky task, identifying when two citations that may not match exactly
24
+ # are the same citation. So this doesn't really work well--we err
25
+ # on the side of missing existing matches, better than finding
26
+ # a false match. So there are seldom matches found. A particular
27
+ # problem is that when the Referent is enhanced by a service,
28
+ # it will no longer match _itself_ as it came in! Oh well.
29
+ def self.find_by_context_object(co)
30
+
31
+ rft = co.referent
32
+
33
+
34
+ # Try to find for re-use by special indexed shortcut values. Create hash
35
+ # of shortcuts.
36
+
37
+ # Preload values as empty, even if they aren't found in our
38
+ # incoming referent--we want to find a match with them empty too, then!
39
+ shortcuts = {:atitle=>"", :title=>"", :issn=>"", :isbn=>"", :volume=>"", :year=>""}
40
+
41
+ # Special handling of title
42
+ incoming_title = rft.metadata['jtitle'] || rft.metadata['btitle'] || rft.metadata['title']
43
+ # DC OpenURL is an array, not a single value. Grr.
44
+ incoming_title = incoming_title[0] if incoming_title.kind_of?(Array)
45
+ shortcuts[:title] = ReferentValue.normalize(incoming_title) if incoming_title
46
+ # Special handling of date/year, since we use year instead of date for
47
+ # stored shortcut.
48
+ # I don't know why.
49
+ shortcuts[:year] = rft.metadata['date'] if rft.metadata['date']
50
+
51
+ # Other four.
52
+ [:atitle, :issn, :isbn, :volume].each do |att|
53
+ shortcuts[att] = ReferentValue.normalize( rft.metadata[att.to_s]) if rft.metadata[ att.to_s ]
54
+ end
55
+ # Don't look up by shortcuts if they're ALL blank. That doesn't do us well.
56
+ found_rft = nil
57
+ found_rft = Referent.find(:first, :conditions => shortcuts) if shortcuts.values.find {|v| ! v.empty?}
58
+ if ( found_rft && found_rft.metadata_intersects?( rft ) )
59
+ return found_rft
60
+ end
61
+
62
+ # found nothing?
63
+ return nil
64
+ end
65
+
66
+ # When provided an OpenURL::ContextObject, it will return a Referent object
67
+ # (creating one if doesn't already exist) . At least that's the idea.
68
+ # But see caveats at #find_by_context_object . Most of the time
69
+ # this ends up creating a new Referent.
70
+ # pass in referrer for source-specific referent munging.
71
+ def self.find_or_create_by_context_object(co)
72
+ # Okay, we need to do some pre-processing on weird context objects
73
+ # sent by, for example, firstSearch.
74
+ self.clean_up_context_object(co)
75
+
76
+ if rft = Referent.find_by_context_object(co)
77
+ return rft
78
+ else
79
+ rft = Referent.create_by_context_object(co)
80
+ return rft
81
+ end
82
+ end
83
+
84
+ # Does call save! on referent created.
85
+ # :permalink => false if you already have a permalink and don't
86
+ # need to create one. Caller should attach that permalink to this referent!
87
+ def self.create_by_context_object(co, options = {})
88
+
89
+ self.clean_up_context_object(co)
90
+ rft = Referent.new
91
+
92
+ # Wrap everything in a transaction for better efficiency, at least
93
+ # with MySQL, I think.
94
+
95
+ Referent.transaction do
96
+
97
+ rft.set_values_from_context_object(co)
98
+
99
+ unless ( options[:permalink] == false)
100
+ permalink = Permalink.new_with_values!(rft, co.referrer.identifier)
101
+ end
102
+
103
+ # Add shortcuts.
104
+ rft.referent_values.each do | val |
105
+ rft.atitle = val.normalized_value if val.key_name == 'atitle' and val.metadata?
106
+ rft.title = val.normalized_value if val.key_name.match(/^[bj]?title$/) and val.metadata?
107
+ rft.issn = val.normalized_value if val.key_name == 'issn' and val.metadata?
108
+ rft.isbn = val.normalized_value if val.key_name == 'isbn' and val.metadata?
109
+ rft.volume = val.normalized_value if val.key_name == 'volume' and val.metadata?
110
+ rft.year = val.normalized_value if val.key_name == 'date' and val.metadata?
111
+ end
112
+ rft.save!
113
+
114
+ # Apply referent filters
115
+ rfr_id = ""
116
+ rfr_id = co.referrer.identifier if (co.referrer && ! co.referrer.identifier.blank?)
117
+ UmlautController.umlaut_config.lookup!("referent_filters", []).each do |regexp, filter|
118
+ if (regexp =~ rfr_id)
119
+ filter.filter(rft) if filter.respond_to?(:filter)
120
+ end
121
+ end
122
+ end
123
+ return rft
124
+ end
125
+
126
+ # Okay, we need to do some pre-processing on weird context objects
127
+ # sent by, for example, firstSearch. Remove invalid identifiers.
128
+ # Also will adjust context objects according to configured
129
+ # umlaut refernet filters (see config.app_config.referent_filters in
130
+ # environment.rb )
131
+ # Mutator: Modifies ContextObject arg passed in.
132
+ def self.clean_up_context_object(co)
133
+ # First, remove any empty DOIs! or other empty identifiers?
134
+ # LOTS of sources send awful empty identifiers.
135
+ # That's not a valid identifier!
136
+ empty_ids = co.referent.identifiers.find_all { |i| i =~ Regexp.new('^[^:]+:[^/:]*(/|:)?$')}
137
+ empty_ids.each { |e| co.referent.delete_identifier( e )}
138
+
139
+ # Now look for ISSN identifiers that are on article_level. FirstSearch
140
+ # gives us ISSN identifiers incorrectly on article level cites.
141
+ issn_ids = co.referent.identifiers.find_all { |i| i =~ /^urn:ISSN/}
142
+ issn_ids.each do |issn_id|
143
+ # Long as we're at it, add an rft.issn if one's not there.
144
+ issn_data = issn_id.slice( (9..issn_id.length)) # actual ISSN without identifier prefix
145
+ co.referent.set_metadata(issn, issn_data) if co.referent.get_metadata('issn').blank? && ! issn_data.blank?
146
+
147
+ # And remove it as an identifier unless we know this is journal-level
148
+ # cite.
149
+ unless ( co.referent.get_metadata('genre') == 'journal' )
150
+ co.referent.delete_identifier( issn_id )
151
+ end
152
+ end
153
+
154
+ # Clean up OCLC numbers from old bad formats that may have snuck in to an info url incorrectly. # also delete preceding 0's
155
+ oclcnum_ids = co.referent.identifiers.find_all { |i| i =~ /^info:oclcnum/}
156
+ oclcnum_ids.each do |oclcnum_id|
157
+ # FIXME Does this regex need "ocn" as well?
158
+ if (oclcnum_id =~ /^info:oclcnum\/(ocm0*|ocn0*|\(OCoLC\)0*|ocl70*|0+)(.*)$/)
159
+ # Delete the original, take out just the actual oclcnum, not
160
+ # those old prefixes. or preceding 0s.
161
+ co.referent.delete_identifier( oclcnum_id )
162
+ co.referent.add_identifier("info:oclcnum/#{$2}")
163
+ end
164
+ end
165
+
166
+
167
+
168
+
169
+ end
170
+
171
+
172
+ # Find or create a ReferentValue object hanging off this
173
+ # Referent, with given key name and value. key_name can
174
+ # be 'identifier', 'format', or any metadata key.
175
+ def ensure_value!(key_name, value)
176
+ normalized_value = ReferentValue.normalize(value)
177
+
178
+ rv = ReferentValue.find(:first,
179
+ :conditions => { :referent_id => self.id,
180
+ :key_name => key_name,
181
+ :normalized_value => normalized_value })
182
+ unless (rv)
183
+ rv = ReferentValue.new
184
+ rv.referent = self
185
+
186
+ rv.key_name = key_name
187
+ rv.value = value
188
+ rv.normalized_value = normalized_value
189
+
190
+ unless (key_name == "identifier" || key_name == "format")
191
+ rv.metadata = true
192
+ end
193
+
194
+ rv.save!
195
+ end
196
+ return rv
197
+ end
198
+
199
+ # Populate the referent_values table with a ropenurl contextobject object
200
+ def set_values_from_context_object(co)
201
+
202
+ rft = co.referent
203
+
204
+
205
+ # Multiple identifiers are possible!
206
+ rft.identifiers.each do |id_string|
207
+ ensure_value!('identifier', id_string)
208
+ end
209
+ if rft.format
210
+ ensure_value!('format', rft.format)
211
+ end
212
+
213
+ rft.metadata.each { | key, value |
214
+ next unless value
215
+ ensure_value!( key, value)
216
+ }
217
+ end
218
+
219
+ # pass in a Referent, or a ropenurl ContextObjectEntity that has a metadata
220
+ # method. Or really anything with a #metadata method returning openurl-style
221
+ # keys and values.
222
+ # Method returns true iff the keys in common to both metadata packages
223
+ # have equal (==) values.
224
+ def metadata_intersects?(arg)
225
+
226
+ # if it's empty, good enough.
227
+ return true unless arg
228
+
229
+ intersect_keys = self.metadata.keys & arg.metadata.keys
230
+ # Take out keys who's values are blank. If one is blank but not
231
+ # both, we can still consider that a match.
232
+ intersect_keys.delete_if{ |k| self.metadata[k].blank? || arg.metadata[k].blank? }
233
+
234
+ self_subset = self.metadata.reject{ |k, v| ! intersect_keys.include?(k) }
235
+ arg_subset = arg.metadata.reject{ |k, v| ! intersect_keys.include?(k) }
236
+
237
+ return self_subset == arg_subset
238
+ end
239
+
240
+ # Creates a hash of values from referrent_values, to assemble what was
241
+ # spread accross differnet db rows into one easy-lookup hash, for
242
+ # easy access. See also #to_citation for a different hash, specifically
243
+ # for use in View to print citation. And #to_context_object.
244
+ def metadata
245
+ metadata = {}
246
+ self.referent_values.each { | val |
247
+ metadata[val.key_name] = val.value if val.metadata? and not val.private_data?
248
+ }
249
+ return metadata
250
+ end
251
+
252
+ def private_data
253
+ self.referent_values
254
+ priv_data = {}
255
+ self.referent_values.each { | val |
256
+ priv_data[val.key_name] = val.value if val.private_data?
257
+ }
258
+ return priv_data
259
+ end
260
+
261
+ def identifiers
262
+ self.referent_values
263
+ identifiers = []
264
+ self.referent_values.each { | val |
265
+ if val.key_name == 'identifier'
266
+ identifiers << val.value
267
+ end
268
+ }
269
+ return identifiers
270
+ end
271
+
272
+ def add_identifier(id)
273
+ unless ( identifiers.find{|i| i == id} )
274
+ self.referent_values.create(:key_name => 'identifier', :value => id, :normalized_value => ReferentValue.normalize(id), :metadata => false, :private_data => false).save!
275
+ end
276
+ end
277
+
278
+ def format
279
+ self.referent_values
280
+ self.referent_values.each { | val |
281
+ if val.key_name == 'format'
282
+ return val.value
283
+ end
284
+ }
285
+ end
286
+
287
+ # Some shortcuts for pulling out/manipulating specific especially
288
+ # useful data elements.
289
+
290
+ # finds and normalizes an LCCN. If multiple LCCNs are in the record,
291
+ # returns the first one. Returns a NORMALIZED lccn, but does NOT do
292
+ # validation. see:
293
+ # http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/
294
+ def lccn
295
+ return get_lccn(self)
296
+ end
297
+
298
+ # Gets an ISSN, makes sure it's a valid ISSN or else returns nil.
299
+ # So will return a valid ISSN (NOT empty string) or nil.
300
+ def issn
301
+ return get_issn(self)
302
+ end
303
+
304
+ def isbn
305
+ return get_isbn(self)
306
+ end
307
+
308
+ def oclcnum
309
+ return get_oclcnum(self)
310
+ end
311
+
312
+ # Creates an OpenURL::ContextObject assembling all the data in this
313
+ # referrent.
314
+ def to_context_object
315
+ co = OpenURL::ContextObject.new
316
+
317
+ # Got to initialize the referent entity properly for our format.
318
+ # OpenURL sucks, this is confusing, yes.
319
+ fmt_uri = 'info:ofi/fmt:xml:xsd:' + self.format
320
+ co.referent = OpenURL::ContextObjectEntity.new_from_format( fmt_uri )
321
+ rft = co.referent
322
+
323
+ # Now set all the values.
324
+ self.referent_values.each do | val |
325
+ next if val.private_data?
326
+ if val.metadata?
327
+ rft.set_metadata(val.key_name, val.value)
328
+ next
329
+ end
330
+ rft.send('set_'+val.key_name, val.value) if rft.respond_to?('set_'+val.key_name)
331
+ end
332
+ return co
333
+ end
334
+
335
+ # Creates a hash for use in View code to display a citation
336
+ def to_citation
337
+ citation = {}
338
+ # call self.metadata once and use the array for efficiency, don't
339
+ # keep calling it. profiling shows it DOES make a difference.
340
+ my_metadata = self.metadata
341
+
342
+
343
+ if my_metadata['atitle'] && ! my_metadata['atitle'].blank?
344
+ citation[:title] = my_metadata['atitle']
345
+ citation[:title_label], citation[:subtitle_label] =
346
+ case my_metadata['genre']
347
+ when /article|journal|issue/ then ['Article Title', 'Journal Title']
348
+ when /bookitem|book/ then ['Chapter/Part Title', 'Book Title']
349
+ when /proceeding|conference/ then ['Proceeding Title', 'Conference Name']
350
+ when 'report' then ['Report Title','Report']
351
+ else
352
+ if self.format == 'book'
353
+ ['Chapter/Part Title', 'Title']
354
+ elsif self.format == 'journal'
355
+ ['Article Title', 'Journal Title']
356
+ else # default fall through, use much what SFX uses.
357
+ ['Title', 'Source']
358
+ end
359
+ end
360
+ ['title','btitle','jtitle'].each do | t_type |
361
+ if ! my_metadata[t_type].blank?
362
+ citation[:subtitle] = my_metadata[t_type]
363
+ citation[:container_title] = my_metadata[t_type]
364
+ break
365
+ end
366
+ end
367
+ else
368
+ citation[:title_label] = case my_metadata["genre"]
369
+ when /article|journal|issue/ then 'Journal Title'
370
+ when /bookitem|book/ then 'Book Title'
371
+ when /proceeding|conference/ then 'Conference Name'
372
+ when 'report' then 'Report Title'
373
+ else'Title'
374
+ end
375
+ ['title','btitle','jtitle'].each do | t_type |
376
+ if ! my_metadata[t_type].blank?
377
+ citation[:title] = my_metadata[t_type]
378
+ break
379
+ end
380
+ end
381
+ end
382
+ # add publisher for books
383
+ if (my_metadata['genre'] == 'book')
384
+ citation[:pub] = my_metadata['pub'] unless my_metadata['pub'].blank?
385
+ end
386
+
387
+ citation[:issn] = issn if issn
388
+ citation[:isbn] = isbn if isbn
389
+
390
+ ['volume','issue','date'].each do | key |
391
+ citation[key.to_sym] = my_metadata[key]
392
+ end
393
+ if ! my_metadata["au"].blank?
394
+ citation[:author] = my_metadata["au"]
395
+ elsif my_metadata["aulast"]
396
+ citation[:author] = my_metadata["aulast"]
397
+ if ! my_metadata["aufirst"].blank?
398
+ citation[:author] += ', '+my_metadata["aufirst"]
399
+ else
400
+ if ! my_metadata["auinit"].blank?
401
+ citation[:author] += ', '+my_metadata["auinit"]
402
+ else
403
+ if ! my_metadata["auinit1"].blank?
404
+ citation[:author] += ', '+my_metadata["auinit1"]
405
+ end
406
+ if ! my_metadata["auinitm"].blank?
407
+ citation[:author] += my_metadata["auinitm"]
408
+ end
409
+ end
410
+ end
411
+ end
412
+ if my_metadata['spage']
413
+ citation[:page] = my_metadata['spage']
414
+ citation[:page] += ' - ' + my_metadata['epage'] if ! my_metadata['epage'].blank?
415
+ end
416
+ citation[:identifiers] = []
417
+ self.identifiers.each do | id |
418
+ citation[:identifiers] << id unless (id.blank? || id.match(/^tag:/))
419
+ end
420
+ return citation
421
+ end
422
+
423
+ def type_of_thing
424
+ genre = self.metadata["genre"]
425
+ genre = nil if genre =~ /^unknown$/i
426
+ genre ||= "resource"
427
+
428
+ genre = "book section" if genre =~ /^bookitem$/i
429
+
430
+ return genre
431
+ end
432
+
433
+ def remove_value(key)
434
+ referent_values.find(:all, :conditions=> ['key_name =?', key]).each do |rv|
435
+ referent_values.delete(rv)
436
+ end
437
+ end
438
+
439
+ # options => { :overwrite => false } to only enhance if not already there
440
+ def enhance_referent(key, value, metadata=true, private_data=false, options = {})
441
+ ActiveRecord::Base.connection_pool.with_connection do
442
+ return if value.nil?
443
+
444
+ matches = self.referent_values.to_a.find_all do |rv|
445
+ (rv.key_name == key) && (rv.metadata == metadata) && (rv.private_data == private_data)
446
+ end
447
+
448
+ matches.each do |rv|
449
+ unless (options[:overwrite] == false || rv.value == value)
450
+ rv.value = value
451
+ rv.save!
452
+ end
453
+ end
454
+
455
+ if (matches.length == 0)
456
+ val = self.referent_values.create(:key_name => key, :value => value, :normalized_value => ReferentValue.normalize(value), :metadata => metadata, :private_data => private_data)
457
+ val.save!
458
+ end
459
+
460
+ if key.match((/(^[ajb]?title$)|(^is[sb]n$)|(^volume$)|(^date$)/))
461
+ case key
462
+ when 'date' then self.year = ReferentValue.normalize(value)
463
+ when 'volume' then self.volume = ReferentValue.normalize(value)
464
+ when 'issn' then self.issn = ReferentValue.normalize(value)
465
+ when 'isbn' then self.isbn = ReferentValue.normalize(value)
466
+ when 'atitle' then self.atitle = ReferentValue.normalize(value)
467
+ else self.title = ReferentValue.normalize(value)
468
+ end
469
+ self.save!
470
+ end
471
+ end
472
+ end
473
+ end