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,14 @@
1
+ class ReferentValue < ActiveRecord::Base
2
+ belongs_to :referent, :include => :referent_values
3
+
4
+ # Class method to normalize a string for normalized_value attribute.
5
+ # Right now normalization is just downcasing. Only
6
+ # metadata values should be normalized (ie, not 'identifier' or 'format').
7
+ # identifier and format shoudl be stored in normalized_value unchanged.
8
+ def self.normalize(input)
9
+ # 'mb_chars' is neccesary for unicode.
10
+ # normalized_value column only holds 254 bytes..
11
+ return input.mb_chars.downcase.to_s[0..254]
12
+ end
13
+
14
+ end
@@ -0,0 +1,449 @@
1
+ # An ActiveRecord which represents a parsed OpenURL resolve service request,
2
+ # and other persistent state related to Umlaut's handling of that OpenURL
3
+ # request) should not be confused with the Rails ActionController::Request
4
+ # class (which represents the complete details of the current 'raw' HTTP
5
+ # request, and is not stored persistently in the db).
6
+ #
7
+ # Constituent openurl data is stored in Referent and Referrer.
8
+ class Request < ActiveRecord::Base
9
+ has_many :dispatched_services
10
+ # Order service_responses by id, so the first
11
+ # added to the db comes first. Less confusing to have a consistent order.
12
+ # Also lets installation be sure services run first will have their
13
+ # responses show up first
14
+ has_many :service_responses, :order => 'id ASC'
15
+
16
+ belongs_to :referent, :include => :referent_values
17
+ # holds a hash representing submitted http params
18
+ serialize :http_env
19
+
20
+ # Either creates a new Request, or recovers an already created Request from
21
+ # the db--in either case return a Request matching the OpenURL.
22
+ # options[:allow_create] => false, will not create a new request, return
23
+ # nil if no existing request can be found.
24
+ def self.find_or_create(params, session, a_rails_request, options = {} )
25
+ # Pull out the http params that are for the context object,
26
+ # returning a CGI::parse style hash, customized for what
27
+ # ContextObject.new_from_form_vars wants.
28
+ co_params = self.context_object_params( a_rails_request )
29
+
30
+ # Create a context object from our http params
31
+ context_object = OpenURL::ContextObject.new_from_form_vars( co_params )
32
+ # Sometimes umlaut puts in a 'umlaut.request_id' parameter.
33
+ # first look by that, if we have it, for an existing request.
34
+ request_id = params['umlaut.request_id']
35
+
36
+ # We're trying to identify an already existing response that matches
37
+ # this request, in this session. We don't actually match the
38
+ # session_id in the cache lookup though, so background processing
39
+ # will hook up with the right request even if user has no cookies.
40
+ # We don't check IP change anymore either, that was too open to
41
+ # mistaken false negative when req.ip was being used.
42
+ req = Request.find_by_id(request_id) unless request_id.nil?
43
+
44
+ # No match? Just pretend we never had a request_id in url at all.
45
+ request_id = nil if req == nil
46
+
47
+ # Serialized fingerprint of openurl http params, suitable for looking
48
+ # up in the db to see if we've seen it before.
49
+ param_fingerprint = self.co_params_fingerprint( co_params )
50
+ client_ip = params['req.ip'] || a_rails_request.remote_ip()
51
+ unless (req)
52
+ # If not found yet, then look for an existing request that had the same
53
+ # openurl params as this one, in the same session. In which case, reuse.
54
+ # Here we do require same session, since we don't have an explicit
55
+ # request_id given.
56
+
57
+ req = Request.find(:first, :conditions => ["session_id = ? and contextobj_fingerprint = ? and client_ip_addr = ?", a_rails_request.session_options[:id], param_fingerprint, client_ip ] ) unless param_fingerprint.blank?
58
+ end
59
+
60
+ # Okay, if we found a req, it might NOT have a referent, it might
61
+ # have been purged. If so, create a new one.
62
+ if ( req && ! req.referent )
63
+ req.referent = Referent.find_or_create_by_context_object(context_object, req.referrer)
64
+ end
65
+
66
+ unless (req || options[:allow_create] == false)
67
+ # didn't find an existing one at all, just create one
68
+ req = self.create_new_request!( :params => params, :session => session, :rails_request => a_rails_request, :contextobj_fingerprint => param_fingerprint, :context_object => context_object )
69
+ end
70
+
71
+ return req
72
+ end
73
+
74
+ # input is a Rails request (representing http request)
75
+ # We pull out a hash of request params (get and post) that
76
+ # define a context object. We use CGI::parse instead of relying
77
+ # on Rails parsing because rails parsing ignores multiple params
78
+ # with same key value, which is legal in CGI.
79
+ #
80
+ # So in general values of this hash will be an array.
81
+ # ContextObject.new_from_form_vars is good with that.
82
+ # Exception is url_ctx_fmt and url_ctx_val, which we'll
83
+ # convert to single values, because ContextObject wants it so.
84
+ def self.context_object_params(a_rails_request)
85
+ require 'cgi'
86
+
87
+ # GET params
88
+ co_params = CGI::parse( a_rails_request.query_string )
89
+ # add in the POST params please
90
+ co_params.merge!( CGI::parse(a_rails_request.raw_post)) if a_rails_request.raw_post
91
+ # default value nil please, that's what ropenurl wants
92
+ co_params.default = nil
93
+
94
+ # CGI::parse annoyingly sometimes puts a nil key in there, for an empty
95
+ # query param (like a url that has two consecutive && in it). Let's get rid
96
+ # of it please, only confuses our code.
97
+ co_params.delete(nil)
98
+
99
+ # Exclude params that are for Rails or Umlaut, and don't belong to the
100
+ # context object. Except leave in umlaut.institution, that matters for
101
+ # cachability.
102
+ excluded_keys = ["action", "controller", "page", /^umlaut\.(?!institution)/, 'rft.action', 'rft.controller']
103
+ co_params.keys.each do |key|
104
+ excluded_keys.each do |exclude|
105
+ co_params.delete(key) if exclude === key;
106
+ end
107
+ end
108
+ # 'id' is a special one, cause it can be a OpenURL 0.1 key, or
109
+ # it can be just an application-level primary key. If it's only a
110
+ # number, we assume the latter--an openurl identifier will never be
111
+ # just a number.
112
+ if co_params['id']
113
+ co_params['id'].each do |id|
114
+ co_params['id'].delete(id) if id =~ /^\d+$/
115
+ end
116
+ end
117
+
118
+ return co_params
119
+ end
120
+
121
+ # Method that registers the dispatch status of a given service participating
122
+ # in this request.
123
+ #
124
+ # Status can be true (shorthand for DispatchedService::Success), false
125
+ # (shorthand for DispatchedService::FailedTemporary), or one of the other
126
+ # DispatchedService status codes.
127
+ # If a DispatchedService row already exists in the db, that row will be
128
+ # re-used, over-written with new status value.
129
+ #
130
+ # Exception can optionally be provided, generally with failed statuses,
131
+ # to be stored for debugging purposes.
132
+ #
133
+ # Safe to call in thread, uses explicit connectionpool checkout.
134
+ def dispatched(service, status, exception=nil)
135
+ ActiveRecord::Base.connection_pool.with_connection do
136
+ ds = self.find_dispatch_object( service )
137
+ unless ds
138
+ ds= self.new_dispatch_object!(service, status)
139
+ end
140
+ # In case it was already in the db, make sure to over-write status.
141
+ # and add the exception either way.
142
+ ds.status = status
143
+ ds.store_exception( exception )
144
+
145
+ ds.save!
146
+ end
147
+ end
148
+
149
+
150
+ # See can_dispatch below, you probably want that instead.
151
+ # This method checks to see if a particular service has been dispatched, and
152
+ # is succesful or in progress---that is, if this method returns false,
153
+ # you might want to dispatch the service (again). If it returns true though,
154
+ # don't, it's been done.
155
+ def dispatched?(service)
156
+ ds= self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
157
+ # Return true if it exists and is any value but FailedTemporary.
158
+ # FailedTemporary, it's worth running again, the others we shouldn't.
159
+ return (! ds.nil?) && (ds.status != DispatchedService::FailedTemporary)
160
+ end
161
+ # Someone asks us if it's okay to dispatch this guy. Only if it's
162
+ # marked as Queued, or Failed---otherwise it should be already working,
163
+ # or done.
164
+ def can_dispatch?(service)
165
+ ds= self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
166
+
167
+ return ds.nil? || (ds.status == DispatchedService::Queued) || (ds.status == DispatchedService::FailedTemporary)
168
+ end
169
+
170
+
171
+
172
+ # Create a ServiceResponse and it's associated ServiceType(s) object,
173
+ # attached to this request.
174
+ # Arg is a hash of key/values. Keys MUST include:
175
+ # * :service, with the value being the actual Service object, not just the ID.
176
+ # * :service_type_value => the ServiceTypeValue object (or string name) for
177
+ # the the 'type' of response this is.
178
+ #
179
+ # Other keys are as conventional for the service. See documentation of
180
+ # conventional keys in ServiceResponse
181
+ #
182
+ # Some keys end up stored in columns in the db directly, others
183
+ # end up serialized in a hash in a 'text' column, caller doesn't have
184
+ # to worry about that, just pass em all in.
185
+ #
186
+ # Eg, called from a service adapter plugin:
187
+ # request.add_service_response(:service=>self,
188
+ # :service_type_value => 'cover_image',
189
+ # :display_text => 'Cover Image',
190
+ # :url => img.inner_html,
191
+ # :asin => asin,
192
+ # :size => size)
193
+ #
194
+ # Safe to call in thread, uses connection pool checkout.
195
+ def add_service_response(response_data)
196
+ raise ArgumentError.new("missing required `:service` key") unless response_data[:service].kind_of?(Service)
197
+ raise ArgumentError.new("missing required `:service_type_value` key") unless response_data[:service_type_value]
198
+
199
+ svc_resp = nil
200
+ ActiveRecord::Base.connection_pool.with_connection do
201
+ svc_resp = ServiceResponse.new
202
+
203
+
204
+ svc_resp.service_id = response_data[:service].service_id
205
+ response_data.delete(:service)
206
+
207
+ type_value = response_data.delete(:service_type_value)
208
+ type_value = ServiceTypeValue[type_value.to_s] unless type_value.kind_of?(ServiceTypeValue)
209
+ svc_resp.service_type_value = type_value
210
+
211
+ svc_resp.request = self
212
+
213
+ # response_data now includes actual key/values for the ServiceResponse
214
+ # send em, take_key_values takes care of deciding which go directly
215
+ # in columns, and which in serialized hash.
216
+ svc_resp.take_key_values( response_data )
217
+
218
+ svc_resp.save!
219
+ end
220
+
221
+ return svc_resp
222
+ end
223
+
224
+
225
+ # Methods to look at status of dispatched services
226
+ def failed_service_dispatches
227
+ return self.dispatched_services.find(:all,
228
+ :conditions => ['status IN (?, ?)',
229
+ DispatchedService::FailedTemporary, DispatchedService::FailedFatal])
230
+ end
231
+
232
+ # Returns array of Services in progress or queued. Intentionally
233
+ # uses cached in memory association, so it wont' be a trip to the
234
+ # db every time you call this.
235
+ def services_in_progress
236
+ # Intentionally using the in-memory array instead of going to db.
237
+ # that's what the "to_a" is. Minimize race-condition on progress
238
+ # check, to some extent, although it doesn't really get rid of it.
239
+ dispatches = self.dispatched_services.to_a.find_all do | ds |
240
+ (ds.status == DispatchedService::Queued) ||
241
+ (ds.status == DispatchedService::InProgress)
242
+ end
243
+
244
+ svcs = dispatches.collect { |ds| ds.service }
245
+ return svcs
246
+ end
247
+ # convenience method to call service_types_in_progress with one element.
248
+ def service_type_in_progress?(svc_type)
249
+ return service_types_in_progress?( [svc_type] )
250
+ end
251
+
252
+ #pass in array of ServiceTypeValue or string name of same. Returns
253
+ # true if ANY of them are in progress.
254
+ def service_types_in_progress?(type_array)
255
+ # convert strings to ServiceTypeValues
256
+ type_array = type_array.collect {|s| s.kind_of?(ServiceTypeValue)? s : ServiceTypeValue[s] }
257
+
258
+ self.services_in_progress.each do |s|
259
+ # array intersection
260
+ return true unless (s.service_types_generated & type_array).empty?
261
+ end
262
+ return false;
263
+ end
264
+
265
+ def any_services_in_progress?
266
+ return services_in_progress.length > 0
267
+ end
268
+
269
+ def to_context_object
270
+ #Mostly just the referent
271
+ context_object = self.referent.to_context_object
272
+
273
+ #But a few more things
274
+ context_object.referrer.add_identifier(self.referrer_id) if self.referrer_id
275
+
276
+ context_object.requestor.set_metadata('ip', self.client_ip_addr) if self.client_ip_addr
277
+
278
+ return context_object
279
+ end
280
+
281
+ # Is the citation represetned by this request a title-level only
282
+ # citation, with no more specific article info? Or no, does it
283
+ # include article or vol/iss info?
284
+ def title_level_citation?
285
+ data = referent.metadata
286
+
287
+ # atitle can't generlaly get us article-level, but it can with
288
+ # lexis nexis, so we'll consider it article-level. Since it is!
289
+ return ( data['atitle'].blank? &&
290
+ data['volume'].blank? &&
291
+ data['issue'].blank? &&
292
+ # A date means we're not title-level only if
293
+ # we're not a book.
294
+ (data['date'].blank? ||
295
+ referent.format == "book") &&
296
+ # pmid or doi is considered article-level, because SFX can
297
+ # respond to those. Other identifiers may be useless.
298
+ (! referent.identifiers.find {|i| i =~ /^info\:(doi|pmid)/})
299
+ )
300
+ end
301
+
302
+ # pass in a ServiceTypeValue (or string name of such), get back list of
303
+ # ServiceResponse objects with that value belonging to this request.
304
+ # :refresh=>true will force a trip to the db to get latest values.
305
+ # otherwise, association is used.
306
+ def get_service_type(svc_type, options = {})
307
+ svc_type_obj = (svc_type.kind_of?(ServiceTypeValue)) ? svc_type : ServiceTypeValue[svc_type]
308
+
309
+ if ( options[:refresh])
310
+ ActiveRecord::Base.connection_pool.with_connection do
311
+ return self.service_responses.find(:all,
312
+ :conditions =>
313
+ ["service_type_value_name = ?",
314
+ svc_type_obj.name ]
315
+ )
316
+ end
317
+ else
318
+ # find on an assoc will go to db, unless we convert it to a plain
319
+ # old array first.
320
+
321
+ return self.service_responses.to_a.find_all { |response|
322
+ response.service_type_value == svc_type_obj }
323
+ end
324
+ end
325
+
326
+
327
+ # Warning, doesn't check for existing object first. Use carefully, usually
328
+ # paired with find_dispatch_object. Doesn't actually call save though,
329
+ # caller must do that (in case caller wants to further initialize first).
330
+ def new_dispatch_object!(service, status)
331
+ service_id = if service.kind_of?(Service)
332
+ service.service_id
333
+ else
334
+ service.to_s
335
+ end
336
+
337
+ ds = DispatchedService.new
338
+ ds.service_id = service_id
339
+ ds.status = status
340
+ self.dispatched_services << ds
341
+ return ds
342
+ end
343
+
344
+ protected
345
+
346
+ # Called by self.find_or_create, if a new request _really_ needs to be created.
347
+ def self.create_new_request!( args )
348
+
349
+ # all of these are required
350
+ params = args[:params]
351
+ session = args[:session]
352
+ a_rails_request = args[:rails_request]
353
+ contextobj_fingerprint = args[:contextobj_fingerprint]
354
+ context_object = args[:context_object]
355
+
356
+ # We don't have a complete Request, but let's try finding
357
+ # an already existing referent and/or referrer to use, if possible, or
358
+ # else create new ones.
359
+
360
+ rft = nil
361
+ if ( params['umlaut.referent_id'])
362
+ rft = Referent.find(:first, :conditions => {:id => params['umlaut.referent_id']})
363
+ end
364
+
365
+
366
+ # No id given, or no object found? Create it.
367
+ unless (rft )
368
+ rft = Referent.find_or_create_by_context_object(context_object)
369
+ end
370
+
371
+ # Create the Request
372
+ req = Request.new
373
+ req.session_id = a_rails_request.session_options[:id]
374
+ req.contextobj_fingerprint = contextobj_fingerprint
375
+ # Don't do this! It is a performance problem.
376
+ # rft.requests << req
377
+ # (rfr.requests << req) if rfr
378
+ # Instead, say it like this:
379
+ req.referent = rft
380
+ req.referrer_id = context_object.referrer.identifier unless context_object.referrer.empty? || context_object.referrer.identifier.empty?
381
+
382
+ # Save client ip
383
+ req.client_ip_addr = params['req.ip'] || a_rails_request.remote_ip()
384
+ req.client_ip_is_simulated = true if req.client_ip_addr != a_rails_request.remote_ip()
385
+
386
+ # Save selected http headers, keep some out to avoid being too long to
387
+ # serialize.
388
+ req.http_env = {}
389
+ a_rails_request.env.each {|k, v| req.http_env[k] = v if ((k.slice(0,5) == 'HTTP_' && k != 'HTTP_COOKIE') || k == 'REQUEST_URI' || k == 'SERVER_NAME') }
390
+
391
+ req.save!
392
+ return req
393
+ end
394
+
395
+ def find_dispatch_object(service)
396
+ return self.dispatched_services.find(:first, :conditions=>{:service_id => service.service_id})
397
+ end
398
+
399
+ # Input is a CGI::parse style of HTTP params (array values)
400
+ # output is a string "fingerprint" canonically representing the input
401
+ # params, which can be stored in the db, so that when another request
402
+ # comes in, we can easily see if this exact request was seen before.
403
+ #
404
+ # This method will exclude certain params that are not part of the context
405
+ # object, or which we do not want to consider for equality, and will
406
+ # then serialize in a canonical way such that two co's considered
407
+ # equivelent will have equivelent serialization.
408
+ #
409
+ # Returns nil if there aren't any params to include in the fingerprint.
410
+ def self.co_params_fingerprint(params)
411
+ # Don't use ctx_time, consider two co's equal if they are equal but for ctx_tim.
412
+ excluded_keys = ["action", "controller", "page", "rft.action", "rft.controller", "ctx_tim"]
413
+ # "url_ctx_val", "request_xml"
414
+
415
+ # Hash.sort will do a first run through of canonicalization for us
416
+ # production an array of two-element arrays, sorted by first element (key)
417
+ params = params.sort
418
+
419
+ # Now exclude excluded keys, and sort value array for further
420
+ # canonicalization
421
+ params.each do |pair|
422
+ # CGI::parse().sort sometimes leaves us a value string with nils in it,
423
+ # annoyingly. Especially for malformed requests, which can happen.
424
+ # Remove them please.
425
+ pair[1].compact! if pair[1]
426
+
427
+ # === works for regexp and string
428
+ if ( excluded_keys.find {|exc_key| exc_key === pair[0]})
429
+ params.delete( pair )
430
+ else
431
+ pair[1].sort! if (pair[1] && pair[1].respond_to?("sort!"))
432
+ end
433
+ end
434
+
435
+ return nil if params.blank?
436
+
437
+ # And YAML-ize for a serliazation
438
+ serialized = params.to_yaml
439
+
440
+
441
+ # And make an MD5 hash/digest. Why store the whole thing if all we need to
442
+ # do is look it up? hash/digest works well for this.
443
+ require 'digest/md5'
444
+ return Digest::MD5.hexdigest( serialized )
445
+ end
446
+
447
+
448
+
449
+ end