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,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