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
data/lib/service.rb ADDED
@@ -0,0 +1,336 @@
1
+ # Services are defined from the config/umlaut_config/services.yml file.
2
+ # hey should have the following properties
3
+ # id : unique internal string for the service, unique in yml file
4
+ # display_name : user displayable string
5
+ # url : A base url of some kind used by specific service
6
+ # type : Class name of class found in lib/service_adaptors to be used for logic
7
+ # priority: 0-9 (foreground) or a-z (background) for order of service operation
8
+ #
9
+ # Specific service_adaptor classes may have specific addtional configuration,
10
+ # commonly including 'password' or 'api_key'.
11
+ # specific service can put " required_config_parms :param1, :param2"
12
+ # in definition, for requirement exception raising on initialize.
13
+ #
14
+ # = Service Sub-classes
15
+ # Can include required config params in the class definition, eg:
16
+ # required_config_params :api_key, :base_url
17
+ #
18
+ # Should define #service_types_generated returning an array of
19
+ # ServiceTypeValues. This is neccesary for the Service to be
20
+ # run as a background service, and have the auto background updater
21
+ # work.
22
+ #
23
+ # The vast majority of services are 'standard' services, however
24
+ # there are other 'tasks' that a service can be. Well, right now, one
25
+ # other, 'link_out_filter'. The services 'task' config property
26
+ # sets the task/function/hook of the service. Default is 'standard'.
27
+ #
28
+ # A standard service defines handle(request)
29
+ #
30
+ # A link_out_filter service defines link_out_filter(request, url). If service
31
+ # returns a new url from filter_url, that's the url the user will be directed
32
+ # to. If service returns original url or nil, original url will still be used.
33
+
34
+ class Service
35
+ attr_reader :priority, :service_id, :url, :task, :status, :name
36
+ attr_writer :session_id
37
+ attr_accessor :request
38
+ @@required_params_for_subclass = {} # initialize class var
39
+
40
+ # Some constants for 'function' values
41
+ StandardTask = 'standard'
42
+ LinkOutFilterTask = 'link_out_filter'
43
+
44
+
45
+ def initialize(config)
46
+
47
+ config.each do | key, val |
48
+ self.instance_variable_set(('@'+key).to_sym, val)
49
+ end
50
+
51
+ # task defaults to standard
52
+ @task ||= StandardTask
53
+
54
+ # check required params, and throw if neccesary
55
+
56
+ required_params = Array.new
57
+ # Some things required for all services
58
+ required_params << "priority"
59
+ # Custom things for this particular sub-class
60
+
61
+ required_params.concat( @@required_params_for_subclass[self.class.name] ) if @@required_params_for_subclass[self.class.name]
62
+ required_params.each do |param|
63
+ begin
64
+ value = self.instance_variable_get('@' + param.to_s)
65
+ # docs say it raises a nameerror if it doesn't exist, docs
66
+ # lie. So we'll just raise one ourselves, and catch it, to
67
+ # handle both cases.
68
+ raise NameError if value.nil?
69
+ rescue NameError
70
+ raise ArgumentError.new("Missing Service configuration parameter. Service type #{self.class} (id: #{self.id}) requires a config parameter named '#{param}'. Check your config/umlaut_config/services.yml file.")
71
+ end
72
+ end
73
+ end
74
+
75
+ # Must be implemented by concrete sub-class. return an Array of
76
+ # ServiceTypeValues constituting the types of ServiceResponses the service
77
+ # might generate. Used by Umlaut infrastructure including the background
78
+ # service execution scheme itslef, as well asxml services returning
79
+ # information on services in progress.
80
+ #
81
+ # Example for a service that only generates fulltext:
82
+ # return [ ServiceTypeValue[:fulltext] ]
83
+ def service_types_generated
84
+ raise Exception.new("#{self.class}: service_types_generated() must be implemented by Service concrete sub-class!")
85
+ end
86
+
87
+
88
+ # Method that should actually be called to trigger the service.
89
+ # Will check pre-emption.
90
+ def handle_wrapper(request)
91
+ unless ( preempted_by(request) )
92
+ return handle(request)
93
+ else
94
+ # Pre-empted, log and close dispatch record as 'succesful'.
95
+ Rails.logger.debug("Service #{service_id} was pre-empted and not run.")
96
+ return request.dispatched(self, true)
97
+ end
98
+ end
99
+
100
+ # Implemented by sub-class. Standard response-generating services implement
101
+ # this method to do their work, generate responses and/or metadata.
102
+ def handle(request)
103
+ raise Exception.new("#{self.class}: handle() must be implemented by Service concrete sub-class, for standard services!")
104
+ end
105
+
106
+ # This method is implemented by a concrete sub-class meant to
107
+ # fulfill the task:link_out_filter. Will be called when the user clicks
108
+ # on a url that will redirect external to Umlaut. The link_out_filter
109
+ # service has the ability to intervene and record and/or change
110
+ # the url. link_out_filters are called in order of priority config param
111
+ # assigned, 0 through 9.
112
+ #
113
+ # orig_url is the current url umlaut is planning on sending the user to.
114
+ # service_type is the ServiceType object responsible for this url.
115
+ # the third argument is reserved for future use an options hash.
116
+ def link_out_filter(orig_url, service_response, other_args = {})
117
+ raise Exception.new("#{self.class}: #link_out_filter must be implemented by Service concrete sub-class with task link_out_filter!")
118
+ end
119
+
120
+
121
+ def display_name
122
+ # If no display_name is set, default to the id string. Not a good idea,
123
+ # but hey.
124
+ return @display_name ||= self.service_id
125
+ end
126
+
127
+
128
+ # Pass this method a ServiceResponse object, it will return a hash-like object of
129
+ # display values, for the view. Implementation is usually in sub-class, by
130
+ # means of a set of methods "to_[service type name]" implemented in sub-class
131
+ #. parseResponse will find those. Subclasses will not generally override
132
+ # view_data_from_service_type, although they can for complete custom
133
+ # handling. Make sure to return a Hash or hash-like (duck-typed) object.
134
+ def view_data_from_service_type(service_response)
135
+
136
+ service_type_code = service_response.service_type_value.name
137
+
138
+ begin
139
+ # try to call a method named "to_#{service_type_code}", implemented by sub-class
140
+ self.send("to_#{service_type_code}", service_response)
141
+ rescue NoMethodError
142
+ # No to_#{response_type} method? How about the catch-all method?
143
+ # If not implemented in sub-class, we have a VERY basic
144
+ # default implementation in this class.
145
+ self.send("response_to_view_data", service_response)
146
+ end
147
+ end
148
+
149
+ # Default implementation to take a ServiceResponse and parse
150
+ # into a hash of values useful to the view. Basic implementation
151
+ # just asks ServiceResposne for it's data_values object, which
152
+ # contains all ServiceResponse data (including arbitrary keys serialized
153
+ # in the hash) in an object with the hash accessor method [] .
154
+ def response_to_view_data(service_response)
155
+ return service_response.data_values
156
+ end
157
+
158
+ # Sub-class can call class method like:
159
+ # required_config_params :symbol1, :symbol2, symbol3
160
+ # in class definition body. List of config parmas that
161
+ # are required, exception will be thrown if not present.
162
+ def self.required_config_params(*params)
163
+ params.each do |p|
164
+ # Key on name of specific sub-class. Since this is a class
165
+ # method, that should be self.name
166
+ @@required_params_for_subclass[self.name] ||= Array.new
167
+ a = @@required_params_for_subclass[self.name]
168
+ a.push( p ) unless a.include?( p )
169
+ end
170
+ end
171
+
172
+
173
+ def session_id
174
+ unless (@session_id)
175
+ raise Exception.new("This service has not been initialized with a request!") unless request
176
+
177
+ @session_id = request.session_id
178
+ end
179
+ return @session_id
180
+ end
181
+
182
+ # FakeSession little placeholder class we'll use in self.session to trick
183
+ # out Rails.
184
+ class FakeSession
185
+ attr_accessor :session_id
186
+ def initialize(a_session_id)
187
+ self.session_id = a_session_id
188
+ end
189
+ def new_session
190
+ return self.session_id
191
+ end
192
+ end
193
+ # Returns a read-only version of the session hash. Lazy loaded.
194
+ # See #update_session for making changes. Will have to be fixed
195
+ # for Rails 2.3.
196
+ def session
197
+ #lazy load
198
+ unless (@session_data || session_id.blank? )
199
+ # Craziness to restore a session in Rails pre 2.2. Will definitely
200
+ # need to be changed for Rails 2.3
201
+
202
+ fake_cgi_session = FakeSession.new(session_id)
203
+
204
+ @session_store_obj =
205
+ ActionController::Base.session_store.new(fake_cgi_session)
206
+ @_session_data = @session_store_obj.restore
207
+ # modifications here are not going to be automatically stored,
208
+ # so don't do them. See #update_session instead.
209
+ @session_data = @_session_data.clone
210
+ @session_data.freeze
211
+ end
212
+ if ( session_id.blank? && @session_data.nil?)
213
+ raise Exception.new("No session_id is available, therefore no session is available.")
214
+ end
215
+ return @session_data
216
+ end
217
+
218
+
219
+ # If we just allowed changes to our session hash, and rewrote it to
220
+ # the store, we'd get a race condition where we could over-write another
221
+ # service's changes. Rails itself is actually subject to that too.
222
+ # So, you tell us exactly which values you want to update, we'll
223
+ # refetch a fresh session, and save it with your changes.
224
+ #
225
+ # We're not actually guarding against the race condition, just
226
+ # fetching and storing quickly and hoping to miss it. FIXME.
227
+ #
228
+ # example: update_session( :new_value => "foo", :other => "bar")
229
+ #
230
+ # Will have to be fixed for Rails 2.3.
231
+ def update_session(new_values)
232
+ # force a new fetch
233
+ @session = nil
234
+ session
235
+ #and update that guy, with our mutable version
236
+ @_session_data.merge!(new_values)
237
+ @session_store_obj.close
238
+ # and update our cached copy to have the changes.
239
+ @session_data = @_session_data.clone
240
+ @session_data.freeze
241
+ end
242
+
243
+ # This method is called by Umlaut when user clicks on a service response.
244
+ # Default implementation here just returns response['url']. You can
245
+ # over-ride in a sub-class to provide custom implementation of on-demand
246
+ # url generation. Second argument is the http request params sent
247
+ # by the client, used for service types that take form submissions (eg
248
+ # search_inside).
249
+ # Should return a String url.
250
+ def response_url(service_response, submitted_params )
251
+ url = service_response[:url]
252
+ raise "No url provided by service response" if url.nil? || url.empty?
253
+ return url
254
+ end
255
+
256
+
257
+ # Pre-emption hashes specify a combination of existing responses or
258
+ # service executions that can pre-empt this service. Can specify
259
+ # a service, a response type (ServiceTypeValue), or a combination of both.
260
+ #
261
+ # service's preempted_by property can either be a single pre-emption hash,
262
+ # or an array of pre-emption hashes.
263
+ #
264
+ # Can also specify that pre-emption is only of a certain service type
265
+ # generated by self.
266
+ #
267
+ # The Service base class will enforce pre-emption and not even run
268
+ # a service at all *so long as self_type is nil or '*' *. If the pre-emption
269
+ # only applies to certain types generated by the service and not the entire
270
+ # execution of the service, the concrete service subclass must implement
271
+ # logic to do that. Calling the preempted method with the second argument
272
+ # set will be helpful in writing this logic.
273
+ #
274
+ # A preemption hash has string keys:
275
+ # existing_service: id of service that will pre-empt this service.
276
+ # If key does not exist or is "*", then not specified,
277
+ # any service. (existing_type will be specified).
278
+ # existing_type: ServiceTypeValue name that pre-empts this
279
+ # service. "+" means that the service specified
280
+ # in existing_service must have generated some
281
+ # response, but type does not matter. "*" means
282
+ # that the service specified in existing_service
283
+ # must have completed succesfully, but may not
284
+ # have generated any responses.
285
+ # self_type: If blank or "*", preemption applies to any running
286
+ # of this service at all. If set to a ServiceTypeValue
287
+ # name, pre-emption is only of certain types generated
288
+ # by this service.
289
+ def preempted_by(uml_request, for_type_generated=nil)
290
+ preempted_by = @preempted_by
291
+ return false if preempted_by.nil?
292
+ preempted_by = [preempted_by] unless preempted_by.kind_of?(Array)
293
+ preemption = nil
294
+
295
+ preempted_by.each do | hash |
296
+ service = hash["existing_service"] || "*"
297
+ other_type = hash["existing_type"] || "*"
298
+ self_type = hash["self_type"] || "*"
299
+
300
+ next unless (self_type == "*" || self_type == for_type_generated)
301
+
302
+ if (other_type == "*")
303
+ # Need to check dispatched services instead of service_types,
304
+ # as we pre-empt even if no services created.
305
+ preemption =
306
+ uml_request.dispatched_services.to_a.find do |disp|
307
+ service == "*" ||
308
+ (disp.service_id == service &&
309
+ (disp.status == DispatchedService.Succesful ))
310
+ end
311
+ else
312
+ # Check service responses
313
+ preemption =
314
+ uml_request.service_responses.to_a.find do |response|
315
+ ( other_type == "*" || other_type == "+" ||
316
+ response.service_type_value.name == other_type) &&
317
+ ( service == "*" ||
318
+ response.service_id == service)
319
+ end
320
+ end
321
+ break if preemption
322
+ end
323
+ return (! preemption.nil? )
324
+ end
325
+
326
+ # used by render_service_credits helper method, returns
327
+ # a hash with keys being a human-displayable name of a third party
328
+ # to give 'credit' to, and value being a URL (or nil) to link the
329
+ # name to.
330
+ # computed from @credits config variable, or returns empty hash.
331
+ def credits
332
+ @credits || {}
333
+ end
334
+
335
+
336
+ end
@@ -0,0 +1,37 @@
1
+ # This is an abstract superclass other services over-ride to get
2
+ # extra ajaxy windows upon click on link.
3
+ class AjaxExport < Service
4
+ required_config_params :ajax_id, :controller
5
+
6
+ def initialize(config)
7
+ super(config)
8
+ end
9
+
10
+ # Standard method, used by background service updater. See Service docs.
11
+ def service_types_generated
12
+ types = [ ServiceTypeValue[:export_citation] ]
13
+
14
+ return types
15
+ end
16
+
17
+ def handle(request)
18
+
19
+ request.add_service_response(:service=>self,
20
+ :display_text => @display_text,
21
+ :link_supports_ajax_call => true,
22
+ :notes=> @note,
23
+ :service_type_value => 'export_citation' )
24
+
25
+ return request.dispatched(self, true)
26
+ end
27
+
28
+ def response_url(service_response, params)
29
+ # Hash that caller will pass to url_for to create an internally
30
+ # facing link.
31
+ return {:controller=>@form_controller,
32
+ :action=>@form_action,
33
+ :id => service_response.id,
34
+ :format => params[:format]}
35
+ end
36
+
37
+ end
@@ -0,0 +1,412 @@
1
+ #
2
+ # As of Aug 15 2009, Amazon API calls will require a secret key from Amazon.
3
+ # If you are unable to get such an account/key, this service can still be used
4
+ # for certain functions by setting 'make_aws_call' to false, and configuring
5
+ # 'service_types' to only include one or more of: ["search_inside",
6
+ # "highlighted_link", "excerpts"]
7
+ # Other services, such as enhance_referent and cover_image require api access.
8
+ #
9
+ # More about registering for and finding your AWS access key and secret key
10
+ # can be found here:
11
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/AWSCredentials.html
12
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/ViewingCredentials.html
13
+ #
14
+ #
15
+ # services.yml params:
16
+ # api_key: required. AWS "access key".
17
+ # secret_key: required unless make_aws_call==false. AWS "secret access key".
18
+ # associate_tag: required unless make_aws_call==false. Now required by Amazon API.
19
+ # sign up for an associates_id at: https://affiliate-program.amazon.com/
20
+ # it's the same thing as your 'associate id'.
21
+ # service_types: Optional. Array of strings of service type values to be
22
+ # loaded, to over-ride defaults.
23
+ # make_aws_call: default true. If false, then either uses an ASIN stored
24
+ # in referent from previous service, or tries assuming
25
+ # that an ISBN, if present, is the ASIN.
26
+ # of services ran an amazon service adaptor
27
+ # Can be used to split amazon into
28
+ # two waves, since highlighted_link and cover_image
29
+ # calls require _another_ HTTP request to amazon
30
+ # and screen scrape. Or can be used if you don't
31
+ # have access to Amazon API.
32
+ #
33
+ # See example of two-wave amazon in config/umlaut_distribution/services.yml-dist.
34
+ #
35
+ class Amazon < Service
36
+ require 'open-uri'
37
+ require 'nokogiri'
38
+ require 'isbn'
39
+
40
+
41
+ include MetadataHelper
42
+
43
+ required_config_params :url, :api_key, :associate_tag
44
+ attr_reader :url
45
+
46
+ def initialize(config)
47
+ # defaults
48
+ @url = 'http://webservices.amazon.com/onca/xml'
49
+ @reader_base_url = 'http://www.amazon.com/gp/reader/'
50
+ # Old version non-lightboxed, whcih doesn't work very well anymore.
51
+ # @reader_base_url = 'http://www.amazon.com/gp/sitbv3/reader/'
52
+ @display_name = "Amazon.com"
53
+ @display_text = "Amazon's page"
54
+ @service_types = ["abstract", "highlighted_link", "cover_image", "search_inside", "referent_enhance", "excerpts"]
55
+ @make_aws_call = true
56
+ @http_timeout = 5
57
+
58
+ @credits = {
59
+ "Amazon" => "http://www.amazon.com/"
60
+ }
61
+
62
+ super(config)
63
+
64
+ # Need the secret_key after 15 aug 09.
65
+ unless (@secret_key || ! @make_aws_call)
66
+ if ( Time.now < Time.gm(2009, 8, 15))
67
+ Rails.logger.warn("Amazon service will require a secret_key after 15 August 2009 to make Amazon API calls.")
68
+ else
69
+ raise Exception.new("Amazon API now requires a secret_key. The Amazon service can only be used with make_aws_call=false unless you have an Amazon secret key configured.")
70
+ end
71
+ end
72
+
73
+ # Only a few service types can get by without an aws call
74
+ if (! @make_aws_call &&
75
+ @service_types.find {|type| ! ["search_inside", "highlighted_link", "excerpts"].include?(type) } )
76
+ raise Exception.new("You can only set make_aws_call == false on the definition of an Amazon service adaptor when the adaptor is also set to generate no service responses other than highlighted_link, search_inside, and excerpts")
77
+ end
78
+ end
79
+
80
+
81
+ def service_types_generated
82
+ types = Array.new
83
+
84
+ @service_types.each do |type|
85
+ types.push( ServiceTypeValue[type])
86
+ end
87
+
88
+ return types
89
+ end
90
+
91
+ def handle(request)
92
+
93
+ isbn = request.referent.metadata['isbn']
94
+ isbn = isbn.gsub(/[^0-9X]/,'') if isbn
95
+
96
+ return request.dispatched(self, true) if isbn.blank?
97
+
98
+ begin
99
+
100
+ selected_aws_vals = {}
101
+ if ( @make_aws_call )
102
+ aws_response = make_aws_request( isbn )
103
+
104
+ return request.dispatched(self, true) if aws_response.blank?
105
+
106
+ # Add service responses based on AWS response
107
+ selected_aws_vals =
108
+ self.add_aws_service_responses(request, aws_response)
109
+ end
110
+
111
+ if ( selected_aws_vals == nil)
112
+ # no aws found.
113
+ return request.dispatched(self, true)
114
+ end
115
+
116
+ # Add service responses based on ASIN--may be run in a
117
+ # later service wave. Look up asin in db if we don't have
118
+ # it from current service exec.
119
+ asin = selected_aws_vals[:asin] ||
120
+ get_identifier(:urn, "asin", request.referent)
121
+
122
+ self.add_asin_service_responses(request, asin, selected_aws_vals[:item_url])
123
+
124
+ rescue TimeoutError
125
+ # Try again later if we timeout; temporary error condition.
126
+ return request.dispatched(self, DispatchedService::FailedTemporary)
127
+ rescue Exception => e
128
+ # Unexpected error, fatal error condition.
129
+ return request.dispatched(self, DispatchedService::FailedFatal, e)
130
+ end
131
+
132
+ return request.dispatched(self, true)
133
+ end
134
+
135
+ def make_aws_request(isbn)
136
+ # We're assuming the ISBN is the ASIN Amazon ID. Not neccesarily valid
137
+ # assumption, but works enough of the time and there's no easy
138
+ # alternative.
139
+ # Convert 13 to 10 if neccesary.
140
+
141
+ # got to try converting to 10. An ISBN-13 is never an ASIN.
142
+ isbn = ISBN.ten( isbn )
143
+
144
+
145
+ query_params = {
146
+ "Service"=>"AWSECommerceService",
147
+ "AWSAccessKeyId"=>@api_key,
148
+ "AssociateTag"=>@associate_tag,
149
+ "Operation"=>"ItemLookup",
150
+ "ResponseGroup"=>"Large",
151
+ "ItemId"=>isbn }
152
+
153
+ # has to be signed
154
+ query = nil
155
+
156
+ if ( @secret_key )
157
+ aws = AwsProductSign.new(:access_key => @api_key,
158
+ :secret_key => @secret_key )
159
+ query = aws.query_with_signature( query_params )
160
+ else
161
+ query = query_params.collect {|key, value| CGI.escape(key) + '=' + CGI.escape(value)}.join("&")
162
+ end
163
+
164
+ uri = URI.parse(self.url+'?'+query)
165
+ # send the request
166
+ http = Net::HTTP.new(uri.host, 80)
167
+ http.open_timeout = @http_timeout
168
+ http.read_timeout = @http_timeout
169
+ http_response = http.send_request('GET', uri.path + '?' + uri.query)
170
+
171
+ return http_response
172
+ end
173
+
174
+ def add_aws_service_responses(request, aws_response)
175
+ return_hash = Hash.new
176
+
177
+ aws = Nokogiri::XML(aws_response.body)
178
+ # extract and collect info from the xml
179
+
180
+ # if we get an error from Amazon, return now.
181
+ err = (aws.at("ItemLookupResponse/Items/Request/Errors/Error"))
182
+ err = (aws.at("ItemLookupErrorResponse")) if err.blank?
183
+
184
+ unless (err.blank?)
185
+ if (err.at('code').inner_text == 'AWS.InvalidParameterValue')
186
+ # Indicates an ISBN that Amazon doesn't know about, or that
187
+ # was mal-formed. We can't tell the difference, so either
188
+ # way let's silently ignore.
189
+ return
190
+ else
191
+ raise Exception.new("Error from Amazon web service: " + err.to_s)
192
+ end
193
+ end
194
+
195
+ asin = (aws.at("ItemLookupResponse/Items/Item/ASIN")).inner_text
196
+
197
+ # Store the asin in the referent as non-metadata private data, so
198
+ # a future background service can use it. Store as a urn identifier.
199
+ request.referent.add_identifier("urn:asin:#{asin}") unless asin.blank?
200
+
201
+ return_hash[:asin] = asin
202
+
203
+ if ( @service_types.include?("cover_image") )
204
+ # collect cover art urls
205
+ ["small","medium","large"].each do | size |
206
+ if (img = aws.at("ItemLookupResponse/Items/Item/"+size.capitalize+"Image/URL"))
207
+ request.add_service_response(
208
+ :service=>self,
209
+ :display_text => 'Cover Image',
210
+ :key=>size,
211
+ :url => img.inner_text,
212
+ :asin => asin,
213
+ :size => size,
214
+ :service_type_value => :cover_image)
215
+ end
216
+ end
217
+
218
+ end
219
+
220
+ item_url = (aws.at("ItemLookupResponse/Items/Item/DetailPageURL")).inner_text
221
+ # Store to return to caller
222
+ return_hash[:item_url] = item_url
223
+
224
+
225
+ # get description
226
+ if ( @service_types.include?("abstract") &&
227
+ desc =
228
+ (aws.at("ItemLookupResponse/Items/Item/EditorialReviews/EditorialReview/Content")))
229
+
230
+ # For some reason we need to un-escape the desc. Don't entirely get it.
231
+ desc_text = CGI.unescapeHTML( desc.inner_text )
232
+
233
+ unless ( desc_text.blank? )
234
+ request.add_service_response(
235
+ :service=>self,
236
+ :display_text => "Description from Amazon.com",
237
+ :url => item_url,
238
+ :key=>'abstract',
239
+ :value_string=>asin,
240
+ :content=>desc_text ,
241
+ :service_type_value => 'abstract')
242
+ end
243
+ end
244
+
245
+
246
+ if ( @service_types.include?("similar_item"))
247
+ # Get Amazon's 'similar products' to help recommend other useful items
248
+ (aws.search("ItemLookupResponse/Items/Item/SimilarProducts/SimilarProduct")).each do |similar|
249
+ request.add_service_response(
250
+ :service=>self,
251
+ :key=>'book',
252
+ :value_string=>(similar.at("ASIN")).inner_text,
253
+ :value_alt_string=>(similar.at("Title")).inner_text,
254
+ :service_type_value => 'similar_item')
255
+ end
256
+
257
+ end
258
+
259
+ if ( @service_types.include?("referent_enhance"))
260
+ item_attributes = aws.at("ItemLookupResponse/Items/Item/ItemAttributes")
261
+
262
+ request.referent.enhance_referent('format', 'book', false) unless request.referent.format == 'book'
263
+ metadata = request.referent.metadata
264
+ unless (metadata['btitle'] || metadata['title'])
265
+ if title = (item_attributes.at("Title"))
266
+ request.referent.enhance_referent('btitle', normalize_aws_title(title.inner_text))
267
+ end
268
+
269
+ end
270
+ # Enhance with full author name string even if aulast is already present, because full string may be useful for worldcat identities.
271
+ unless (metadata['au'] )
272
+ if author = (item_attributes.at("Author"))
273
+ request.referent.enhance_referent('au', author.inner_text)
274
+ end
275
+ end
276
+
277
+ unless metadata['pub']
278
+ if pub = (item_attributes.at("Publisher"))
279
+ request.referent.enhance_referent('pub', pub.inner_text)
280
+ end
281
+ end
282
+ unless metadata['tpages']
283
+ if tpages = (item_attributes.at("NumberOfPages"))
284
+ request.referent.enhance_referent('tpages', tpages.inner_text)
285
+ end
286
+ end
287
+ end
288
+
289
+ return return_hash
290
+ end
291
+
292
+ def add_asin_service_responses(request, asin, item_url)
293
+ # we want to highlight Amazon to link to 'search in this book', etc.
294
+ if asin
295
+ # Search or Look inside the book offered? We only know by trying and
296
+ # then screen-scraping.
297
+ search_inside = false
298
+ look_inside = false
299
+
300
+ # Check for search_inside or look_inside if we're configured
301
+ # to supply highlighted_link or search_inside, that's what we
302
+ # need it for.
303
+ if ( @service_types.include?("highlighted_link") ||
304
+ @service_types.include?("search_inside"))
305
+ inside_base = @reader_base_url + asin
306
+ # lame screen-scrape for search inside availability. We need to
307
+ # distinguish between no results, "look inside", and "search inside".
308
+ response = open(inside_base).read
309
+
310
+ # This regexp only suitable for screen-scraping the old-style "sitbv3"
311
+ # reader page screen
312
+ if (response.include?("<div class='sitb-pop-search'>"))
313
+ # then we have search_inside. I think this always includes 'look', but
314
+ # we'll test seperate for that.
315
+ search_inside= true
316
+ end
317
+
318
+ if (response.include?('<a href="/gp/reader/'))
319
+ # then we have look inside, not neccesarily search.
320
+ look_inside = true
321
+ end
322
+ end
323
+
324
+ if ( @service_types.include?("search_inside") && search_inside )
325
+ request.add_service_response(
326
+ :service => self,
327
+ :display_text=>@display_name,
328
+ :url=> inside_base,
329
+ :service_type_value => :search_inside
330
+ )
331
+ end
332
+
333
+ # Link to look inside if we have it, otherwise ordinary amazon detail
334
+ # page.
335
+
336
+ if (@service_types.include?("excerpts") &&
337
+ ( search_inside || look_inside ))
338
+
339
+
340
+ request.add_service_response(
341
+ :service=>self,
342
+ :url => inside_base,
343
+ :asin=>asin,
344
+ :display_text => @display_name,
345
+ :service_type_value => 'excerpts')
346
+
347
+ elsif ( @service_types.include?("highlighted_link"))
348
+ # Just link to Amazon page if we can. If we did the AWS request
349
+ # before, afraid we didn't store the item url, just the
350
+ # asin, reconstruct a valid one, even if not the one given to us
351
+ # by AWS.
352
+ amazon_page = item_url || ("http://www.amazon.com/o/ASIN/" + asin)
353
+
354
+
355
+ request.add_service_response(
356
+ :service=>self,
357
+ :url => amazon_page,
358
+ :asin=>asin,
359
+ :display_text => @display_text,
360
+ :service_type_value => 'highlighted_link')
361
+ end
362
+
363
+ end
364
+
365
+ end
366
+
367
+ # Catch url_for call for search_inside, because we're going to redirect
368
+ def response_url(service_response, submitted_params)
369
+ if ( ! (service_response.service_type_value.name == "search_inside" ))
370
+ return super(service_response, submitted_params)
371
+ else
372
+ # search inside!
373
+ base = service_response[:url]
374
+ query = CGI.escape(submitted_params["query"] || "")
375
+ url = base + "/ref=sib_dp_srch_pop?v=search-inside&keywords=#{query}&go=Go%21"
376
+ return url
377
+ end
378
+ end
379
+
380
+ #amazon is in the habit of including things in parens at the end
381
+ #of the title that aren't really part of the title. The parens
382
+ # are really an edition and/or series statement. We have nowhere
383
+ # good to store that.
384
+ def normalize_aws_title(title)
385
+ title.sub(/\([^)]*\)\s*$/, '')
386
+ end
387
+
388
+
389
+ end
390
+
391
+ # Example of no look or search:
392
+ # www.amazon.com/gp/reader/0794521789
393
+
394
+ # Example of look and search:
395
+ # http://www.amazon.com/gp/reader/1851960511
396
+
397
+ # Example of 'look inside' with no search:
398
+ # http://www.amazon.com/gp/reader/0140441115/
399
+
400
+ # Scraping /gp/reader page.
401
+
402
+ # Only look inside (preview) if link like:
403
+ # <a href="/gp/reader/1851960511/ref=sib_dp_pop_sup?ie=UTF8&amp;p=random#reader-link
404
+ # OR:
405
+ # <a href="/gp/reader/0140441115/ref=sib_dp_kd#reader-link" onclick="if (typeof(SitbReader) != 'undefined')
406
+
407
+
408
+ # Only search inside if:
409
+
410
+ # '<td class="tinypopup">Search Inside This Book:</td>'
411
+ # OR: <div class='sitb-pop-search'>
412
+