umlaut 3.0.0alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. data/LICENSE +7 -0
  2. data/README.md +49 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/images/error.gif +0 -0
  5. data/app/assets/images/export_bg_bot.gif +0 -0
  6. data/app/assets/images/export_bg_mid.gif +0 -0
  7. data/app/assets/images/export_bg_top.gif +0 -0
  8. data/app/assets/images/famfamfam/book_open.png +0 -0
  9. data/app/assets/images/famfamfam/cross.png +0 -0
  10. data/app/assets/images/famfamfam/page_sound.gif +0 -0
  11. data/app/assets/images/famfamfam/page_text.gif +0 -0
  12. data/app/assets/images/famfamfam/page_up.gif +0 -0
  13. data/app/assets/images/famfamfam/page_white.png +0 -0
  14. data/app/assets/images/famfamfam/readme.html +1495 -0
  15. data/app/assets/images/famfamfam/tiny_cross.png +0 -0
  16. data/app/assets/images/frame_remove.gif +0 -0
  17. data/app/assets/images/ico_go.gif +0 -0
  18. data/app/assets/images/jhu_findit.gif +0 -0
  19. data/app/assets/images/list_closed.png +0 -0
  20. data/app/assets/images/list_open.png +0 -0
  21. data/app/assets/images/more_info.gif +0 -0
  22. data/app/assets/images/rails.png +0 -0
  23. data/app/assets/images/request.gif +0 -0
  24. data/app/assets/images/spinner.gif +0 -0
  25. data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
  26. data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
  27. data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
  28. data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
  29. data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
  30. data/app/assets/javascripts/umlaut/update_html.js +152 -0
  31. data/app/assets/javascripts/umlaut.js +17 -0
  32. data/app/assets/stylesheets/umlaut.css +857 -0
  33. data/app/controllers/application_controller.rb +14 -0
  34. data/app/controllers/export_email_controller.rb +123 -0
  35. data/app/controllers/js_helper_controller.rb +10 -0
  36. data/app/controllers/link_router_controller.rb +87 -0
  37. data/app/controllers/open_search_controller.rb +9 -0
  38. data/app/controllers/resolve_controller.rb +288 -0
  39. data/app/controllers/resource_controller.rb +83 -0
  40. data/app/controllers/search_controller.rb +328 -0
  41. data/app/controllers/search_methods/sfx3.rb +148 -0
  42. data/app/controllers/search_methods/sfx4.rb +257 -0
  43. data/app/controllers/search_methods/sfx_api.rb +47 -0
  44. data/app/controllers/store_controller.rb +64 -0
  45. data/app/controllers/umlaut/controller_behavior.rb +20 -0
  46. data/app/controllers/umlaut/controller_logic.rb +96 -0
  47. data/app/controllers/umlaut/error_handling.rb +48 -0
  48. data/app/controllers/umlaut_controller.rb +112 -0
  49. data/app/helpers/application_helper.rb +4 -0
  50. data/app/helpers/emailer_helper.rb +43 -0
  51. data/app/helpers/export_email_helper.rb +34 -0
  52. data/app/helpers/open_search_helper.rb +7 -0
  53. data/app/helpers/resolve_helper.rb +225 -0
  54. data/app/helpers/search_helper.rb +50 -0
  55. data/app/helpers/umlaut/footer_helper.rb +64 -0
  56. data/app/helpers/umlaut/helper.rb +62 -0
  57. data/app/helpers/umlaut/html_head_helper.rb +37 -0
  58. data/app/helpers/umlaut/url_generation.rb +77 -0
  59. data/app/mailers/emailer.rb +48 -0
  60. data/app/models/clickthrough.rb +2 -0
  61. data/app/models/collection.rb +259 -0
  62. data/app/models/crossref_lookup.rb +2 -0
  63. data/app/models/dispatched_service.rb +58 -0
  64. data/app/models/permalink.rb +29 -0
  65. data/app/models/referent.rb +473 -0
  66. data/app/models/referent_value.rb +14 -0
  67. data/app/models/request.rb +449 -0
  68. data/app/models/service_response.rb +179 -0
  69. data/app/models/service_store.rb +59 -0
  70. data/app/models/service_type_value.rb +58 -0
  71. data/app/models/service_wave.rb +150 -0
  72. data/app/models/sfx_db/az_additional_title.rb +11 -0
  73. data/app/models/sfx_db/az_letter_group.rb +11 -0
  74. data/app/models/sfx_db/az_title.rb +38 -0
  75. data/app/models/sfx_db/az_title_v2.rb +34 -0
  76. data/app/models/sfx_db/isbn.rb +12 -0
  77. data/app/models/sfx_db/issn.rb +12 -0
  78. data/app/models/sfx_db/object.rb +35 -0
  79. data/app/models/sfx_db/object_portfolio.rb +6 -0
  80. data/app/models/sfx_db/publisher.rb +10 -0
  81. data/app/models/sfx_db/sfx_db_base.rb +54 -0
  82. data/app/models/sfx_db/target.rb +9 -0
  83. data/app/models/sfx_db/target_service.rb +10 -0
  84. data/app/models/sfx_db/title.rb +10 -0
  85. data/app/models/sfx_db.rb +10 -0
  86. data/app/models/sfx_url.rb +35 -0
  87. data/app/views/emailer/citation.text.erb +28 -0
  88. data/app/views/emailer/short_citation.text.erb +8 -0
  89. data/app/views/export_email/_email.html.erb +25 -0
  90. data/app/views/export_email/_send_email.html.erb +3 -0
  91. data/app/views/export_email/_send_txt.html.erb +3 -0
  92. data/app/views/export_email/_txt.html.erb +62 -0
  93. data/app/views/export_email/email.html.erb +3 -0
  94. data/app/views/export_email/send_email.html.erb +1 -0
  95. data/app/views/export_email/send_txt.html.erb +1 -0
  96. data/app/views/export_email/txt.html.erb +3 -0
  97. data/app/views/js_helper/loader.erb.js +13 -0
  98. data/app/views/layouts/umlaut.html.erb +52 -0
  99. data/app/views/open_search/index.html.erb +9 -0
  100. data/app/views/resolve/_api_in_progress.xml.erb +21 -0
  101. data/app/views/resolve/_background_progress.html.erb +51 -0
  102. data/app/views/resolve/_background_updater.html.erb +38 -0
  103. data/app/views/resolve/_citation.html.erb +87 -0
  104. data/app/views/resolve/_coins.html.erb +1 -0
  105. data/app/views/resolve/_compact_citation.html.erb +33 -0
  106. data/app/views/resolve/_cover_image.html.erb +35 -0
  107. data/app/views/resolve/_fulltext.html.erb +55 -0
  108. data/app/views/resolve/_help.html.erb +17 -0
  109. data/app/views/resolve/_holding.html.erb +91 -0
  110. data/app/views/resolve/_related_items.html.erb +35 -0
  111. data/app/views/resolve/_search_inside.html.erb +62 -0
  112. data/app/views/resolve/_section_display.html.erb +49 -0
  113. data/app/views/resolve/_service_errors.html.erb +29 -0
  114. data/app/views/resolve/_standard_response_item.html.erb +89 -0
  115. data/app/views/resolve/api.xml.builder +72 -0
  116. data/app/views/resolve/background_status.html.erb +26 -0
  117. data/app/views/resolve/index.html.erb +73 -0
  118. data/app/views/resolve/partial_html_sections.xml.erb +30 -0
  119. data/app/views/search/_a_to_z.html.erb +6 -0
  120. data/app/views/search/_citation.html.erb +94 -0
  121. data/app/views/search/_pager.html.erb +60 -0
  122. data/app/views/search/books.html.erb +103 -0
  123. data/app/views/search/journal_search.html.erb +90 -0
  124. data/app/views/search/journals.html.erb +167 -0
  125. data/app/views/search/opensearch_description.rxml +10 -0
  126. data/app/views/testing/index.html.erb +1 -0
  127. data/app/views/umlaut/README +5 -0
  128. data/app/views/umlaut/error.html.erb +45 -0
  129. data/db/migrate/01_umlaut_init.rb +113 -0
  130. data/db/orig_fixed_data/service_type_values.yml +120 -0
  131. data/db/seeds.rb +7 -0
  132. data/lib/CronTab.rb +192 -0
  133. data/lib/aws_product_sign.rb +146 -0
  134. data/lib/exlibris/aleph/patron.rb +64 -0
  135. data/lib/exlibris/aleph/record.rb +54 -0
  136. data/lib/exlibris/aleph/rest_api.rb +29 -0
  137. data/lib/exlibris/primo/holding.rb +192 -0
  138. data/lib/exlibris/primo/rsrc.rb +17 -0
  139. data/lib/exlibris/primo/searcher.rb +276 -0
  140. data/lib/exlibris/primo/source/aleph.rb +46 -0
  141. data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
  142. data/lib/exlibris/primo/toc.rb +17 -0
  143. data/lib/exlibris/primo_ws.rb +140 -0
  144. data/lib/generators/templates/umlaut_services.yml +237 -0
  145. data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
  146. data/lib/generators/umlaut/install_generator.rb +110 -0
  147. data/lib/hip3/bib.rb +291 -0
  148. data/lib/hip3/bib_searcher.rb +302 -0
  149. data/lib/hip3/custom_field_lookup.rb +44 -0
  150. data/lib/hip3/holding.rb +50 -0
  151. data/lib/hip3/item.rb +65 -0
  152. data/lib/hip3/receipt.rb +7 -0
  153. data/lib/hip3/serial_copy.rb +82 -0
  154. data/lib/holding.rb +32 -0
  155. data/lib/marc_helper.rb +254 -0
  156. data/lib/metadata_helper.rb +312 -0
  157. data/lib/opensearch_feed.rb +398 -0
  158. data/lib/opensearch_query.rb +98 -0
  159. data/lib/referent_filter.rb +16 -0
  160. data/lib/referent_filters/dissertation_catch.rb +45 -0
  161. data/lib/section_renderer.rb +503 -0
  162. data/lib/service.rb +336 -0
  163. data/lib/service_adaptors/ajax_export.rb +37 -0
  164. data/lib/service_adaptors/amazon.rb +412 -0
  165. data/lib/service_adaptors/blacklight.rb +327 -0
  166. data/lib/service_adaptors/book_finder.rb +40 -0
  167. data/lib/service_adaptors/bx.rb +51 -0
  168. data/lib/service_adaptors/cover_thing.rb +73 -0
  169. data/lib/service_adaptors/elsevier_cover.rb +57 -0
  170. data/lib/service_adaptors/email_export.rb +10 -0
  171. data/lib/service_adaptors/ezproxy.rb +171 -0
  172. data/lib/service_adaptors/google_book_search.rb +442 -0
  173. data/lib/service_adaptors/gpo.rb +124 -0
  174. data/lib/service_adaptors/hathi_trust.rb +308 -0
  175. data/lib/service_adaptors/hip3_service.rb +150 -0
  176. data/lib/service_adaptors/hip_holding_search.rb +237 -0
  177. data/lib/service_adaptors/internet_archive.rb +488 -0
  178. data/lib/service_adaptors/isbn_db.rb +86 -0
  179. data/lib/service_adaptors/isi.rb +258 -0
  180. data/lib/service_adaptors/jcr.rb +146 -0
  181. data/lib/service_adaptors/opac.rb +351 -0
  182. data/lib/service_adaptors/open_library.rb +316 -0
  183. data/lib/service_adaptors/open_library_cover.rb +73 -0
  184. data/lib/service_adaptors/primo_service.rb +392 -0
  185. data/lib/service_adaptors/primo_source.rb +78 -0
  186. data/lib/service_adaptors/pubmed.rb +133 -0
  187. data/lib/service_adaptors/request_to_fixture.rb +68 -0
  188. data/lib/service_adaptors/scopus.rb +295 -0
  189. data/lib/service_adaptors/sfx-new.rb +557 -0
  190. data/lib/service_adaptors/sfx.rb +566 -0
  191. data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
  192. data/lib/service_adaptors/txt_holding_export.rb +32 -0
  193. data/lib/service_adaptors/ulrichs_cover.rb +57 -0
  194. data/lib/service_adaptors/ulrichs_link.rb +47 -0
  195. data/lib/service_adaptors/worldcat.rb +116 -0
  196. data/lib/service_adaptors/worldcat_identities.rb +591 -0
  197. data/lib/tasks/umlaut.rake +134 -0
  198. data/lib/umlaut/default_configuration.rb +5 -0
  199. data/lib/umlaut/routes.rb +136 -0
  200. data/lib/umlaut/version.rb +3 -0
  201. data/lib/umlaut.rb +37 -0
  202. data/lib/umlaut_configurable.rb +343 -0
  203. data/lib/umlaut_http.rb +100 -0
  204. data/lib/xml_schema_helper.rb +109 -0
  205. data/test/dummy/Rakefile +7 -0
  206. data/test/dummy/app/assets/javascripts/application.js +13 -0
  207. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  208. data/test/dummy/app/controllers/application_controller.rb +3 -0
  209. data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
  210. data/test/dummy/app/helpers/application_helper.rb +2 -0
  211. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  212. data/test/dummy/config/application.rb +45 -0
  213. data/test/dummy/config/boot.rb +10 -0
  214. data/test/dummy/config/database-jhu.yml +44 -0
  215. data/test/dummy/config/database.yml +25 -0
  216. data/test/dummy/config/environment.rb +5 -0
  217. data/test/dummy/config/environments/development.rb +34 -0
  218. data/test/dummy/config/environments/production.rb +60 -0
  219. data/test/dummy/config/environments/test.rb +39 -0
  220. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  221. data/test/dummy/config/initializers/inflections.rb +10 -0
  222. data/test/dummy/config/initializers/mime_types.rb +5 -0
  223. data/test/dummy/config/initializers/secret_token.rb +7 -0
  224. data/test/dummy/config/initializers/session_store.rb +8 -0
  225. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  226. data/test/dummy/config/locales/en.yml +5 -0
  227. data/test/dummy/config/routes.rb +61 -0
  228. data/test/dummy/config/umlaut_services.yml +237 -0
  229. data/test/dummy/config.ru +4 -0
  230. data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
  231. data/test/dummy/db/schema.rb +124 -0
  232. data/test/dummy/log/development.log +12981 -0
  233. data/test/dummy/log/production.log +0 -0
  234. data/test/dummy/public/404.html +26 -0
  235. data/test/dummy/public/422.html +26 -0
  236. data/test/dummy/public/500.html +26 -0
  237. data/test/dummy/public/favicon.ico +0 -0
  238. data/test/dummy/script/rails +6 -0
  239. data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
  240. data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
  241. data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
  242. data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
  243. data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
  244. data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
  245. data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
  246. data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
  247. data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
  248. data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
  249. data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
  250. data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
  251. data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
  252. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  253. data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
  254. data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
  255. data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
  256. data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
  257. data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
  258. data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
  259. data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
  260. data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
  261. data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
  262. data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
  263. data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
  264. data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
  265. data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
  266. data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
  267. data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
  268. data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
  269. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  270. data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
  271. data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
  272. data/test/fixtures/dispatched_services.yml +5 -0
  273. data/test/fixtures/permalinks.yml +5 -0
  274. data/test/fixtures/referent_values.yml +1734 -0
  275. data/test/fixtures/referents.yml +156 -0
  276. data/test/fixtures/requests.yml +284 -0
  277. data/test/fixtures/service_responses.yml +5 -0
  278. data/test/fixtures/sfx_urls.yml +4 -0
  279. data/test/performance/browsing_test.rb +9 -0
  280. data/test/test_helper.rb +10 -0
  281. data/test/umlaut_test.rb +7 -0
  282. data/test/unit/aleph_patron_test.rb +39 -0
  283. data/test/unit/aleph_record_benchmarks.rb +28 -0
  284. data/test/unit/aleph_record_test.rb +30 -0
  285. data/test/unit/aws_product_sign_test.rb +93 -0
  286. data/test/unit/collection_test.rb +76 -0
  287. data/test/unit/google_book_search_test.rb +101 -0
  288. data/test/unit/primo_searcher_test.rb +403 -0
  289. data/test/unit/primo_service_test.rb +939 -0
  290. data/test/unit/primo_ws_test.rb +131 -0
  291. data/test/unit/service_response_test.rb +9 -0
  292. data/test/unit/service_test.rb +33 -0
  293. metadata +580 -0
@@ -0,0 +1,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